如何在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绑定到所有的属性上。显然有多种方法可以做到这一点,我想知道不同的方法(缺点)的优点。让我们列一个清单:

  1. 我的第一个方法是:

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

    这工作,是相当简单的,但它是很多多余的代码,如果我必须为所有的textblock输入它。在这个例子中,我们可以只是复制粘贴,但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有同样的问题。但是,Name可以在其他地方设置

  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. 还有其他好的/有趣的选择吗?

那么在什么情况下该怎么做呢?我在什么地方出错了吗?
指定问题:
这些方法都对吗?有些人比其他人优越吗?

如何在UserControl中绑定多个属性

看起来你在这里做了很多好的测试!正如你可能已经注意到的那样,你的大多数方法都是有效的,但我建议你使用第一种方法。虽然它可能看起来有点重复,但它非常明确,而且维护起来相当简单。它还使不太精通xaml的人也能很容易地读懂您的代码。

你已经知道解决方案2和3的问题。创建自己的标记扩展有点过分(至少在本例中),并且使代码更难理解。解决方案6工作得很好,但正如你所说,它使它不可能知道什么文本块是绑定到xaml.

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

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

见ReSharper WPF错误:"Cannot resolve symbol "MyVariable"由于未知的数据文本"

对于任何使用用户控件将DataContext设置为RelativeSource Self的人来说,这是一种常见的做法,请参阅我如何绑定到RelativeSource Self?