使用绑定应用样式

问题描述

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

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

我曾尝试将我的画布包裹在 ControlTemplates 中,但无论出于何种原因,“Canvas”都不是有效的 targettype

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

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

我尝试使用 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),new PropertyMetadata(Brushes.DarkGray));
    public static readonly DependencyProperty PieceTypeProperty = DependencyProperty.Register(nameof(PieceType),typeof(Enums.PieceType),new PropertyMetadata(Enums.PieceType.Pawn));
    public static readonly DependencyProperty SwapColorsProperty = DependencyProperty.Register(nameof(SwapColors),typeof(bool),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,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,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,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,Path=Color2}"
              stroke="{Binding RelativeSource={RelativeSource AncestorType=local:PieceImage,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 的指南,这两个指南更多地用于数据项样式,而不是使用设置另一个变量控件一种风格。 谢谢,

解决方法

如果我正确理解您的问题,您希望在您的 Canvas 控件中显示基于 PieceType 的一块 PieceImage。然后,您不必将 Canvas 嵌套在另一个 Canvas 中。您可以将 Canvas 模板中的 PieceImage 替换为 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 .这里我假设我们可以使用模式 PieceType 映射 <Enum Constant Name>Image。如果它更复杂,您可以使用字典或自定义转换器。

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相同实例 将被重复使用。

设置为 false 时,修改 WPF 资源检索行为,以便对属性资源的请求为每个请求创建一个新实例,而不是为所有请求共享相同的实例。

这是有问题的,因为每个控件只能有一个父元素。这意味着当您多次分配同一个 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,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,无需任何额外的代码或标记。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...