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

WPF Concept for Attached Properties on a Binding

0.00/5 (No votes)
10 Oct 2016 1  
This article presents a behavior that can be used to attach properties to a bound class.

Introduction

There is sometimes the need to have a functionality on a class that is not there, and it is not necessarily a good idea to modify the class. To accomplish this a behavior is used to take the DataContext of the bound FrameworkElement and use this DataContext to create a new DataContext. Then all bindings within the FrameworkElement will then be bound to this new DataContext.

Background

While working a WPF Drop Down Menu Button control for a project I was working, I ran into issues because the Control was actually bound to a Model and did not use a ViewModel (the control is presenting in the codeproject article WPF Drop Down Menu Button). The reason is that the ICommand properties were in the DataContext for the UserControl and RelativeBinding was used to get to those properties. However, the ContextMenu that is used to contain the DropDownButton is not in the VisualTree, so cannot use the RelativeBinding feature to find ancestors. That meant I had to either encapsulate all the Model classes into a ViewModel, or come up with something different. I really did not want to have to change the design that dreastically if I did not have to. I initially tried to initialize the ViewModel containing the attached properties in XAML for the DataContext for the control, but that did not work. It thus seemed like it was best to create a behavior to initialize this attached properties ViewModel using the initial ViewModel in the DataContext, and the set the DataContext to this new ViewModel.

The Implementation

The behavior that is used to implement this functionality is:

public class AttachedPropertiesBehavior
{
    public Type Type { get; set; }

    #region static part
    public static readonly DependencyProperty ViewModelTypeProperty
        = DependencyProperty.RegisterAttached("ViewModelType", typeof(Type),
    typeof(AttachedPropertiesBehavior),
    new PropertyMetadata(null, delegate(DependencyObject o,
            DependencyPropertyChangedEventArgs args)
            {
                new AttachedPropertiesBehavior(o, (Type) args.NewValue);
            }));

    public static Type GetViewModelType(FrameworkElement control)
    {
        return (Type)control.GetValue(ViewModelTypeProperty);
    }

    public static void SetViewModelType(FrameworkElement control, Type value)
    {
        control.SetValue(ViewModelTypeProperty, value);
    }
    #endregion static part

    #region instance part
    private bool _isBusy;
    private readonly FrameworkElement _frameworkElement;

    public AttachedPropertiesBehavior(object sender, Type type)
    {
        _frameworkElement = (FrameworkElement)sender;
        CreateNewDataContext(type);
        _frameworkElement.DataContextChanged += (s, args)
    => CreateNewDataContext((Type)args.NewValue);
    }

    private void CreateNewDataContext(Type type)
    {
        if (_isBusy) return;
        _isBusy = true;
        Debug.Assert(_frameworkElement.DataContext != null,
    $"The was no DataContext for FrameworkElement");
        var newDataContext = Activator.CreateInstance(type);
        Debug.Assert(newDataContext != null, $"Could not create an instance of type {type}");
        var property = type.GetProperty("ViewModel");
        Debug.Assert(newDataContext != null,
    $"Could not access a 'ViewModel' property for type {type}");
        property.SetValue(newDataContext, _frameworkElement.DataContext);
        _frameworkElement.DataContext = newDataContext;
        _isBusy = false;
    }
    #endregion instance part
}

This class has only a single DependencyProperty that is used to provide the Type to use to create the new ViewModel. When this DependencyProperty is changed, and instance of this Type is created, and the required property of this Type, This new ViewModel is set to the current DataContext of the FrameworkElement that this behavior is attached to. If this property does not exist, then the behavior will throw and error. Once the ViewModel property is set to the DataContext of the FrameworkElement, the DataContext of the FrameworkElement is set to this new ViewModel.

This class is divided into two parts, a static and an instance. An instance is created the static part whenever the Type is changed, and this would probably only occur once. The instance creates the new ViewModel, and will create a new ViewModel whenever the DataContact of the attached FrameworkElement is changed. It was the need to update the ViewModel on DataContext changed, but not when updating the ViewModel that drove the need for the instance code.

There are two required features of the new attached property ViewModel class:

  1. The class must have a property with a setter named ViewModel that will take a class of the Type of the DataContext
  2. The class must have a default constructor

To make it easier to create the attached property ViewModel, the sample project has a generic abstract class that the property with the name 'ViewModel' that can be used to create the attached property ViewModel through inheritance: 

public abstract class AttachedPropertiesViewModel<T>
{
    public T ViewModel { set; protected get; }
}

The generic T would be the typeof the original ViewModel.

An example of inheriting from this class is the following:

public class InsertCommandViewModel : AttachedPropertiesViewModel<ViewModel>
{
    public RelayCommand IncrementCommand => new RelayCommand(() => Increment());

    private void Increment()
    {
        ViewModel.Counter++;
    }
}

Using the code

The following is an example of using this behavior:.

<Button Grid.Row="1"
        Grid.Column="0"
        Grid.ColumnSpan="2"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        local:AttachedPropertiesBehavior.ViewModelType="{x:Type local:InsertCommandViewModel}"
        Command="{Binding IncrementCommand}"
        Content="Increment" />

The ViewModelType DependencyProperty is passed the Type of the ViewModel to create using the x:Type tag. It can also be seen that the ViewModel property for IncrementCommand is used for the Command attribute. It is not neccessary to have this behaviour on the FrameworkElement using the attached properties, it can be used by any child of a container with this behaviour.

Option

There is an article posted by  that could be used with this idea: Dynamic WPF MVVM View Model Event, Command and Property Generation. This would allow access to all of the properties of the Model without having to provide code to do it.

History

  • 2016/10/07: 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