如何在 UserControl 中绑定多个属性
How to bind multiple properties in a 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 有同样的问题。不过,名称可以从其他地方设置。
如果我们编写自己的 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 更难阅读,因为现在缺少有关绑定的信息。
还有哪些 good/interesting 选项?
那么在什么情况下应该怎么走呢?我是不是哪里弄错了?
要指定问题:
这些方法都对吗?有些人优于其他人吗?
只需使用#2,并假设 DataContext 是正确的。这就是标准。使用用户控件的人有责任确保正确设置 DataContext。
其他任何事情都只会增加不必要的复杂性。
参见ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"。
使用用户控件将 DataContext 设置为 RelativeSource Self
的任何人的常见做法请参见 How do I bind to RelativeSource Self?
看来你在这里做了很多不错的测试!正如您可能已经注意到的那样,您执行此操作的大部分方法都有效,但我建议您使用第一种方法。虽然它看起来有点重复,但它非常明确且维护起来相当简单。它还使您的代码很容易被那些不是 xaml.
专家的人阅读
您已经知道解决方案 2 和 3 的问题。创建自己的标记扩展有点矫枉过正(至少在这种情况下)并且会使您的代码更难理解。解决方案 6 工作正常,但正如您所说,它使得无法知道文本块在 xaml.
中绑定到什么
假设我们有一个这样的用户控件:
<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 有同样的问题。不过,名称可以从其他地方设置。
如果我们编写自己的 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 更难阅读,因为现在缺少有关绑定的信息。
还有哪些 good/interesting 选项?
那么在什么情况下应该怎么走呢?我是不是哪里弄错了?
要指定问题:
这些方法都对吗?有些人优于其他人吗?
只需使用#2,并假设 DataContext 是正确的。这就是标准。使用用户控件的人有责任确保正确设置 DataContext。
其他任何事情都只会增加不必要的复杂性。
参见ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"。
使用用户控件将 DataContext 设置为 RelativeSource Self
的任何人的常见做法请参见 How do I bind to RelativeSource Self?
看来你在这里做了很多不错的测试!正如您可能已经注意到的那样,您执行此操作的大部分方法都有效,但我建议您使用第一种方法。虽然它看起来有点重复,但它非常明确且维护起来相当简单。它还使您的代码很容易被那些不是 xaml.
专家的人阅读您已经知道解决方案 2 和 3 的问题。创建自己的标记扩展有点矫枉过正(至少在这种情况下)并且会使您的代码更难理解。解决方案 6 工作正常,但正如您所说,它使得无法知道文本块在 xaml.
中绑定到什么