Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF
Article

Structuring Your XAML Sources

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
2 Aug 2012CPOL3 min read 49K   740   23   8
Structuring your XAML source files by using dictionaries.

Introduction

When creating custom controls in Visual Studio, all controls are added to /Themes/Generic.xaml. Soon this file becomes quite big, and the maintenance of the file becomes tedious. XAML files should be structured in a similar way as any other source file. For instance in C# and in C++ one should attempt to put only a single class in a file. Similarly the XAML files can be structured where the net result should be that you as a developer find your way around your own sources a lot better.

To put things to the test I will develop a very simple custom control, an ImageButton.

Step 1: Creating a Custom Control

In Visual Studio I created myself a solution with two projects. One project for a WPF application, and another one for a WPF Custom Control Library. Leave the WPF application untouched for now, it is only used to test the custom control, but we first have to create it.

So in Visual Studio add a WPF Custom control to the library and name it ImageButton. The /Themes/Generic.xaml should look similar like this:

XML
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CodeProjectLibrary">
   
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
       </Setter>
    </Style>
</ResourceDictionary>

And the code that is being generated will look like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CodeProjectLibrary
{
    public class ImageButton : Control
    {
        static ImageButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
        }
    }
}

Where I already removed the comment which you can read yourself if you create a custom control.

This is a fully operational control, though it does not do a whole lot. So let's make a real control out of this. Since this is an explanation about how to structure your XAML, I won’t explain the control itself. Bottom line is that either you immediately apply the structuring, or you first create the control in generic.xaml, and apply the structuring later. I will follow the latter approach so I create my control first.

Step 2: Authoring the Custom Control

The authored code for the control in C# is like this :

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;

namespace ICeTechControlLibrary
{
    public class ImageButton : ButtonBase
    {
        private const string NormalImageSourcePropertyName = "NormalImageSource";
        private const string MouseOverImageSourcePropertyName = "MouseOverImageSource";
        private const string MouseOverPressedImageSourcePropertyName = "MouseOverPressedImageSource";
        private const string PressedImageSourcePropertyName = "PressedImageSource";

        public static readonly DependencyProperty NormalImageSourceProperty =
            DependencyProperty.Register(NormalImageSourcePropertyName, typeof(ImageSource), typeof(ImageButton));
        public static readonly DependencyProperty MouseOverImageSourceProperty =
            DependencyProperty.Register(MouseOverImageSourcePropertyName,
            typeof(ImageSource), typeof(ImageButton));
        public static readonly DependencyProperty MouseOverPressedImageSourceProperty =
            DependencyProperty.Register(MouseOverPressedImageSourcePropertyName,
            typeof(ImageSource), typeof(ImageButton));
        public static readonly DependencyProperty PressedImageSourceProperty =
            DependencyProperty.Register(PressedImageSourcePropertyName, typeof(ImageSource), typeof(ImageButton));

        static ImageButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
        }

        public ImageSource NormalImageSource
        {
            get
            {
                return (ImageSource)GetValue(NormalImageSourceProperty);
            }
            set
            {
                SetValue(NormalImageSourceProperty, value);
            }
        }

        public ImageSource MouseOverImageSource
        {
            get
            {
                return (ImageSource)GetValue(MouseOverImageSourceProperty);
            }
            set
            {
                SetValue(MouseOverImageSourceProperty, value);
            }
        }

        public ImageSource MouseOverPressedImageSource
        {
            get
            {
                return (ImageSource)GetValue(MouseOverPressedImageSourceProperty);
            }
            set
            {
                SetValue(MouseOverPressedImageSourceProperty, value);
            }
        }

        public ImageSource PressedImageSource
        {
            get
            {
                return (ImageSource)GetValue(PressedImageSourceProperty);
            }
            set
            {
                SetValue(PressedImageSourceProperty, value);
            }
        }
    }
}

This is basically an average button control with some extra dependency properties where different images can be referred to in case the button is being hovered by the mouse or if the button is simply pressed.

The XAML source, still in generic.xaml is like this:

XML
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CodeProjectLibrary">
   
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Image x:Name="ButtonImage" Source="{TemplateBinding NormalImageSource}" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                                <Condition Property="IsPressed" Value="False"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
   
                            <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverPressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="False"/>
                                <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=PressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

Step 3: Using the Custom Control

Now you can actually use this control. So change the XAML in mainwindow.xaml to the following. Don’t forget to add a reference to the control library!

XML
<Window x:Class="CodeProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:CodeProjectLibrary;assembly=CodeProjectLibrary">
    <Grid>
        <my:ImageButton HorizontalAlignment="Left" Margin="64,44,0,0" Name="imageButton1" VerticalAlignment="Top" Width="16" Height="16" 
                        NormalImageSource="/CodeProject;component/checkbox-unchecked.png" 
                        MouseOverImageSource="/CodeProject;component/checkbox-unchecked.png" 
                        MouseOverPressedImageSource="/CodeProject;component/checkbox-checked.png" 
                        PressedImageSource="/CodeProject;component/checkbox-checked.png" />
    </Grid>
</Window>

This is just a simple test application, but at this point, it serves its purpose to show that it works.

Now we want to move all XAML code in generic.xaml to its own file. For this we have to create a separate dictionary, move the style to the dictionary, and include the dictionary in the generic.xaml as a merged dictionary. 

Step 4: Add a Resource Dictionary

In the custom control library create a resource dictionary. I named it ImageButtonDictionary.xaml, and placed it in a project folder ResourceDictionaries. The XAML code looks like this :

XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ICeTechControlLibrary">
 
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Image x:Name="ButtonImage" Source="{TemplateBinding NormalImageSource}" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                                <Condition Property="IsPressed" Value="False"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                                <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverPressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="False"/>
                                <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=PressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

This is actually the same content as the generic.xaml we used before. Of course suppose you did not start from a clean generic.xaml, you would only have to copy/paste the style of the imagebutton control to this resource file.

The style code in generic.xaml for this control can be removed like this :

XML
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CodeProjectLibrary">

</ResourceDictionary>

Run the application again. Oops, it does not work anymore. The reason is that the generic.xaml does not contain the style anymore, which was the whole point right ? Yes, but we don’t have to put the whole style here, we can use the merged dictionary concept.

The generic.xaml file will look like this.

XML
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CodeProjectLibrary">
 
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/CodeProjectLibrary;component/ResourceDictionaries/ImageButtonDictionary.xaml"/>
    </ResourceDictionary.MergedDictionaries>
 
</ResourceDictionary>

Run the project again, it should work.

Using this method, you can actually create lots of resource dictionaries and use the merged dictionary mechanism to include whatever you need in another dictionary.

To have an operational control, you have to make sure you include it in the merged dictionary list of /Themes/generic.xaml.

History

30/07/2012 First version created.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ICeTech
Belgium Belgium
I am working since 1992 with Alcatel-Lucent where I started working on narrow band telephone exchanges.
Since then things have evolved into broadband internet services. Currently I am working on DSL and GPON in a product called ISAM. More specifically I am doing development in a realtime system in C and C++.
I also have my own company ICeTech, where I create administrative software using Microsoft technologies. I am using C#, Windows forms, WPF, Silverlight, SQL Server, WCF, ... whatever it takes.
The kind of software I develop ranges from very simple things to more complex planning systems. But it is usually a one man job.

Comments and Discussions

 
QuestionYou ARE a life-saver :) Pin
Mohammad Mahdi Saffari8-May-15 14:20
Mohammad Mahdi Saffari8-May-15 14:20 
Question/Themes/Generic.xaml Pin
rctaubert23-Aug-12 10:11
rctaubert23-Aug-12 10:11 
AnswerRe: /Themes/Generic.xaml Pin
Philip Stuyck24-Aug-12 5:49
Philip Stuyck24-Aug-12 5:49 
GeneralRe: /Themes/Generic.xaml Pin
rctaubert24-Aug-12 6:30
rctaubert24-Aug-12 6:30 
QuestionMy vote of 5 Pin
CandyJoin9-Aug-12 3:34
CandyJoin9-Aug-12 3:34 
QuestionWhy place in generic.xaml? Pin
CodingVictim6-Aug-12 9:43
CodingVictim6-Aug-12 9:43 
Why merge the resource dictionaries in the generic.xaml and not define them in the app.xaml? If they are in the app.xaml the entire solution would be able to see the various styles and templates you have designed.

Thanks
AnswerRe: Why place in generic.xaml? Pin
Philip Stuyck6-Aug-12 10:20
Philip Stuyck6-Aug-12 10:20 
GeneralMy vote of 5 Pin
Christian Amado2-Aug-12 10:36
professionalChristian Amado2-Aug-12 10:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.