如何传递UpdateSourceTrigger值给UserControl或运行时更新该属性?
要解决你这个问题,核心是把外部设置的UpdateSourceTrigger传递到UserControl内部TextBox的Text绑定上——因为外部View里的绑定是ViewModel ↔ UserControl.Text,而内部是TextBox.Text ↔ UserControl.Text,这是两个独立的绑定链条,外部设置的Trigger只会影响前者,所以得专门做属性传递。
最佳方案:给UserControl添加可绑定的UpdateSourceTrigger属性
这是最符合WPF设计规范、封装性最好的方案,步骤如下:
1. 在UserControl后台添加DependencyProperty
给LabelWithTextBox新增一个类型为UpdateSourceTrigger的依赖属性,用来接收外部传入的触发方式,同时添加属性变更回调来更新内部TextBox的绑定:
public static readonly DependencyProperty TextUpdateSourceTriggerProperty = DependencyProperty.Register( nameof(TextUpdateSourceTrigger), typeof(UpdateSourceTrigger), typeof(LabelWithTextBox), new PropertyMetadata(UpdateSourceTrigger.LostFocus, OnTextUpdateSourceTriggerChanged)); public UpdateSourceTrigger TextUpdateSourceTrigger { get => (UpdateSourceTrigger)GetValue(TextUpdateSourceTriggerProperty); set => SetValue(TextUpdateSourceTriggerProperty, value); } private static void OnTextUpdateSourceTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (LabelWithTextBox)d; // 获取内部TextBox的Text绑定表达式 var bindingExpression = control.TextBox.GetBindingExpression(TextBox.TextProperty); if (bindingExpression != null && bindingExpression.ParentBinding != null) { // 创建新的Binding(因为Binding是不可变对象,不能直接修改原属性) var newBinding = new Binding { Path = bindingExpression.ParentBinding.Path, Mode = bindingExpression.ParentBinding.Mode, ElementName = bindingExpression.ParentBinding.ElementName, UpdateSourceTrigger = (UpdateSourceTrigger)e.NewValue }; // 重新绑定TextBox的Text属性 control.TextBox.SetBinding(TextBox.TextProperty, newBinding); } }
2. 外部View中使用时传入Trigger
现在你可以在外部直接指定TextUpdateSourceTrigger,它会自动传递到内部TextBox的绑定上:
<controls:LabelWithTextBox Grid.Row="1" Margin="0,5,0,0" Label="Excel Blattname:" SharedSizeGroup="LabelsX" Text="{Binding SheetName, Mode=TwoWay}" TextUpdateSourceTrigger="PropertyChanged" />
这个方案保持了UserControl的封装性,默认还是LostFocus(符合你的性能需求),需要实时更新时再显式设置PropertyChanged。
可选方案
方案1:添加布尔开关属性(简化版)
如果只需要两种状态(默认延迟更新/实时更新),可以用一个布尔属性代替UpdateSourceTrigger枚举,代码更简洁:
public static readonly DependencyProperty IsLiveTextUpdateProperty = DependencyProperty.Register( nameof(IsLiveTextUpdate), typeof(bool), typeof(LabelWithTextBox), new PropertyMetadata(false, (d, e) => { var control = (LabelWithTextBox)d; var trigger = (bool)e.NewValue ? UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger.LostFocus; // 同上面的绑定更新逻辑... var bindingExpression = control.TextBox.GetBindingExpression(TextBox.TextProperty); if (bindingExpression != null && bindingExpression.ParentBinding != null) { var newBinding = new Binding { Path = bindingExpression.ParentBinding.Path, Mode = bindingExpression.ParentBinding.Mode, ElementName = bindingExpression.ParentBinding.ElementName, UpdateSourceTrigger = trigger }; control.TextBox.SetBinding(TextBox.TextProperty, newBinding); } })); public bool IsLiveTextUpdate { get => (bool)GetValue(IsLiveTextUpdateProperty); set => SetValue(IsLiveTextUpdateProperty, value); }
外部使用时只需设置IsLiveTextUpdate="True"即可。
方案2:通过附加属性直接修改内部绑定(不推荐)
如果不想修改UserControl的代码,可以用附加属性来穿透修改内部TextBox的绑定,但这会破坏封装性,只适合临时场景:
public static class TextBoxTriggerHelper { public static readonly DependencyProperty UpdateSourceTriggerProperty = DependencyProperty.RegisterAttached( "UpdateSourceTrigger", typeof(UpdateSourceTrigger), typeof(TextBoxTriggerHelper), new PropertyMetadata(UpdateSourceTrigger.LostFocus, (d, e) => { var textBox = d as TextBox; if (textBox != null) { var bindingExpr = textBox.GetBindingExpression(TextBox.TextProperty); if (bindingExpr != null && bindingExpr.ParentBinding != null) { var newBinding = new Binding { Path = bindingExpr.ParentBinding.Path, Mode = bindingExpr.ParentBinding.Mode, ElementName = bindingExpr.ParentBinding.ElementName, UpdateSourceTrigger = (UpdateSourceTrigger)e.NewValue }; textBox.SetBinding(TextBox.TextProperty, newBinding); } } })); public static void SetUpdateSourceTrigger(DependencyObject obj, UpdateSourceTrigger value) { obj.SetValue(UpdateSourceTriggerProperty, value); } public static UpdateSourceTrigger GetUpdateSourceTrigger(DependencyObject obj) { return (UpdateSourceTrigger)obj.GetValue(UpdateSourceTriggerProperty); } }
外部使用时需要先把内部TextBox设为可访问(比如给UserControl的TextBox添加x:FieldModifier="public"),再通过ElementName指定:
<controls:LabelWithTextBox x:Name="myControl" ...> <telerik:RadWatermarkTextBox x:Name="innerTextBox" x:FieldModifier="public" /> </controls:LabelWithTextBox>
然后在代码后台设置附加属性:
TextBoxTriggerHelper.SetUpdateSourceTrigger(myControl.innerTextBox, UpdateSourceTrigger.PropertyChanged);
运行时修改UpdateSourceTrigger的方法
基于最佳方案的实现,你可以在运行时直接修改UserControl的TextUpdateSourceTrigger属性,回调函数会自动更新内部TextBox的绑定:
// 切换为实时更新 myLabelWithTextBox.TextUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; // 切回默认的失去焦点更新 myLabelWithTextBox.TextUpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
如果用的是布尔开关方案,直接修改IsLiveTextUpdate属性即可。
内容的提问来源于stack exchange,提问作者Devid

