【问题标题】:How to bind multiple properties in a UserControl如何在用户控件中绑定多个属性
【发布时间】: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 绑定到所有这些属性。显然有多种方法可以做到这一点,我想知道不同方法的(缺点)优势。让我们列个清单:

  1. 我的第一个方法是这样的:

    <TextBlock Name="TextBlock1" Text="{Binding Path=Text1, RelativeSource={RelativeSource AncestorType=UserControl}}" />
    

    这很有效,而且相当简单,但是如果我必须为所有的 TextBlocks 键入它,那就是很多冗余代码。在本例中,我们可以只复制粘贴,但 UserControl 可能更复杂。

  2. 当我搜索问题时,我发现了这个解决方案:

    <UserControl ...
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
        <TextBlock Name="TextBlock1" Text="{Binding Path=Text1}" />
    

    这在 xaml 中看起来很干净,但是可以在其他情况下使用 DataContext。所以如果有人使用这个 UserControl 并改变了 DataContext,我们就完蛋了。

  3. 另一个常见的解决方案似乎是这样的:

    <UserControl ...
        x:Name="MyUserControl">
        <TextBlock Name="TextBlock1" Text="{Binding Path=Text1, ElementName=MyUserControl}" />
    

    这和 2 有同样的问题。不过,名称可以从其他地方设置。

  4. 如果我们编写自己的 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。

  5. 类似于 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}}" />
    
  6. 我们可以在代码中做到这一点!

    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 更难阅读,因为现在缺少有关绑定的信息。

  7. 还有哪些其他好的/有趣的选择?

那么在什么情况下该怎么走呢?我是不是在某个地方弄错了?
指定问题:
所有这些方法都正确吗?有些人比其他人优越吗?

【问题讨论】:

  • 您在第 3 点的结论是错误的。在某处使用 UserControl 时设置名称不会破坏 ElementName 绑定。我个人更喜欢RelativeSource AncestorType 绑定。
  • @Clemens 很高兴知道,感谢您的评论。

标签: c# wpf xaml binding user-controls


【解决方案1】:

看起来你在这里做了很多很好的测试!正如您可能已经注意到的那样,您的大多数方法都有效,但我建议您使用您的第一种方法。虽然它可能看起来有点重复,但它非常明确且维护起来相当简单。它还使任何不熟悉 xaml 的人都可以轻松阅读您的代码。

您已经知道解决方案 2 和 3 的问题。创建自己的标记扩展有点矫枉过正(至少在这种情况下),并且会使您的代码更难理解。解决方案 6 工作正常,但正如您所说,它无法知道文本块在 xaml 中绑定到什么。

【讨论】:

  • 不错的答案!即使我的答案与你的答案竞争,也投票给你 :) 欢迎来到 StackOverflow!
【解决方案2】:

只需使用 #2,并假设 DataContext 是正确的。那是标准。使用用户控件的人有责任确保正确设置 DataContext。

其他任何事情都只会增加不必要的复杂性。

ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"

使用用户控件将 DataContext 设置为 RelativeSource Self 的任何人的常见做法见 How do I bind to RelativeSource Self?

【讨论】:

  • 感谢您的链接。您的第二个链接实际上有一个答案,特别建议避免在 UserControl 中设置 DataContext。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-07
  • 2013-04-20
  • 1970-01-01
相关资源
最近更新 更多