将绑定公开为控件的属性






4.54/5 (5投票s)
演示如何创建多功能控件,
我必须承认,这篇文章的标题并不是完全清楚,但是我找不到用一个简短的句子来概括内容的方法,所以我们将直接进入一个例子。比如说,您已经开发了一个时髦的名片,如上图所示,使用下面的简单XAML
<Border BorderBrush="LightGray" Background="White"
BorderThickness="1.5" CornerRadius="20"
Width="220" Height="120">
<Border Margin="5" BorderBrush="LightGray"
BorderThickness="1.5" CornerRadius="15">
<Border.Background>
<ImageBrush>
<ImageBrush.ImageSource>
<BitmapImage UriSource="back.jpg" />
</ImageBrush.ImageSource>
</ImageBrush>
</Border.Background>
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}"
FontSize="15" FontWeight="Bold" />
<Rectangle Stroke="DarkGray" HorizontalAlignment="Stretch"
StrokeThickness="0.5" Fill="Black" Height="1"/>
<TextBlock Margin="0,10,0,0" Text="{Binding Company}"/>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="e: " FontSize="10"/>
<TextBlock Text="{Binding Email}" FontSize="10"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="w: " FontSize="10"/>
<HyperlinkButton Content="{Binding Web}" FontSize="10"
NavigateUri="{Binding Web}"/>
</StackPanel>
</StackPanel>
</Border>
</Border>
上面的XAML创建了一个简单的可视布局,它绑定到继承的DataContext
所呈现的数据对象的Name
,Email
,Web
和Company
属性。很好也很简单。
现在,假设您想在应用程序中的多个位置重用此XAML标记。显而易见的解决方案是将其打包在UserControl
中。但是,在应用程序的其他地方,联系人详细信息存储在不同类型中,具有不同的属性,也许属性Web在某些地方使用,而属性URL在其他地方使用。如果我们的UserControl
仅包含上面给出的XAML标记,则绑定路径是硬编码且不灵活的。那么,我们如何打包上面的标记以进行重用,同时保持灵活性?
在不支持数据绑定的UI框架中,显而易见的选择是定义一个interface
IContactDetails
,它定义了一个编译时合同,控件的客户端必须在其业务对象上实现该合同。这将使我们能够强制存在具有特定名称的属性。但是,对于WPF / Silverlight,绑定框架消除了控件要求存在某个interface
的需求,结果现在生活变得更加美好!
所以……我们真正想要的是允许我们的ContactDetails
用户控件的客户端为用户控件本身中的各种框架元素提供绑定。
可以通过调用在DependencyObject
上定义的SetBinding
方法在代码隐藏中构造绑定,如下面的示例所示
// create a binding with a given source and property-path
Binding binding = new Binding();
binding.Source = myClass;
binding.Path = new PropertyPath("WindowTitle");
// bind the button's Content property
button.SetBinding(Button.ContentProperty, binding);
因此,如果我们公开控件的各种绑定作为属性(因此是这篇博文的标题),我们可以使用Setbinding
方法相应地设置名称,电子邮件,web元素的绑定。
我第一次尝试时,我将一个类型为Binding
的NameBinding
依赖项属性添加到了我的UserControl
,因此客户端XAML如下所示
<local:ContactDetailsControl
NameBinding="{Binding Name}"/>
但是,这并没有完全达到预期的效果。当XAML阅读器处理上面的标记时,它会创建我们想要的Binding
对象,但是它不会将NameBinding
属性值设置为此绑定。相反,它使用它来绑定 NameBinding
属性。
解决此问题的方法是使NameBinding
成为标准CLR属性。这次,当XAML阅读器再次遇到上面的标记时,它再次创建了Binding
对象,但是由于NameBinding
不是依赖项属性,因此它不使用它来绑定该属性,而只是设置属性值。
我们的ContactDetails
用户控件的代码隐藏如下所示
public partial class ContactDetailsControl : UserControl
{
public Binding NameBinding { get; set; }
public Binding CompanyBinding { get; set; }
public Binding WebBinding { get; set; }
public Binding EmailBinding { get; set; }
public ContactDetailsControl()
{
InitializeComponent();
}
private void Border_Loaded(object sender, RoutedEventArgs e)
{
nameText.SetBinding(TextBlock.TextProperty, NameBinding);
webText.SetBinding(HyperlinkButton.ContentProperty, WebBinding);
webText.SetBinding(HyperlinkButton.NavigateUriProperty, WebBinding);
companyText.SetBinding(TextBlock.TextProperty, CompanyBinding);
emailText.SetBinding(TextBlock.TextProperty, EmailBinding);
}
}
这四个绑定作为CLR属性公开。当UI Loaded
时,我们使用它们来绑定控件中的各种元素。最终结果是,我们现在拥有一个可重用的控件,同时保持了绑定框架提供的灵活性,如下面的用法所示
<local:ContactDetailsControl
NameBinding="{Binding FullName}"
WebBinding="{Binding Website}"
CompanyBinding="{Binding Company}"
EmailBinding="{Binding Email}"/>
现在,来看另一个例子,更有趣一些……
(CodeProject不支持嵌入式Silverlight内容……在我的博客上观看。)
上面的Silverlight应用程序显示了许多表现出布朗运动的粒子。
此应用程序在ObservableCollection
中创建了50个Particle
对象的实例
class Particle : INotifyPropertyChanged
{
public double XLocation { ... }
public double YLocation { ... }
public double Excitement { ... }
}
这些粒子由ScatterControl
呈现,这是一个UserControl
<Border BorderBrush="LightGray" BorderThickness="1">
<!-- ItemsSource property set in code-behind -->
<local:ScatterControl x:Name="scatterControl"
XValueBinding="{Binding Path=XLocation}"
YValueBinding="{Binding Path=YLocation}"
FillBinding="{Binding Path=Excitement,
Converter={StaticResource FillConverter}}"/>
</Border>
此控件具有类型为IEnumerable
的ItemsSource
属性,并公开了另外三个类型为Binding
的属性。
此控件的标记如下所示
<UserControl ... >
<ItemsControl x:Name="itemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Ellipse Width="10" Height="10" Fill="Red"
Loaded="Ellipse_Loaded"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
这些粒子使用ItemsControl
呈现,该控件为每个绑定的粒子创建一个Ellipse
实例,并将其放置在Canvas
中(由ItemsControl.ItemsPanel
属性定义)。当每个椭圆加载时,我们在代码隐藏中设置绑定
private void Ellipse_Loaded(object sender, RoutedEventArgs e)
{
Ellipse ellipse = sender as Ellipse;
ellipse.SetBinding(Canvas., XValueBinding);
ellipse.SetBinding(Canvas., YValueBinding);
ellipse.SetBinding(Ellipse.FillProperty, FillBinding);
}
同样,通过将Binding
作为属性公开,此用户控件与直接将其标记包含在页面中一样灵活。此示例还说明了另一个有趣的观点,客户端提供的Binding
已针对ItemsControl
创建的每个Ellipse
重复使用。之所以可以这样做,是因为在Ellipse
上调用SetBinding
时,Binding
不会与Ellipse
关联,而是用于创建BindingExpression的实例。Binding
用于声明绑定,而BindingExpression
是实现,即,它执行从源到目标(反之亦然)复制数据的繁重工作。
您可以在此处下载两个示例的完整源代码。
此致,
Colin E.