如何在UserControl中绑定多个属性
本文关键字:属性 绑定 UserControl | 更新日期: 2023-09-27 17:54:11
假设我们有一个UserControl:
<UserControl x:Class="...>
<StackPanel>
<TextBlock Name="TextBlock1" />
<TextBlock Name="TextBlock2" />
<TextBlock Name="TextBlock3" />
...
<TextBlock Name="TextBlock10" />
</StackPanel>
</UserControl>
我们有这样定义的属性:
public string Text1 { get; set; }
public string Text2 { get; set; }
public string Text3 { get; set; }
...
public string Text10 { get; set; }
我想把所有的textblock绑定到所有的属性上。显然有多种方法可以做到这一点,我想知道不同的方法(缺点)的优点。让我们列一个清单:
我的第一个方法是:
<TextBlock Name="TextBlock1" Text="{Binding Path=Text1, RelativeSource={RelativeSource AncestorType=UserControl}}" />
这工作,是相当简单的,但它是很多多余的代码,如果我必须为所有的textblock输入它。在这个例子中,我们可以只是复制粘贴,但UserControl可以更复杂。
当我在谷歌上搜索这个问题时,我找到了这个解决方案:
<UserControl ... DataContext="{Binding RelativeSource={RelativeSource Self}}"> <TextBlock Name="TextBlock1" Text="{Binding Path=Text1}" />
这在xaml中看起来很干净,但是DataContext可以用其他方式使用。所以如果有人使用这个UserControl并改变了DataContext,我们就完蛋了。
另一个常见的解决方案似乎是:
<UserControl ... x:Name="MyUserControl"> <TextBlock Name="TextBlock1" Text="{Binding Path=Text1, ElementName=MyUserControl}" />
这和2有同样的问题。但是,Name可以在其他地方设置
如果我们写我们自己的MarkupExtension呢?
public class UserControlBindingExtension : MarkupExtension { public UserControlBindingExtension() { } public UserControlBindingExtension(string path) { this.Path = path; } private Binding binding = null; private string path; [ConstructorArgument("path")] public string Path { get { return path; } set { this.path = value; binding = new Binding(this.path); binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1); } } public override object ProvideValue(IServiceProvider serviceProvider) { if(binding == null) return null; return binding.ProvideValue(serviceProvider); } }
现在我们可以这样做:
<UserControl ... xmlns:self="clr-namespace:MyProject"> <TextBlock Name="TextBlock1" Text="{self:UserControlBinding Path=Text1}"
整洁!但我不相信我的实现是防弹的,我宁愿不写我自己的MarkupExtension。
与4类似。我们可以这样做:
public class UserControlBindingHelper : MarkupExtension { public UserControlBindingHelper() { } public UserControlBindingHelper(Binding binding) { this.Binding = binding; } private Binding binding; [ConstructorArgument("binding")] public Binding Binding { get { return binding; } set { binding = value; binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1); } } public override object ProvideValue(IServiceProvider serviceProvider) { if (binding == null) return null; return binding.ProvideValue(serviceProvider); } }
将产生如下代码:
<TextBlock Name="TextBlock1" Text="{self:UserControlBindingHelper {Binding Text1}}" />
我们可以用代码来做!
private void setBindingToUserControl(FrameworkElement element, DependencyProperty dp, string path) { Binding binding = new Binding(path); binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(StateBar), 1); element.SetBinding(dp, binding); }
然后我们这样做:
setBindingToUserControl(this.TextBlock1, TextBlock.Text, "Text1");
相当好,但是它使xaml更难阅读,因为现在丢失了关于绑定的信息。
还有其他好的/有趣的选择吗?
那么在什么情况下该怎么做呢?我在什么地方出错了吗?
指定问题:
这些方法都对吗?有些人比其他人优越吗?
看起来你在这里做了很多好的测试!正如你可能已经注意到的那样,你的大多数方法都是有效的,但我建议你使用第一种方法。虽然它可能看起来有点重复,但它非常明确,而且维护起来相当简单。它还使不太精通xaml的人也能很容易地读懂您的代码。
你已经知道解决方案2和3的问题。创建自己的标记扩展有点过分(至少在本例中),并且使代码更难理解。解决方案6工作得很好,但正如你所说,它使它不可能知道什么文本块是绑定到xaml.
仅使用#2,并假设DataContext是正确的。这是标准。使用用户控件的人有责任确保DataContext被正确设置。
其他任何东西都只会增加不必要的复杂性。
见ReSharper WPF错误:"Cannot resolve symbol "MyVariable"由于未知的数据文本"
对于任何使用用户控件将DataContext设置为RelativeSource Self
的人来说,这是一种常见的做法,请参阅我如何绑定到RelativeSource Self?