[C# WPF] ContextMenuのCheckBoxをThreeStateで表示する

2019年4月18日

ContextMenuに3つの状態を遷移するチェックボックスを付けたくなりました。
標準のMenuItemはIsThreeStateを使用できなかったので、それっぽい動きをするものを自作しました。
CheckBoxと同様に、ContextMenuにもIsThreeStateを付けてくれればこんなことをしなくてもいいのですが。

具体的には、MenuItemを継承して状態フラグを追加したMenuItemを作っておいて、それに表示用のStyleをくっつけるだけです。

スポンサーリンク

完成図

こんな感じに3パターンでチェックボックスを表示できます。

 
 
 

MenuItemを継承

Styleから参照できるように依存関係プロパティを追加するだけ。
ここではフラグをbool!で宣言しています。

///チェックボックスで3つの状態を表示できるMenuItem
public class ThreeStateMenuItem : MenuItem
{
    ///MenuItemの状態の依存関係プロパテ意
    public static readonly DependencyProperty ThreeStateProperty =
        DependencyProperty.Register("ThreeState", typeof(bool?), typeof(ThreeStateMenuItem), new PropertyMetadata(false));

    ///MenuItemの状態のCLRプロパティ
    public bool? ThreeState
    {
        get { return (bool?)GetValue(ThreeStateProperty); }
        set { SetValue(ThreeStateProperty, value); }
    }
}

ThreeStateMenuItem用のスタイルを作成

3つの状態を表示するためのスタイルを作ります。
状態フラグをbool!型で宣言しているので、falseのときには非表示、trueのときにはチェック、nullのときにはハイフン、ということにしてあります。

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:ThreeStateMenuItem}">
            <Border BorderThickness="1">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="13"/>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>

                    <Canvas Grid.Column="0">
                        <!-- Check Mark -->
                        <Path
                            x:Name="PART_CheckMark" Width="9" Height="9" Margin="6,4,0,0"
                            Visibility="Collapsed" IsHitTestVisible="False"
                            SnapsToDevicePixels="False" Stroke="Green" StrokeThickness="2"
                            Data="M 0 4 L 3 7 M 3 9 L 9 0"
                            />
                        <!-- Indeterminate Mark -->
                        <Path
                            x:Name="PART_IndeterminateMark" Width="7" Height="7" Margin="8,5,0,0"
                            Visibility="Collapsed" IsHitTestVisible="False"
                            SnapsToDevicePixels="False" Stroke="Green" StrokeThickness="2"
                            Data="M 0 4 L 7 4"
                            />
                    </Canvas>

                    <ContentPresenter
                        Grid.Column="2"
                        ContentSource="Header" HorizontalAlignment="Left" VerticalAlignment="Center"
                        />
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="ThreeState" Value="True">
                    <Setter TargetName="PART_CheckMark" Property="Visibility" Value="Visible"/>
                </Trigger>
                <Trigger Property="ThreeState" Value="{x:Null}">
                    <Setter TargetName="PART_IndeterminateMark" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Setter.Value>
</Setter>

動作を確認するために状態を切り替える処理を作る

こっちは主題ではないので適当にコンテキストメニューを開くたびに状態をひとつずつずらしていくようにしました。

まずはXaml。

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:ThreeStateMenuItem}">
            <Border BorderThickness="1">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="13"/>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>

                    <Canvas Grid.Column="0">
                        <!-- Check Mark -->
                        <Path
                            x:Name="PART_CheckMark" Width="9" Height="9" Margin="6,4,0,0"
                            Visibility="Collapsed" IsHitTestVisible="False"
                            SnapsToDevicePixels="False" Stroke="Green" StrokeThickness="2"
                            Data="M 0 4 L 3 7 M 3 9 L 9 0"
                            />
                        <!-- Indeterminate Mark -->
                        <Path
                            x:Name="PART_IndeterminateMark" Width="7" Height="7" Margin="8,5,0,0"
                            Visibility="Collapsed" IsHitTestVisible="False"
                            SnapsToDevicePixels="False" Stroke="Green" StrokeThickness="2"
                            Data="M 0 4 L 7 4"
                            />
                    </Canvas>

                    <ContentPresenter
                        Grid.Column="2"
                        ContentSource="Header" HorizontalAlignment="Left" VerticalAlignment="Center"
                        />
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="ThreeState" Value="True">
                    <Setter TargetName="PART_CheckMark" Property="Visibility" Value="Visible"/>
                </Trigger>
                <Trigger Property="ThreeState" Value="{x:Null}">
                    <Setter TargetName="PART_IndeterminateMark" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Setter.Value>
</Setter>

コード。

///
/// MainWindow.xaml の相互作用ロジック
/// 
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    ///コンテキストメニューを開いた際に呼び出されるイベント
    private void WindowContextMenu_Opened(object sender, RoutedEventArgs e)
    {
        var menu = sender as ContextMenu;
        if (menu == null)
        {
            return;
        }

        foreach (ThreeStateMenuItem item in menu.Items)
        {
            // メニューを開くたびに状態をひとつ遷移する
            if (item.ThreeState == true)
            {
                item.ThreeState = false;
            }
            else if (item.ThreeState == false)
            {
                item.ThreeState = null;
            }
            else
            {
                item.ThreeState = true;
            }
        }
    }
}

おしまい。

スポンサーリンク

C#, WPFC#, WPF

Posted by peliphilo