Welcome to WindowsClient.net | Sign in | Join

David Wetzel's Blog

WPF / Silverlight Blog

Sponsors





  • advertise here

Tags

No tags have been created or used yet.

June 2009 - Posts

WPF Styles and Templates – Part II (ComboBox Customization)

Introduction

This is the second part of my article on customizing controls in WPF. If you have not read the first part and are not familiar with Styles you may want to read part I http://windowsclient.net/blogs/airborneengineer/archive/2009/06/09/wpf-styles-and-templates-part-i.aspx . In this article we will focus on ComboBoxes. We make a ComboBox that is capable of displaying an Image and Text and also show how binding works when storing classes in ComboBoxes.

image

Customizing a ComboBox

In order to get an image and text to display in the ComboBox, we need to add a new Style to the ComboBox which will modify the ContentPresenter having it display the controls that we want it to display in this case a StackPanel with an Image and TextBlock inside. As described in the previous article, I created a copy of the existing template and began breaking it up into pieces. The first thing I did was located the ContentPresenter located in the ComboBox main template:

 
<ContentPresenter IsHitTestVisible="false" 
                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                  Margin="{TemplateBinding Padding}" 
                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                  Content="{TemplateBinding SelectionBoxItem}" 
                  ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" 
                  ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>

This I broke up into a DataTemplate more to make it easier to read than anything else. The class that I will be binding to the ComboBox will have two properties that we are interested in, Image (string property which contains the path of the image) and Text (string property which contains the words that will be displayed below the image). The width and max properties are specified here to keep everything the same size so that when the item changes the control is not resizing itself.

<DataTemplate x:Key="DisplayImageWithText">
    <Border Name="Border" Padding="2" 
            SnapsToDevicePixels="true" MaxHeight="65">
        <StackPanel>
            <Image Source="{Binding Image}" Stretch="Fill" 
                   Width="42" Height="Auto"/>
            <TextBlock Text="{Binding Text}" 
                       TextAlignment="Center" 
                       TextWrapping="Wrap" MaxWidth="50"/>
        </StackPanel>
    </Border>
</DataTemplate>

This DataTemplate must then be referenced as a resource in the ContentPresenter:

<ContentPresenter Name="ContentSite" 
                  IsHitTestVisible="False" 
                  Content="{TemplateBinding SelectionBoxItem}" 
                  ContentTemplate="{StaticResource DisplayImageWithText}" 
                  ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" 
                  Margin="3,3,23,3" 
                  VerticalAlignment="Center" 
                  HorizontalAlignment="Left"/>

This takes care of the ComboBox when it is closed. Next we will work on the ComboBoxItem; this is when the drop down is clicked. This is similar to the ComboBox itself but not exact. I use a StackPanel again and specify the size to ensure that all of the images are the same size.

<ControlTemplate TargetType="ComboBoxItem">
    <Border Name="Border" Padding="2" 
            SnapsToDevicePixels="true">
        <StackPanel>
            <Image Source="{Binding Image}" 
                   Stretch="Fill" 
                   Width="60" Height="60"/>
            <ContentPresenter HorizontalAlignment="Center"/>
        </StackPanel>
    </Border>

That completes the customization of the resource. In the source code attached to this article, I have the complete ResourceDictionary that has all of the colors and sections broken out for easy customization.

Binding Data

For this example I have two XML files with data but I chose to use two different methods to access the data to show multiple ways to accomplish the same thing. If we look at the window resources we see the following:

<Window.Resources>
    <XmlDataProvider x:Key="StateData" 
                     Source="Data\StateCodes.xml" 
                     XPath="//State" />

    <ObjectDataProvider x:Key="DropDownProvider" 
                        ObjectType="{x:Type data:CodeItemLoader}" 
                        MethodName="GetSampleData" />

</Window.Resources>

These are the data sources for our application. The first is just a XML data reader that will open the file StateCodes.xml located in the Data directory and return all of the nodes State.

The second data source is a data provider that creates a instance of the CodeItemLoader class and executes the method GetSampleData passing no parameters. If you open the class you will see the class opens an XML file called SampleData.xml and uses LINQ to parse the data and return a generic list of the class CodeItem.

Our first ComboBox is a general drop down list for states. The desire is the drop down list should show the entire state name while the ComboBox itself should only show the abbreviation. The data in the XML looks like this:

<State name="ALABAMA" id="AL" />

Since the data provider is returning back the State node, this is how we would code the ComboBox:

<ComboBox Name="cboStates"
          ItemsSource="{Binding Source={StaticResource StateData}}"  
          SelectedValuePath="@id" 
          DisplayMemberPath="@name" />

If we want to display the code selected by the ComboBox, we do not need to write a bunch of code, all we need to do is use binding. The following TextBox is bound to the ComboBox:

<TextBox Text="{Binding ElementName=cboStates, Path=SelectedValue}" />

The second ComboBox be bound to our second data source and will have the Style that we created earlier to display an image with text. We add the MaxWidth and MaxHeight in here to control the drop down. You could make it longer or taller adjusting these values.

<ComboBox x:Name="cboImageSample" 
          ItemsSource="{Binding Source={StaticResource DropDownProvider}}"
          Style="{StaticResource ImageComboBox}" 
          MaxWidth="350" MaxHeight="70" />

Since we have a list of CodeItem classes in our ComboBox, we can bind to each of the properties of the class as show below:

<TextBox 
Text="{Binding ElementName=cboImageSample, Path=SelectedItem}" />
<TextBox 
Text="{Binding ElementName=cboImageSample, Path=SelectedItem.Code}" />
<TextBox 
Text="{Binding ElementName=cboImageSample, Path=SelectedItem.Text}" />

image

Summary

Microsoft’s approach to controls has really made WPF a powerful tool allowing you to do almost anything you want to a control. Use of binding allows code to be much leaner and easier to maintain.

Source Code demo.zip

Technorati Tags: ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Windows Live Tags: ComboBox,Customization,Introduction,article,archive,ComboBoxes,Image,Text,classes,Style,ContentPresenter,StackPanel,TextBlock,template,IsHitTestVisible,SnapsToDevicePixels,HorizontalAlignment,HorizontalContentAlignment,Margin,VerticalAlignment,VerticalContentAlignment,Content,SelectionBoxItem,ContentTemplate,SelectionBoxItemTemplate,ContentTemplateSelector,ItemTemplateSelector,DataTemplate,path,words,width,size,item,DisplayImageWithText,Border,Name,Source,Stretch,Fill,Auto,TextAlignment,Center,Wrap,MaxWidth,resource,ContentSite,False,StaticResource,Left,ComboBoxItem,images,ControlTemplate,TargetType,code,ResourceDictionary,Data,example,files,Resources,XmlDataProvider,StateData,StateCodes,XPath,State,ObjectDataProvider,DropDownProvider,ObjectType,Type,CodeItemLoader,MethodName,GetSampleData,reader,directory,provider,instance,method,SampleData,LINQ,CodeItem,abbreviation,ALABAMA,node,ItemsSource,SelectedValuePath,DisplayMemberPath,TextBox,ElementName,SelectedValue,ImageComboBox,SelectedItem,Summary,Microsoft,tool,Combo,Demo,Templates,pieces,methods,parameters,easier,itself,cboStates,cboImageSample

WordPress Tags: ComboBox,Customization,Introduction,article,archive,ComboBoxes,Image,Text,classes,Style,ContentPresenter,StackPanel,TextBlock,template,IsHitTestVisible,SnapsToDevicePixels,HorizontalAlignment,HorizontalContentAlignment,Margin,VerticalAlignment,VerticalContentAlignment,Content,SelectionBoxItem,ContentTemplate,SelectionBoxItemTemplate,ContentTemplateSelector,ItemTemplateSelector,DataTemplate,path,words,width,size,item,DisplayImageWithText,Border,Name,Source,Stretch,Fill,Auto,TextAlignment,Center,Wrap,MaxWidth,resource,ContentSite,False,StaticResource,Left,ComboBoxItem,images,ControlTemplate,TargetType,code,ResourceDictionary,Data,example,files,Resources,XmlDataProvider,StateData,StateCodes,XPath,State,ObjectDataProvider,DropDownProvider,ObjectType,Type,CodeItemLoader,MethodName,GetSampleData,reader,directory,provider,instance,method,SampleData,LINQ,CodeItem,abbreviation,ALABAMA,node,ItemsSource,SelectedValuePath,DisplayMemberPath,TextBox,ElementName,SelectedValue,ImageComboBox,SelectedItem,Summary,Microsoft,tool,Combo,Demo,Templates,pieces,methods,parameters,easier,itself,cboStates,cboImageSample
WPF Styles and Templates – Part I

I originally started out with the intention on describing how to make an image or image and text display in a ComboBox.  As I started writing the article, I realized that instead of making one massive article describing what needs to be done, I would break it down is to functional areas.  – Now, even blogs need to have design documents.  J

One of the features that I really like about WPF is that it allows you to change a control’s look and feel into almost anything.  Prior to WPF if you wanted to have an image or and image and text in a ComboBox, you need to look at a third party vendor to create it for you.  This article is part I of a series looking at customizing controls.  In this article we will focus on Styles and Templates. 

Styling Basics

When I first wanted to change my controls to give them more a 3D look, I tried to play around with Styles and Templates I ended up getting some results that were undesired.  Let’s say that you want to change the font to red so you create a control template:

 <!-- x:Key = Name used to reference template 
             TargetType = Type of control template will update -->
 <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
    <ContentControl Content="{TemplateBinding Content}" 
                Foreground="#FFFF0000" />
</ControlTemplate>
<!-- Template = Name used in the x:Key above -->
<Button Content="Close" Template="{StaticResource ButtonTemplate}" />
This results in the following:

 image

Not exactly what we were looking for, but the template did exactly what we told it to do, make the font red and nothing else.  We lost the mouse over highlight functionality and the 3D click effects.  So at this point, many people bail on the idea of doing this and go to the web to find code to borrow.  But there are some really easy ways to go forward.

First off, if you have Microsoft Expression Blend, you have an easy way to create a template with everything filled in for you.  If you do not, download a trial version from http://www.microsoft.com/expression/ .  There are several articles that describe the limitations of the Visual Studio UI for working with controls – Expression Blend will improve your productivity.  That’s all for my sales pitch now back to work…

Open your project and the page that has the button you would like to customize.  (Or create a new project and throw one button on the form.)  On the Interaction Panel, there is a section called Objects and Timeline.  This shows the controls on the page as well as the storyboards resources for this page.  If you expand out the page, you should see your control, right mouse click on the control and the following menu will be displayed:   

image

Edit Control Parts (Template) is the section that we are looking for.  The following is a description for each of the menu items:

1.        Edit Template – Allows you to modify the template associated to the control.  It will be grayed out if no template is currently associated to the control.

2.       Edit a Copy… - Allows you to take the current settings and make a new Style based on those settings.  This includes making all of the child resources such as the colors or edit boxes for ComboBoxes.

3.       Create Empty… - Allows you to create an empty template that you need to fill in all of the holes.

4.       Apply Resource – Allows you to select an existing template and associate it to your control.

One of the additional benefits for using Expression Blend to create your template is that it automatically adds any additional references to your project and page as needed.  If you are working along with this you will notice two changes that occurred behind the scenes for you.  The PresentationFramework.Aero DLL has been added to your project and a new namespace (xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero") was added to your page for you.

Edit a Copy is a great way to get started but it will make your page fill up pretty quick, especially if all you want to do is change one property such as the foreground.  The following is the code generated by Blend:

<Style x:Key="ButtonFocusVisual">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate>
                <Rectangle SnapsToDevicePixels="true" Margin="2" Stroke="Black" StrokeDashArray="1 2" StrokeThickness="1"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
 
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#F3F3F3" Offset="0"/>
    <GradientStop Color="#EBEBEB" Offset="0.5"/>
    <GradientStop Color="#DDDDDD" Offset="0.5"/>
    <GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
 
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
 
<Style x:Key="GoodButtonTemplate" TargetType="{x:Type Button}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
    <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
    <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Padding" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Microsoft_Windows_Themes:ButtonChrome SnapsToDevicePixels="true" x:Name="Chrome" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" RenderDefaulted="{TemplateBinding IsDefaulted}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}">
                    <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
                </Microsoft_Windows_Themes:ButtonChrome>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsKeyboardFocused" Value="true">
                        <Setter Property="RenderDefaulted" TargetName="Chrome" Value="true"/>
                    </Trigger>
                    <Trigger Property="ToggleButton.IsChecked" Value="true">
                        <Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="#ADADAD"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

So the question is: is there something in the middle of these two options.  The answer is “yes”.  Styles have a property called BasedOn.  This property allows you to base a Style on another Style (yours or Microsoft’s).  So here I have created a style basing it on the button control and changed only the foreground to red:

<Style x:Key="ButtonTemplate" 
       TargetType="{x:Type Button}"
       BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="Foreground" Value="Red"/>
</Style>
<Button Content="Close" Style="{StaticResource ButtonTemplate}" />
The result is what we were looking for originally:

image

And it continues to have all of the other built in functionality that the button comes with by default.

Extending Resources

Ideally, you are not creating styles for just one page; you are creating resources (styles) that can be used throughout your application.  To do so, there are two choices; you can just add them to your App.xaml as resources in the <Application.Resources> tag or you can add them as an external file referred to as a resource dictionary.  As with most things in software development, there is no right answer on how to do something only a more correct answer.  Personally, I like to keep things in separate resource dictionaries in logical groups.  That being said, if I am doing a little prototype with two screens, it may not make sense to create a bunch of files that I am just going to throw away. 

Examples of global resources:

<Application x:Class="ComboBoxDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>
        <Style x:Key="ButtonTemplate" 
               TargetType="{x:Type Button}"
               BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Foreground" Value="Red"/>
        </Style>
    </Application.Resources>
</Application>

<Application x:Class="ComboBoxDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    
    <Application.Resources>
        <ResourceDictionary Source="ComboBox.xaml" />
    </Application.Resources>
</Application>
Cross Project Resources

When considering styles for multiple projects to get the same look and feel you are probably thinking that you will need to go into each one of your controls and add a style tag for each of your controls but it is much easier than that.  When you create your style, there were two properties that we needed to specify x:Key and TargetType.  x:Key was the unique name for the style and TargetType was the type of control that the style would update.  We can actually change this slightly if we want it to apply to all controls of a specific type.  By specifying the type as the key, it specifies this to be used as the default style as shown below:

<Style x:Key="{x:Type Button}" TargetType="Button">
        <!-- Style would go here (see attached source code) -->
</Style> 
Allowing my button:

<Button Grid.Row="2" Content="No Style"/>

To look like this:

image

Since this affects all buttons that reference the default style, it would also affect the style that we have based on the default button but would not affect the button that we explicitly declared its properties as shown below:

image

Summary

There is a lot more that can be done with Styles and Templates including automation through storyboards.  This article was intended to give an overview on how it works together.  In the next segment, we will update a ComboBox by using a custom Style which will allow an image and text to display together as one object.

Source code for this project has been attached. 

Source Code Source Code

Page view counter