Introduction
I recently had to create a window with a images of which one could be selected, but the items were in different groups. The RadioButton
would work great for this requirement. However, my idea of using a ListBox
with the DataTemplate
of a RadioButton
would not be so straight forward. I had wanted to use a ListBox
because that way would not have to have all the items specified in both the ViewModel
and the View
. In addition, there was an image associated with each one that was a resource. To specify the image to be displayed I would require some way of specifying which image.
I started thinking and decided it would be better to put all the information required for displaying each item in the XAML and not try to depend on the ViewModel
for any of this information, and then just have a way to pass the needed information from the selected RadioButton
to the ViewModel
using Binding
. This is when I came up with creating a Control
to contain the part of the display that contained XAML which included all the RadioButton
controls. In addition, I added a GroupName
DependencyProperty
to allow multiple groups of RadioButton
controls to be contained with this XAML, the ones that will be associated with this new ContentControl
determined by the GroupName
. If a GroupName
is not provided, then all RadioButton
controls within the XAML would be associated with this ContentControl
.
The Design
This Control
is derived from the ContentControl
:
public class SelectedRadioButtonControl : ContentControl
{
public SelectedRadioButtonControl()
{
Loaded += (sender, args) =>
{
var items = FindVisualChildren(this).ToList();
if (!string.IsNullOrWhiteSpace(GroupName))
items = items.Where(j => j.GroupName == GroupName).ToList();
items.ToList().ForEach(i => i.Checked += (radioButton, arg)
=> SelectedItem = radioButton);
};
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object),
typeof(SelectedRadioButtonControl),
new PropertyMetadata(null));
public string GroupName
{
get { return (string)GetValue(GroupNameProperty); }
set { SetValue(GroupNameProperty, value); }
}
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.Register("GroupName", typeof(string), typeof(SelectedRadioButtonControl),
new PropertyMetadata(null));
public IEnumerable<RadioButton> FindVisualChildren(DependencyObject depObj)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is RadioButton)
yield return (RadioButton)child;
foreach (RadioButton childOfChild in FindVisualChildren(child))
yield return childOfChild;
}
}
}
When this Control
is Loaded
the content of the SelectedRadioButtonControl
is searched using the VisualTreeHelper
to find all instances of RadioButton
. If a GroupName
is specified (meaning GroupName
is not null nor empty), then this collection is filtered for all RadioButton
controls that have that GroupName
. For each of these RadioButton controls, its Checked event is subscribed to so that the control knows when one of its RadioButton
controls is selected. Also, the IsChecked
property is checked and if the value is true, this is known to be the currently selected RadioButton
.
There is a DependencyPropery
defined for SelectedRadioButton
and GroupName
. The SelectedRadioButton
DependencyPropery
is of Type
RadioButton
and is set to the last RadioButton
that fire its Checked
event
. The second DependencyProperty
, GroupName
is of Type
string
and is used to filter the contained RadioButton
controls to those that have a GroupName
that matches this GroupName
.
Using the Control
The following is an example of how to use this control:
<local:SelectedRadioButtonControl x:Name="Group1SelectedRadioButtonControl1"
GroupName="Group1">
<local:SelectedRadioButtonControl x:Name="Group1SelectedRadioButtonControl2"
GroupName="Group2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<RadioButton Content="Group 1 A"
GroupName="Group1" />
<RadioButton Content="Group 1 B"
GroupName="Group1" />
<RadioButton Content="Group 2 A"
GroupName="Group2" />
<RadioButton Content="Group 1 C"
GroupName="Group1" />
<RadioButton Content="Group 2 B"
GroupName="Group2" />
<RadioButton Content="Group 2 C"
GroupName="Group2" />
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding ElementName=Group1SelectedRadioButtonControl1,
Path=SelectedRadioButton.Content,
StringFormat='Group 1 selected RadioButton: {0}'}" />
<TextBlock Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding ElementName=Group1SelectedRadioButtonControl2,
Path=SelectedRadioButton.Content,
StringFormat='Group 2 selected RadioButton: {0}'}" />
</Grid>
</local:SelectedRadioButtonControl>
</local:SelectedRadioButtonControl>
In this sample there are two SelectedRadioButtonControl
controls, one inside of the other, and each associated with a different GroupName
. The six RadioButton
controls are associated with one group or the other using its GroupName
which matches the GroupName
of one of the SelectedRadioButtonControl
controls. A TextBlock
Text
property is bound to each of the SelectedRadioButtonControl SelectedRadioButton.Content
property.
History
- 08/23/17: Initial Version