WPF Simplified Part 10: WPF Framework Class Hierarchy

The WPF framework contains three major components, the PresentationFramework, the PresentationCore, and the milcore (Media Integration Layer Core).

image

While PresentationFramework and PresentationCore form the managed WPF programming model, MSDN says this about the milcore,

Milcore is written in unmanaged code in order to enable tight integration with DirectX. All display in WPF is done through the DirectX engine, allowing for efficient hardware and software rendering. WPF also required fine control over memory and execution. The composition engine in milcore is extremely performance sensitive, and required giving up many advantages of the CLR to gain performance.

The WPF framework contains many classes and its useful to get an overall view of the class hierarchy. The diagrams below show some of the major class in the framework (not all classes are included) in the WPF class hierarchy.

image

The FrameworkElement contains most of the UI controls that we usually work with, and is expanded below,

image

Some notes about the class hierarchy,

1. All classes in the WPF framework derive from System.Object.

2. Deriving from System.Threading.DispatcherObject gives us a CLR object that has single thread affinity (STA) behavior. Since all the UI controls derive from DispatcherObject, all WPF UI controls can be accessed only on the thread that created it, and are thus inherently thread-unsafe. Some of the methods are of the class are,

    public Dispatcher Dispatcher { get; }
    public bool CheckAccess();
    public void VerifyAccess();
 

3. System.Windows.Dependency is the base class that supports dependency and attached properties. Some of the methods are of the class are,

    public void SetValue(DependencyProperty dp, object value);
    public object GetValue(DependencyProperty dp);
    public void ClearValue(DependencyProperty dp);
 

4. System.Windows.Media.Visual is the entry point to the WPF composition system. This is the base class for all objects that have their own visual representation. Some of the methods are of the class are,

    protected DependencyObject VisualParent { get; }
    protected void AddVisualChild(Visual child);
    protected void RemoveVisualChild(Visual child);
 

5. System.Windows.UIElement adds support for core features like, Events, Input, Layout, and CommandBindings. Some of the methods are of the class are,

    public event MouseButtonEventHandler PreviewMouseLeftButtonDown;
    public event MouseButtonEventHandler MouseLeftButtonDown;
    public static readonly DependencyProperty IsEnabledProperty;
    public bool IsMouseOver { get; }
 

6. System.Windows.FrameworkElement extends the layout features of UIElement and adds support for features like, styles, data binding, resources, data templates, tooltips, and animation. Some of the methods are of the class are,

    public double MinHeight { get; set; }
    public Style Style { get; set; }
    public ResourceDictionary Resources { get; set; }
    public object FindResource(object resourceKey);
    public object ToolTip { get; set; }
    public void BeginStoryboard(Storyboard storyboard);
 

7. System.Windows.Controls.Control adds support for templating and is the base class for the familiar UI controls like Button, Grid, and TextBox. Some of the methods are of the class are,

    public ControlTemplate Template { get; set; }
    public Brush Background { get; set; }
    public FontFamily FontFamily { get; set; }
Digg This
Advertisements

WPF Simplified Part 9: Logical Resources

There are two types of resources in WPF – binary and logical. Binary resources are plain old .NET resources like bitmaps, fonts, and strings that can either be embedded inside the assembly or packaged in a resource file. We’ll discuss this in another post.

Logical resources, henceforth called resources are new to WPF. In XAML, resources are simply objects stored in an element’s Resource property so that they can be shared by child elements. Just about any object in WPF can be a resource, like a brush, a color, some text, some style etc.

Consider the simple XAML,

<Grid>
    <Button Height="100" Width="100" Content="Some Text" FontStyle="Italic" />
    <RadioButton Content="Some Text" FontStyle="Italic" />

</Grid>

Since we want both the child elements of Grid to have the same FontStyle we could define a FontStyle resource, put it in the Grid’s Resource property and point the FontStyles of the Button and RadioButton to the resource, like below,

<Grid>
     <Grid.Resources>
         <!-- Define a FontStyle resource -->
         <FontStyle x:Key="GridFontStyle">Italic</FontStyle>
     </Grid.Resources>

     <Button Height="100" Width="100" Content="Some Text" 
             FontStyle="{StaticResource GridFontStyle}" />
     <RadioButton Content="Some Text" FontStyle="{StaticResource GridFontStyle}" />     
 </Grid>

Doing this in code is simple enough,

<Grid x:Name="MyGrid">
    <Button x:Name="MyButton"  Height="100" Width="100" Content="Some Text" />
    <RadioButton x:Name="MyRadioButton" Content="Some Text" />    
</Grid>

// And in code...
this.MyGrid.Resources.Add("GridFontStyle", FontStyles.Italic);
this.MyButton.FontStyle = (FontStyle)this.MyGrid.FindResource("GridFontStyle");
this.MyRadioButton.FontStyle = (FontStyle)this.MyGrid.FindResource("GridFontStyle");

Every resource in the element’s Resource property must be unique, this is because internally WPF uses a ResourceDictionary to implement the resources. Thus, the x:Key that you specify in XAML, or equivalently the first parameter to Add, becomes the key in the dictionary for that resource. Note that although we have used a String object as the key any object can be used as a key. However, we don’t have to explicitly set the x:Key on all resources, for example in the case of Styles setting the TargetType is sufficient (for an example see here), and WPF implicitly creates an x:Key for the resource.

Resources are scoped hierarchically, like this,imageSo when we use FindResource to get a resource like in the above example, FindResource will start looking at the parent’s resources, then the parent’s parent’s resources etc., traversing the logical tree upward all the way to Window’s resources, then the entire Application’s resources and eventually the System’s resources! FindResource is quite a hard worker,

[In App.xaml]

<Application.Resources>
    <FontStyle x:Key="ApplicationFontStyle">Italic</FontStyle>
</Application.Resources>

[In Window1.xaml]

<Window.Resources>
    <FontStyle x:Key="WindowFontStyle">Italic</FontStyle>
</Window.Resources>
    
<Grid x:Name="MyGrid">
    <Button Height="100" Width="100" Content="Some Text" FontStyle="{StaticResource WindowFontStyle}" />
    <RadioButton Content="Some Text" FontStyle="{StaticResource ApplicationFontStyle}" />
</Grid>

We can use FrameworkElement.Resources to search the element’s resource dictionary directly, which will return null if the resource isn’t found.

// Looks up the Grid's ResourceDictionary directly using a key and returns null
this.MyButton.FontStyle = (FontStyle)this.MyGrid.Resources["WindowFontStyle"];

// Looks up the logical tree and finds the resource in Window's resources
this.MyButton.FontStyle = (FontStyle)this.MyGrid.FindResource("WindowFontStyle");

In addtion to using StaticResource in XAML, we could also use DynamicResource, the differences being,

                                                  StaticResource                                                DynamicResource
The reference must appear after the resource to which it refers. Forward references are allowed.
The resource is applied only once – the first time it is needed. The resource is reapplied every time the resource changes.
Can be used on regular .NET properties and almost anywhere. Can only be used to set dependency property values.
Resources are loaded when the Window or Page loads. Not loaded until the resource is actually used.
Code equivalent: FindReference Code equivalent: SetResourceReference

As an example, consider the XAML,

<Window.Resources>
    <RadioButton x:Key="MyRadioButton" x:Shared="False" Content="Some Text" />
</Window.Resources>
    
<Grid>
    <!-- The button’s background is set to a System resource -->
    <Button x:Name="MyButton" Height="100" Width="100" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" >

        <!-- The static resource can be used anywhere... --> 
        <StaticResource ResourceKey="MyRadioButton" />
    </Button>

    <!-- We can use the same resource here because we set x:Shared to False -->
    <StaticResource ResourceKey="MyRadioButton" />        
</Grid>

A few notes about the XAML above,

1. The button’s Background dependency property is set to a system resource. Setting the property like this,

    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"

is interpreted as a dynamic reference to a resource named by the string SystemColors.ControlBrushKey. Setting x:Static means the text should be treated as the name of a static property not a string. The equivalent in code is,

    this.MyButton.SetResourceReference(Button.BackgroundProperty, SystemColors.ControlBrushKey);

2. The RadioButton resource is used to instantiate a radio button inside the button and also inside the grid. By default, when a resource is applied in multiple places, the same object instance is used, so a resource can only be applied once in an element tree because each resource application is the same instance. This can be overridden by setting x:Shared to false. Note that you’ll get a VisualStudio designer error if you’re trying to use x:Shared and instantiate a new copy of the resource, x:Shared will work fine in a compiled XAML.

image

Digg This

WPF Simplified Part 8: Data Templates

In WPF, a control can specify how it wants to display a .NET object, by specifying a UI template for the object. Thus, when the object is bound to the control, the object is displayed using the (data) template for it that the control has specified. This data template overrides the control’s visual tree just like a control template. The DataTemplate can determine both the visual aspect of how the data is presented and also how data binding accesses different properties of the data object. You can store the data template for an object as a resource and have different controls use the same data template to display the object, so that the object’s UI representation is similar across different controls. Also, by applying different data templates to the same data, you can flexibly change the visual appearance of the data in your application.

Consider the simple XAML,

<Window x:Class="WpfApplication3.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Button Height="100" Width="100" />
    </Grid>
</Window>

And say we want to display this simple .NET object,

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}

We can do this by defining a DataTemplate and assigning to the button’s ContentTemplate,

<Button Height="100" Width="100" Content="{Binding}">
    <Button.ContentTemplate>
        <!-- Define a DataTemplate for the Person type... --> 
        <DataTemplate DataType="{x:Type local:Person}">
            <!-- ...with a custom visual tree for the data object... -->
            <TextBlock>
                <!-- ...and binding to different properties of the data object -->
                <TextBlock Text="{Binding Name}" />
                (age:<TextBlock Text="{Binding Age}" />)
            </TextBlock>
        </DataTemplate>
    </Button.ContentTemplate>
</Button>
 
// And in code, do this,
Person person = new Person() { Name = "John", Age = 32 };
this.DataContext = person;
 

The XAML above creates a DataTemplate asociated with the Person type. This data template has its own visual tree (consisting of TextBlocks or anything else we could want). Now when the button’s Content is bound to an object of type Person, the data template will handle the way the button displays the Person object.

Say we want to reuse this DataTemplate (that is associated with the Person type), we can declare it as a resource and any control can then use the data template to display the Person object.

<Grid>
    <Grid.Resources>       
        <!-- Define a DataTemplate for the Person type -->
        <DataTemplate DataType="{x:Type local:Person}">
            <TextBlock>
                <TextBlock Text="{Binding Name}" />
                (age:<TextBlock Text="{Binding Age}" />)
            </TextBlock>
        </DataTemplate>
    </Grid.Resources>
    
    <!-- The button's Content is bound to a Person type -->
    <Button Height="100" Width="100" Content="{Binding}" />
    
    <!-- The radiobutton's Content is bound to a Person type -–> 
    <RadioButton>
        <RadioButton.Content>
            <Binding />
        </RadioButton.Content>
    </RadioButton>

</Grid>
 

// And in code, do this, 
Person person = new Person() { Name = "John", Age = 32 }; 
this.DataContext = person; 
 

DataTemplates can be applied to both content controls and item controls. For content controls, the ContentTemplate property is set to a DataTemplate, and for item controls the ItemTemplate property is set to a DataTemplate. The following is a partial list of the controls that support DataTemplates,

   Content controls using the  
   ContentTemplate property
     Item controls using the

     ItemTemplate property
     Other properties that can

     be set to a DataTemplate
                        Button                   ComboBox               CellTemplate
                     CheckBox                 ContextMenu           HeaderTemplate
                         Label                      ListBox      ColumnHeaderTemplate
                 RadioButton                     ListView     SelectedContentTemplate
                  TabControl                    StatusBar    SelectionBoxItemTemplate
                     ToolTip                     ToolBar
                     Window                    TreeView  

 

There are three ways we can change the data template based on properties of the bound data object:

1. Data triggers:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication3"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Grid.Resources>
           
            <!-- Define a named DataTemplate -->
            <DataTemplate DataType="{x:Type local:Person}">
                <!-- Custom visual tree of how the data will be presented -->
                <TextBlock>
                    <!-- Binding to different properties of the data object -->
                    <TextBlock Text="{Binding Name}" />
                    (age:<TextBlock x:Name="AgeTextBlock" Text="{Binding Age}" />)
                </TextBlock>
    
                <!-- Add a trigger to the template... –>
                <DataTemplate.Triggers>
                    <!-- ...that binds to the Age property of the data -->
                    <DataTrigger Binding="{Binding Path=Age}">
                        <!-- ...and if the Age is 32 –>
                        <DataTrigger.Value>
                            <System:Int32>32</System:Int32>
                        </DataTrigger.Value>
                        <!-- ...we’ll change the font color to red -->
                        <Setter TargetName="AgeTextBlock" Property="Foreground" Value="Red" />
                    </DataTrigger>
                </DataTemplate.Triggers>

            </DataTemplate>
        </Grid.Resources>
 
        <!-- The button's Content is bound to a Person type via code -->
        <Button Height="100" Width="100" Content="{Binding}" />
    </Grid>
</Window>
 
// And in code, do this,
Person person = new Person() { Name = "John", Age = 32 };
this.DataContext = person;

We could also use a trigger inside a Style to change the DataTemplate of the button,

<Grid>
    <Grid.Resources>
 
        <!-- Define a Style element... -->
        <Style TargetType="{x:Type Button}">
            <Style.Triggers>
                <!-- ...with a watch on the binding object’s Name property -->
                <DataTrigger Binding="{Binding Path=Name}" Value="John">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <!-- define any custom UI here -->
                                <TextBlock Text="{Binding Path=Name}" Foreground="Red" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>

    </Grid.Resources>
    <!-- The button's Content is bound to a Person type via code -->
    <Button Height="100" Width="100" Content="{Binding}" />
</Grid>

 
// And in code, do this,
Person person = new Person() { Name = "John", Age = 32 };
this.DataContext = person;

or we could set the ContentTemplate to a named DataTemplate,

<Grid>
    <Grid.Resources>
 
        <!-- Define a named data template -->        
        <DataTemplate x:Key="FirstTemplate">
            <TextBlock Text="{Binding Path=Name}" Foreground="Red" />
        </DataTemplate>

        <!-- Define a Style element -->
        <Style TargetType="{x:Type Button}">
            <Style.Triggers>
                <!-- ...with a watch on the binding object’s Name property -->
                <DataTrigger Binding="{Binding Path=Name}" Value="John">
                    <!-- apply the named data template -->
                    <Setter Property="ContentTemplate" Value="{StaticResource FirstTemplate}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>

    </Grid.Resources>
    <!-- The button's Content is bound to a Person type via code -->
    <Button Height="100" Width="100" Content="{Binding}" />
</Grid>

 
// And in code, do this,
Person person = new Person() { Name = "John", Age = 32 };
this.DataContext = person;

2. Template Selector: Use the DataTemplateSelector class to choose the template you want to apply based on the bound object’s properties. In XAML,

<Grid>
    <Grid.Resources>
        <!-- Define a named DataTemplate -->
        <DataTemplate x:Key="firstTemplate">
            <TextBlock>
                <TextBlock Text="{Binding Name}" />
                (age:<TextBlock Text="{Binding Age}" />)
            </TextBlock>
        </DataTemplate>

        <!-- Define a named DataTemplate -->
        <DataTemplate x:Key="secondTemplate">
            <TextBlock>
                <TextBlock Text="{Binding Age}" />
                (name:<TextBlock Text="{Binding Name}" />)
            </TextBlock>
        </DataTemplate>
 
        <!-- Select the template based on the code behind -->
        <local:PersonTemplateSelector x:Key="templateSelector"
            FirstTemplate="{StaticResource firstTemplate}"
            SecondTemplate="{StaticResource secondTemplate}" />

    </Grid.Resources>
    
    <!-- Add the TemplateSelector property -->
    <Button Height="100" Width="100" Content="{Binding}" 
                         ContentTemplateSelector="{StaticResource templateSelector}" />
</Grid>
 

And in the code behind,

/// <summary>
/// The template selector class
/// </summary>
public class PersonTemplateSelector : DataTemplateSelector {
    // Define a set of properties that represent all the templates 
    // that this selector can return.
    public DataTemplate FirstTemplate { get; set; }
    public DataTemplate SecondTemplate { get; set; }

    public override DataTemplate SelectTemplate (object item, DependencyObject container) {

        if (item != null) {
            Person person = (Person)item;

            // Return a template based on the object’s properties,
            if (person.Age < 32)
                return FirstTemplate;
            else
                return SecondTemplate;
        }
        return null;
    }
}
 

3. Value Converter: Takes an incoming bound object property value and returns a changed UI property value. This is the topic of a future post.

Digg This