Controls Content Model Overview
This topic discusses the content models used by classes that inherit from Control. The content model specifies the types of objects that a control can contain. In this topic, the term "control" is limited to a class that has the Control class somewhere in its class hierarchy. The four content models discussed in this topic are defined by the four following classes that inherit from Control:
ContentControl contains a single item.
HeaderedContentControl contains a header and a single item.
ItemsControl contains a collection of items.
HeaderedItemsControl contains a header and a collection of items.
These four classes act as base classes for most of the controls in WPF. Classes that use these content models can contain the same types of content and treat the content in the same way; any type of object that can be placed in a ContentControl (or a class that inherits from ContentControl) can be placed in a control that has any of the other three content models. The following figure shows one control from each content model that contains an image and some text.
This topic contains the following sections.
- Prerequisites
- ContentControl
- HeaderedContentControl
- ItemsControl
- HeaderedItemsControl
- Related Topics
Prerequisites
This topic assumes that you have a basic understanding of WPF and know how to add controls to an application. For more information, see Getting Started with Windows Presentation Foundation and Controls Overview.
ContentControl
The simplest content model of the four is the ContentControl, which has a Content property. The Content property is of type Object, so there are no restrictions on what you can put in a ContentControl. You can use either Extensible Application Markup Language (XAML) or code to set the Content.
The following controls use the ContentControl content model:
The following example demonstrates how to create four Button controls with Content set to one of the following:
Note
The example Extensible Application Markup Language (XAML) version could use the <Button.Content> tags around the content of each button, but it is not necessary. For more information, see XAML Overview.
<!--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;
The following figure shows the four buttons created in the previous example.
HeaderedContentControl
The HeaderedContentControl inherits the Content property from ContentControl and defines the Header property that is of type Object. Header provides a heading for the control. Like the Content property of a ContentControl, the Header can be any type. WPF ships three controls that inherit from HeaderedContentControl:
The following example creates a TabControl (an ItemsControl) that contains two TabItem objects. The first TabItem has rich content in both the Header and the Content: the Header is set to a StackPanel that contains an Ellipse and a TextBlock, and the Content is set to a StackPanel that contains a TextBlock and a Label. The Header of the second TabItem is set to a string, and the Content is set to a single 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>
The following illustration shows the TabControl created by the previous example.
ItemsControl
Controls that inherit from ItemsControl contain a collection of objects. An example of an ItemsControl is the ListBox. You can use either the ItemsSource property or the Items property to populate an ItemsControl.
ItemsSource Property
The ItemsSource property of the ItemsControl enables you to use any type that implements IEnumerable as the content of the ItemsControl. ItemsSource is typically used to display a data collection or to bind an ItemsControl to a collection object.
The following example creates a class called MyData that is a simple string collection.
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");
}
}
The following example binds ItemsSource to 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);
The following figure shows the ListBox created in the previous example.
For more information about data binding, see Data Binding Overview.
Items Property
If you do not want to use an object that implements IEnumerable to populate the ItemsControl, you can add items by using the Items property. The items in an ItemsControl can have types that are different from each other. For example, a ListBox can contain one item that is a string and another item that is an Image.
Note
When the ItemsSource property is set, you can use the Items property to read the ItemCollection, but you cannot add to or modify the ItemCollection. Setting the ItemsSource property to a null reference (Nothing in Visual Basic) removes the collection and restores usage to Items, which will be an empty ItemCollection.
The following example creates a ListBox with four different types of items.
<!--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);
The following figure shows the ListBox created in the previous example.
Item Container Classes
Each ItemsControl that ships with WPF has a corresponding class that represents an item in the ItemsControl. The following table lists the ItemsControl objects that ship with WPF and their corresponding item containers.
ItemsControl |
Item container |
---|---|
You can explicitly create an item container for each item in the ItemsControl, but it is not necessary. Whether to create an item container in your ItemsControl depends largely on your scenario. For example, if you bind data to the ItemsSource property, you will not explicitly create an item container. The following points are important to keep in mind:
The type of the objects in the ItemCollection differs depending on whether you explicitly create an item container.
You can get the item container even if you do not explicitly create it.
A Style with the TargetType set to an item container is applied regardless of whether the item container is explicitly created.
Property inheritance behaves differently for implicitly and explicitly created item containers because only explicitly created item containers are part of the logical tree.
To illustrate these points, the following example creates the two ListBox controls. The example creates ListBoxItem objects for the first ListBox, but not the second ListBox. In the second case, a ListBoxItem is implicitly created for each item in the ListBox.
<!--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>
The ItemCollection for each ListBox is different. Each item in the Items property of the first ListBox is a ListBoxItem, but is a different type in the second ListBox. The following example confirms this by iterating through the items in both ListBox controls and checking the type of each item.
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
*/
The following figure shows the two ListBox controls that were created in the previous example.
Often, you will want the item container for an item. but you have not explicitly created it in your application. To get the item container that is associated with a particular item, use the ContainerFromItem method. The following example shows how to get an item container associated with an item when a ListBoxItem is not explicitly created. The example assumes that the object called itemToSelect is not a ListBoxItem and has been added to the 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;
}
Styles that have the TargetType set to an item container are applied to both implicitly and explicitly created item containers. The following example creates a Style as a resource for a ListBoxItem that horizontally centers the contents in the ListBoxItem. When this style is applied to the ListBox objects, the items in both of the ListBox objects are centered.
<!--Create a Style as a Resource.-->
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
The following figure shows the two ListBox controls when the style in the previous example is applied.
How property inheritance works with styles and item containers is related to how the logical tree is structured. When you explicitly create the item container, it is part of the logical tree. If you do not create the item container, it is not part of the logical tree. The following figure shows the difference in the logical tree for the two ListBox controls in the previous example.
Objects that inherit from the Visual class inherit property values from their logical parent. The following example creates a ListBox with two TextBlock controls and sets the Foreground property of the ListBox to blue. The first TextBlock, textBlock1, is contained within an explicitly created ListBoxItem and the second TextBlock, textBlock2, is not. The example also defines a Style for a ListBoxItem that sets the Foreground of a ListBoxItem to 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>
The following figure shows the ListBox that was created in the previous example.
The string in textBlock1 is green and the string in textBlock2 is blue because each TextBlock control inherits the Foreground property from its respective logical parent. The logical parent of textBox1 is the ListBoxItem, and the logical parent of textBox2 is the ListBox. For more information, see Property Value Inheritance.
HeaderedItemsControl
The HeaderedItemsControl inherits from the ItemsControl class. The HeaderedItemsControl defines the Header property, which follows the same rules as the Header property of a HeaderedContentControl. WPF ships three controls that inherit from HeaderedItemsControl:
The following example creates a TreeViewItem. The TreeView contains a single TreeViewItem, which is labeled TreeViewItem 1, and has the following items:
A string.
A DateTime object
A TreeViewItem that contains a Rectangle in its Header.
A TreeViewItem whose Header property is set to a StackPanel that contains two objects.
Note
The example explicitly creates TreeViewItem objects for the last two items because Rectangle and StackPanel inherit from the Visual class. The default style for the TreeViewItem sets the Foreground property. The child objects inherit the property value from the explicitly created TreeViewItem, which is typically the desired behavior.
<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>