Binding WPF UserControl to View Model and Code Behind

I'm attempting to understand the best way of wiring up a custom control to use Dependency Properties and a View Model. The Dependency Properties are implemented to expose properties that can be used in XAML to initialise the properties in the View Model. For example, in the code behind of a custom control I can define the following dependency property:

public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
         "MyProperty", typeof(string), typeof(MyControl), new PropertyMetadata(null));

public string MyProperty
{
  get { return (string)GetValue(MyPropertyProperty); }
  set { SetValue(MyPropertyProperty, value); }
}

where the View Model is defined as

public class MyControlViewModel : INotifyPropertyChanged
   {
      public MyControlViewModel()
      {
         _myProperty = "Default View Model string";
      }

      private string _myProperty;

      public string MyProperty
      {
         get
         {
            return _myProperty;
         }
         set
         {
            _myProperty = value;
            OnPropertyChanged("MyProperty");
         }
      }

      public event PropertyChangedEventHandler PropertyChanged;

      protected virtual void OnPropertyChanged(string propertyName = null)
      {
         var handler = PropertyChanged;
         if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));

      }
   }

and the custom control binds to the View Model MyProperty as follows

<UserControl x:Class="MyProject.MyControlView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MyProject">

    <UserControl.DataContext>
        <local:MyControlViewModel/>
    </UserControl.DataContext>

    <Grid>
        <TextBlock Text="{Binding MyProperty}"/>                
    </Grid>

</UserControl>

Now since I have defined the Dependency Property MyProperty in the custom control's code behind, I want to be able to use this to initialise MyProperty in the ViewModel. So something like this

 <Window x:Class="MyProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MyProject"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:MyControlView MyProperty="This was set in XAML"/>
    </Grid>
</Window>

Running the above will display the string "Default View Model string" that was set in the View Model's constructor. How do I hook up the Dependency Property value so that it correctly initialises the string in the View Model? i.e. it should display "This was set in XAML".

UPDATE

I can set a property changed callback in the code behind and set the value in the View Model, i.e.

public static readonly DependencyProperty MyPropertyProperty =
          DependencyProperty.Register("MyProperty", typeof(string), typeof(MyControlView), new PropertyMetadata("Default", OnMyPropertyChanged));

      private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var view = d as MyControlView;
         if (view != null)
         {
            var viewModel = view.DataContext as MyControlViewModel;
            if (viewModel != null)
            {
               viewModel.MyProperty = e.NewValue as string;
            }
         }
      }

Is this correct, or does it smell?

Answers


You have created dependency property that is nice. But, you have made tightly coupled by instiantiating your view model and using its property into code behind. Its wrong way.

You should inherit your class from "Control" and for binding value and create one DP like below in custom control class:

public static readonly DependencyProperty myValueProperty = DependencyProperty.Register(
            "MyProperty", typeof(object), typeof(FieldControl), new FrameworkPropertyMetadata(null, myValueChanged));

Then,

private static void myValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var fc = dependencyObject as FieldControl;
            if (fc != null)
            {
                fc.SetValue(BindingExpression1Key, fc.GetBindingExpression(ValueProperty));
                fc.RaiseValueChangedEvent(dependencyPropertyChangedEventArgs);
            }
        }

Now, use it and bind your property like below:

 <local:MyControlView MyProperty="{Binding MyProperty, Mode=TwoWay}"/>

You can update the ViewModel (INPC) property based on the View's Dependency Property (DP) using a Blend Behaviour. This solution avoids having to add a property changed callback in the code-behind:

<UserControl x:Class="MyProject.MyControlView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"             
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
         xmlns:local="clr-namespace:MyProject"
         x:Name="MyControl">

<UserControl.DataContext>
    <local:MyControlViewModel/>
</UserControl.DataContext>

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <ic:ChangePropertyAction
            TargetObject="{Binding}"
            PropertyName="MyProperty"
            Value="{Binding ElementName=MyControl, Path=MyProperty}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>
    <TextBlock Text="{Binding MyProperty}"/>
</Grid>

You will have to add references to the System.Windows.Interactivity and Microsoft.Expression.Interactivity.Core assemblies to your projects in order to use Blend Behaviours (note the i and ic namespaces that I added).

In this case I hook into the UserControl Loaded event with an EventTrigger that calls a ChangePropertyAction. By specifying {Binding} for the TargetObject, I'm informing the Behaviour to use the View Model from the bound DataContext. The PropertyName refers to the property on the View Model. The Value refers to the DP on the View. I gave your UserControl a name so that I could easily reference it when querying the value of MyProperty.

The benefit of doing it this way means that you could swap out your View Model in the DataContext (as long as the new one also has a MyProperty property) without having to update the code-behind.


Need Your Help