在写 WPF 程序的时候会遇到依赖属性绑定了,但是值没有更新或者没有绑定上的问题,本文告诉大家可以如何调试

依赖属性不对应

在写依赖属性的时候,默认使用快捷键创建,但是如果是自己写的,需要注意引用的类以及属性名字符串是否对应

例如我在 Foo 类里面定义了 Name 属性,那么我应该确定注册的 Name 和 ower type 是对应的,如下代码

        public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string),
            typeof(Foo), new PropertyMetadata(default(string)));

如果我逗比将 ower type 修改为其他类型,例如主窗口,那么此时绑定将会失效

        public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string),
            typeof(MainWindow), new PropertyMetadata(default(string)));

如果我此时将 Name 写为其他字符串,例如下面代码写为 NameProperty 字符串,那么通过以下的绑定也是没有绑定到依赖属性

        public static readonly DependencyProperty NameProperty = DependencyProperty.Register("NameProperty", typeof(string),
            typeof(MainWindow), new PropertyMetadata(default(string)));

在前台 XAML 绑定代码如下

        <TextBlock x:Name="Text" Text="{Binding Name}"></TextBlock>

那么此时将绑定失败,而如果将前台代码做如下更改,也就是绑定的名字就是注释的时候写的属性名,那么将可以绑定成功

        <TextBlock x:Name="Text" Text="{Binding NameProperty}"></TextBlock>

或者在后台代码通过下面代码绑定

            var binding = new Binding()
            {
                Path = new PropertyPath("NameProperty")
            };
            BindingOperations.SetBinding(Text, TextBlock.TextProperty, binding);

不过这样的绑定方式是比较简单,很快就可以看出来的,但是有小伙伴问我的问题是在后台代码绑定的时候可以成功,但是将代码写在前台绑定的时候就失败,他的后台代码如下,此时绑定是反过来绑定的是,绑定也是对的

            var binding = new Binding()
            {
                Path = new PropertyPath("Text"),
                Source = Text
            };
            BindingOperations.SetBinding(Foo, Foo.NameProperty, binding);

但是反过来写就没有注意到绑定的名字

        <TextBlock x:Name="Text" Text="{Binding Name}"></TextBlock>

所以在发现没有绑定上的时候需要首先看是否定义没有写对

使用 VisualStudio 调试

在 VisualStudio 2019 可以使用实时可视化树查看界面元素和元素的属性,其实这个功能在 VisualStudio 2017 就可以使用

点击调试->窗口->实时可视化树 就可以打开实时可视化树,从可视化树找到对应的需要调试的元素,然后右击就可以查看属性

例如查看 TextBlock 的属性绑定,如果看到了是绑定表达式,那么证明至少绑定存在

绑定属性被修改

使用绑定属性的时候,属性是表达式,而如果给属性赋值,那么属性将会是某个值

例如我在 xaml 绑定了 Name 属性

        <TextBlock x:Name="Text" Text="{Binding Name}"></TextBlock>

但是在执行某个逻辑的时候将属性修改为 123 那么之后这个属性将没有再次和 Name 绑定

            Text.Text = "123";

那么此时可以如何调试?可以通过监听依赖属性的修改拿到在哪个地方修改了值

通过 DependencyPropertyDescriptor.FromProperty 方法可以拿到依赖属性修改的方法,使用下面代码拿到文本属性被修改

            DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty,typeof(TextBlock)).AddValueChanged(Text,
                (sender, args) =>
                {
                    Console.WriteLine("文本被修改");
                });

通过在 Console.WriteLine 这一行添加断点,在执行代码的时候发现进入断点,通过调用堆栈就可以知道是哪个业务修改了属性的值

如何使用调用堆栈和添加断点请看dotnet 代码调试方法

没有找到绑定对象

另一个绑定没有上的原因是可能没有设置对的 DataContext 等,例如我没有设置一个元素的上下文然后进行绑定,如下面代码

        <TextBlock x:Name="Text" Text="{Binding Name}"></TextBlock>

我期望的绑定到 Foo 属性的 Name 属性,但是实际上 Text 没有上下文,可以通过实时可视化树找到元素的上下文看绑定的是哪个类

如我看到了 TextBlock 的上下文实际上是主窗口而不是期望绑定的类,那么就知道为什么没有绑定上

这个方法会用在列表里面的元素和用户控件绑定不上,因为在列表和用户控件里面的上下文可能不是上层元素的上下文而是被指定的,请看WPF Frame 的 DataContext 不能被 Page 继承

没有通知

如果绑定的是普通的 CLR 类,那么需要这个类继承 INotifyPropertyChanged 然后在每个需要通知的属性上面调用通知方法

以下是标准写法,在属性修改的时候调用事件通知

        private string _name;

        public string Name
        {
            get => _name;
            set
            {
                if (value == _name) return;
                _name = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

而此时如果只是给类继承 INotifyPropertyChanged 接口而没有给属性修改得到时候调用事件,那么绑定是不会修改

为啥在Code Behind进行RelativeSource的binding会丢失

WPF 如何调试 binding


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/WPF-%E4%BE%9D%E8%B5%96%E5%B1%9E%E6%80%A7%E7%BB%91%E5%AE%9A%E4%B8%8D%E4%B8%8A%E8%B0%83%E8%AF%95%E6%96%B9%E6%B3%95.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

以下是广告时间

推荐关注 Edi.Wang 的公众号