绑定到非 DependencyProperty 的属性
许多控件公开的属性不是 DependencyProperty,因此您无法对其进行绑定。在某些其他情况下,您只有一个 getter 作为访问器,你也无法对其进行绑定...
许多控件公开的属性不是 DependencyProperty,因此您无法对其进行绑定。在某些其他情况下,您只有一个 getter 作为访问器,你也无法对其进行绑定...
例如,Office Ribbon 的 Ribbon Group 或转换器的参数就是这种情况。
如果你曾经尝试过这样做,你肯定会抛出一个异常
无法在类型为 '
Tralala
' 的 'SetCEDEJDED
' 属性上设置 'Binding'。
'Binding' 只能设置在DependencyObject
的DependencyProperty
上。
在这篇文章中,我们将探索一种解决方法…
主要思想是使用一种代理/观察者(可以在这篇文章中找到一个定义),它会将源对象上的每个更改反映到目标对象,反之亦然。
以下是解决方案的主要部分。
规范:我们将使用的 XAML 代码
以下代码片段描述了我们将在 XAML 中如何使用我们的代理。没有代码隐藏。
<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 的唯一方法是将其添加到 resourceDictonary
中。
这是一个缺点,因为这样做后,它将不再在控件的树中,因此无法对其属性之一进行绑定。请注意,仍然可以将其用作 XAML 中其他位置的目标,但您无法对其属性之一进行绑定。此代码将不起作用
<Windows.Resources>
<MyDependencyObject x:Key="myKey"
MyProperty="{Binding Tralala, ElementName=myTarget}" />
</Windows.Resources>
然后,要将其放入控件的树中,您只需使其成为一个 UIElement
即可... 不,因为在框架的实际版本中,您将没有 DataContext
的继承,并且禁止使用 'ElementName
' 绑定。 幸运的是,有一个解决方案,我们的代理必须从 FrameworkElement 继承,一切都会顺利进行!
DependencyProperties
我们将添加两个 dependencyProperties,一个将是目标,第二个将是源。
这些 DP 将通过使用 FrameworkPropertyMetadata
来定制,以启用这些功能
- 将使用 TwoWay 模式完成绑定
- 使用的
UpdateSourceTrigger
将是PropertyChanged
事件
工作原理
我们代理的核心是覆盖 DependencyObject 的 OnPropertyChanged 方法。 源或目标上的每次更改都会更新其对应方。
我们必须注意不要陷入循环:当我们更新目标或源时,我们也会引发一个 PropertyChanged
事件,我们必须忽略这个事件...
最终代码
public class ExtendedBinding : FrameworkElement
{
#region Source DP
//We don't know what will be the Source/target type so we keep 'object'.
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
//We don't know what will be the Source/target type so we keep 'object'.
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;
}
}
}