#1,191 – Custom Element with a Single Child Element

Below is an example of a simple custom element that derives from FrameworkElement and includes a single child element (UIElement), set using the Child dependency property.

Here’s the full code for the custom element.  We do the following:

  • Override both Measure and Layout
  • Override VisualChildrenCount and GetVisualChild to respond indicating that we have a single child
  • Override Render to render the graphical portion of the control
  • Use AddVisualChildAddLogicalChild to indicate that the child element belongs to the parent element
    public class MyElement : FrameworkElement
    {
        private static FrameworkPropertyMetadata childMetadata =
                   new FrameworkPropertyMetadata(null,
                            FrameworkPropertyMetadataOptions.AffectsParentArrange,
                            new PropertyChangedCallback(OnChildChanged));

        public static void OnChildChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            MyElement thisElement = obj as MyElement;
            if (thisElement == null)
                throw new Exception("Child property must be attached to MyElement");

            // Remove old one
            Visual oldChild = e.OldValue as Visual;
            if (oldChild != null)
            {
                thisElement.RemoveVisualChild(oldChild);
                thisElement.RemoveLogicalChild(oldChild);
            }

            // Attach new one
            Visual newChild = e.NewValue as Visual;
            if (newChild != null)
            {
                thisElement.AddVisualChild(newChild);
                thisElement.AddLogicalChild(newChild);
            }
        }

        public static readonly DependencyProperty ChildProperty =
            DependencyProperty.RegisterAttached("Child", typeof(UIElement),
                typeof(MyElement), childMetadata);

        public static void SetChild(DependencyObject depObj, UIElement value)
        {
            depObj.SetValue(ChildProperty, value);
        }

        protected override int VisualChildrenCount
        {
            get
            {
                UIElement childElement = (UIElement)GetValue(ChildProperty);
                return childElement != null ? 1 : 0;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            // (ignoring index)
            return (UIElement)GetValue(ChildProperty);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            UIElement childElement = (UIElement)GetValue(ChildProperty);
            if (childElement != null)
                childElement.Measure(availableSize);

            // "X" and child both use all of the available space
            return availableSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            UIElement childElement = (UIElement)GetValue(ChildProperty);
            if (childElement != null)
                childElement.Arrange(new Rect(new Point(0.0, 0.0), finalSize));

            return finalSize;
        }

        // Render a big "X"
        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(ActualWidth, ActualHeight));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(ActualWidth, 0.0),
                new Point(0.0, ActualHeight));
        }
    }

We can now use this element as follows:

    <Grid>
        <loc:MyElement>
            <loc:MyElement.Child>
                <Label Content="I'm the child" HorizontalAlignment="Center"/>
            </loc:MyElement.Child>
        </loc:MyElement>
    </Grid>

When rendered, it looks like this:

1191-001

#1,190 – VisualChildrenCount and GetVisualChild

FrameworkElement provides the VisualChildrenCount property and the GetVisualChild method, typically used during layout to walk through the child elements of the element participating in layout.  When you create a custom control that derives from FrameworkElement, you typically override both members, providing values that make sense for your control.

Below, we use these members to get a report of the visual children of a custom StackPanel.  (Because neither member is public, we override StackPanel to allow access).

XAML is:

    <StackPanel>
        <loc:MyStackPanel x:Name="aStackPanel">
            <Label Content="Hi there"/>
            <Button Content="Click me"/>
        </loc:MyStackPanel>

        <Button Content="Visual Children" Click="Button_Click"/>
    </StackPanel>

Code-behind is:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            StringBuilder sbMsg = new StringBuilder();

            sbMsg.Append("Children of MyStackPanel:\n");
            int childCount = aStackPanel.MyVisualChildrenCount;
            for (int i = 0; i < childCount; i++)
            {
                Visual child = aStackPanel.MyGetVisualChild(i);
                sbMsg.AppendFormat(" - {0}\n", child.GetType().ToString());
            }

            MessageBox.Show(sbMsg.ToString());
        }

Results at run-time:
1190-001

#1,189 – MeasureOverride Input and Output

The MeasureOverride method in FrameworkElement takes as input a Size and then returns a Size.  These parameters are intended to be used as follows:

  • Input Size – during layout, this is how much room is available for the element to be rendered
  • Output Size – this is how much room the element says that it needs

For example, below we have a custom element that draws a big “X” using all of the available space.  (When rendering, it uses ActualWidth and ActualHeight).  Because it will use as much room as it is given, MeasureOverrides return value is all of the available space.

    public class MyElement : FrameworkElement
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            Size sizeIWillNeed = availableSize;

            return sizeIWillNeed;
        }

        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(ActualWidth, ActualHeight));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(ActualWidth, 0.0),
                new Point(0.0, ActualHeight));
        }
    }

#1,188 – Using an ItemContainerStyle to Animate Items in an ItemsControl

One common use when defining a new ItemContainerStyle for an ItemsControl is to do something special with either the currently selected item or the item that the mouse is currently over.

We can set property values for the currently selected item or the item that the mouse is over, using triggers and property setters.  You can also use a storyboard to animate a transition to a new property value.

Below, we do two things to animate the item that the mouse is currently over.  (Both are done in the Style used for the ItemContainerStyle).

  • Add a Setter for the LayoutTransform, containing a ScaleTransform
  • Trigger on IsMouseOver, setting up a storyboard to animate the ScaleX and ScaleY properties of the ScaleTransform
        <Style x:Key="FocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
        <SolidColorBrush x:Key="Item.MouseOver.Border" Color="#a826A0Da"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>

        <Style x:Key="lbDefaultItemContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="Padding" Value="4,1"/>
            <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
            <Setter Property="LayoutTransform">
                <Setter.Value>
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Trigger.EnterActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation To="1.1" Duration="0:0:0.3"
                                                             Storyboard.TargetProperty="LayoutTransform.(ScaleTransform.ScaleX)"/>
                                            <DoubleAnimation To="1.1" Duration="0:0:0.3"
                                                             Storyboard.TargetProperty="LayoutTransform.(ScaleTransform.ScaleY)"/>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </Trigger.EnterActions>
                                <Trigger.ExitActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation To="1" Duration="0:0:0.3"
                                                             Storyboard.TargetProperty="LayoutTransform.(ScaleTransform.ScaleX)"/>
                                            <DoubleAnimation To="1" Duration="0:0:0.3"
                                                             Storyboard.TargetProperty="LayoutTransform.(ScaleTransform.ScaleY)"/>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </Trigger.ExitActions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
                            </Trigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="False"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="True"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

 

#1,187 – Using an ItemContainerStyle to Change Items in an ItemsControl

One common use when defining a new ItemContainerStyle for an ItemsControl is to do something special with either the currently selected item or the item that the mouse is currently over.  The default template used as an ItemContainerStyle for a ListBox, for example, changes the background and border for both the selected item and the item that the mouse is currently over.

Below is an example where we change the FontWeight for the currently selected item (to SemiBold) and make the item larger, using a transform.  This method uses a trigger to change several properties.

        <Style x:Key="FocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
        <SolidColorBrush x:Key="Item.MouseOver.Border" Color="#a826A0Da"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>
        
        <Style x:Key="lbDefaultItemContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="Padding" Value="4,1"/>
            <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
                                <Setter Property="LayoutTransform">
                                    <Setter.Value>
                                        <ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
                                    </Setter.Value>
                                </Setter>
                                <Setter Property="FontWeight" Value="SemiBold"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="False"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="True"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

The resulting behavior looks like this:
1187-001

#1,186 – Default ItemContainerStyle for a ListBox

The default style for the ItemContainerStyle of a ListBox (version 4.5.1 of the .NET Framework) is shown below.  The template is included within a style that includes some other default property values.

This style is applied to each ListBoxItem rendered in the ListBox.  The structure of each ListBoxItem is a Border surrounding a ContentPresenter.  More importantly, the style shows the default triggers that control how items are rendered when you move the mouse over an item or select an item.

        <Style x:Key="lbDefaultItemContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="Padding" Value="4,1"/>
            <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="False"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="True"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

The style references some other predefined resources, shown below:

        <Style x:Key="FocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
        <SolidColorBrush x:Key="Item.MouseOver.Border" Color="#a826A0Da"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>

You would use the main style as follows:

        <ListBox Margin="5" ItemsSource="{Binding MovieList}"
                 ItemContainerStyle="{DynamicResource lbDefaultItemContainerStyle}" />

#1,185 – ItemsControl Customization Summary

You can can customize an ItemsControl, or any of the controls that derive from it (ComboBox, DataGrid, ListBoxTabControl, TreeView et al), in a number of ways.  The various customization mechanisms are summarized below.

  • Set Style to apply a set of property values for the main control
  • Set Template to change the control template of the control.
    • This changing the highest level of the control (e.g. for a ListBox, a Border wrapping a ScrollViewer, in turn wrapping an ItemsPresenter)
  • Set ItemsPanel to change the panel used to lay out the individual items
    • E.g. User horizontally oriented StackPanel to lay out ListBox items horizontally, rather than vertically
  • Set ItemContainerStyle to set properties that apply to the container for each item
    • E.g. Properties that apply to each ListBoxItem within a ListBox
  • Set ItemTemplate to change the data template used to render each item in the list
    • E.g. Create a custom layout for each item in a ListBox

 

#1,184 – Where Data Templates Are Used, part II

A data template is typically set as the value of the ContentTemplate property of a ContentControl or the ItemTemplate property of an ItemsControl.

The ContentTemplate and ItemTemplate properties are typically used as follows:

  • The ContentTemplate is used as the ContentTemplate of a ContentPresenter, which is itself contained in the control’s control template
  • The ItemTemplate is used as the ContentTemplate for each item presented by an ItemsPresenter (e.g. used as ContentTemplate for a ListBoxItem).

Below is a fragment of the default control template for a Label. You can see that it contains a ContentPresenter that has its ContentTemplate set to the ContentTemplate of the parent control.

                    <ControlTemplate TargetType="{x:Type Label}">
                        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                            <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>

#1,183 – Where Data Templates Are Used

Data templates dictate how bound data is mapped to one or more control.  A data template can be used in two places:

  • As value of ContentTemplate property for a ContentControl  (e.g. a Label)
  • As value of ItemTemplate property for an ItemsControl  (e.g. a ListBox)

Here’s an example of a DataTemplate used as the ContentTemplate for a Label:

<Label Name="lblPerson" Content="{Binding}">
    <Label.ContentTemplate>
        <DataTemplate>
            <Border BorderThickness="2" BorderBrush="DarkBlue">
                <StackPanel Orientation="Vertical">
                    <StackPanel Orientation="Horizontal">
                        <Label Content="{Binding Path=FirstName}"/>
                        <Label Content="{Binding Path=LastName}" FontWeight="Bold"/>
                    </StackPanel>
                    <Label Content="{Binding Path=BirthYear}" FontStyle="Italic"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </Label.ContentTemplate>
</Label>

Here’s an example of a DataTemplate used as the ItemTemplate for a ListBox:

  <ListBox Margin="15" Width="270" Height="320"
         ItemsSource="{Binding ActorList}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Image Source="{Binding Image}" Height="80"/>
                    <StackPanel Margin="5">
                        <TextBlock Text="{Binding FullName}" FontSize="12" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Dates}"/>
                        <TextBlock Text="{Binding KnownFor}" Margin="0,5,0,0" FontStyle="Italic"/>
                    </StackPanel>
                    <Label Content="Dead Fred !" Foreground="Red"
                           FontWeight="Bold"
                           Visibility="{Binding Converter={StaticResource deadFredConverter}}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

#1,182 – Using RenderSize Properties in Custom Elements

When writing rendering code for a custom element that derives from FrameworkElement, you can use the ActualWidth and ActualHeight properties to know how to render the element.  These properties indicate the desired final size of the element, after all layout calculations have been done.

    public class MyFrameworkElement : FrameworkElement
    {
        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(ActualWidth, ActualHeight));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(ActualWidth, 0.0),
                new Point(0.0, ActualHeight));
        }
    }

If a custom control derives from UIElement, it won’t have access to the ActualWidth and ActualHeight properties, but can instead use RenderSize.Width and RenderSize.Height.  (You can also use these properties from within an element that derives from FrameworkElement, since FrameworkElement inhertis from UIElement).

    public class MyUIElement : UIElement
    {
        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(RenderSize.Width, RenderSize.Height));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(RenderSize.Width, 0.0),
                new Point(0.0, RenderSize.Height));
        }
    }