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 - カスタムコントール - ToggleButton 外観変更

Posted at

はじめに

WPF カスタムコントールを用いることで、手軽に標準コントールの外観/挙動を変更することができます。
本記事では、ToggleButton 外観変更について記載します。

WPF カスタムコントール については下記記事もあります

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • WPF - .NET Framework 4.8
  • WPF - .NET 8

Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。

ToggleButton

ToggleButton は、CheckBox、RadioButton といった状態を切り替えることができるコントロールの基本クラスです。
CheckBox 外観変更の記事で記載した IsThreeState プロパティは、そもそも ToggleButton で実装されているプロパティで、IsThreeState を true にすることで、 Indeterminate も使用可能となります。

状態 IsChecked プロパティ値
Checked(選択, ON) true
Unchecked(非選択, OFF) false
Indeterminate(不確定, Undefined) null

IsThreeState = false 時、ToggleButton クリック操作は、Checked, Unchecked トグル動作です。
IsThreeState = true 時、Checked, Indeterminate, Unchecked 循環トグル動作となります。

サンプル

初期デザイン

視認性などの観点で Switch / CheckBox が推奨されて、お目にかからなくなった Android の ToggleButton は、底辺にインジケータを配置して、TextOn / TextOff で指定された文字を ON / OFF で切り替える UI でした。

上記をベースとして、インジケータ表示位置を底辺から、左端にした UI を目指します。

ToggleButton.png

インジケータエリア横幅を 20pixel とします。
インジケータ(indicator)は、コーナーラウンド有りの 1pixel 黒枠で、マージン(3,3,5,3)を設定して、Background は状態値によって配色を変更します。
ON時の表示(textOn)、OFF時の表示(textOff)を indicator 右に配置して、ON / OFF で表示を切り替えます。
IsThreeState="True" で利用される未定義時の表示(textUndefined)も同様に用意します。

indicator, textOn, textOff, textUndefined という名称を、後述 xaml サンプルコードで利用します。

最終デザイン

標準 ToggleButton と、後述サンプルコード CustomToggleButton の実行結果を掲載します。
上段が ToggleButton、下段が CustomToggleButton で、それぞれ「OFF」「ON」「ONでDIsable」を配置しています。

sample-ToggleButton.png

下段左は IsThreeState="True" としていて、「Undefined」時の表示を掲載しておきます。

sample-ToggleButton2.png

サンプルプロジェクト

カスタムコントール追加

Visual Studio で WPF アプリケーションのプロジェクト WpfApp1 を作成して、ソリューションエクスプローラで Controls というサブフォルダを用意します。
この Controls というサブフォルダを選択して、追加 - 新しい項目 を選択します。

vs-01.png

新しい項目追加で カスタムコントール(WPF)を選択して、CustomToggleButton.cs を作成します。

vs-02-3.png

カスタムコントールを追加すると Theme\Generic.xaml が自動的に追加されます。

namespace 修正

カスタムコントールを Controls 配下に配置したので、下記2ファイルの local namespece を修正します。

  • Themes\Generic.xaml
  • MainWindow.xaml
xmlns:local="clr-namespace:WpfApp1"
 ↓
xmlns:local="clr-namespace:WpfApp1.Controls"

サンプルコード

CustomToggleButton

CustomToggleButton 実装ポイントを記載します。

  • DependencyProperty
    • TextOff(OFF時の表示文字)TextOn(ON時の表示文字)を追加
    • IsThreeState="True" で利用可能な TextUndefined(未定義時の表示文字)を追加
  • コントール外観
    • 全体を Border として、内部に Grid(横幅 20pixel、* )を配置
    • Column="0" で「サンプル - 初期デザイン」に基づく Indicator を配置
    • Column="1" に TextOff をバインドした textOff を Opacity="1" で配置
    • Column="1" に TextOn をバインドした textOn を Opacity="0" で配置
    • Column="1" に TextUndefined をバインドした textUndefined を Opacity="0" で配置

ControlTemplate.Triggers では下記を指定します。

  • Checked 状態
    • indicator.Background 配色変更
    • textOff の Opacity を 0 として非表示
    • textOn の Opacity を 1 として表示
    • textUndefined の Opacity を 0 として非表示
  • Indeterminate 状態
    • indicator.Background 配色変更
    • textOff の Opacity を 0 として非表示
    • textOn の Opacity を 0 として非表示
    • textUndefined の Opacity を 1 として表示
  • Disable 状態
    • indicator.Background, Background, BorderBrush, Foreground 配色変更
  • MouseOver 状態
    • Background 配色変更

特定の条件でのプロパティ変更として ControlTemplate.Triggers を利用しましたが、VisualStateManager を利用することもできます。
VisualStateManager は、より柔軟な状態管理を提供する仕組みで、特にアニメーションを伴う UI の変更に適しています。

Generic.xml
<Style TargetType="{x:Type local:CustomToggleButton}"
    BasedOn="{StaticResource {x:Type ToggleButton}}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:CustomToggleButton}">
        <Border Width="{TemplateBinding Width}"
                Height="{TemplateBinding Height}"
                Background="{TemplateBinding Background}" 
                BorderBrush="{TemplateBinding BorderBrush}" 
                BorderThickness="{TemplateBinding BorderThickness}">
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="20"/>
              <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <!-- Indicator -->
            <Border x:Name="indicator" Grid.Column="0" Margin="3,3,5,3"
                    BorderBrush="Black" Background="Gainsboro"
                    BorderThickness="1" CornerRadius="2"/>
            <!-- 表示文字:Off -->
            <TextBlock x:Name="textOff" Opacity="1"
                       Grid.Column="1" Text="{TemplateBinding TextOff}"
                       HorizontalAlignment="Left" VerticalAlignment="Center"/>
            <!-- 表示文字:On -->
            <TextBlock x:Name="textOn" Opacity="0"
                       Grid.Column="1" Text="{TemplateBinding TextOn}"
                       HorizontalAlignment="Left" VerticalAlignment="Center"/>
            <!-- 表示文字:Undefined -->
            <TextBlock x:Name="textUndefined" Opacity="0"
                       Grid.Column="1" Text="{TemplateBinding TextUndefined}"
                       HorizontalAlignment="Left" VerticalAlignment="Center"/>
          </Grid>
        </Border>
        <ControlTemplate.Triggers>
          <!-- Checked 状態 -->
          <Trigger Property="IsChecked" Value="True">
            <Setter TargetName="indicator" Property="Background" Value="SkyBlue"/>
            <Setter TargetName="textOff" Property="Opacity" Value="0"/>
            <Setter TargetName="textOn" Property="Opacity" Value="1"/>
            <Setter TargetName="textUndefined" Property="Opacity" Value="0"/>
          </Trigger>
          <!-- Indeterminate 状態 -->
          <Trigger Property="IsChecked" Value="{x:Null}">
            <Setter TargetName="indicator" Property="Background" Value="Salmon"/>
            <Setter TargetName="textOff" Property="Opacity" Value="0"/>
            <Setter TargetName="textOn" Property="Opacity" Value="0"/>
            <Setter TargetName="textUndefined" Property="Opacity" Value="1"/>
          </Trigger>
          <!-- Disable 状態 -->
          <Trigger Property="IsEnabled" Value="False">
            <Setter TargetName="indicator" Property="Background" Value="White"/>
            <Setter Property="Background" Value="GhostWhite" />
            <Setter Property="BorderBrush" Value="Gray" />
            <Setter Property="Foreground" Value="Gray"/>
          </Trigger>
          <!-- MouseOver 状態 -->
          <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="LightBlue" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
CustomToggleButton.cs
namespace WpfApp1.Controls
{
  public class CustomToggleButton : ToggleButton
  {
    // Off 時の表示文字
    public string TextOff
    {
      get { return (string)GetValue(TextOffProperty); }
      set { SetValue(TextOffProperty, value); }
    }
    public static readonly DependencyProperty TextOffProperty =
        DependencyProperty.Register("TextOff", typeof(string),
        typeof(CustomToggleButton), new PropertyMetadata("Off"));
    // On 時の表示文字
    public string TextOn
    {
      get { return (string)GetValue(TextOnProperty); }
      set { SetValue(TextOnProperty, value); }
    }
    public static readonly DependencyProperty TextOnProperty =
        DependencyProperty.Register("TextOn", typeof(string),
        typeof(CustomToggleButton), new PropertyMetadata("On"));
    // Undefined 時の表示文字
    public string TextUndefined
    {
      get { return (string)GetValue(TextUndefinedProperty); }
      set { SetValue(TextUndefinedProperty, value); }
    }
    public static readonly DependencyProperty TextUndefinedProperty =
        DependencyProperty.Register("TextUndefined", typeof(string),
        typeof(CustomToggleButton), new PropertyMetadata("Undefined"));

    // 静的コンストラクタ - クラス全体の初期化で1度だけ呼び出される
    static CustomToggleButton()
    {
      // DefaultStyleKeyの設定
      DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomToggleButton), 
          new FrameworkPropertyMetadata(typeof(CustomToggleButton)));
    }
    // インスタンス コンストラクタ - インスタンスごとに呼び出される
    public CustomToggleButton()
    {

    }
  }
}

メイン画面

メイン画面の xaml も掲載しておきます。

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:WpfApp1.Controls"
  mc:Ignorable="d"
  Title="MainWindow" Height="200" Width="600">
  <StackPanel>
    <Grid Height="120" Margin="2">
      <Grid.RowDefinitions>
        <RowDefinition Height="1*"/>
        <RowDefinition Height="1*"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
      </Grid.ColumnDefinitions>
      <!-- 標準コントール  -->
      <ToggleButton x:Name="btnHoge00" Grid.Column="0" Grid.Row="0"
                    Width="150" Height="40"
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Content="〇〇機能" IsThreeState="True"/>
      <ToggleButton x:Name="btnHoge10" Grid.Column="1" Grid.Row="0"
                    Width="150" Height="40"
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Content="△△機能" IsChecked="True"/>
      <ToggleButton x:Name="btnHoge20" Grid.Column="2" Grid.Row="0"
                    Width="150" Height="40"
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Content="□□機能" IsChecked="True" IsEnabled="False"/>

      <!-- カスタムコントール  -->
      <local:CustomToggleButton x:Name="btnHoge01" Grid.Column="0" Grid.Row="1"
                                Width="150" Height="40"
                                HorizontalAlignment="Left" VerticalAlignment="Center"
                                TextOn="〇〇機能-標準" TextOff="〇〇機能-無効"
                                TextUndefined="〇〇機能-拡張" IsThreeState="True"
                                IsChecked="False"/>
      <local:CustomToggleButton x:Name="btnHoge11" Grid.Column="1" Grid.Row="1"
                                Width="150" Height="40"
                                HorizontalAlignment="Left" VerticalAlignment="Center"
                                TextOn="△△機能-有効" TextOff="△△機能-無効"
                                IsChecked="True"/>
      <local:CustomToggleButton x:Name="btnHoge21" Grid.Column="2" Grid.Row="1"
                                Width="150" Height="40"
                                HorizontalAlignment="Left" VerticalAlignment="Center"
                                TextOn="□□機能-有効" TextOff="□□機能-無効"
                                IsChecked="True" IsEnabled="False"/>
    </Grid>
  </StackPanel>
</Window>
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?