【发布时间】:2015-06-08 15:02:20
【问题描述】:
假设我们有一个这样的 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; }
并且知道我想将所有这些 TextBlocks 绑定到所有这些属性。显然有多种方法可以做到这一点,我想知道不同方法的(缺点)优势。让我们列个清单:
-
我的第一个方法是这样的:
<TextBlock Name="TextBlock1" Text="{Binding Path=Text1, RelativeSource={RelativeSource AncestorType=UserControl}}" />这很有效,而且相当简单,但是如果我必须为所有的 TextBlocks 键入它,那就是很多冗余代码。在本例中,我们可以只复制粘贴,但 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 有同样的问题。不过,名称可以从其他地方设置。
-
如果我们编写自己的 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 更难阅读,因为现在缺少有关绑定的信息。
还有哪些其他好的/有趣的选择?
那么在什么情况下该怎么走呢?我是不是在某个地方弄错了?
指定问题:
所有这些方法都正确吗?有些人比其他人优越吗?
【问题讨论】:
-
您在第 3 点的结论是错误的。在某处使用 UserControl 时设置名称不会破坏 ElementName 绑定。我个人更喜欢
RelativeSource AncestorType绑定。 -
@Clemens 很高兴知道,感谢您的评论。
标签: c# wpf xaml binding user-controls