[英]Applying Styles With Binding
I am trying to create a custom control called PieceImage
that contains one of several predefined Canvas
elements.我正在尝试创建一个名为
PieceImage
的自定义控件,其中包含几个预定义的Canvas
元素之一。 I have defined these Canvases in a ResourceDictionary
called PieceImageDictionary.xaml.我在名为 PieceImageDictionary.xaml 的
ResourceDictionary
中定义了这些画布。 I have set up some dependency properties in PieceImage
, Color1
and Color2
, that I'd like to bind to the Fill
and Stroke
of the paths in the Canvases.我在
PieceImage
、 Color1
和Color2
中设置了一些依赖属性,我想将它们绑定到画布中路径的Fill
和Stroke
。 I have a pretty good understanding of custom controls and simple databinding but styles are a little confusing to me.我对自定义控件和简单的数据绑定有很好的理解,但是 styles 让我有点困惑。
So basically I have a ResourceDictionary
with Canvases that I am treating as images and I want be able to have multiple PieceImage
Control
instances that can each choose one of these images independently (not a global style) and I want to be able to set the colors using a DependencyProperty
on the PieceImage
Controls.所以基本上我有一个带有 Canvases 的
ResourceDictionary
,我将其视为图像,我希望能够拥有多个PieceImage
Control
实例,每个实例可以独立选择其中一个图像(不是全局样式),我希望能够设置 colors在PieceImage
控件上使用DependencyProperty
。
I have tried wrapping my Canvases in ControlTemplates
but for whatever reason " Canvas
" is not a valid TargetType
.我尝试将 Canvases 包装在
ControlTemplates
中,但无论出于何种原因,“ Canvas
”都不是有效的TargetType
。
I got it to apply this template if I set the TargetType
to " Control
" however when I tried adding TemplateBinding
to the Paths
, the Property I wanted to set (Color1)
couldn't be found.如果我将
TargetType
设置为“ Control
”,我可以应用此模板,但是当我尝试将TemplateBinding
添加到Paths
时,找不到我想要设置的属性(Color1)
。 I kind of understand this because the Color1
property is in my PieceImage
control, so TemplateBinding
to a Control
or even a Canvas
won't work.我有点理解这一点,因为
Color1
属性在我的PieceImage
控件中,因此TemplateBinding
到Control
甚至Canvas
将不起作用。
I then tried RelativeBinding
with an AncestorType
of " PieceImage
" but this doesn't work either.然后我尝试了使用
AncestorType
为“ PieceImage
”的RelativeBinding
,但这也不起作用。 No error, just a blank Canvas
.没有错误,只是一个空白
Canvas
。 I've tried calling UpdateLayout()
and InvalidateVisual()
on the Canvas
just in case but no change.我试过在
Canvas
上调用UpdateLayout()
和InvalidateVisual()
以防万一,但没有改变。
I tried using a XamlReader
and Writer to create these canvases instead of using templates, which initially worked when I statically defined the colors but when I tried to add the RelativeBinding
I got a Xaml Parse Error saying it couldnt create the type from the string " local:PieceImage
"我尝试使用
XamlReader
和 Writer 来创建这些画布,而不是使用模板,这最初在我静态定义 colors 时起作用,但是当我尝试添加RelativeBinding
时,我得到了 Xaml 解析错误,说它无法从字符串“ local:PieceImage
”创建类型local:PieceImage
"
I tried adding a binding in the code but also got a blank Canvas
.我尝试在代码中添加绑定,但也得到了一个空白
Canvas
。 I will admit I don't know much about coding bindings, this was my implementation:我承认我对编码绑定不太了解,这是我的实现:
var binding = new Binding(Color1Property.Name);
binding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
BindingOperations.SetBinding(path, Path.FillProperty, binding);
Here is my base code, I've cleaned out all the failed attempts for clarity because it would otherwise be a mess.这是我的基本代码,为了清楚起见,我已经清除了所有失败的尝试,否则会一团糟。
PieceImage.cs 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 (partial) 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 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>
I was hoping someone could provide some tips or point me in the right direction because MSDocs have guides for StyleSelectors
and DataTemplateSelectors
both of which are more for data item styling rather than setting another variable control using a style.我希望有人可以提供一些提示或指出正确的方向,因为 MSDocs 有
StyleSelectors
和DataTemplateSelectors
的指南,这两者都更多地用于数据项样式,而不是使用样式设置另一个变量控件。 Thanks,谢谢,
If I understand your issue correctly, you want to show a piece Canvas
based on the PieceType
in a your PieceImage
control.如果我正确理解您的问题,您想根据
PieceType
控件中的PieceImage
显示一块Canvas
。 Then, you do not have to nest a Canvas
inside another Canvas
.然后,您不必将
Canvas
嵌套在另一个Canvas
中。 You can replace the Canvas
in your PieceImage
template with a ContentPresenter
( PART_ContentPresenter
).您可以将
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>
In your PieceImage
control, you can then search for the corresponding PieceType
resource ( Canvas
) using the FindResource
method and assign it to the ContentPresenter
.在您的
PieceImage
控件中,您可以使用FindResource
方法搜索相应的PieceType
资源 ( Canvas
) 并将其分配给ContentPresenter
。 Here I assume that we can map the PieceType
using the pattern <Enum Constant Name>Image
.这里我假设我们可以使用
<Enum Constant Name>Image
模式 map PieceType
。 If it is more complex, you could use a dictionary or a custom converter.如果它更复杂,您可以使用字典或自定义转换器。
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;
}
}
Since the original Canvas
in the control template is gone, remove all the AncestorLevel=2
attributes in your piece images, otherwise the binding source will not be found.由于控制模板中原来的
Canvas
没有了,把你的作品图片中的AncestorLevel=2
属性全部去掉,否则找不到绑定源。 Furthermore, add x:Shared="False"
to your Canvas
es.此外,将
x:Shared="False"
添加到您的Canvas
es。 This is important, because your piece images are now potentially used multiple times (I guess your are building a chess board), but without setting x:Shared
to false
the same instances will be reused.这很重要,因为您的棋子图像现在可能会被多次使用(我猜您正在构建棋盘),但是如果不将
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.当设置为
false
时,修改 WPF 资源检索行为,以便对属性资源的请求为每个请求创建一个新实例,而不是为所有请求共享同一个实例。
This is problematic, because each control can only have a single parent element.这是有问题的,因为每个控件只能有一个父元素。 This means that when you would assign the same
Canvas
instance multiple times, only the last element will get it as child.这意味着当您多次分配相同的
Canvas
实例时,只有最后一个元素会将其作为子元素。 You can imagine a chess board where only the last assinged pieces are shown and the rest of the board is empty.您可以想象一个棋盘,其中只显示最后分配的棋子,棋盘的 rest 是空的。 Here is an excerpt of the modifications to your code.
这是对您的代码的修改的摘录。
<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. -->
The bindings will work now and resolve to PieceImage
without any additional code or markup.绑定现在可以工作并解析为
PieceImage
,无需任何额外的代码或标记。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.