WPF自定义MyTextBox绑定失效:修改Text未触发AString setter原因?
问题
基于TextBox类创建了派生类MyTextBox,添加了名为AString的依赖属性和名为ContentChangedEvent的路由事件。在XAML代码中,将Text属性绑定到自身的AString属性(已设置TwoWay双向绑定模式),但修改Text属性值时,AString的setter并未触发,绑定未生效,请问这是什么原因?
C#代码:
public class MyTextBox : TextBox { public static readonly RoutedEvent ContentChangedEvent; public event RoutedEventHandler ContentChanged { add { AddHandler(ContentChangedEvent, value); } remove { RemoveHandler(ContentChangedEvent, value); } } public string AString { get { return (string)GetValue(AStringProperty); } set { SetValue(AStringProperty, value); OnContentChanged(); } } public static readonly DependencyProperty AStringProperty; static MyTextBox() { ContentChangedEvent = EventManager.RegisterRoutedEvent("ContentChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyTextBox)); var a = new FrameworkPropertyMetadata("555"); a.BindsTwoWayByDefault = true; AStringProperty = DependencyProperty.Register("AString", typeof(string), typeof(MyTextBox), a); } private void OnContentChanged() { RaiseEvent(new RoutedEventArgs(ContentChangedEvent, this)); } }
XAML代码:
<local:MyTextBox x:Name="mtb1" ContentChanged="MyTextBox_ContentChanged" Text="{Binding AString, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" />
已尝试将绑定模式改为双向绑定,但问题仍未解决。
原因分析与解决方案
核心原因
- WPF依赖属性的CLR包装器(即AString的get/set方法)不会被绑定系统调用。绑定系统直接通过
GetValue和SetValue操作依赖属性的底层存储区,完全绕开了手动编写的setter逻辑,所以修改Text触发绑定更新时,AString的setter不会执行,OnContentChanged自然也不会被调用。 - 另外,TextBox的Text属性默认
UpdateSourceTrigger为LostFocus,如果在输入过程中直接查看AString值,可能会误以为绑定未生效,实际只有焦点离开控件后才会同步值到绑定源。
修复步骤
- 清理CLR包装器的额外逻辑:依赖属性的CLR包装器应该仅包含
GetValue和SetValue,不能添加业务逻辑,因为绑定、动画等WPF内部操作都会跳过包装器。 - 用依赖属性元数据回调处理值变更:注册AStringProperty时,给
FrameworkPropertyMetadata添加PropertyChangedCallback,通过这个回调响应属性值变化,触发路由事件。 - 可选:调整绑定的更新时机:如果需要输入时实时同步Text和AString,将绑定的
UpdateSourceTrigger设为PropertyChanged。
修改后的C#代码
public class MyTextBox : TextBox { public static readonly RoutedEvent ContentChangedEvent; public event RoutedEventHandler ContentChanged { add { AddHandler(ContentChangedEvent, value); } remove { RemoveHandler(ContentChangedEvent, value); } } public string AString { get { return (string)GetValue(AStringProperty); } set { SetValue(AStringProperty, value); } // 仅保留基础SetValue操作 } public static readonly DependencyProperty AStringProperty; static MyTextBox() { ContentChangedEvent = EventManager.RegisterRoutedEvent("ContentChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyTextBox)); var metadata = new FrameworkPropertyMetadata( defaultValue: "555", propertyChangedCallback: OnAStringChanged); // 添加值变更回调 metadata.BindsTwoWayByDefault = true; AStringProperty = DependencyProperty.Register("AString", typeof(string), typeof(MyTextBox), metadata); } // 依赖属性值变更时触发的静态回调 private static void OnAStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is MyTextBox textBox) { textBox.OnContentChanged(); } } private void OnContentChanged() { RaiseEvent(new RoutedEventArgs(ContentChangedEvent, this)); } }
修改后的XAML(实现实时同步)
<local:MyTextBox x:Name="mtb1" ContentChanged="MyTextBox_ContentChanged" Text="{Binding AString, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
内容的提问来源于stack exchange,提问作者FaGGG
相关产品推荐
相关产品推荐

