mvvmdependency-propertiesinotifypropertychangedpostsharp

MVVM "A 'Binding' can only be set on a DependencyProperty of a DependencyObject."


I have this Model

[NotifyPropertyChanged]
public class WidgetConfiguration
{
    #region Properties
    #endregion Properties
}

Which i use in my ViewModel for a Collection and a Selected item property (ListView / GridView SelectedItem="{Binding SelectedWidget}" ... )

[NotifyPropertyChanged]
public class WidgetViewModel
{
    public ObservableCollection<WidgetConfiguration> Configurations { get; set; } = new ObservableCollection<WidgetConfiguration>();

    public WidgetConfiguration SelectedWidget { get; set; }
}

I then want to Bind SelectedWidget to a UserControl that function as editor for the SelectedItem:

<controls:WidgetConfig Widget="{Binding SelectedWidget}" />

The UserControl is defined like this (using PostSharp to declare DependencyProperties)

[NotifyPropertyChanged]
public partial class WidgetConfig : UserControl
{
    [DependencyProperty]
    public WidgetConfiguration Widget { get; set; }

    public WidgetConfig()
    {
        InitializeComponent();

        this.DataContext = this;
    }
}

But im getting an error on the UserControl binding:

Severity Code Description Project File Line Suppression State Error A 'Binding' cannot be set on the 'Widget' property of type 'Squiddy_Client_Views_WidgetConfig_10_577403948'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject. Client C:\develop\Squiddy\Client\Views\WidgetManager.xaml 21

I've tried implementing the DependencyProperties manually and ensured that all types was correct, even the default type and default value. it didn't help.

I've read all results on google and don't know what to do.

Is this even possible or do i need to make a proxy binding ?

EDIT:

Just for the sake of it, i tried implementing the DependencyProperty manually:

public static readonly DependencyProperty WidgetProperty = 
    DependencyProperty.Register("Widget", typeof(WidgetConfiguration),
    typeof(WidgetConfig));

[SafeForDependencyAnalysis]
public WidgetConfiguration Widget 
{
    get { return GetValue(WidgetProperty) as WidgetConfiguration; }
    set { SetValue(WidgetProperty, value); }
}

Now the XAML error is gone, but the binding is "dead". When selecting a new object in the ListView, the UserControl doesn't get updated:

EDIT 2:

I totally missed this part in the PostSharp documentation, i lacked adding the DependencyProperty along with the attribute. (thanks to Daniel Balas)

public static DependencyProperty WidgetProperty { get; private set; }

[DependencyProperty]
public WidgetConfiguration Widget { get; set; }

EDIT 3:

I finally found the answer to DataContext / root after watching this video: https://www.youtube.com/watch?v=h7ZrdGiOm3E

  1. I removed "this.DataContext = this" from the UserControl constructor
  2. I added a Name="root" in the UserControl element in XAML
  3. The bindings inside the UserControl should point to ElementName=root and use the property Widget.xxx

like this:

<UserControl Name="root">
  <TextBlock Text="{Binding Header, ElementName=root}"></TextBlock>
  <Label Content="{Binding Widget.Name, ElementName=root}" />
</UserControl>

Solution

  • The Xaml/Baml compiler determines whether the property Foo is a dependency property by looking for a FooProperty static field or property with DependencyProperty type. This field is not automatically injected by the [DependencyProperty] aspect (due to limitations of PostSharp's aspect framework).

    However, when you declare this field or property it would be enough for the Xaml compiler to recognize the property as a dependency property. ( The aspect will then set the field/property at runtime, so it has a correct value at runtime and is usable. )

    public static DependencyProperty WidgetProperty { get; private set; }
    
    [DependencyProperty]
    public WidgetConfiguration Widget { get; set; }
    

    Your property setter is not invoked, because WPF bindings change the value store on the DependencyObject itself instead of accessing the property setter.

    The problem seems to be in the fact that you are changing the DataContext of your control in the constructor. This is going to break Bindings set on the DataContext in the parent control (bindings use DataContext of controls they assigned to). One way to reference properties of your control is like this:

    <Label x:Name="label" Content="{Binding ElementName=root, Path=Widget.Name}" HorizontalAlignment="Left" />
    

    Where "root" is a x:Name="root" given to your control (the root UserControl element).