WPF 依赖属性:兼容 XAML 绑定和代码后台绑定。






3.29/5 (7投票s)
一份易于理解的指南,用于编写与 XAML 绑定和代码后台绑定兼容的 WPF 依赖属性。
引言
WPF 和依赖属性是不可或缺的部分。
在 WPF 开发中,没有依赖属性和 MVVM,几乎寸步难行。但依赖属性的不当使用可能会造成噩梦。
让我们看下面的例子。
假设我们创建了一个自定义媒体播放器控件,并希望从 XAML 绑定 Source 属性。需要注意的是,一旦设置了 source 属性,我们需要执行一些额外的相关操作。
这是 Source 依赖属性的初始代码片段
public Uri Source
{
get { return (Uri)GetValue(SourceProperty); }
set
{
SetValue(SourceProperty, value);
if (MediaElement != null)
{
SetMediaElementSource();
}
}
}
// Using a DependencyProperty as the backing store for Source.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof (Uri), typeof (MediaPlayer));
这是我们的 XAML
<media:MediaPlayer Source="{Binding Source}"
x:Name="MainPlayer"></media:MediaPlayer>
现在如果我们运行应用程序,我们会看到 SetValue(SourceProperty, value);
之后编写的任何代码都不会执行。原因是,如果从 XAML 设置属性,则直接调用 SetValue()
方法。因此,如果从 XAML 绑定 source 属性,我们的播放器将无法工作!如果使用 DependencyProperty.Register
方法的以下重载版本,我们可以轻松克服这个问题。
// Using a DependencyProperty as the backing store for Source.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(Uri),
typeof(MediaPlayer), new FrameworkPropertyMetadata(OnMediaSourceChanged));
private static void OnMediaSourceChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var player = d as MediaPlayer;
if (player != null) player.Source = e.NewValue as Uri;
}
正如你所看到的,新的 FrameworkPropertyMetaData
只是一个方法,每当 Source
属性发生变化时都会被触发,无论它是从 XAML 还是代码后台更改的。由于我们正在 OnMediaSourceChanged
方法中设置 Source
属性,因此 source 的 Set
方法将执行所有业务逻辑!看起来我们的问题解决了!!但事实并非如此,它只是产生了一个新的问题。如果从代码后台设置 Source
属性,Source
的 Set
属性以及 Set
方法中编写的相关代码将执行两次,一次是从代码后台设置,另一次是从 OnMediaSourceChanged
方法。解决此问题的办法是,保持 Set
属性不包含任何业务逻辑(或任何代码!)。相反,任何需要执行的代码都应放在一个单独的方法中,并从 OnMediaSourceChanged
中调用该方法。以下是与 XAML 绑定和代码后台完全兼容的代码。
/// <summary>
/// Gets or sets the source.
/// </summary>
/// <value>The source.</value>
public Uri Source
{
get { return (Uri)GetValue(SourceProperty); }
set
{
SetValue(SourceProperty, value);
}
}
// Using a DependencyProperty as the backing store for Source.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(Uri), typeof(MediaPlayer),
new FrameworkPropertyMetadata(OnMediaSourceChanged));
/// <summary>
/// Called when [media source changed].
/// </summary>
/// The d.
/// The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>
/// instance containing the event data.
private static void OnMediaSourceChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var player = d as MediaPlayer;
if (player != null)
{
player.Source = e.NewValue as Uri;
player.PostSourceSetupWork();
}
}
/// <summary>
/// Posts the source setup work.
/// </summary>
public void PostSourceSetupWork()
{
if (MediaElement != null)
{
mediaElement.Pause();
SetMediaElementSource();
}
}
历史
- 2010 年 12 月 13 日:初始发布