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

如何向 Silverlight 控件添加用户自定义属性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (9投票s)

2014年1月29日

CPOL

5分钟阅读

viewsIcon

18552

downloadIcon

138

为任何 Silverlight 控件添加用户自定义属性都很简单。

引言

您是否曾经需要将某些信息传递给一个UserControl(甚至是一个原生控件),却发现自己不得不对该控件的Tag属性进行CSV字符串操作?
我曾遇到过这种情况,但最终发现,为任何控件创建附加属性其实非常简单。
为了演示,我将使用一个Button,但您可以在任何您喜欢的控件上使用此方法。
让我们来看看如何操作。
我将使用 Silverlight 4、VS 2010 和 Blend 4。

演示页面

为了演示其工作原理,首先创建一个名为“PropertiesDemo”的 Silverlight 项目,并在MainPage中添加 3 个Buttons和一个TextBox

<UserControl x:Class="PropertiesDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >

  <Grid x:Name="LayoutRoot" Background="White">
    <Button Content="Powered Up Button" Height="23" 
    HorizontalAlignment="Left" Margin="10,10,0,0" 
    x:Name="Button1" VerticalAlignment="Top" Width="126" />
    <Button Content="Value 1" Height="23" 
    HorizontalAlignment="Left" Margin="10,43,0,0" 
    Name="Button2" VerticalAlignment="Top" Width="61" />
    <Button Content="Value 2" Height="23" 
    HorizontalAlignment="Left" Margin="77,43,0,0" 
    Name="Button3" VerticalAlignment="Top" Width="59" />
    <TextBox Height="23" HorizontalAlignment="Left" 
    Margin="157,10,0,0" Name="TextBox1" 
    VerticalAlignment="Top" Width="120" />
  </Grid>
</UserControl>

Using the Code

首先,为了创建这个附加属性,我们需要创建一个新Class,它继承自Button类,并将其作为基类。
向客户端项目添加一个名为“ButtonEx”的新Class文件,并将代码替换为以下行。

using System.Windows;
using System.Windows.Controls;
namespace PropertiesDemo
{
  public class ButtonEx : Button
  {
    public ButtonEx() : base() { }
  }
}

由于Button1将是功能增强的按钮,我们需要在MainPage XAMLUserControl标签中引用我们刚刚创建的新类。

xmlns:my="clr-namespace:PropertiesDemo"

并将Button1的类型标签“<Button”替换为“<my:ButtonEx”。

<my:ButtonEx Content="Powered Up Button" Height="23" 
HorizontalAlignment="Left" Margin="10,10,0,0" x:Name="Button1" 
VerticalAlignment="Top" Width="126" />

现在,我们可以在ButtonEx类中创建这个附加属性,它将成为

using System.Windows;
using System.Windows.Controls;

namespace PropertiesDemo
{
  public class ButtonEx : Button
  {
    private static DependencyProperty ExtraPropProperty = DependencyProperty.Register
    ("ExtraProp", typeof(string), typeof(ButtonEx), 
    new PropertyMetadata("No extra prop set"));
    public string ExtraProp
    {
      get { return (string)GetValue(ExtraPropProperty); }
      set { SetValue(ExtraPropProperty, value); }
    }

    public ButtonEx() : base() { }
  }
}

如果此时我们生成项目,该属性将与其他按钮属性一样可用。

现在是关于Click事件的部分。
Button1 Click事件将弹出一个MessageBox,显示ExtraProp属性的值。
Button2Button3将只更改Button1上新属性的值。

<my:ButtonEx Content="Powered Up Button" Height="23" 
HorizontalAlignment="Left" Margin="10,10,0,0" x:Name="Button1" 
VerticalAlignment="Top" Width="126" Click="Button1_Click"/>
<Button Content="Value 1" Height="23" HorizontalAlignment="Left" 
Margin="10,43,0,0" Name="Button2" VerticalAlignment="Top" 
Width="61" Click="Button2_Click" />
<Button Content="Value 2" Height="23" HorizontalAlignment="Left" 
Margin="77,43,0,0" Name="Button3" VerticalAlignment="Top" 
Width="59" Click="Button3_Click" />
private void Button1_Click(object sender, RoutedEventArgs e)
{
  MessageBox.Show(Button1.ExtraProp.ToString());
}

private void Button2_Click(object sender, RoutedEventArgs e)
{
  Button1.ExtraProp = "Hallo from first button";
}

private void Button3_Click(object sender, RoutedEventArgs e)
{
  Button1.ExtraProp = "Bye from second button";
}

F5 试试看!太棒了!

现在,是关于绑定部分。
正如我之前所说的,这个新属性与其他任何属性一样可用,并且会出现在VSBlendProperty窗口中。

MainPage XAML中的TextBox1行替换为

<TextBox Height="23" HorizontalAlignment="Left" 
Margin="157,10,0,0" Name="TextBox1" VerticalAlignment="Top" 
Width="120" Text="{Binding Path=ExtraProp, Mode=TwoWay, ElementName=Button1}" />

按 F5 并享受。

TwoWay绑定功能齐全,您可以通过更改TextBox内容来更改属性值。

当然,您可以根据需要添加任意数量的DependencyProperties,并且可以使用它们在任何其他类型的控件中。

希望这对您有所帮助。

DependencyProperty - 关键点

我在此示例中使用的DependencyProperty,虽然简单,但有些地方必须按此方式处理。

基本上,我们将使用DependencyProperty类中的Register方法在类上注册一个新属性。

对于该方法,我们有三种选择。我们将使用第二个选项,“Register(String, Type, Type, PropertyMetadata)”。

private static DependencyProperty ExtraPropProperty = DependencyProperty.Register
("ExtraProp", typeof(string), typeof(ButtonEx), 
new PropertyMetadata("No extra prop set"));

命名约定规定,DependencyProperty的名称必须是已注册属性的名称加上后缀Property

因此,如果我们的新属性是ExtraPropRegister方法中的String参数),那么DependencyProperty的名称将是ExtraPropProperty

第一个Type是属性类型(在我们的例子中是string),第二个是属性所有者(要创建此 DP 的类,在我们的例子中是我们基类的名称ButtonEx)。

PropertyMetadata用于在基类初始化时设置新属性的默认值。

属性包装器

public string ExtraProp
{
  get { return (string)GetValue(ExtraPropProperty); }
  set { SetValue(ExtraPropProperty, value); }
}

它没什么特别的,只是它与 DP 的注册名称相同,并且get;set;引用了创建的 DP。

更改通知

DependencyProperty的一个优点是它内置了INotifyPropertyChange,因此XAML上的绑定效果很好,并且在 DP 值发生任何更改时都会得到通知和更新。

您甚至可以添加其他代码在值更改时执行。

一种方法

public string ExtraProp
{
  get { return (string)GetValue(ExtraPropProperty); }
  set 
    { 
      SetValue(ExtraPropProperty, value); 
      //Additional code goes here
    }
}

关于更改通知的两个警告

更改通知将在且仅在(这一点很重要)DependencyProperty的值更改时引发。

使用原生类型(stringintbool等)时效果很好。

但是,在使用复杂类型或用户定义类型时,您必须意识到只有类成员的更改才会引发通知更改。这意味着 DP 的值不会改变,只有它的某个成员改变了,因此 DP 不会引发任何更改通知事件。

如果您想让 DP 引发通知事件,您必须从您的用户定义类中将其冒泡。

请参阅本文中的Items_Class类作为示例,其中我使用了该技术来冒泡ObservableCollection中某个项的通知更改事件。

另一个您会遇到麻烦的地方是,当您在一个UserControl中使用 DP,并且该UserControl将被用作绑定集合的ItemTemplate时。

您会发现,在属性包装器的set;SetValue()之后添加的附加代码不会运行。我不知道原因,但它就是不运行。

这时我使用Register()方法的变体,并使用PropertyMetadata类的第二种形式,“PropertyMetadata(Object, PropertyChangedCallback)”,并定义一个在属性值更改时执行的回调函数。

private static DependencyProperty ExtraPropProperty = DependencyProperty.Register("ExtraProp", typeof(string), typeof(ButtonEx), new PropertyMetadata("No extra prop set", new PropertyChangedCallback(ExtraPropChanged)));

private static void ExtraPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as ButtonEx).ExtraPropChanged(e); }
private void ExtraPropChanged(DependencyPropertyChangedEventArgs e)
{
  MessageBox.Show("Old value: " + (e.OldValue != null ? 
  e.OldValue.ToString() : "null") + "\n" + "New value: " + 
  (e.NewValue != null ? e.NewValue.ToString() : "null"));
}

更多关于 DependencyProperty

请查看这些链接以获得更深入的DependencyProperty知识

完整代码

MainPage.xaml

<UserControl x:Class="PropertiesDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:PropertiesDemo"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >

  <Grid x:Name="LayoutRoot" Background="White">
    <my:ButtonEx Content="Powered Up Button" Height="23" 
    HorizontalAlignment="Left" Margin="10,10,0,0" x:Name="Button1" 
    VerticalAlignment="Top" Width="126" Click="Button1_Click"/>
    <Button Content="Value 1" Height="23" 
    HorizontalAlignment="Left" Margin="10,43,0,0" Name="Button2" 
    VerticalAlignment="Top" Width="61" Click="Button2_Click" />
    <Button Content="Value 2" Height="23" 
    HorizontalAlignment="Left" Margin="77,43,0,0" Name="Button3" 
    VerticalAlignment="Top" Width="59" Click="Button3_Click" />
    <TextBox Height="23" HorizontalAlignment="Left" Margin="157,10,0,0" 
    Name="TextBox1" VerticalAlignment="Top" Width="120" 
    Text="{Binding Path=ExtraProp, Mode=TwoWay, ElementName=Button1}" />
  </Grid>
</UserControl>

MainPage.cs

using System.Windows.Controls;
using System.Windows;

namespace PropertiesDemo
{
  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();
    }

    private void Button1_Click(object sender, RoutedEventArgs e)
    {
      MessageBox.Show(Button1.ExtraProp.ToString());
    }

    private void Button2_Click(object sender, RoutedEventArgs e)
    {
      Button1.ExtraProp = "Hallo from first button";
    }

    private void Button3_Click(object sender, RoutedEventArgs e)
    {
      Button1.ExtraProp = "Bye from second button";
    }
  }
}

ButtonEx.cs

using System.Windows;
using System.Windows.Controls;
namespace PropertiesDemo
{
  public class ButtonEx : Button
  {
    private static DependencyProperty ExtraPropProperty = DependencyProperty.Register
    ("ExtraProp", typeof(string), typeof(ButtonEx), 
    new PropertyMetadata("No extra prop set", new PropertyChangedCallback(ExtraPropChanged)));
    public string ExtraProp
    {
      get { return (string)GetValue(ExtraPropProperty); }
      set { SetValue(ExtraPropProperty, value); }
    }

    private static void ExtraPropChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e) { (d as ButtonEx).ExtraPropChanged(e); }
    private void ExtraPropChanged(DependencyPropertyChangedEventArgs e)
    {
      MessageBox.Show("Old value: " + (e.OldValue != null ? 
      e.OldValue.ToString() : "null") + "\n" + 
      "New value: " + (e.NewValue != null ? e.NewValue.ToString() : "null"));
    }

    public ButtonEx() : base() { }
  }
}

结论

这是为控件添加附加属性的一种非常简单的方法,并且它带来了学习和使用DependencyProperties的便捷方式。

我的建议:使用并滥用DependencyProperties。它可以为您节省大量代码行。

请不要忘记投票和/或评论!

历史

  • 2014 年 1 月 29 日 - 添加了一些关于DependencyProperties的解释
© . All rights reserved.