24

Multiple sources on the net tells us that, in MVVM, communication/synchronization between views and viewmodels should happen through dependency properties. If I understand this correctly, a dependency property of the view should be bound to a property of the viewmodel using two-way binding. Now, similar questions have been asked before, but with no sufficient answer.

Before I start analyzing this rather complex problem, here's my question:

How do I synchronize a custom view's DependencyProperty with a property of the viewmodel?

In an ideal world, you would simply bind it as this:

<UserControl x:Class="MyModule.MyView" MyProperty="{Binding MyProperty}">

That does not work since MyProperty is not a member of UserControl. Doh! I have tried different approaches, but none proved successful.

One solution is to define a base-class, UserControlEx, with necessary dependency properties to get the above to work. However, this soon becomes extremely messy. Not good enough!

l33t
  • 18,692
  • 16
  • 103
  • 180
  • The object containing MyProperty should be set as the DataContext of the control. – Scroog1 Feb 28 '13 at 10:05
  • When you *use* your UserControl, wouldn't you just write ``, where `myModule` is an appropriate `xmlns` declaration? And where of course the source of the binding is set somehow, either by the control's `DataContext`, or by explicitly setting the Binding's `Source` or `RelativeSource`. – Clemens Feb 28 '13 at 10:18
  • @Clemens, you mean that the binding should occur where the view is instantiated? In that case, how do you tell it to use its own `DataContext` and not the `DataContext` of the parent (where it is instantiated)? – l33t Feb 28 '13 at 10:25
  • @l33t Just to make sure we're talking about the same thing. The *bound* property (the first `MyProperty`) is a dependency property in the UserControl class MyView. The binding source property (the second `MyProperty`, in the Binding declaration) is a property in a ViewModel class, typically a CLR property in a class that implements `INotifyPropertyChanged`. – Clemens Feb 28 '13 at 10:32
  • @Clemens, yes. The idea is to communicate between the view and the viewmodel through their bound properties. This technique has been mentioned (but never demonstrated :P) on a number of sites/forums. – l33t Feb 28 '13 at 13:02
  • "but never demonstrated"? What about the answer below? It does exactly what you ask for. It may perhaps be improved by declaring the binding in XAML instead of code. And you would probably also drop the comparision between old and new value in the `ViewModelString` property. – Clemens Feb 28 '13 at 13:17
  • @Clemens Everything you've said makes perfect sense, but the problem is that I can't actually use a `MyCustomProperty` attribute on my `SomeUserControl` node (where `MyCustomProperty` is the target property and the latter is my user control). `SomeUserControl` only contains a static member for the dependency property, not the actual `MyCustomProperty` member, which is in the viewmodel. The answer below is basically asking you to replicate all of your viewmodel properties in your user control and write getters and setters to tie them together, which is inconvenient. – Asad Saeeduddin Apr 11 '14 at 19:12

3 Answers3

18

If you want to do it in XAML, you could try using styles to achieve that.

Here's an example:

<UserControl x:Class="MyModule.MyView"
             xmlns:local="clr-namespace:MyModule">
    <UserControl.Resources>
        <Style TargetType="local:MyView">
            <Setter Property="MyViewProperty" Value="{Binding MyViewModelProperty, Mode=TwoWay}"/>
        </Style>
    </UserControl.Resources>
    <!-- content -->
</UserControl>

In your case both MyViewProperty and MyViewModelProperty would be named MyProperty but I used different names just to be clear about what is what.

Grx70
  • 10,041
  • 1
  • 40
  • 55
  • 1
    Interesting idea! Maybe I will try it the next time I encounter this problem. – l33t Apr 12 '14 at 13:32
  • Thank you, I was looking for this – Mohsen Afshin Oct 26 '15 at 14:06
  • I have tried this, but when I have set DependencyProperty of user control in main window xaml - setter on my viewmodel is not used during app/window/control initialization. Any ideas? – Kamil Aug 04 '22 at 23:26
7

I use Caliburn.Micro for separating the ViewModel from the View. Still, it might work the same way in MVVM. I guess MVVM sets the view's DataContext property to the instance of the ViewModel, either.

VIEW

// in the class of the view: MyView
public string ViewModelString // the property which stays in sync with VM's property
{
    get { return (string)GetValue(ViewModelStringProperty); }
    set
    {
        var oldValue = (string) GetValue(ViewModelStringProperty);
        if (oldValue != value) SetValue(ViewModelStringProperty, value);
    }
}

public static readonly DependencyProperty ViewModelStringProperty =
    DependencyProperty.Register(
        "ViewModelString",
        typeof(string),
        typeof(MyView),
        new PropertyMetadata(OnStringValueChanged)
        );

private static void OnStringValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    // do some custom stuff, if needed
    // if not, just pass null instead of a delegate
}    

public MyView()
{
    InitializeComponent();
    // This is the binding, which binds the property of the VM
    // to your dep. property.
    // My convention is give my property wrapper in the view the same
    // name as the property in the VM has.
    var nameOfPropertyInVm = "ViewModelString"
    var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
    this.SetBinding(SearchStringProperty, binding);
}

VM

// in the class of the ViewModel: MyViewModel
public string ViewModelStringProperty { get; set; }

Note, that this kind of implementation lacks completely of implementation of the INotifyPropertyChanged interface. You'd need to update this code properly.

Michael Schnerring
  • 3,584
  • 4
  • 23
  • 53
  • Calling `SetBinding` will conflict with any present ViewModelString-binding defined in `XAML`. It should work as long as you don't bind to ViewModelString in `XAML`. Do you know if this can be done in `XAML` instead? – l33t Feb 28 '13 at 12:29
  • What do you mean? Bindings between XAML and its codebehind work only if you do this in the view: `DataContext = this`. MVVM sets the `DataContext` property to the instance of the ViewModel. I don't know in which way this might cause a conflict? – Michael Schnerring Feb 28 '13 at 12:51
  • I must get back to you on that. Anyway, can the above be accomplished in XAML? – l33t Feb 28 '13 at 13:04
  • The only way I know, is the one I provided. If there is any XAML way, I think it's less popular, because I always used the approach above. When googling this issue, no other results than this came across my way, yet. – Michael Schnerring Feb 28 '13 at 13:20
  • the conflict I mentioned is described here: https://groups.google.com/forum/?fromgroups=#!topic/microsoft.public.windows.developer.winfx.avalon/uXjLOsSAsVI – l33t Mar 04 '13 at 15:58
  • Missing semi-colon after var nameOfPropertyInVm = "ViewModelString" but it solved my problem for me. – Jackson Nov 26 '14 at 11:40
  • I think its easier (in my case, at least) to cast sender to the control type, cast control.DataContext to the VM type and set the desired property to e.newValue in the OnStringValueChanged callback. Its ugly either which way and appears to be an impedance mismatch in the WPF binding system. –  Oct 09 '15 at 01:32
5

Lets say you have defined your DependencyProperty "DepProp" in the View and want to use the exactly same value in your ViewModel (which implements INotifyPropertyChanged but not DependencyObject). You should be able to do the following in your XAML:

<UserControl x:Class="MyModule.MyView"
         xmlns:local="clr-namespace:MyModule"
             x:Name="Parent">
    <Grid>
        <Grid.DataContext>
            <local:MyViewModel DepProp="{Binding ElementName=Parent, Path=DepProp}"/>
        </Grid.DataContext>
    ...
    </Grid>
</UserControl>
somebody
  • 89
  • 1
  • 4
  • For me - it says 'System.Windows.Data.Binding' cannot be converted to type 'System.Int32' (Int32 is type in my ViewModel) – Kamil Aug 04 '22 at 23:14