我有一个简单的附加依赖属性,它应该为我的自定义控件提供鼠标颜色:
public class Ext
{
public static readonly DependencyProperty HighlightBrushProperty = DependencyProperty.RegisterAttached("HighlightBrush", typeof(Brush), typeof(Ext), new PropertyMetadata(default(Brush)));
public static void SetHighlightBrush(DependencyObject element, Brush value)
{
element.SetValue(HighlightBrushProperty, value);
}
public static Brush GetHighlightBrush(DependencyObject element)
{
return (Brush) element.GetValue(HighlightBrushProperty);
}
}
我在我的Generic.xaml
:
<Style TargetType="{x:Type local:MyButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="{Binding (local:Ext.HighlightBrush), RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
但是现在我必须在客户端中使用相同的命名空间:
<Window ...
xmlns:local="clr-namespace:Eval.Wpf.AttachedProperty.Controls;assembly=Eval.Wpf.AttachedProperty.Controls">
<StackPanel>
<local:MyButton Background="LightGray" local:Ext.HighlightBrush="DarkOrange" Content=" - Press me - " />
</StackPanel>
</Window>
将命名空间更改为其他内容,例如<c:MyButton Background="LightGray" c:Ext.HighlightBrush="DarkOrange" Content=" - Press me - " />
最终会在
System.Windows.Data Error: 40 : BindingExpression path error: '(local:Ext.HighlightBrush)' property not found on 'object' ''MyButton' (Name='')'. BindingExpression:Path=(local:Ext.HighlightBrush); DataItem='MyButton' (Name=''); target element is 'Border' (Name='Border'); target property is 'Background' (type 'Brush')
为什么使用附加属性不安全?我该如何解决这个问题?
答案 0 :(得分:1)
正如我的评论所述,您的案例中的解决方案是明确使用Binding.Path
属性,即:
{Binding Path=(local:Ext.HighlightBrush), ...}
我不确定这种行为背后的原因是什么,但我已经做了一些测试,并将分享结果和观察结果。
首先,这是一个说明问题的简明示例(摘自Window
XAML 定义):
<Window.Resources>
<ControlTemplate x:Key="T1" xmlns:foo="clr-namespace:FooBar">
<TextBlock Text="{Binding (foo:MainWindow.Test),
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Window.Resources>
<Grid xmlns:bar="clr-namespace:FooBar">
<Control x:Name="C1" bar:MainWindow.Test="test" Template="{StaticResource T1}" />
</Grid>
我们最终遇到绑定错误,并显示以下消息:
System.Windows.Data Error: 40 : BindingExpression path error:
'(foo:MainWindow.Test)' property not found on 'object' ''Control' (Name='C1')'.
BindingExpression:Path=(foo:MainWindow.Test);
DataItem='Control' (Name='C1');
target element is 'TextBlock' (Name='');
target property is 'Text' (type 'String')
当然,如果我们使用Binding.Path
属性,一切正常。
现在,如果我们在绑定上设置PresentationTraceSource.TraceLevel="High"
,我们可以看到输出中的(至关重要的)差异 - 在前一种情况下我们得到
System.Windows.Data Warning: 58 : Path: '(foo:MainWindow.Test)'
但在后一种情况下我们得到
System.Windows.Data Warning: 58 : Path: '(0)'
所以似乎使用第一种语法相当于用
实例化路径new PropertyPath("(foo:MainWindow.Test)")
(我认为它是由new Binding("(foo:MainWindow.Test)")
构造函数重载完成的),但第二个似乎使用了PathProperty
构造函数的另一个重载:
new PropertyPath("(0)", MainWindow.TestProperty)
<强>声明强> 接下来我要说的只是我的假设,所以它可能只是错误。
看起来"(foo:MainWindow.Test)"
路径被视为CLR访问器,只有在有可用的绑定源时才会解析。在这种情况下,源是C1
控件。该框架足够聪明,可以知道控件定义的 XAML 的哪个部分,因此它尝试使用该上下文解析附加属性。但在我们的情况下,foo
前缀未在该上下文中定义,因此解析失败,因此绑定失败。一个似乎证实该假设的观察是,如果你定义所述前缀,即使在控制本身上:
<Control x:Name="C1" xmlns:foo="clr-namespace:FooBar" (...) />
绑定开始按预期工作。另外,如果我们修改我们的模板:
<ControlTemplate x:Key="T1">
<TextBlock Text="{Binding (bar:MainWindow.Test),
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
即使在此上下文中未定义bar
前缀,并且 XAML 设计器抱怨它,它仍然 在运行时工作。
第二种方法的作用是在实例化Binding
时解析属性 - 此时必须解析该属性才能传递给PropertyPath
构造函数。因为在 XAML Binding
的上下文中定义了foo
前缀,所以解析成功并且一切正常。
总之,似乎这两种情况的区别仅在于框架尝试将(foo:MainWindow.Test)
字符串解析为实际属性的时间,但这不仅仅是差异,差异。