Home > database >  DependencyProperty not being set if owner type is the class that owns it, instead of "string&qu
DependencyProperty not being set if owner type is the class that owns it, instead of "string&qu

Time:10-10

I have this user control codebehind:

public partial class MyControl : UserControl, INotifyPropertyChanged
{
    public string MyProperty
    {
        get => (string)GetValue(MyPropertyProperty);
        set
        {
            SetValue(MyPropertyProperty, value);
            PropertyChanged?.Invoke(null, null);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(string));

    public MyControl()
    {
        InitializeComponent();
    }
}

XAML:

<Grid>        
    <TextBox Text="{Binding MyProperty,
                    Mode=OneWayToSource,
                    UpdateSourceTrigger=PropertyChanged,
                    RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
             VerticalAlignment="Center"
             Height="20"
             Margin="5"/>
</Grid>

I have one of those controls in my MainWindow and when I put a breakpoint on the "SetValue" line and change the value of the TextBox the breakpoint is hit and everything is right with the world. If I change the DP registering to:

    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl));

That breakpoint is no longer hit even though nothing else has been changed and afaik this is the better, correct way to register the DP.

Why is this happening and how to fix it?

CodePudding user response:

Why is this happening and how to fix it?

Because you are violating WPF conventions. And the XAML constructor (compiler) cannot guess how you are breaking this convention. In fact, in the first option, you create a regular CLR property. And, if you try to set a binding for it, you will most likely get a compilation error.

By convention, by which the XAML compiler works, the CLR wrapper should not contain ANY LOGIC, except for calling GetValue () and SetValue () and the owner of the property should be the class in which they are declared. In this case, the compiler can find the original DependecyProperty and when setting / getting values ​​it will use it without calling the CLR wrapper of the property. And the CLR wrapper is designed for the convenience of the programmer when "hand-coding" in Sharp.

The INotifyPropertyChanged interface also looks completely pointless. DependecyProperty has its own change notification mechanism and you don't need to duplicate it by calling PropertyChanged.

If you need to track changes in the DependecyProperty value, you must do this in the callback method specified when declaring DependecyProperty.

    public partial class MyControl : UserControl
    {
        public string MyProperty
        {
            get => (string)GetValue(MyPropertyProperty);
            set => SetValue(MyPropertyProperty, value);
        }

        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl), new PropertyMetadata(string.Empty, MyPropertyChanged));

        private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Here the logic for handling value change
            MyControl myControl = (MyControl)d;
            // Some Code
        }

        public MyControl()
        {
            InitializeComponent();
        }
    }

How would I achieve the same with the MyPropertyChanged callback?

This is very wrong, but if you need to, you can raise PropertyChanged in the callback method as well.

    public partial class MyControl : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

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

        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl), new PropertyMetadata(string.Empty, MyPropertyChanged));

        private static readonly PropertyChangedEventArgs MyPropertyPropertyChangedArgs = new PropertyChangedEventArgs(nameof(MyProperty));
        private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Here the logic for handling value change
            MyControl myControl = (MyControl)d;
            myControl.PropertyChanged?.Invoke(myControl, MyPropertyPropertyChangedArgs);
            // Some Code
        }

        public MyControl()
        {
            InitializeComponent();
        }
    }

What would be the proper way to notify the ViewModel of the window the user control is in that the value of "MyProperty" has changed when the user types text in the TextBox?

If the VM needs to get the changed value of the MyProperty property, you need to set a binding to this property at the place where your control is used.
And already in the VM itself, process the change in its properties. Usually, there is a corresponding method in a base class implementation for a VM.
But if there is no such method, then you can use the setter of this property.

Example:

        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.Register(
                nameof(MyProperty),
                typeof(string),
                typeof(MyControl),
                new FrameworkPropertyMetadata(string.Empty, MyPropertyChanged) { BindsTwoWayByDefault = true });
<local:MyControl MyProperty="{Binding VmProperty}" .../>
public class MainViewModel: ....
{
    public string VmProperty
    {
        get => _vmProperty;
        set
        {
           if(Set(ref _vmProperty, value))
           {
               // Some code to handle value change.
           }
        }
    }
}

Additional advice.
Your given implementation is not very suitable for UserControl.
You'd better change it to Custom Control.

  • Related