Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using Style instead of UserControl in WPF

0.00/5 (No votes)
3 Nov 2017 1  
This tip shows how easy it is to create a Style with DependencyProperty definitions and avoid the much heavier UserControl

Introduction

I had a case where somebody gave me some XAML that had a set of selections that were mutually exclusive, and each selection consisted of a bunch of controls and a CheckBox. In the code I was given, you had to click on the CheckBox to select the item, but they wanted to be able to click on the group and select the CheckBox, and then any other CheckBox controls would be set to false. This could obviously be done in either the ViewModel or in code-behind, but both were clumsy. I had done stuff like this before and knew there was a better way, which was to put all the controls that were associated with an item into a container, and then use a single container for each item, and in this case the container would be derived from a RadioButton since you automatically obtain an exclusive selection. The problem was that the contents were complex, and you would need to customize the RadioButton with a ControlTemplate to get the CheckBox in the right place. Therefore, I needed some additional DependencyProperty definitions for special properties that could customize the ControlTemplate for each item. These DependencyProperty definitions are defined in a C# class inherits from RadioButton and only contains these definitions. The layout is contained in a style that is a TargetType of this class.

Class

The code for the class is as follows:

public class ExperimentStepControl : RadioButton
{
    static ExperimentStepControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ExperimentStepControl),
            new FrameworkPropertyMetadata(typeof(ExperimentStepControl)));
    }

    public object Icon
    {
        get => (object) GetValue(IconProperty);
        set => SetValue(IconProperty, value);
    }

    public static readonly DependencyProperty IconProperty =
        DependencyProperty.Register("Icon", typeof(object),
            typeof(ExperimentStepControl), new PropertyMetadata(null));

    public string Text
    {
        get => (string) GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string),
            typeof(ExperimentStepControl), new PropertyMetadata(null));

    public Brush TextBackground
    {
        get => (Brush) GetValue(TextBackgroundProperty);
        set => SetValue(TextBackgroundProperty, value);
    }

    public static readonly DependencyProperty TextBackgroundProperty =
        DependencyProperty.Register("TextBackground", typeof(Brush),
            typeof(ExperimentStepControl), new PropertyMetadata(null));
}

As can be seen, this class inherits from RadioButton and has a static constructor that executes the call to OverrideMetaData, passing information related to this class to the method. Then, there are three DependencyPropertiy definitions for Icon, Text, and TextBackground that allow the display customizations to support the different items.

XAML

The XAML associate with this control is:

<Style TargetType="{x:Type local:ExperimentStepControl}">
    <Style.Resources>
        <ResourceDictionary>
            <Style x:Key="LabelStackPanel"
                   TargetType="{x:Type StackPanel}">
                <Setter Property="Margin" Value="0,-3" />
                <Setter Property="Orientation" Value="Horizontal" />
                <Setter Property="Width" Value="90" />
            </Style>
            <Style x:Key="IconContentPresent"
                   TargetType="{x:Type ContentPresenter}">
                <Setter Property="Width" Value="105" />
            </Style>
            <Style x:Key="OptionCheckBox"
                   TargetType="{x:Type CheckBox}">
                <Setter Property="HorizontalAlignment" Value="Right" />
                <Setter Property="VerticalAlignment" Value="Center" />
            </Style>
        </ResourceDictionary>
    </Style.Resources>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:ExperimentStepControl}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="10" />
                    </Grid.RowDefinitions>
                    <ContentPresenter Grid.Row="2"
                                      Content="{TemplateBinding Icon}"
                                      Style="{StaticResource IconContentPresent}" />
                    <StackPanel Grid.Row="3"
                                Background="{TemplateBinding TextBackground}"
                                Style="{StaticResource LabelStackPanel}">
                        <TextBlock Foreground="White"
                                   Text="{TemplateBinding Text}" />
                        <CheckBox IsChecked="{TemplateBinding IsChecked}"
                                  IsHitTestVisible="False"
                                  Style="{StaticResource OptionCheckBox}" />
                    </StackPanel>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This uses TemplateBinding to a DependencyProperty defined in the ExperimentStepControl class for an image at the top, the Background for the Text and CheckBox at the bottom, and the Text at the bottom. The CheckBox is bound to the IsChecked property of the RadioButton using a TemplateBinding. In addition, the IsIsHitTestVisible is set to False so that clicking on the RadioButton has the same effect as clicking on any other part of the Control. There are also a couple of defined in the RadioButton Resources that provide the values for other important attributes, which could just as easily been put within the ControlTemplate, but since I did basically a cut and paste, are defined in separate Style definitions.

Using the Code

Below, you can see an example of using the control with the DependencyProperty values of Icon, Text, and TextBackground set.

<local:ExperimentStepControl Text="The Fate of the Furious" 

	     TextBackground="Green">
    <local:ExperimentStepControl.Icon>
        <Image Source="Images/The Fate of the Furious.jpg" />
    </local:ExperimentStepControl.Icon>
</local:ExperimentStepControl>

I used jpeg files for the sample instead of code that used paths in the original application. A control that contains a path meant the use of an XML attribute with StaticResource: elements, so it was simpler. To do the same thing with a CheckBox would create a lot more code than using this control.

Also the IsChecked property of the RadioButton was bound to the ViewModel in the project for which I designed this Control.

Normally, I would have attempted to use a collection in the ViewModel so that I could use an ItemsControl and DataTemplate, but the design was already done. Using a DataTemplate would give a similar advantage.

Points of Interest

If you want to have these exclusive controls in a more complex VisualTree than all of the controls in a single container, then the use of the GroupName attribute of the base CheckBox Control can be used. This can also be used to provide sets of exclusive controls.

If the functionality does not require the specialized capabilities of the RadioButton, can inherit from the Button which will give you the ICommand and Click capabilities; the RadioButton is inherited from the Button. For even more basic functionality, inherit from the ContentControl.

History

  • 11/03/17: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here