7

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));     
}
joerg
  • 717
  • 2
  • 8
  • 18
  • Do you also have a property named SomeProperty? – Håkan Fahlstedt Mar 10 '14 at 08:38
  • The owner type of the dependency property, specified by the third argument in the `Register` call, must be the type that declares the property. This is `MyUserControlVM` here, instead of `MyUserControl`. – Clemens Mar 10 '14 at 09:02
  • Tried that, but same compiler error message... – joerg Mar 10 '14 at 09:04
  • 1
    Yes, because you are trying to set `SomeProperty` on `MyUserControl`, which did not declare this property. You are confusing the UserControl and the ViewModel classes. – Clemens Mar 10 '14 at 09:06
  • 3
    The question is, why at all do you want to use dependency properties in your view model? Why not just keep INotifyPropertyChanged? – Clemens Mar 10 '14 at 09:09
  • Because I try to bind to the property. It's possible via DP and not over INotifyPropertyChanged-Properties – joerg Mar 10 '14 at 09:17
  • 1
    But you have to declare the property in your UserControl class, not in your view model. Which would be the typical WPF approach anyway, DP in the view, INotifyPropertyChanged in the view model. – Clemens Mar 10 '14 at 09:25
  • If you search this topic on the web you will find quite a number of examples that show DPs in ViewModels (and some arguments to use them). Problem is that I can't make the examples to compile... – joerg Mar 10 '14 at 09:32
  • I think the problem that I want to achieve two things: 1. I want to have my properties possible to have as Binding Target (that means it has to be DP) AND 2. I want to have this property in my ViewModel of the UserControl because of my MVVM structure. Right now I am not seeing a way to do that. – joerg Mar 10 '14 at 09:56

3 Answers3

7

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...

NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
Marc
  • 12,706
  • 7
  • 61
  • 97
  • Please check the type on which you register the DP (see my edit). Does it work then? – Marc Mar 10 '14 at 10:13
  • OK, but I cannot put my Property `PropertyName` in the VM, right? That would be my goal... – joerg Mar 10 '14 at 10:31
  • That is possible. If you just change typeof(MyUserControl) to typeof(MyUserViewModel), the DP is on the ViewModel. And the conventional property definition needs to be on the VM as well. – Marc Mar 10 '14 at 10:36
  • I am sorry, but I cannot set it up to run. Could you please give some source-code to show the whole implementation? – joerg Mar 10 '14 at 10:42
  • 1
    OK, I think I am getting it slowly (but not completely, sorry). I think my problem with understanding is with the nested structure of the controls and corresponding VM's. I try again: I have MainWindow and its DataContext MainWindowVM and a UserControl1 and its DataContext UserControl1VM in this MainWindow. I want to bind a (normal) property in MainWindowVM to a DP on UserControl1, that is again bound to a (normal) property in UserControl1VM. The connection that I am missing right now is the one between the DP in UserControl1 and the property in the UserControl1VM. Thanks again!! – joerg Mar 10 '14 at 14:44
  • I think I am already having it: I setup a binding that connects the two things. Because it's too much code for here I have uploaded the solution: [Download Code](http://www.sendspace.com/file/f8s4hl) The bindings work one by one, but not all together. [This link](http://www.codeproject.com/Articles/325911/A-Simple-Pattern-for-Creating-Re-useable-UserContr) helped me with the implementation. – joerg Mar 11 '14 at 09:20
  • Hi again @joerg. I am slowly understanding what you are trying to do, I think there is a much simpler solution to your problem though, when you separate concerns between views and view models properly. I cannot download the solution, can you upload it to a more trustable location, i.e. SkyDrive or Dropbox? Also, it would be nice if you could upvote my answer and mark it as a solution, if it helped you... – Marc Mar 11 '14 at 09:51
  • [Downloadlink at my GoogleDrive](https://drive.google.com/folderview?id=0ByVeCgu38eM-eDVuSmIzNjVmN2M&usp=sharing), what do you think is the simpler solution? – joerg Mar 11 '14 at 10:01
  • I am still hoping that you can answer the open questions (1. how would you do it? What is your simpler solution? 2. My bindings-problem (see download) is still not solved). Your help would be really appreciated! – joerg Mar 12 '14 at 14:10
  • @joerg: Sorry, I have lot of work, currently. I'll edit my answer and appreciate an upvote! – Marc Mar 14 '14 at 10:43
  • Even if that is not the "ideal" solution I was looking for I mark it as solved because it's well explained and it's working. I have asked a more general question again [here](http://stackoverflow.com/questions/22405500/customcontrol-usercontrol-view-mvvm-dependencyproperty-confused) Thank you, Marc! – joerg Mar 14 '14 at 12:51
1

Besides from this:

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

You also need this:

public string SomeProperty 
{
    get 
    {
        return (string)GetValue(SomePropertyProperty);
    }
    set
    {
        SetValue(SomePropertyProperty, value);
    }
}
Håkan Fahlstedt
  • 2,040
  • 13
  • 17
1

When you say

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

You mean create an object of class MyUserControl and set its property SomeProperty with the Binding...

What you have done wrong - If you look at this line

("SomeProperty", typeof(string), typeof(MyUserControl))

Just declaring the typeof(MyUserControl) is not enough to make the property available for MyUserControl

MyUserControl class does not have the property SomeProperty instead it is within the view Model hence you get the compiler error.

Ideally DependencyProperty should be declared within code behind of UserControl not in the ViewModel like the code below.

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

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

public class MyUserControlVM
{
  public string SomePropertyBindingValue{get;set;}
}

Then you can bind the Dependency property with the viewModel property. like this

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

Edit - Positing your question from your comment

I have MainWindow and its DataContext MainWindowVM and a UserControl1 and its DataContext UserControl1VM in this MainWindow. I want to bind a (normal) property in MainWindowVM to a DP on UserControl1, that is again bound to a (normal) property in UserControl1VM. The connection that I am missing right now is the one between the DP in UserControl1 and the property in the UserControl1VM.

I would suggest you to rethink your requirement, may be this is not the best approach to solve your problem. The reason is as follows:

You want a property to be common to both MainWindowVM and UserControl1VM and you are trying to achieve this by binding ViewModel to the View's Dependency property and passing it from the View back to the Child ViewModel. This breaks the purpose of MVVM pattern - separation of responsibility between View and its ViewModel.

It would be easier if you pass it as a constructor parameter to UserControl1VM from MainWIndowVM. That way you can bind them to the View wherever you like rather than passing it via the View.

Carbine
  • 7,849
  • 4
  • 30
  • 54