sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

WPF ToggleButton 注意点の整理

タイトルのとおり、WPF のコントール ToggleButton の使い方についてのメモです。ToggleButton は、通常のボタンに加えて IsChecked プロパティを持つコントールです。

IsChecked プロパティの動作で注意しないと VM 上で困ることがあったのでメモ。

ToggleButton のデザイン

ToggleButton にアイコンを表示したいときは XAML 上で Context に設定するのが手早い。このとき ToggleButton の色が変化する場合 Context に設定したコントールも色が変化しないと困る。

<ToggleButton x:Name="Toggle">
    <ui:SymbolIcon x:Name="Icon"
                    Padding="0" Margin="0"
                    Symbol="Edit32" 
                    Foreground="{Binding ElementName=Toggle, Path=Foreground}"
                    Filled="{Binding ElementName=Toggle, Path=IsChecked}">
    </ui:SymbolIcon>
</ToggleButton>

Foregournd と Filled を変更すると以下のようになって、ちょうどよいかもしれない。

ToggleButton のイベント

Clicked イベントを Command 化してもよいのですが IsChecked が ON/OFF のときで、それぞれ呼び出すイベントを切り替えたほうが便利。EventTrigger を利用した例を示す。

コード上で IsChecked を調べて if 文で2つで分けるのも同じだけど、メソッド名で整理できるだけ有利だと思う。

<ToggleButton>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Checked">
            <prism:InvokeCommandAction Command="{Binding OnCommand}" />
        </i:EventTrigger>
        <i:EventTrigger EventName="Unchecked">
            <prism:InvokeCommandAction Command="{Binding OffCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ToggleButton>

コード上からの注意点

ToggleButton を ON や OFF に切り替えたいとき、Checked イベントや Unchecked イベントに Binding した各種コマンドを実行するだけだと、ビューのビジュアル上で整合性がとれなくなる。

OnCommand?.Invoke(); // IsChecked が切り替わらない

理由は、Toggle の ON/OFF のカラーが切り替わらないため。コード上から ON/OFF の切り替えを指示したいときは IsChecked プロパティを直接変更する必要があると思います。

<ToggleButton IsChecked="{Binding IsChecked}">
</ToggleButton>
IsChecked = true or false;

個人的に ToggleButton はあまり使わないこともあって ON/OFF 時のビジュアル変化が VM 上の不整合を起こすミスをしてもエラーにならないのは困る。仕様的なものなので、アプリケーションがクラッシュするようなバグにならないので気づくのに遅れる恐れがあると思う。

余談:添付プロパティで対応できるか?

過去の .NET Framework 3.5 ごろの WPF における ToggleButtonIsChecked は Binding に問題があったようです。そのせいか、私も Binding できないような記憶があって、別の方法を考えていました。

仮に添付プロパティで対応できないか検討します。(検討した内容を記録しただけの余談です)

これは問題があって、添付プロパティの値が切り替わったときにしか callback メソッドを実行できない。なので、初期化で苦労することになります。(添付プロパティは初期化メソッドの実行が無い!)

添付プロパティの値を Binding しておき Checked と Unchecked イベントが発生したときに添付プロパティの値を自動的に更新できるようにすればよいと思ったけど、初期値を true, false どちらの値でもよい、と考えるとイベントの割り当て初期化をどうするか、という話になる。

もうひとつ IsAttached を true にするような添付プロパティを用意しておけば、対応できるが冗長すぎる仕様になってしまう。個人的に添付プロパティで IsChecked を操作する意味はあまり無いと思った。

参考