概要
WPFで作成していたとある画面に対し、改修対応を行いました。改修内容から判断して一瞬で終わるかと思いきや、意外と時間が掛かったので備忘録として残します。
仕様や対応内容
改修前
ボタン1とボタン2はトグルボタンで、押下状態と未押下状態でボタンの色やボタンの文字の色が変わります。ボタン1とボタン2は画面を起動する条件によって、活性状態になったり非活性状態になったりすることもあります。
改修後
変更としては、ボタン2の文字の下に、小さく注釈を記載するのみでした。
ボタンの文言と、文字のサイズの変更だけだったのでそれほど時間が掛からないと思っていましたが、思っていたより時間が掛かりました。
対応前のコード
View
-
SampleView.xaml
にToggleButton
でボタン1とボタン2を定義しています -
ToggleButton
はResourceDictonary.xaml
で定義したテンプレートを使用しています
<Window
x:Class="HogeNamespace.SampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="サンプル画面"
Width="530"
Height="240"
ResizeMode="NoResize">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../StyleDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<!-- トグルボタン -->
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<StackPanel Width="260" Margin="30,20,0,0">
<ToggleButton
Width="180"
Height="70"
HorizontalAlignment="Left"
Command="{Binding LeftButtonCommand}"
FontSize="15"
Content="ボタン1"
IsChecked="{Binding IsCheckedLeftButton}"
Template="{StaticResource SampleViewButton}" />
</StackPanel>
<StackPanel Width="320" Margin="0,20,0,0">
<ToggleButton
Width="180"
Height="70"
HorizontalAlignment="Left"
Command="{Binding RightButtonCommand}"
FontSize="15"
Content="ボタン2"
IsChecked="{Binding IsCheckedRightButton}"
Template="{StaticResource SampleViewButton}" />
</StackPanel>
</StackPanel>
<Border
Width="450"
Height="1"
Margin="0,30,0,0"
BorderBrush="LightGray"
BorderThickness="0,10,0,0" />
<!-- OK、キャンセルボタン -->
<StackPanel
Margin="30,20,0,0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<TextBlock Text="いずれかのボタンを選択して下さい"
Margin="0,0,70,0"
FontSize="15"/>
<StackPanel Margin="0,0,15,0" HorizontalAlignment="Right">
<Button
Width="90"
Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding OKCommand}"
Content="OK"/>
</StackPanel>
<StackPanel Margin="0,0,15,0" HorizontalAlignment="Right">
<Button
Width="90"
Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding CancelCommand}"
Content="Cancel"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ResourceDictonary
-
SampleViewButton
という名前で、ControlTemplate
を定義しています -
TextBlock
のText
は{TemplateBinding Content}
という形で、テンプレートを使用するコントロールのContent
をバインディングするようにしていました -
MultiDataTrigger
を使用して、ボタンの押下状態や活性状態の組み合わせで、ボタンの色やボタンの表示名の色を変更していました
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="SampleViewButton" TargetType="{x:Type ToggleButton}">
<Border x:Name="border" CornerRadius="5">
<TextBlock
x:Name="text"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="15"
FontWeight="Bold"
Text="{TemplateBinding Content}" />
</Border>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="False" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter TargetName="border" Property="Background" Value="Gainsboro" />
<Setter TargetName="border" Property="BorderBrush" Value="Transparent" />
<Setter TargetName="text" Property="Foreground" Value="White" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="False" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="border" Property="Background" Value="DarkGray" />
<Setter TargetName="border" Property="BorderBrush" Value="Transparent" />
<Setter TargetName="text" Property="Foreground" Value="White" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="True" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="border" Property="Background" Value="DodgerBlue" />
<Setter TargetName="border" Property="BorderBrush" Value="Transparent" />
<Setter TargetName="text" Property="Foreground" Value="Black" />
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
対応後のコード
View
-
ToggleButton
のContent
を消去し、TextBlock
を直接編集する形にしました -
TextBlock
はResourceDictionary
で定義したStyle
を使用する形にしています
<Window
x:Class="HogeNamespace.SampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="サンプル画面"
Width="530"
Height="240"
ResizeMode="NoResize">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../StyleDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<!-- トグルボタン -->
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<StackPanel Width="260" Margin="30,20,0,0">
<ToggleButton
Width="180"
Height="70"
HorizontalAlignment="Left"
Command="{Binding LeftButtonCommand}"
IsChecked="{Binding IsCheckedLeftButton}"
Template="{StaticResource SampleViewButton}" >
<ToggleButton.Content>
<StackPanel>
<TextBlock
FontWeight="Bold"
Style="{StaticResource SampleViewButtonTextBlockStyle}">
<Run FontSize="15" FontWeight="Bold">ボタン1</Run>
</TextBlock>
</StackPanel>
</ToggleButton.Content>
</ToggleButton>
</StackPanel>
<StackPanel Width="320" Margin="0,20,0,0">
<ToggleButton
Width="180"
Height="70"
HorizontalAlignment="Left"
Command="{Binding RightButtonCommand}"
IsChecked="{Binding IsCheckedRightButton}"
Template="{StaticResource SampleViewButton}" >
<ToggleButton.Content>
<StackPanel HorizontalAlignment="Left">
<TextBlock TextAlignment="Center" Style="{StaticResource SampleViewButtonTextBlockStyle}">
<Run FontSize="15" FontWeight="Bold">ボタン2</Run>
<LineBreak/>
<Run FontSize="10">※注釈</Run>
</TextBlock>
</StackPanel>
</ToggleButton.Content>
</ToggleButton>
</StackPanel>
</StackPanel>
<Border
Width="450"
Height="1"
Margin="0,30,0,0"
BorderBrush="LightGray"
BorderThickness="0,10,0,0" />
<!-- OK、キャンセルボタン -->
<StackPanel
Margin="30,20,0,0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<TextBlock Text="いずれかのボタンを選択して下さい"
Margin="0,0,70,0"
FontSize="15"/>
<StackPanel Margin="0,0,15,0" HorizontalAlignment="Right">
<Button
Width="90"
Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding OKCommand}"
Content="OK"/>
</StackPanel>
<StackPanel Margin="0,0,15,0" HorizontalAlignment="Right">
<Button
Width="90"
Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding CancelCommand}"
Content="Cancel"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ResourceDictonary
-
ControlTemplate
のTextBlock
部分をContentPresenter
に変更しました - また、ボタンの表示名の
TextBlock
部分は新しくStyle
として別に定義しました
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="SampleViewButton" TargetType="{x:Type ToggleButton}">
<Border x:Name="border" CornerRadius="5">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"/>
</Border>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="False" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter TargetName="border" Property="Background" Value="Gainsboro" />
<Setter TargetName="border" Property="BorderBrush" Value="Transparent" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="False" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="border" Property="Background" Value="DarkGray" />
<Setter TargetName="border" Property="BorderBrush" Value="Transparent" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="True" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="border" Property="Background" Value="DodgerBlue" />
<Setter TargetName="border" Property="BorderBrush" Value="Transparent" />
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="SampleViewButtonTextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="TextAlignment" Value="Center" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="False" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="White" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="False" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="White" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="True" />
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="Black" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
時間が掛かった対応箇所
そもそも改修前の対応で、xamlのToggleButton
のContent
にボタンの表示名 (「ボタン1」や「ボタン2」) を記載していました。ただContent
だと、Content
の中の特定の文字だけ形式を変更することはできません。
そのため、ToggleButton
中のTextBlock
を直接書き換え<Run>
を使用する形でボタンの表示名を定義する方針にしました。その対応でボタン内の表示名の特定の文字だけ形式を変えることはできました。
ここから少し時間が掛かったのですが、元々ボタン1と2はResourceDictonary
にControlTemplate
定義し、そのテンプレートを使用していました。ただControlTemplate
に定義していたボタンのTextBlock
で、Text="{TemplateBinding Content}"という記述を使用していましたが、今回の対応でToggleButton
のContent
がTextBlock
でラップされているため、単純な文字列として扱えないためか、文字がバインディングされず表示されなくなっていました。
そのため、ControlTemplate
のTextBlock
部分はControlTemplate
内で任意のコンテンツをレンダリングできる、ContentPresenter
に変更しました。また、ResourceDictonary
で定義する部分もToggleButton
とTextBlock
部分で分けることで、従来通りResourceDictonary
の定義を参照する形で対応することができました。
ToggleButton
のTextBlock
のStyle
を別途切り出した理由は、ControlTemplate
のSampleViewButton
の中にある状態だと、今回の場合はTargetName
を使用しないとプロパティを変更するターゲットコントロールが指定できなかったためです。
ContentPresenter
にx:Name
を指定して、SetterからTargetNameを指定してもうまくいきませんでした。
なので、ToggleButton
の中のTextBlock
に直接適用できるStyle
を作成し、TargetName
を使用せずともプロパティをセットできる形にしました。
おわりに
今回時間が掛かった理由は、ToggleButton
のContent
を消去したため、それに伴うResourceDictonary
の改修が少し多くなった点でした。
もっとよりシンプルに改修できる方法があれば知りたいです。