控件内容模型概述

更新:2007 年 11 月

本主题讨论从 Control 继承的类所使用的内容模型。该内容模型指定控件可以包含的对象类型。在本主题中,术语“控件”仅限于在其类层次结构中的某处具有 Control 类的类。本主题中讨论的四个内容模型由从 Control 继承的以下四个类定义:

这四个类用作 WPF 中大多数控件的基类。使用这些内容模型的类可以包含相同类型的内容,并以相同的方式处理该内容;可以放置在某个 ContentControl(或从 ContentControl 继承的类)中的任何类型的对象都可以放置在具有其他三个内容模型中的任何一个的控件中。 下图显示了来自每个内容模型的包含一个图像和一些文本的控件。

Button、GroupBox、Listbax、TreeViewItem

本主题包括下列各节。

  • 先决条件
  • ContentControl
  • HeaderedContentControl
  • ItemsControl
  • HeaderedItemsControl
  • 相关主题

先决条件

本主题假设您已经基本了解 WPF 并且知道如何向应用程序添加控件。有关更多信息,请参见Windows Presentation Foundation 入门控件概述

ContentControl

在这四个内容模型中,最简单的是 ContentControl,该内容模型具有一个 Content 属性。Content 属性的类型为 Object,因此,对于您在 ContentControl 中可以放置的内容没有任何限制。可以使用可扩展应用程序标记语言 (XAML) 或代码来设置 Content

以下控件使用 ContentControl 内容模型:

下面的示例演示如何创建四个 Button 控件,这些控件的 Content 设置为以下类型之一:

说明:

示例 可扩展应用程序标记语言 (XAML) 版本可以在每个按钮内容的两旁使用 <Button.Content> 标记,但这不是必需的。有关更多信息,请参见 XAML 概述

<!--Create a Button with a string as its content.-->
<Button>This is string content of a Button</Button>

<!--Create a Button with a DateTime object as its content.-->
<Button xmlns:sys="clr-namespace:System;assembly=mscorlib">
  <sys:DateTime>2004/3/4 13:6:55</sys:DateTime>
</Button>

<!--Create a Button with a single UIElement as its content.-->
<Button>
  <Rectangle Height="40" Width="40" Fill="Blue"/>
</Button>

<!--Create a Button with a panel that contains multiple objects 
as its content.-->
<Button>
  <StackPanel>
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <TextBlock TextAlignment="Center">Button</TextBlock>
  </StackPanel>
</Button>
' Add a string to a button.
Dim stringContent As New Button()
stringContent.Content = "This is string content of a Button"

' Add a DateTime object to a button.
Dim objectContent As New Button()
Dim dateTime1 As New DateTime(2004, 3, 4, 13, 6, 55)

objectContent.Content = dateTime1

' Add a single UIElement to a button.
Dim uiElementContent As New Button()

Dim rect1 As New Rectangle()
rect1.Width = 40
rect1.Height = 40
rect1.Fill = Brushes.Blue
uiElementContent.Content = rect1

' Add a panel that contains multpile objects to a button.
Dim panelContent As New Button()
Dim stackPanel1 As New StackPanel()
Dim ellipse1 As New Ellipse()
Dim textBlock1 As New TextBlock()

ellipse1.Width = 40
ellipse1.Height = 40
ellipse1.Fill = Brushes.Blue

textBlock1.TextAlignment = TextAlignment.Center
textBlock1.Text = "Button"

stackPanel1.Children.Add(ellipse1)
stackPanel1.Children.Add(textBlock1)

panelContent.Content = stackPanel1
// Create a Button with a string as its content.
Button stringContent = new Button();
stringContent.Content = "This is string content of a Button";

// Create a Button with a DateTime object as its content.
Button objectContent = new Button();
DateTime dateTime1 = new DateTime(2004, 3, 4, 13, 6, 55);

objectContent.Content = dateTime1;

// Create a Button with a single UIElement as its content.
Button uiElementContent = new Button();

Rectangle rect1 = new Rectangle();
rect1.Width = 40;
rect1.Height = 40;
rect1.Fill = Brushes.Blue;
uiElementContent.Content = rect1;

// Create a Button with a panel that contains multiple objects 
// as its content.
Button panelContent = new Button();
StackPanel stackPanel1 = new StackPanel();
Ellipse ellipse1 = new Ellipse();
TextBlock textBlock1 = new TextBlock();

ellipse1.Width = 40;
ellipse1.Height = 40;
ellipse1.Fill = Brushes.Blue;

textBlock1.TextAlignment = TextAlignment.Center;
textBlock1.Text = "Button";

stackPanel1.Children.Add(ellipse1);
stackPanel1.Children.Add(textBlock1);

panelContent.Content = stackPanel1;

下图显示了上一个示例中创建的四个按钮。

四个按钮

HeaderedContentControl

HeaderedContentControlContentControl 继承 Content 属性,并且定义类型为 ObjectHeader 属性。Header 提供控件的标头。如同 ContentControlContent 属性,Header 可以是任何类型。WPF 附带三个从 HeaderedContentControl 继承的控件:

下面的示例创建一个 TabControl(一个 ItemsControl),其中包含两个 TabItem 对象。 第一个 TabItemHeaderContent 中均具有丰富内容:Header 设置为包含一个 Ellipse 和一个 TextBlockStackPanelContent 设置为包含一个 TextBlock 和一个 LabelStackPanel。 第二个 TabItemHeader 设置为一个字符串,Content 设置为一个 TextBlock

<TabControl>
  <TabItem>
    <TabItem.Header>
      <StackPanel Orientation="Horizontal">
        <Ellipse Width="10" Height="10" Fill="DarkGray"/>
        <TextBlock>Tab 1</TextBlock>
      </StackPanel>
    </TabItem.Header>
    <StackPanel>
      <TextBlock>Enter some text</TextBlock>
      <TextBox Name="textBox1" Width="50"/>
    </StackPanel>
  </TabItem>
  <TabItem Header="Tab 2">
    <!--Bind TextBlock.Text to the TextBox on the first
    TabItem.-->
    <TextBlock Text="{Binding ElementName=textBox1, Path=Text}"/>
  </TabItem>
</TabControl>

下图显示了在上一示例中创建的 TabControl

TabControl

ItemsControl

ItemsControl 继承的控件包含一个对象集合。 ItemsControl 的一个示例是 ListBox。可以使用 ItemsSource 属性或 Items 属性来填充一个 ItemsControl

ItemsSource 属性

通过 ItemsControlItemsSource 属性,您可以使用实现 IEnumerable 的任何类型作为 ItemsControl 的内容。ItemsSource 通常用于显示一个数据集合,或将 ItemsControl 绑定到某个集合对象。

下面的示例创建一个名为 MyData 的类,该类是一个简单字符串集合。

Public Class MyData
    Inherits ObservableCollection(Of String)

    Public Sub New()  '

        Add("Item 1")
        Add("Item 2")
        Add("Item 3")

    End Sub 'New
End Class 'MyData
public class MyData : ObservableCollection<string>
{
    public MyData()
    {
        Add("Item 1");
        Add("Item 2");
        Add("Item 3");
    }
}

下面的示例将 ItemsSource 绑定到 MyData。

<!--Create an instance of MyData as a resource.-->
<src:MyData x:Key="dataList"/>


...


<ListBox ItemsSource="{Binding Source={StaticResource dataList}}"/>
Dim listBox1 As New ListBox()
Dim listData As New MyData()
Dim binding1 As New Binding()

binding1.Source = listData
listBox1.SetBinding(ListBox.ItemsSourceProperty, binding1)
ListBox listBox1 = new ListBox();
MyData listData = new MyData();
Binding binding1 = new Binding();

binding1.Source = listData;
listBox1.SetBinding(ListBox.ItemsSourceProperty, binding1);

下图显示在上一个示例中创建的 ListBox

ListBox

有关数据绑定的更多信息,请参见 数据绑定概述

Items 属性

如果您不想使用实现 IEnumerable 的对象来填充 ItemsControl,则可以通过使用 Items 属性来添加项。ItemsControl 中的项可以具有相互不同的类型。例如,ListBox 可以包含一个字符串类型的项和另一个 Image 类型的项。

说明:

   设置了 ItemsSource 属性时,可以使用 Items 属性来读取 ItemCollection,但不能添加到或修改 ItemCollection。将 ItemsSource 属性设置为 null 引用(在 Visual Basic 中为 Nothing)的操作将移除集合,并将用法还原为 Items,后者将是一个空的 ItemCollection

下面的示例创建一个具有四个不同类型项的 ListBox

<!--Create a ListBox that contains a string, a Rectangle,
     a Panel, and a DateTime object. These items can be accessed
     via the Items property.-->
<ListBox xmlns:sys="clr-namespace:System;assembly=mscorlib"
         Name="simpleListBox">

  <!-- The <ListBox.Items> element is implicitly used.-->
  This is a string in a ListBox

  <sys:DateTime>2004/3/4 13:6:55</sys:DateTime>

  <Rectangle Height="40" Width="40"  Fill="Blue"/>

  <StackPanel Name="itemToSelect">
    <Ellipse Height="40" Fill="Blue"/>
    <TextBlock>Text below an Ellipse</TextBlock>
  </StackPanel>

  <TextBlock>String in a TextBlock</TextBlock>
  <!--</ListBox.Items>-->
</ListBox>
' Create a Button with a string as its content.
listBox1.Items.Add("This is a string in a ListBox")

' Create a Button with a DateTime object as its content.
Dim dateTime1 As New DateTime(2004, 3, 4, 13, 6, 55)

listBox1.Items.Add(dateTime1)

' Create a Button with a single UIElement as its content.
Dim rect1 As New Rectangle()
rect1.Width = 40
rect1.Height = 40
rect1.Fill = Brushes.Blue
listBox1.Items.Add(rect1)

' Create a Button with a panel that contains multiple objects 
' as its content.
Dim ellipse1 As New Ellipse()
Dim textBlock1 As New TextBlock()

ellipse1.Width = 40
ellipse1.Height = 40
ellipse1.Fill = Brushes.Blue

textBlock1.TextAlignment = TextAlignment.Center
textBlock1.Text = "Text below an Ellipse"

stackPanel1.Children.Add(ellipse1)
stackPanel1.Children.Add(textBlock1)

listBox1.Items.Add(stackPanel1)
// Add a String to the ListBox.
listBox1.Items.Add("This is a string in a ListBox");

// Add a DateTime object to a ListBox.
DateTime dateTime1 = new DateTime(2004, 3, 4, 13, 6, 55);

listBox1.Items.Add(dateTime1);

// Add a Rectangle to the ListBox.
Rectangle rect1 = new Rectangle();
rect1.Width = 40;
rect1.Height = 40;
rect1.Fill = Brushes.Blue;
listBox1.Items.Add(rect1);

// Add a panel that contains multpile objects to the ListBox.
Ellipse ellipse1 = new Ellipse();
TextBlock textBlock1 = new TextBlock();

ellipse1.Width = 40;
ellipse1.Height = 40;
ellipse1.Fill = Brushes.Blue;

textBlock1.TextAlignment = TextAlignment.Center;
textBlock1.Text = "Text below an Ellipse";

stackPanel1.Children.Add(ellipse1);
stackPanel1.Children.Add(textBlock1);

listBox1.Items.Add(stackPanel1);

下图显示在上一个示例中创建的 ListBox

具有四种类型的内容的 ListBox

项容器类

随 WPF 附带的每个 ItemsControl 具有一个对应的类,该类代表 ItemsControl 中的一个项。下表列出了随 WPF 附带的 ItemsControl 对象及其相应的项容器。

ItemsControl

项容器

ComboBox

ComboBoxItem

ContextMenu

MenuItem

ListBox

ListBoxItem

ListView

ListViewItem

Menu

MenuItem

StatusBar

StatusBarItem

TabControl

TabItem

TreeView

TreeViewItem

您可以为 ItemsControl 中的每个项显式创建一个项容器,但这不是必需的。是否在您的 ItemsControl 中创建项容器,很大程度上依赖于您的方案。 例如,如果您将数据绑定到 ItemsSource 属性,您将不会显式创建项容器。以下几点很重要,请务必记住:

  • ItemCollection 中的对象类型各不相同,具体取决于您是否显式创建项容器。

  • 即使您不显式创建项容器,也可以得到它。

  • 将应用 TargetType 设置为项容器的 Style,而无论是否显式创建了该项容器。

  • 对于显式和隐式创建的项容器来说,属性继承的作用方式是不同的,因为只有显式创建的项容器才属于逻辑树的一部分。

为了阐释这些观点,下面的示例创建了两个 ListBox 控件。该示例为第一个 ListBox 创建 ListBoxItem 对象,但不为第二个 ListBox 创建这些对象。在第二种情况下,为 ListBox 中的每个项隐式创建一个 ListBoxItem

<!--Explicitly create a ListBoxItem for each item in the ListBox-->
<ListBox xmlns:sys="clr-namespace:System;assembly=mscorlib"
         Name="listBoxItemListBox">
  <!-- The <ListBox.Items> element is implicitly used.-->
  <ListBoxItem>
    This is a string in a ListBox
  </ListBoxItem>
  <ListBoxItem>
    <sys:DateTime>2004/3/4 13:6:55</sys:DateTime>
  </ListBoxItem>
  <ListBoxItem>
    <Rectangle Height="40" Width="40" Fill="Blue"/>
  </ListBoxItem>
  <ListBoxItem>
    <StackPanel>
      <Ellipse Height="40" Width="40" Fill="Blue"/>
      <TextBlock>Text below an Ellipse</TextBlock>
    </StackPanel>
  </ListBoxItem>
  <!--</ListBox.Items>-->
</ListBox>


...


<!--Create a ListBox that contains a string, a Rectangle,
     a Panel, and a DateTime object. These items can be accessed
     via the Items property.-->
<ListBox xmlns:sys="clr-namespace:System;assembly=mscorlib"
         Name="simpleListBox">

  <!-- The <ListBox.Items> element is implicitly used.-->
  This is a string in a ListBox

  <sys:DateTime>2004/3/4 13:6:55</sys:DateTime>

  <Rectangle Height="40" Width="40"  Fill="Blue"/>

  <StackPanel Name="itemToSelect">
    <Ellipse Height="40" Fill="Blue"/>
    <TextBlock>Text below an Ellipse</TextBlock>
  </StackPanel>

  <TextBlock>String in a TextBlock</TextBlock>
  <!--</ListBox.Items>-->
</ListBox>

每个 ListBoxItemCollection 是不同的。第一个 ListBoxItems 属性中的每个项是一个 ListBoxItem,但在第二个 ListBox 中则是不同的类型。下面的示例通过在两个 ListBox 控件中循环访问这些项并检查每个项的类型来确认这一点。

    Console.WriteLine("Items in simpleListBox:")

    For Each item As Object In simpleListBox.Items
        Console.WriteLine(item.GetType().ToString())
    Next item

    Console.WriteLine(vbCr + "Items in listBoxItemListBox:")

    For Each item As Object In listBoxItemListBox.Items
        Console.WriteLine(item.GetType().ToString())
    Next item

End Sub 'ReportLBIs


...


'
'        Items in simpleListBox:
'        System.String
'        System.Windows.Shapes.Rectangle
'        System.Windows.Controls.StackPanel
'        System.DateTime
'
'        Items in listBoxItemListBox:
'        System.Windows.Controls.ListBoxItem
'        System.Windows.Controls.ListBoxItem
'        System.Windows.Controls.ListBoxItem
'        System.Windows.Controls.ListBoxItem
'        
Console.WriteLine("Items in simpleListBox:");
foreach (object item in simpleListBox.Items)
{
    Console.WriteLine(item.GetType().ToString());
}

Console.WriteLine("\rItems in listBoxItemListBox:");

foreach (object item in listBoxItemListBox.Items)
{
    Console.WriteLine(item.GetType().ToString());
}


...


/*
Items in simpleListBox:
System.String
System.Windows.Shapes.Rectangle
System.Windows.Controls.StackPanel
System.DateTime

Items in listBoxItemListBox:
System.Windows.Controls.ListBoxItem
System.Windows.Controls.ListBoxItem
System.Windows.Controls.ListBoxItem
System.Windows.Controls.ListBoxItem
*/

下图显示在上一个示例中创建的两个 ListBox 控件。

比较显式和隐式项容器

通常,您希望每个项都有项容器,但您尚未在您的应用程序中显式创建该项容器。若要获取与特定项关联的项容器,请使用 ContainerFromItem 方法。 下面的示例演示在未显式创建 ListBoxItem 的情况下如何获取与某个项关联的项容器。该示例假设名为 itemToSelect 的对象不是 ListBoxItem 并且已添加到 ListBox、simpleListBox。

Dim lbi As ListBoxItem = _
    CType(simpleListBox.ItemContainerGenerator.ContainerFromItem(itemToSelect),  _
          ListBoxItem)

If Not (lbi Is Nothing) Then
    lbi.IsSelected = True
End If
ListBoxItem lbi =
    simpleListBox.ItemContainerGenerator.ContainerFromItem(itemToSelect) 
    as ListBoxItem;

if (lbi != null)
{
    lbi.IsSelected = true;
}

TargetType 设置为项容器的样式既应用于隐式创建的项容器,也应用于显式创建的项容器。下面的示例创建一个 Style 作为 ListBoxItem 的资源(ListBoxItem 中的内容水平居中)。该样式应用于 ListBox 对象时,两个 ListBox 对象中的项均居中。

<!--Create a Style as a Resource.-->
<Style TargetType="ListBoxItem">
  <Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>

下图显示应用了上一个示例中的样式的两个 ListBox 控件。

两个 ListBox 控件

属性继承如何处理样式和项容器与逻辑树的结构方式相关。 显式创建的项容器是逻辑树的一部分。如果您未创建项容器,则项容器不是逻辑树的一部分。下图显示上一个示例中的两个 ListBox 控件在逻辑树中的差异。

两个 ListBox 对象的可视化树

Visual 类继承的对象从它们的逻辑父级继承属性值。下面的示例创建一个带有两个 TextBlock 控件的 ListBox,并将 ListBoxForeground 属性设置为蓝色。 第一个 TextBlock(textBlock1)包含在显式创建的 ListBoxItem 中,第二个 TextBlock(textBlock2)则不是。该示例还为 ListBoxItem 定义一个 Style,用于将 ListBoxItemForeground 设置为绿色。

<!--Create a Style as a Resource.-->
<Style TargetType="ListBoxItem">
  <Setter Property="Foreground" Value="Green"/>
</Style>


...


<ListBox Foreground="Blue">
  <ListBoxItem>
    <TextBlock Name="textBlock1">TextBlock in a ListBoxItem.</TextBlock>
  </ListBoxItem>
  <TextBlock Name="textBlock2">TextBlock not in a ListBoxItem.</TextBlock>
</ListBox>

下图显示在上一个示例中创建的 ListBox

一个 ListBox 中的两个 ListBoxItem

textBlock1 中的字符串为绿色,textBlock2 中的字符串为蓝色,因为每个 TextBlock 控件都从其相应的逻辑父级继承 Foreground 属性。textBox1 的逻辑父级是 ListBoxItem,textBox2 的逻辑父级是 ListBox。有关更多信息,请参见属性值继承

HeaderedItemsControl

HeaderedItemsControlItemsControl 类继承。HeaderedItemsControl 定义 Header 属性,该属性遵从相同的规则,因为 HeaderedContentControl. WPF 的 Header 属性附带三个从 HeaderedItemsControl 继承的控件:

下面的示例创建一个 TreeViewItemTreeView 包含一个 TreeViewItem,后者标记为 TreeViewItem 1,并且包含以下项:

说明:

该示例显式为后两个项创建 TreeViewItem 对象,因为 RectangleStackPanelVisual 类继承。TreeViewItem 的默认样式设置 Foreground 属性。子对象从显式创建的 TreeViewItem(通常是所需样式)继承属性值。

<TreeView xmlns:sys="clr-namespace:System;assembly=mscorlib"
          Margin="10">
  <TreeViewItem Header="TreeViewItem 1" IsExpanded="True">
    TreeViewItem 1a
    <sys:DateTime>2004/3/4 13:6:55</sys:DateTime>
    <TreeViewItem>
      <TreeViewItem.Header>
        <Rectangle Height="10" Width="10" Fill="Blue"/>
      </TreeViewItem.Header>
    </TreeViewItem>
    <TreeViewItem>
      <TreeViewItem.Header>
        <StackPanel Orientation="Horizontal">
          <Ellipse Width="10" Height="10" Fill="DarkGray"/>
          <TextBlock >TreeViewItem 1d</TextBlock>
        </StackPanel>
      </TreeViewItem.Header>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>

请参见

概念

WPF 内容模型