如果所有者类型是拥有它的 class 而不是 "string",则不会设置 DependencyProperty

DependencyProperty not being set if owner type is the class that owns it, instead of "string"

我有这个用户控件代码隐藏:

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>

我的 MainWindow 中有这些控件之一,当我在“SetValue”行上放置一个断点并更改 TextBox 的值时,断点被击中,一切都与世界正确。如果我将 DP 注册更改为:

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

即使没有其他任何更改,该断点也不再命中,据我所知,这是注册 DP 的更好、正确的方法。

为什么会发生这种情况以及如何解决?

Why is this happening and how to fix it?

因为您违反了 WPF 约定。 XAML 构造函数(编译器)无法猜测你是如何打破这个约定的。 事实上,在第一个选项中,您创建了一个常规 CLR 属性。 而且,如果您尝试为其设置绑定,您很可能会遇到编译错误。

按照惯例,XAML 编译器工作时,CLR 包装器不应包含任何逻辑,除了调用 GetValue () 和 SetValue () 并且 属性 的所有者应该是声明它们的 class 。 在这种情况下,编译器可以找到原始的 DependecyProperty 并且在设置/获取值时它将使用它而不调用 属性 的 CLR 包装器。 CLR 包装器是为了方便程序员在 Sharp 中“手动编码”时设计的。

INotifyPropertyChanged 接口看起来也毫无意义。 DependecyProperty 有自己的更改通知机制,您不需要通过调用 PropertyChanged 来复制它。

如果您需要跟踪 DependecyProperty 值的变化,您必须在声明 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?

这是非常错误的,但如果需要,您也可以在回调方法中引发 PropertyChanged。

    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?

如果 VM 需要获取 MyProperty 的更改值 属性,您需要在使用控件的地方设置对此 属性 的绑定。
并且已经在 VM 本身中处理其属性的更改。 通常,VM 的基础 class 实现中有相应的方法。
但是如果没有这个方法,那么可以使用这个属性.

的setter

示例:

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

补充建议。
您给定的实现不太适合 UserControl。
你最好改成自定义控件。