注意:是Killercam 的问题的答案,已被问到here。这个问题的答案特别适合他的赏金,所以应他的要求,我在这里发布。
在此答案中,Button 控件用于演示如何使用模板。
Part I. Binding in ControlTemplate
如果你想在 ControlTemplate 中使用 Binding,你应该使用以下构造:
<ControlTemplate TargetType="{x:Type SomeControl}">
<Rectangle Fill="{TemplateBinding Background}" />
引用自MSDN:
TemplateBinding 是针对模板场景的 Binding 优化形式,类似于使用 {Binding RelativeSource={RelativeSource TemplatedParent}}. 构造的 Binding
Notes about using TemplateBinding
TemplateBinding 在模板之外或其 VisualTree 属性之外不起作用,因此您甚至不能在模板的触发器中使用 TemplateBinding。此外,TemplateBinding 在应用于 Freezable 时不起作用(主要是人为原因),例如 - VisualBrush。在这种情况下,可以像这样使用 Binding:
<FreezableControl Property="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Background}" />
此外,您始终可以使用 TemplateBinding 的替代方法:
<Rectangle Fill="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Background}" />
作为另一种可能,您还可以尝试以下方法:
<Rectangle Fill="{Binding Background,
RelativeSource={RelativeSource AncestorType={x:Type SomeControl}},
Path=Background}" />
Part II. Notes about your version
在您的情况下,这可能会导致ControlTemplate 中的名称冲突,因为您已经在为边框使用绑定背景。因此,为 Border 删除此 Binding,或使用另一个属性,例如 Tag 或 attached 依赖属性来绑定背景颜色。
Example of using
而不是ChartingToolkit控件,以Button控件为基础,因为这样更容易展示这种风格的思想。
Solution 1: using Tag
<Window.Resources>
<Style x:Key="TestButtonStyle" TargetType="{x:Type Button}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<!-- Here we are set Tag for Border Background -->
<Border Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Rectangle Width="24"
Height="24"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"
Stroke="{TemplateBinding BorderBrush}" />
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button Name="TestButton"
Style="{StaticResource TestButtonStyle}"
Content="Test"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Tag="Green"
Background="Aquamarine"
Width="100"
Height="100" />
</Grid>
Output
这里为Rectangle,设置两种颜色:默认为Rectangle,在标签中为Border。我认为这不是一个好的解决方案,原因如下:
可以得出结论,我们应该找到一个替代方案,作为替代方案,我使用具有 attached 依赖属性的扩展器类。
扩展类ButtonExt.cs
public static class ButtonExt
{
#region RectangleBackground Property
public static readonly DependencyProperty RectangleBackgroundProperty;
public static void SetRectangleBackground(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(RectangleBackgroundProperty, value);
}
public static Brush GetRectangleBackground(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(RectangleBackgroundProperty);
}
#endregion
#region RectangleBorderBrush Property
public static readonly DependencyProperty RectangleBorderBrushProperty;
public static void SetRectangleBorderBrush(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(RectangleBorderBrushProperty, value);
}
public static Brush GetRectangleBorderBrush(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(RectangleBorderBrushProperty);
}
#endregion
#region Button Constructor
static ButtonExt()
{
#region RectangleBackground
PropertyMetadata BrushPropertyMetadata = new PropertyMetadata(Brushes.Transparent);
RectangleBackgroundProperty = DependencyProperty.RegisterAttached("RectangleBackground",
typeof(Brush),
typeof(ButtonExt),
BrushPropertyMetadata);
#endregion
#region RectangleBorderBrush
RectangleBorderBrushProperty = DependencyProperty.RegisterAttached("RectangleBorderBrush",
typeof(Brush),
typeof(ButtonExt),
BrushPropertyMetadata);
#endregion
}
#endregion
}
MainWindow.xaml
<Window.Resources>
<Style x:Key="TestButtonExtensionStyle" TargetType="{x:Type Button}">
<Setter Property="Width" Value="80" />
<Setter Property="Height" Value="80" />
<Setter Property="Background" Value="Green" />
<Setter Property="BorderBrush" Value="Pink" />
<Setter Property="BorderThickness" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Rectangle Fill="{TemplateBinding PropertiesExtension:ButtonExt.RectangleBackground}"
Stroke="{TemplateBinding PropertiesExtension:ButtonExt.RectangleBorderBrush}"
Width="30"
Height="30" />
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button Style="{StaticResource TestButtonExtensionStyle}"
PropertiesExtension:ButtonExt.RectangleBackground="Aquamarine"
PropertiesExtension:ButtonExt.RectangleBorderBrush="Black"
Content="Test" />
</Grid>
Output
Part III. Setting values for dependency properties
当你创建和注册你的 attached 依赖属性时,你必须声明 Set 和 Get 方法才能与他一起工作:
public static void SetRectangleBackground(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(RectangleBackgroundProperty, value);
}
public static Brush GetRectangleBackground(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(RectangleBackgroundProperty);
}
那么与他们的合作将如下:
Set
ButtonExt.SetRectangleBackground(MyButton, Brushes.Red);
Get
Brush MyBrush = ButtonExt.GetRectangleBackground(MyButton);
但在我们的例子中,它并不是那么简单。当我使用附加的依赖属性时,更新值的问题都没有。但在我们的例子中,该属性在模板中,而在我的例子中,没有更新Button。我尝试在Binding和属性声明中设置Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,GetBindingExpression().UpdateTarget(),但是没有用。
请注意,对于设置 new 值的属性,并且没有来自模板的通知,表明该属性已更新。也许我错了,你会工作,或者它是专门制作的,例如避免内存泄漏。
无论如何,最好不要直接更新依赖属性,将Model和ViewModel中的属性绑定到它上面来设置值。
例子:
<Button Style="{StaticResource TestButtonExtensionStyle}"
adp:ButtonExt.RectangleBackground="{Binding Path=Model.RectBackground,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
adp:ButtonExt.RectangleBorderBrush="{Binding Path=Model.RectBorderBrush,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
其中RectBackground 和RectBorderBrush 实现INotifyPropertyChanged 接口。
在这种情况下,作为替代方案,不要使用依赖属性并使用DataTemplate 作为控件。 DataTemplate 非常适合 MVVM,非常灵活和动态。
例如,使用DataTemplate,你可以看到我的答案:
Make (create) reusable dynamic Views
One ViewModel for UserControl and Window or separate ViewModels