通过绑定应用样式

Applying Styles With Binding

我正在尝试创建一个名为 PieceImage 的自定义控件,其中包含多个预定义 Canvas 元素之一。我在名为 PieceImageDictionary.xaml 的 ResourceDictionary 中定义了这些画布。我在 PieceImageColor1Color2 中设置了一些依赖属性,我想将它们绑定到 FillStroke 中的路径画布。我对自定义控件和简单的数据绑定有很好的理解,但样式让我有点困惑。

所以基本上我有一个 ResourceDictionary 和画布,我将其视为图像,我希望能够有多个 PieceImage Control 实例,每个实例都可以选择这些图像之一独立(不是全局样式)并且我希望能够使用 PieceImage 控件上的 DependencyProperty 设置颜色。

我曾尝试将我的画布包装在 ControlTemplates 中,但出于某种原因“Canvas”不是有效的 TargetType

如果我将 TargetType 设置为“Control”,我就会应用此模板,但是当我尝试将 TemplateBinding 添加到 Paths 时,属性 我想设置 (Color1) 找不到。我有点理解这一点,因为 Color1 属性 在我的 PieceImage 控制中,所以 TemplateBindingControl 甚至 Canvas 都不会没用。

然后我尝试 RelativeBinding 并使用 AncestorType 的“PieceImage”,但这也不起作用。没有错误,只是一个空白 Canvas。我试过在 Canvas 上调用 UpdateLayout()InvalidateVisual() 以防万一,但没有改变。

我尝试使用 XamlReader 和 Writer 来创建这些画布,而不是使用模板,这最初在我静态定义颜色时有效,但当我尝试添加 RelativeBinding 时,我得到了 Xaml 解析错误说它无法从字符串“local:PieceImage

创建类型

我尝试在代码中添加绑定,但还是空白 Canvas。我承认我不太了解编码绑定,这是我的实现:

var binding = new Binding(Color1Property.Name);
binding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
BindingOperations.SetBinding(path, Path.FillProperty, binding);

这是我的基本代码,为了清楚起见,我已经清除了所有失败的尝试,否则它会变得一团糟。

PieceImage.cs

public class PieceImage : Control
{
    private Canvas _canvas;
    static PieceImage()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(PieceImage), new FrameworkPropertyMetadata(typeof(PieceImage)));
    }
    public static readonly DependencyProperty Color1Property = DependencyProperty.Register(nameof(Color1), typeof(Brush), typeof(PieceImage), new PropertyMetadata(Brushes.BlanchedAlmond));
    public static readonly DependencyProperty Color2Property = DependencyProperty.Register(nameof(Color2), typeof(Brush), typeof(PieceImage), new PropertyMetadata(Brushes.DarkGray));
    public static readonly DependencyProperty PieceTypeProperty = DependencyProperty.Register(nameof(PieceType), typeof(Enums.PieceType), typeof(PieceImage), new PropertyMetadata(Enums.PieceType.Pawn));
    public static readonly DependencyProperty SwapColorsProperty = DependencyProperty.Register(nameof(SwapColors), typeof(bool), typeof(PieceImage), new PropertyMetadata(false));

    public Brush Color1
    {
        get { return (Brush)GetValue(Color1Property); }
        set { SetValue(Color1Property, value); }
    }
    public Brush Color2
    {
        get { return (Brush)GetValue(Color2Property); }
        set { SetValue(Color2Property, value); }
    }
    public Enums.PieceType PieceType
    {
        get { return (Enums.PieceType)GetValue(PieceTypeProperty); }
        set { SetValue(PieceTypeProperty, value); }
    }
    public bool SwapColors
    {
        get { return (bool)GetValue(SwapColorsProperty); }
        set { SetValue(SwapColorsProperty, value); }
    }
    public override void OnApplyTemplate()
    {
        _canvas = Template.FindName("PART_Canvas", this) as Canvas;
        //Here is where most of my logic would go when I was loading xaml or coding bindings
        base.OnApplyTemplate();
    }
}

Generic.xaml(部分)

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Wagner.Chess.UI.Controls">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="\pieceimagedictionary.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <Style TargetType="{x:Type local:PieceImage}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:PieceImage">
                    <Canvas x:Name="PART_Canvas"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

PieceImageDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Wagner.Chess.UI.Controls">
    <Canvas Height="64" Width="64"  x:Key="BishopImage">
        <Path Fill="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color1}"
              Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color2}"
              StrokeThickness="1.5" StrokeMiterLimit="1" StrokeLineJoin="Round">
                <Path.Data>
                    <GeometryGroup>
                        <PathGeometry Figures="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38
                                      C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.65,38.99
                                      6.68,38.97 6,38 C 7.35,36.54 9,36 9,36 z"/>
                        <PathGeometry Figures="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5
                                      22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z"/>
                        <PathGeometry Figures="M 25 8 A 2.5 2.5 0 1 1  20,8 A 2.5 2.5 0 1 1  25 8 z"/>
                    </GeometryGroup>
                </Path.Data>
            </Path>
        <Path Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color2}"
               StrokeThickness="1.5" StrokeMiterLimit="1" StrokeEndLineCap="Round" StrokeStartLineCap="Round" StrokeLineJoin="Miter">
                <Path.Data>
                    <GeometryGroup>
                        <PathGeometry Figures="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18" />
                    </GeometryGroup>
                </Path.Data>
            </Path>
            <Canvas.RenderTransform>
                <ScaleTransform ScaleX="1.42222222222" ScaleY="1.42222222222"/>
            </Canvas.RenderTransform>
        </Canvas>
    <Canvas Height="64" Width="64" x:Key="KnightImage">
        <Path Fill="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color1}"
              Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color2}"
              StrokeThickness="1.5" StrokeMiterLimit="1"  StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
            <Path.Data>
                <GeometryGroup>
                    <PathGeometry Figures="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"/>
                    <PathGeometry Figures="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,
                                  30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 
                                  18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"/>
                </GeometryGroup>
            </Path.Data>
        </Path>
        <Path Fill="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color2}"
              Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,AncestorLevel=2,Mode=FindAncestor},Path=Color2}"
              StrokeThickness="0.5" StrokeMiterLimit="1" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
            <Path.Data>
                <GeometryGroup>
                    <PathGeometry Figures="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"/>
                    <PathGeometry Figures="M 15 15.5 A 0.5 1.5 0 1 1  14,15.5 A 0.5 1.5 0 1 1  15 15.5 z">
                        <PathGeometry.Transform>
                            <MatrixTransform Matrix="0.866,0.5,-0.5,0.866,9.693,-5.173"/>
                        </PathGeometry.Transform>
                    </PathGeometry>
                </GeometryGroup>
            </Path.Data>
        </Path>
        <Canvas.RenderTransform>
            <ScaleTransform ScaleX="1.42222222222" ScaleY="1.42222222222"/>
        </Canvas.RenderTransform>
    </Canvas>
</ResourceDictionary>

我希望有人可以提供一些提示或为我指明正确的方向,因为 MSDocs 有 StyleSelectorsDataTemplateSelectors 的指南,这两个指南更多地用于数据项样式而不是设置另一个变量使用样式进行控制。 谢谢,

如果我对您的问题的理解正确,您希望在 PieceImage 控件中显示基于 PieceType 的片段 Canvas。然后,您不必将 Canvas 嵌套在另一个 Canvas 中。您可以将 PieceImage 模板中的 Canvas 替换为 ContentPresenter (PART_ContentPresenter).

<Style TargetType="{x:Type local:PieceImage}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="local:PieceImage">
            <ContentPresenter x:Name="PART_ContentPresenter"/>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

在您的 PieceImage 控件中,您可以使用 FindResource 方法搜索相应的 PieceType 资源(Canvas)并将其分配给 ContentPresenter.这里我假设我们可以使用模式 <Enum Constant Name>Image 映射 PieceType。如果它更复杂,您可以使用字典或自定义转换器。

public class PieceImage : Control
{
   private ContentPresenter _contentPresenter;

   // ...your constructor, dependency property definitions, etc..

   public override void OnApplyTemplate()
   {
      base.OnApplyTemplate();

      _contentPresenter = Template.FindName("PART_ContentPresenter", this) as ContentPresenter;

      if (_contentPresenter != null)
         _contentPresenter.Content = FindPieceImage();
   }

   private Canvas FindPieceImage()
   {
      var pieceTypeName = PieceType.ToString();
      var pieceTypeCanvasKey = $"{pieceTypeName}Image";
      return FindResource(pieceTypeCanvasKey) as Canvas;
   }
}

由于控件模板中原来的Canvas没有了,请把你的作品图片中的所有AncestorLevel=2属性去掉,否则绑定源不会被发现。此外,将 x:Shared="False" 添加到您的 Canvases。这很重要,因为你的作品图像现在可能会被多次使用(我猜你正在构建一个棋盘),但没有将 x:Shared 设置为 false 相同的实例 将被重复使用。

When set to false, modifies WPF resource-retrieval behavior so that requests for the attributed resource create a new instance for each request instead of sharing the same instance for all requests.

这是有问题的,因为每个控件只能有一个父元素。这意味着当您多次分配相同的 Canvas 实例时,只有最后一个元素会将其作为子元素。您可以想象一个棋盘,其中只显示最后被分配的棋子,棋盘的其余部分是空的。这是对您的代码所做修改的摘录。

<Canvas Height="64" Width="64"  x:Key="BishopImage"  x:Shared="False">
    <Path Fill="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,Mode=FindAncestor},Path=Color1}"
          Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,Mode=FindAncestor},Path=Color2}"
          StrokeThickness="1.5" StrokeMiterLimit="1" StrokeLineJoin="Round">
            <Path.Data>
                <GeometryGroup>
                    <PathGeometry Figures="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38
                                  C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.65,38.99
                                  6.68,38.97 6,38 C 7.35,36.54 9,36 9,36 z"/>
                    <PathGeometry Figures="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5
                                  22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z"/>
                    <PathGeometry Figures="M 25 8 A 2.5 2.5 0 1 1  20,8 A 2.5 2.5 0 1 1  25 8 z"/>
                </GeometryGroup>
            </Path.Data>
        </Path>
        <!-- ...other markup code. -->

绑定现在可以工作并解析为 PieceImage,无需任何额外的代码或标记。