Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF
Tip/Trick

Using Style instead of UserControl in WPF

Rate me:
Please Sign up or sign in to vote.
4.94/5 (8 votes)
3 Nov 2017CPOL3 min read 16.8K   207   8   4
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:

C#
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:

XML
<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.

Image 1

Using the Code

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

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
QuestionAtomic Blonde Icon Pin
cocis486-Nov-17 10:26
cocis486-Nov-17 10:26 
GeneralLookless Controls Pin
Graeme_Grant3-Nov-17 13:36
mvaGraeme_Grant3-Nov-17 13:36 
AnswerRe: Lookless Controls Pin
Clifford Nelson4-Nov-17 8:08
Clifford Nelson4-Nov-17 8:08 
GeneralRe: Lookless Controls Pin
Graeme_Grant4-Nov-17 13:15
mvaGraeme_Grant4-Nov-17 13:15 
Clifford Nelson wrote:
Even when I was working for Microsoft we did not use this.

I guess for internal project you could justify this. However, for reusability in other projects, or for distribution outside of your own use, then a generic theme is a must.
Clifford Nelson wrote:
Do you have a reference for the use of Generic controls, I would be interested is reading their reasons.

You could Google the Microsoft docs and find this, however you don't need to. Just right-click any Microsoft control and edit a copy of the template and you will get a copy from the generic theme as in my original post. This should be a good enough reference for you. Wink | ;)


Graeme


"I fear not the man who has practiced ten thousand kicks one time, but I fear the man that has practiced one kick ten thousand times!" - Bruce Lee

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.