Controls 內容模型概觀

更新: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

HeaderedContentControl 會從 ContentControl 繼承 Content 屬性,並定義類型為 ObjectHeader 屬性。Header 提供控制項的標題。與 ContentControlContent 屬性類似,Header 可以是任何類型。WPF 會提供繼承自 HeaderedContentControl 的三個控制項:

下列範例會建立內含兩個 TabItem 物件的 TabControl (ItemsControl)。第一個 TabItemHeaderContent 中都具有豐富內容:Header 是設定為內含 EllipseTextBlockStackPanel,而 Content 是設定為內含 TextBlockLabelStackPanel。第二個 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 屬性來填入 (Populate) 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 物件和其對應的項目容器 (Container)。

ItemsControl

項目容器

ComboBox

ComboBoxItem

ContextMenu

MenuItem

ListBox

ListBoxItem

ListView

ListViewItem

Menu

MenuItem

StatusBar

StatusBarItem

TabControl

TabItem

TreeView

TreeViewItem

您可以明確地針對 ItemsControl 中的每個項目建立項目容器,但這不是必要動作。是否要在 ItemsControl 中建立項目容器,絕大部分是取決於案例。例如,如果將資料繫結至 ItemsSource 屬性,則不會明確建立項目容器。下列是需要記住的重點:

  • ItemCollection 中的物件類型會根據是否明確建立項目容器而不同。

  • 即使未明確建立項目容器,還是可以取得項目容器。

  • 不論是否明確建立項目容器,都會套用 TargetType 設定為項目容器的 Style

  • 因為只有明確建立的項目容器才是屬於邏輯樹狀結構的一部分,所以在隱含和明確建立的項目容器中,屬性繼承 (Inheritance) 的行為會不同。

為了說明這些重點,下列範例會建立兩個 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 類別的物件會繼承其邏輯父代 (Parent) 的屬性值。下列範例會建立具有兩個 TextBlock 控制項的 ListBox,並將 ListBoxForeground 屬性設定為 blue。第一個 TextBlock (textBlock1) 是包含在明確建立的 ListBoxItem 內,而第二個 TextBlock (textBlock2) 則不是。這個範例也會為 ListBoxItem 定義 Style,以將 ListBoxItemForeground 設定為 green。

<!--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

因為每個 TextBlock 控制項的 Foreground 屬性都是繼承自它的個別邏輯父代,所以 textBlock1 中的字串是綠色,而 textBlock2 中的字串會是藍色。textBox1 的邏輯父代是 ListBoxItem,而 textBox2 的邏輯父代是 ListBox。如需詳細資訊,請參閱屬性值繼承

HeaderedItemsControl

HeaderedItemsControl 繼承自 ItemsControl 類別。HeaderedItemsControl 定義 Header 屬性,而遵循的規則是與 HeaderedContentControlHeader 屬性相同。WPF 提供三個繼承自 HeaderedItemsControl 的控制項:

下列範例將建立 TreeViewItemTreeView 包含單一 TreeViewItem (標籤為 TreeViewItem 1),而且具有下列項目:

注意事項:

因為 RectangleStackPanel 是繼承自 Visual 類別,所以這個範例會明確建立後兩個項目的 TreeViewItem 物件。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 內容模型