65.9K
CodeProject 正在变化。 阅读更多。
Home

将绑定公开为控件的属性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (5投票s)

2009年6月19日

CPOL

4分钟阅读

viewsIcon

22471

演示如何创建多功能控件, 该控件为其内部包含的元素公开绑定

card

我必须承认,这篇文章的标题并不是完全清楚,但是我找不到用一个简短的句子来概括内容的方法,所以我们将直接进入一个例子。比如说,您已经开发了一个时髦的名片,如上图所示,使用下面的简单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所呈现的数据对象的NameEmailWebCompany属性。很好也很简单。

现在,假设您想在应用程序中的多个位置重用此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元素的绑定。

我第一次尝试时,我将一个类型为BindingNameBinding依赖项属性添加到了我的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>

此控件具有类型为IEnumerableItemsSource属性,并公开了另外三个类型为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.

© . All rights reserved.