Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using WPF Behavior to Reduce Loading Time

0.00/5 (No votes)
18 Apr 2018 1  
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 UserControls 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:

  1. StateProperty: This property is set to "Enabled" to enable the behavior.
  2. 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:

  1. 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.
  2. Each of the RadioButton controls has the behavior's ExposedValue set to a ViewModel class instance that is exposed in the base MainViewModel.
  3. 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here