Introduction
First of all, let me say that this behavior solved a very specific problem that another developer is unlikely to face. However, it does provide some insight into another way that behaviors can be used. It also demonstrates a way that might be used with another custom behavior for setting a ContentControl
Content
for something like a ListBox
.
Because of slow loading of a UserControl
on a Button Press, I started to look at a way of using a ContentControl
to contain the different UserControl
s that were displayed with various action by the user. The control of the which UserControl
is active was controlled by effectively RadioButton
controls because selecting a RadioButton
will deselect all other RadioButton
controls.
I did not want to add that complexity of specialized ViewModel
to handle this and wanted to leave the implementation within the View
as much as possible. To encapsulate this, I wanted to put the functionality in a behavior, and using a ContentControl
with DataTemplate
definitions to associate the ViewModel
for each UserControl
.
The Behavior
The following is the code for the behavior:
public class SelectedRadioButtonPropertyBahavior : IDisposable
{
#region static part
public enum BahaviorStates { Disabled, Enabled }
public static readonly DependencyProperty StateProperty =
DependencyProperty.RegisterAttached("State", typeof(BahaviorStates),
typeof(SelectedRadioButtonPropertyBahavior),
new PropertyMetadata(BahaviorStates.Disabled, OnStateChanged));
public static void SetState(DependencyObject element, BahaviorStates value)
{
element.SetValue(StateProperty, value);
}
public static BahaviorStates GetState(DependencyObject element)
{
return (BahaviorStates)element.GetValue(StateProperty);
}
private static void OnStateChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
GetInstance(d)?.Dispose();
if ((BahaviorStates)e.NewValue != BahaviorStates.Disabled)
SetInstance(d, new SelectedRadioButtonPropertyBahavior(d));
}
public static readonly DependencyProperty ExposedValueProperty =
DependencyProperty.RegisterAttached("ExposedValue",
typeof(object), typeof(SelectedRadioButtonPropertyBahavior),
new PropertyMetadata(null));
public static void SetExposedValue(DependencyObject element, object value)
{
element.SetValue(ExposedValueProperty, value);
}
public static object GetExposedValue(DependencyObject element)
{
return (object)element.GetValue(ExposedValueProperty);
}
private static readonly DependencyProperty InstanceProperty
= DependencyProperty.RegisterAttached("Instance",
typeof(SelectedRadioButtonPropertyBahavior),
typeof(SelectedRadioButtonPropertyBahavior),
new PropertyMetadata(null));
private static void SetInstance(DependencyObject element,
SelectedRadioButtonPropertyBahavior value)
{
element.SetValue(InstanceProperty, value);
}
private static SelectedRadioButtonPropertyBahavior
GetInstance(DependencyObject element)
{
return (SelectedRadioButtonPropertyBahavior)
element.GetValue(InstanceProperty);
}
#endregion
#region instance part
private List<RadioButton> _radioButtons;
private UIElement _uIElement;
private SelectedRadioButtonPropertyBahavior(DependencyObject d)
{
((FrameworkElement)d).Initialized += (sender, args) =>
{
_uIElement = (UIElement)d;
_radioButtons = FindVisualChildren<RadioButton>(d).ToList();
_radioButtons.ForEach(i => i.Unchecked += RadioButtonUnchecked);
_radioButtons.ForEach(i => i.Checked += RadioButtonChecked);
};
}
private void RadioButtonChecked(object sender, RoutedEventArgs e)
{
var radioButton = (RadioButton)sender;
var exposedProperty = GetExposedValue(radioButton);
SetExposedValue(_uIElement, exposedProperty);
}
private void RadioButtonUnchecked(object sender, RoutedEventArgs e)
{
var radioButton = (RadioButton)sender;
var exposedProperty = GetExposedValue(radioButton);
var parentExposedProperty = GetExposedValue(_uIElement);
if (exposedProperty == parentExposedProperty)
SetExposedValue(_uIElement, null);
}
public void Dispose()
{
_radioButtons.ForEach(i => i.Checked -= RadioButtonChecked);
_radioButtons.ForEach(i => i.Unchecked -= RadioButtonUnchecked);
}
#endregion
#region private static
private static IEnumerable<T> FindVisualChildren<T>
(DependencyObject root) where T : DependencyObject
{
if (root != null)
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(root, i);
if (child is T variable) yield return variable;
foreach (var childOfChild in FindVisualChildren<T>(child))
yield return childOfChild;
}
}
#endregion
}
This behavior has two parts: as static, and an instance. The static part contains the two public
DependencyProperty
definitions and the private
DependencyProperty
definition used to maintain an association of each instance with the Control
enabling the behavior.
This behavior has two public
DependencyProperty
definitions:
StateProperty
: This property is set to "Enabled
" to enable the behavior. ExposedValueProperty
: This property is used to contain the value within the child control (a RadioButton
) that will be associated with the attached ExposedValueProperty
of the parent control with the StateProperty
set to "Enabled
" when its IsChecked
value is changed to true
.
When the instance property is created with the setting of the StateProperty
to "Enabled
," the VisualTree
under the control is searched for RadioButton
controls so that these event handlers can be associated with their Checked
and Unchecked
events.
With the Checked
event, the RadioButton
, the ExposedValueProperty
value for that control is now associated with the ExposdedValueProperty
of the parent control.
Using the Code
Here is the code for the MainWindow
in the sample:
<Window x:Class="BehaviorForContentControlSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BehaviorForContentControlSample"
Title="MainWindow"
Width="800"
Height="450">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:DynamicContentView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:DynamicContentView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<local:DynamicContentView />
</DataTemplate>
</Window.Resources>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
local:SelectedRadioButtonPropertyBahavior
.ExposedValue="{Binding
Path=Content,
Mode=OneWayToSource}"
local:SelectedRadioButtonPropertyBahavior.State="Enabled">
<RadioButton Margin="5"
local:SelectedRadioButtonPropertyBahavior.ExposedValue="{Binding ViewModel1}"
Content="Select View Model 1" />
<RadioButton Margin="5"
local:SelectedRadioButtonPropertyBahavior.ExposedValue="{Binding ViewModel2}"
Content="Select View Model 2" />
<RadioButton Margin="5"
local:SelectedRadioButtonPropertyBahavior.ExposedValue="{Binding ViewModel3}"
Content="Select View Model 3" />
<Border Margin="5"
Padding="5"
BorderBrush="Black"
BorderThickness="2">
<ContentControl Name="ContentControl"
Width="200"
Height="50"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" />
</Border>
</StackPanel>
</Window>
Things to note:
- The parent control for the
RadioButton
controls is a StackPanel
and it has the behavior's State
property set to Enabled
, and the behaviour's ExposedValue
is bound to the ContentControl
Content
property with a Mode
of OneWayToSource
. This is because the behavior will always be setting this property, and not the ContentControl
, and this DependencyProperty
needs to signal the ContentControl
when the value is changed. - Each of the
RadioButton
controls has the behavior's ExposedValue
set to a ViewModel
class
instance that is exposed in the base MainViewModel
. - There is a
DataTemplate
defined for each ViewModel
associated with a RadioButton
so as to associate a View
with the ViewModel
. In this case, all are the same.
You should note that the Binding
to the ContentControl
Content
is done with the behavior's property. This is because it does not seem to be possible to use the Binding
of an attached property on the Content.
History
- 04/18/2018: Initial version