在WPF开发过程中,经常会遇到需要对非依赖属性进行双向绑定的情况。例如,Office功能区的ribbon组或转换器的参数。如果尝试进行绑定,通常会抛出异常,提示无法在非依赖属性上设置绑定。本文将介绍一种解决方案,通过创建一个代理/观察者对象来实现数据的双向同步。
以下是使用代理在XAML中的代码示例。这里没有使用代码后置(code-behind)。
<Window x:Class="BindOnNonDependencyProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:us="clr-namespace:BindOnNonDependencyProperty"
Title="BindOnNonDependencyProperty">
<DockPanel>
<TextBox x:Name="myTextBox" DockPanel.Dock="Top" />
<TextBox x:Name="monTextBlockCible" DockPanel.Dock="Top" />
<us:ExtendedBinding Source="{Binding ElementName=myTextBox,Path=Text,Mode=TwoWay}"
Target="{Binding ElementName=monTextBlockCible,Path=Text,Mode=TwoWay}" />
</DockPanel>
</Window>
将创建一个名为ExtendedBinding的代理/观察者,它必须继承自DependencyObject才能拥有DependencyProperty。但是,将DependencyObject添加到XAML中的唯一方法是将其添加到资源字典中。这样做的缺点是,它将不再在控件树中,因此无法在其属性上进行绑定。
然而,将其作为UIElement添加到控件树中是行不通的,因为在当前版本的框架中,不会有DataContext的继承,而且使用ElementName绑定将被禁止。幸运的是,有一种解决方案:代理必须继承自FrameworkElement,这样一切都会正常工作。
将添加两个依赖属性,一个作为目标,另一个作为源。这些依赖属性将使用FrameworkPropertyMetadata进行自定义,以启用以下功能:
public class ExtendedBinding : FrameworkElement
{
#region Source DP
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(
"Source",
typeof(object),
typeof(ExtendedBinding),
new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
public Object Source
{
get { return GetValue(ExtendedBinding.SourceProperty); }
set { SetValue(ExtendedBinding.SourceProperty, value); }
}
#endregion
#region Target DP
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register(
"Target",
typeof(object),
typeof(ExtendedBinding),
new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
public Object Target
{
get { return GetValue(ExtendedBinding.TargetProperty); }
set { SetValue(ExtendedBinding.TargetProperty, value); }
}
#endregion
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == ExtendedBinding.SourceProperty.Name)
{
// no loop wanted
if (!object.ReferenceEquals(Source, Target))
Target = Source;
}
else if (e.Property.Name == ExtendedBinding.TargetProperty.Name)
{
// no loop wanted
if (!object.ReferenceEquals(Source, Target))
Source = Target;
}
}
}