Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DependencyProperty Registration in ViewModel

I am finding a lot of discussions about ViewModels and their Properties that compare two approches: implementation of INotifyPropertyChanged or implementation via Dependency Properties.

While I am doing INotifyPropertyChanged a lot (and it's working) I am having difficulties implementing the DP-approach.

When I am registering the DP in the ViewModel like this

    public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), typeof(MyUserControl));

and trying to use it somewhere with:

<myNameSpace:MyUserControl SomeProperty="{Binding ...}"/>

there is an compiler error:

The property 'SomeProperty' does not exist in XML namespace 'clr-namespace:myNameSpace'.

What am I doing wrong??


EDIT1

The ViewModel looks like this:

public class MyUserControlVM : DependencyObject
{

    public string SomeProperty
    {
        get { return (string)GetValue(SomePropertyProperty); }
        set { SetValue(SomePropertyProperty, value); }
    }

    public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), typeof(MyUserControl));     
}
like image 235
joerg Avatar asked Mar 10 '14 08:03

joerg


1 Answers

Have you implemented the standard property accessors? A complete DP signature looks like this:

public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("propertyName", typeof (PropertyType), typeof (MyUserViewModel), new PropertyMetadata(default(PropertyType)));

    public PropertyType PropertyName
    {
        get { return (PropertyType) GetValue(PropertyNameProperty); }
        set { SetValue(PropertyNameProperty  value); }
    }

Then your code should work. One more info regarding DP's vs. INotifyPropertyChanged: For me, the main tradeoff is speed vs. readability. It's a pain littering your ViewModels with dependency property declarations, but you gain about 30% speed in the notification pipeline.

EDIT:

You register the property on the View's type, it should be the ViewModel's type, i.e.

public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("propertyName", 
        typeof (PropertyType), 
        typeof (MyUserViewModel), 
        new PropertyMetadata(default(PropertyType)));

instead of

public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("propertyName", 
        typeof (PropertyType),
        typeof (MyUserControl), 
        new PropertyMetadata(default(PropertyType)));

EDIT 2:

Ok, you're mixing something up here: You can have dependency properties both, on your ViewModel and your View. For the former, you define the DP in the control's codebehind (i.e. MyUserControl.xaml.cs). For the latter you define it in the ViewModel as I have shown it above. The problem with your code lies in the usage:

You are trying to bind some value of your DataContext to a property called SomeProperty on the view:

<myNameSpace:MyUserControl SomeProperty="{Binding SomePropertyBindingValue}"/>

As you've defined the dependency property on the view model, there is no property SomeProperty on the view, hence you get the compiler error. To make the above usage work, you need to put the DP in the View's codebehind and define a normal property SomePropertyBindingValue on the ViewModel.

To define the DP on the ViewModel and use it in the view, you need to bind TO this property:

<myNameSpace:MyUserControl Width="{Binding SomeProperty}"/>

Supposed you've wired up ViewModel and View correctly, this will bind the views width to your ViewModel's property SomeProperty. Now, if SomeProperty is set on the ViewModel, the UI will update, though you haven't implemented INPC.

EDIT 3:

From what I understand your problem is that - to get the desired behavior - you would need to bind one dependency property on the control to two properties on separate ViewModels: One property on MainWindowVM should be bound to the UserControl and then - from the UserControl - back to another ViewModel (UserControl1VM). There is a bit of twist in the design here and without knowing the exact context, I don't see why you couldn't handle the property synch on ViewModel level:

I let my ViewModels more or less resemble the nested structure of the View:

Say you have a view (pseudo-code):

<Window>
    <UserControl1 />
</Window>

Let the data context of the window be MainWM, whereever it comes from, this is not proper XAML(!):

<Window DataContext="[MainVM]">
    <UserControl1 />
</Window>

Question 1 is, why does the user control need it's own ViewModel? You could simply bind it to MainVMs property 'SomeProperty':

<Window DataContext="[MainVM]">
    <UserControl Text="{Binding SomeProperty}" />
</Window>

Ok, say you really have agood reason why you would need a UserControlViewModel which has it's own property 'UCSomeProperty':

public class UserControlVM
{
    public string UCSomeProperty { get; set; } // Let INPC etc. be implemented
}

Add a UserControlVM property to MainVM:

public class MainVM
{
    public UserControlVM UserControlVM { get; set; } // INPC etc.
}

Now, you can set up the binding:

<Window DataContext="[MainVM]">
    <UserControl DataContext="{Binding UserControlVM}" 
                 Text="{Binding UCSomeProperty}" />
</Window>

Last, again without knowing your specific case and whether it makes sense, but let's say you now want a property on 'MainVM' which is in synch with the property on the user control's ViewModel's property:

public class MainVM
{
    public string SomeProperty
    {
         get { return UserControlVM.UCSomeProperty; }
         set { UserControlVM.UCSomeProperty = value; }
    }

    public UserControlVM UserControlVM { get; set; } // INPC etc.

    public MainVM()
    {
         UserControlVM = new UserControlVM();
         UserControlVM.NotifyPropertyChanged += UserControlVM_PropertyChanged;
    }

    private void UserControlVM_PropertyChanged(object sender, BlaArgs e)
    {
         if (e.PropertyName == "UCSomeProperty")
              RaisePropertyChanged("SomeProperty");
    }
 }

You could use the binding like this, for example:

 <Window DataContext="[MainVM]">
    <UserControl DataContext="{Binding UserControlVM}" 
                 Text="{Binding UCSomeProperty}" />
    <TextBlock Text="{Binding SomeProperty}" />
</Window>

SomeProperty on MainVM and UCSomeProperty on USerControlVM are always the same now and available on both ViewModels. I hope this helps...

like image 109
Marc Avatar answered Nov 30 '22 19:11

Marc