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

WPF Concept for Attached Properties on a Binding

Rate me:
Please Sign up or sign in to vote.
3.72/5 (8 votes)
10 Oct 2016CPOL3 min read 18K   93   4   6
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:

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

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

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

XML
<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, 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

 
GeneralMy vote of 5 Pin
johannesnestler12-Oct-16 1:42
johannesnestler12-Oct-16 1:42 
QuestionSomeone voted 1 without justification...obviously sour grapes Pin
Clifford Nelson10-Oct-16 10:04
Clifford Nelson10-Oct-16 10:04 
GeneralMy vote of 3 Pin
johannesnestler10-Oct-16 7:52
johannesnestler10-Oct-16 7:52 
GeneralRe: My vote of 3 Pin
Clifford Nelson10-Oct-16 8:25
Clifford Nelson10-Oct-16 8:25 
GeneralRe: My vote of 3 Pin
johannesnestler11-Oct-16 6:06
johannesnestler11-Oct-16 6:06 
AnswerRe: My vote of 3 Pin
Clifford Nelson11-Oct-16 7:18
Clifford Nelson11-Oct-16 7:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.