0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【WPF】トグルボタンに表示されている文字の修正を行った際、微修正だと思ったら意外と改修が必要だった【xaml】

Posted at

概要

WPFで作成していたとある画面に対し、改修対応を行いました。改修内容から判断して一瞬で終わるかと思いきや、意外と時間が掛かったので備忘録として残します。

仕様や対応内容

改修前

元々、以下のような画面を作成していました。

ボタン1とボタン2はトグルボタンで、押下状態と未押下状態でボタンの色やボタンの文字の色が変わります。ボタン1とボタン2は画面を起動する条件によって、活性状態になったり非活性状態になったりすることもあります。

改修後

上記の画面から、以下のような画面に改修を行いました。

変更としては、ボタン2の文字の下に、小さく注釈を記載するのみでした。
ボタンの文言と、文字のサイズの変更だけだったのでそれほど時間が掛からないと思っていましたが、思っていたより時間が掛かりました。

対応前のコード

View

  • SampleView.xamlToggleButtonでボタン1とボタン2を定義しています
  • ToggleButtonResourceDictonary.xamlで定義したテンプレートを使用しています
SampleView.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を定義しています
  • TextBlockText{TemplateBinding Content}という形で、テンプレートを使用するコントロールのContentをバインディングするようにしていました
  • MultiDataTriggerを使用して、ボタンの押下状態や活性状態の組み合わせで、ボタンの色やボタンの表示名の色を変更していました
StyleDictionary.xaml
<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

  • ToggleButtonContentを消去し、TextBlockを直接編集する形にしました
  • TextBlockResourceDictionaryで定義したStyleを使用する形にしています
SampleView.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}"
                        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

  • ControlTemplateTextBlock部分をContentPresenterに変更しました
  • また、ボタンの表示名のTextBlock部分は新しくStyleとして別に定義しました
StyleDictionary.xaml
<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のToggleButtonContentにボタンの表示名 (「ボタン1」や「ボタン2」) を記載していました。ただContentだと、Contentの中の特定の文字だけ形式を変更することはできません。
そのため、ToggleButton中のTextBlockを直接書き換え<Run>を使用する形でボタンの表示名を定義する方針にしました。その対応でボタン内の表示名の特定の文字だけ形式を変えることはできました。

ここから少し時間が掛かったのですが、元々ボタン1と2はResourceDictonaryControlTemplate定義し、そのテンプレートを使用していました。ただControlTemplateに定義していたボタンのTextBlockで、Text="{TemplateBinding Content}"という記述を使用していましたが、今回の対応でToggleButtonContentTextBlockでラップされているため、単純な文字列として扱えないためか、文字がバインディングされず表示されなくなっていました。
そのため、ControlTemplateTextBlock部分はControlTemplate内で任意のコンテンツをレンダリングできる、ContentPresenterに変更しました。また、ResourceDictonaryで定義する部分もToggleButtonTextBlock部分で分けることで、従来通りResourceDictonaryの定義を参照する形で対応することができました。

ToggleButtonTextBlockStyleを別途切り出した理由は、ControlTemplateSampleViewButtonの中にある状態だと、今回の場合はTargetNameを使用しないとプロパティを変更するターゲットコントロールが指定できなかったためです。
ContentPresenterx:Nameを指定して、SetterからTargetNameを指定してもうまくいきませんでした。
なので、ToggleButtonの中のTextBlockに直接適用できるStyleを作成し、TargetNameを使用せずともプロパティをセットできる形にしました。

おわりに

今回時間が掛かった理由は、ToggleButtonContentを消去したため、それに伴うResourceDictonaryの改修が少し多くなった点でした。
もっとよりシンプルに改修できる方法があれば知りたいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?