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