Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF
Tip/Trick

Using WPF Behavior to Reduce Loading Time

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
18 Apr 2018CPOL3 min read 9.4K   100   12  
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:

C#
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:

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
-- There are no messages in this forum --