Introduction

忘備録。

WPF は見た目を自由に定義できるが、その方法がいくつもある。

  • Triger
  • Style
  • VisualState

加えて、コントロールの見た目を根本的に変えることができる ControlTemplate が絡んでくると、どの方法を使えばいいのかで混乱してしまう。

そんな中で掲題の ControlTemplate 内で DataTrigger を定義し、カスタムコントロールの依存関係プロパティに Binding した場合にはまった場合についてメモ。

サンプルソースは、GitHub に置きました。

What is problem?

ControlTemplate 内にも、当然 DataTrigger を設定することはできるが…
下記は DatTrigger を指定し、カスタムコントロールの依存関係プロパティに Binding した、 「間違った」 Generic.xaml の例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Demo.Controls"
xmlns:local="clr-namespace:Demo">

<Style TargetType="{x:Type controls:CustomButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomButton}">
<Button Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
Command="{TemplateBinding Command}"/>
<ControlTemplate.Triggers>
<DataTrigger Binding="{TemplateBinding Checked}"
Value="True">
<Setter TargetName="Button" Property="Foreground" Value="{TemplateBinding CheckedForeground}"/>
</DataTrigger>
<DataTrigger Binding="{TemplateBinding Checked}"
Value="False">
<Setter TargetName="Button" Property="Foreground" Value="{TemplateBinding UncheckedForeground}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

</ResourceDictionary>

カスタムコントロールの定義は下記。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Demo.Controls
{

public sealed class CustomButton : Button
{

#region Constructors

static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}

#endregion

#region Dependency Properties

#region Checked

public static readonly DependencyProperty CheckedProperty =
DependencyProperty.Register(nameof(Checked),
typeof(bool),
typeof(CustomButton),
new UIPropertyMetadata(false));

public bool Checked
{
get => (bool)this.GetValue(CheckedProperty);
set => this.SetValue(CheckedProperty, value);
}

#endregion

#region CheckedForeground

public static readonly DependencyProperty CheckedForegroundProperty =
DependencyProperty.Register(nameof(CheckedForeground),
typeof(Brush),
typeof(CustomButton),
new UIPropertyMetadata(null));

public Brush CheckedForeground
{
get => (Brush)this.GetValue(CheckedForegroundProperty);
set => this.SetValue(CheckedForegroundProperty, value);
}

#endregion

#region UncheckedForeground

public static readonly DependencyProperty UncheckedForegroundProperty =
DependencyProperty.Register(nameof(UncheckedForeground),
typeof(Brush),
typeof(CustomButton),
new UIPropertyMetadata(null));

public Brush UncheckedForeground
{
get => (Brush)this.GetValue(UncheckedForegroundProperty);
set => this.SetValue(UncheckedForegroundProperty, value);
}

#endregion

#endregion

}

}

Unable to cast object of type ‘System.Windows.TemplateBindingExpression’ to type ‘System.Windows.Data.BindingBase’

実行すると下記のような例外を投げてしまう。

Exception

System.Windows.Markup.XamlParseException: ‘’プロパティ ‘System.Windows.DataTrigger.Binding’ の Set で例外がスローされました。’ 行番号 ‘16’、行位置 ‘26’。’
内部例外
InvalidCastException: Unable to cast object of type ‘System.Windows.TemplateBindingExpression’ to type ‘System.Windows.Data.BindingBase’.

げんなりするが、ポイントは、 System.Windows.Data.BindingBaseSystem.Windows.TemplateBindingExpression にキャストできない、という点。

ControlTemplate 内で、カスタムコントロールに定義した依存関係プロパティに Binding するには、TemplateBinding を使うのが仕様だが、こと DataTrigger に対しては通用しない。

この場合、カスタムコントロールに定義した依存関係プロパティに反応するようにするには、シンプルに下記でいい。

1
2
3
4
5
- <DataTrigger Binding="{TemplateBinding Checked}"
+ <DataTrigger Binding="{Binding Checked}"
Value="True">
<Setter TargetName="Button" Property="Foreground" Value="{TemplateBinding CheckedForeground}"/>
</DataTrigger>

ArgumentException: 式の型は、有効な Style 値ではありません。

前述のように直すと今度は違う例外を投げてしまう。

System.Windows.Markup.XamlParseException: ‘’プロパティ ‘System.Windows.Setter.Value’ の Set で例外がスローされました。’ 行番号 ‘20’、行位置 ‘30’。’
内部例外
ArgumentException: 式の型は、有効な Style 値ではありません。

今回は Setter の使い方がおかしいことは理解できる。
下記のように直す。

1
2
3
4
5
  <DataTrigger Binding="{Binding Checked}"
Value="True">
- <Setter TargetName="Button" Property="Foreground" Value="{TemplateBinding CheckedForeground}"/>
+ <Setter TargetName="Button" Property="Foreground" Value="{Binding CheckedForeground, RelativeSource={RelativeSource TemplatedParent}}" />
</DataTrigger>

以上で DataTrigger がカスタムコントロールに定義した依存関係プロパティに反応して動作するようになる。

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/WPF/17_DataTriggerInControlTemplate