WPF如何将UserControl绑定到数据模型的嵌套属性?
我有一个非ViewModel的数据模型(不含依赖属性,希望保持此状态),结构如下:
public class Foo { public Bar Data { get; set; } // 其他属性 } public class Bar { public string Text { get; set; } // 其他属性 }
同时有一个自定义UserControl,结构大致如下:
<MyUserControl ...> <Grid> <TextBox Text="{Binding ???}" /> </Grid> </MyUserControl>
UserControl的代码后台持有一个Foo实例的引用。
需求:将TextBox的Text值绑定到该Foo对象的Data.Text属性。
注意:
- 希望避免在MyUserControl中声明大量依赖属性,因为Foo引用了多个其他类型,这些类型存在同名属性,若用依赖属性需添加前缀区分,过于繁琐。
已尝试的方法及问题:
- 为数据模型附加依赖属性:
using DP = System.Windows.DependencyProperty; static class PropertyProvider { public static readonly DP TextProperty = DP.RegisterAttached ( "Text", typeof (string), typeof (Bar), new UIPropertyMetadata ("") ); public static string GetText (DependencyObject obj) => (string)obj.GetValue (TextProperty); public static void SetText (DependencyObject obj, string value) => obj.SetValue (TextProperty, value); }
此方法绑定失败,经研究发现依赖属性应附加到控件而非数据模型,但疑惑如何通过控件上的附加属性实现与上下文对象的数据读写。
- 将Foo实例设为UserControl的DataContext,绑定路径设为
{Binding Data.Text},但绑定失败,报错“在Foo类型对象上未找到Text属性”。
方案1:修正DataContext绑定的问题
之前绑定失败大概率是因为Foo的Data属性未初始化,或者绑定路径书写有误。确保Foo实例的Data属性不为null,同时正确设置DataContext:
在UserControl的代码后台(例如构造函数或加载事件中):
public MyUserControl() { InitializeComponent(); // 假设你的Foo实例是_foo _foo = new Foo { Data = new Bar() }; // 必须初始化Data属性,否则绑定找不到Bar对象 this.DataContext = _foo; }
然后XAML中的绑定改为:
<TextBox Text="{Binding Data.Text, Mode=TwoWay}" />
如果需要双向绑定,加上Mode=TwoWay,默认对于TextBox的Text是双向,但显式声明更清晰。
方案2:直接绑定到UserControl的Foo字段/属性
如果不想把Foo设为整个UserControl的DataContext(避免影响其他控件的绑定),可以给UserControl起个名字,然后用ElementName绑定:
首先在UserControl的根标签添加x:Name:
<MyUserControl ... x:Name="selfControl"> <Grid> <TextBox Text="{Binding ElementName=selfControl, Path=FooInstance.Data.Text}" /> </Grid> </MyUserControl>
然后在代码后台把Foo实例暴露为公共属性:
public Foo FooInstance { get; private set; } public MyUserControl() { InitializeComponent(); FooInstance = new Foo { Data = new Bar() }; }
注意:如果Foo实例是私有字段,需要改为公共属性才能被绑定访问。
方案3:使用RelativeSource绑定
如果不想给UserControl命名,可以用RelativeSource向上查找UserControl类型:
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=local:MyUserControl}, Path=FooInstance.Data.Text}" />
这里的local是你的UserControl所在的命名空间,需要在XAML开头声明,例如xmlns:local="clr-namespace:YourNamespace"。
关于附加属性的正确用法
你之前尝试给数据模型加附加属性是错误的,附加属性的正确用法是给控件扩展属性,然后通过绑定关联到数据模型。例如,你可以给TextBox加一个附加属性,然后在附加属性的回调中处理与Foo的Data.Text的同步,但这种方式反而更复杂,不如上面的方案直接。
内容的提问来源于stack exchange,提问作者Zenvin

