Using WPF Behavior to Reduce Loading Time





5.00/5 (6 votes)
I had a situation where a control appearing was significantly affected by the initialization of the UserControls. This behavior fixed my problem.
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 (aRadioButton
) that will be associated with the attachedExposedValueProperty
of the parent control with theStateProperty
set to "Enabled
" when itsIsChecked
value is changed totrue
.
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 aStackPanel
and it has the behavior'sState
property set toEnabled
, and the behaviour'sExposedValue
is bound to theContentControl
Content
property with aMode
ofOneWayToSource
. This is because the behavior will always be setting this property, and not theContentControl
, and thisDependencyProperty
needs to signal theContentControl
when the value is changed. - Each of the
RadioButton
controls has the behavior'sExposedValue
set to aViewModel
class
instance that is exposed in the baseMainViewModel
. - There is a
DataTemplate
defined for eachViewModel
associated with aRadioButton
so as to associate aView
with theViewModel
. 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