syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

WPFのAero2/AeroLiteテーマ

以前書いたModern UI for WPFの導入に関する記事にて、WPF 4.5におけるAero2/AeroLiteテーマの外観上の問題点について言及しましたが、具体的にどういうことなのか説明しましょう。
まずはサンプルコードから。

以下はWindows 7でのスクリーンショットです。
https://sygh-jp.github.io/content_hosting/my_program_ss/WpfAero2ThemeTest1_ss_2014_06_24b.png

以下はWindows 8でのスクリーンショットです。
https://sygh-jp.github.io/content_hosting/my_program_ss/WpfAero2ThemeTest1_ss_2014_06_24a.png

ComboBox.Backgroundの問題

サンプルにおいて、偶数番のコンボボックスはIsEditableがTrue、奇数番はFalseです。
Combo0, Combo1, Combo6, Combo7は特にテーマを明示的に指定していないため、Win7ではAero、Win8ではAero2になります。
Combo0, Combo1はBackground="LightPink"を指定していて、常に背景色が固定されるはずであり、実際にAeroではそうなっているのですが、Aero2では一切変わっていません。
Combo2, Combo3, Combo8, Combo9はAeroLiteテーマを明示的に適用しています。そのため、Win7でもAeroLiteの外観になります。
Combo2, Combo3はBackground="LightPink"を指定していて、常に背景色が固定されるはずなのですが、IsEditableがTrueのときはおかしな感じになっています。

「ComboBoxの背景色を変更するなんておかしなことをするもんだ、そんなのなくてもいいじゃないか」という意見があるかもしれません。実際に自分もそう思います。
ですが、「コンボボックスにフォーカス(キーボードフォーカス)が当たっているとき、コンボボックスの背景色をライトグリーンなどに変更したい」という要求が発生することがあります。こういった要求を満たしたい場合、Aeroテーマであれば「IsKeyboardFocusWithinプロパティの変化をTriggerにしてBackgroundを変更する」というStyleを作成して適用するだけでよかったのですが、Aero2/AeroLiteではそういった単純な方法が通用しません。

ではどうすればいいのか。方法は2通りあります。

  1. WPFのControlTemplateをカスタマイズして、独自の変更用Trigger/MultiTriggerを仕込み、そのStyleを適用する。
  2. Nameプロパティを元に探索・取得した内部コントロール実装依存)に対して、C#/VBコードビハインドで変更を行なうイベントハンドラーを仕込む。

Combo4からCombo9は上記の方法により、「コンボボックスにフォーカスが当たっているとき背景色を変更する」処理を実装しています。
Combo4, Combo5はAero2のComboBoxから取得したControlTemplateをもとに、数行のXAMLコードを追加する方法をとりました。
具体的にはVisual StudioXAMLデザイナー上ですっぴんのComboBoxを右クリックして、[テンプレートの編集] → [コピーして編集] を実行すれば、現在適用中のテーマにおけるXAMLコードがStyleリソースとして自動的に挿入されるので、それをもとに編集していけばよいだけです。
Combo6からCombo9はコードビハインドを使っています。内部コントロールの型およびNameプロパティの特定は、カスタムControlTemplate手法で取得したXAMLコードを参考にして行なっています。
プログラマーによる記述量(コード変更量)はどちらが少ないかと言われるとカスタムControlTemplateを使う方法なのですが、こちらは元にしたテーマ(Aero2)のレンダリングコードがそのまま使われるため、Win7においてもAeroではなくAero2の外観になってしまうという欠点があります(逆に元にしたテーマがAeroだった場合、Win8においてもAero2ではなくAeroになってしまうという状況になります)。
どちらかというと個人的にはコードビハインドによるピンポイントかつアドホックな対処をお勧めします(Modern UI for WPFのように徹底したカスタマイズを施さないのであれば)。とはいえ「Aero3テーマ」なんてのが今後WPF 5.0 for Windows 10とかで出てきたときに修正なしで対処できるかどうか問われればたぶん通用しない(内部仕様が変更される可能性があり、追加のAero3対処コードを書く羽目になる)と答えるでしょう。ちなみにWPF 3.5が対応しているのはAeroまでで、Windows 8上でWPF 3.5アプリを実行してもAero2テーマは適用されません。

TreeViewItem/ListBoxItemの問題

下記で示されているように、「TreeViewのノード(アイテム)選択時の背景色をカスタマイズする」という場合、Aero/Aero2テーマであれば、

    <TreeView.Resources>
        <SolidColorBrush Color="Red" x:Key="{x:Static SystemColors.HighlightBrushKey}"/>
    </TreeView.Resources>

というリソースをTreeViewに加えるだけでよいです。実にシンプルです。

ですが、AeroLiteではこの方法が通用しません。アイテムを選択してもアイテムの背景色が変わりません。実際にサンプルプログラムを動かしてみれば分かります。なお、ListBoxおよびListBoxItemに至っては、AeroLiteだけでなくAero2でもこの方法が通用しません。

TextBox内コンテキスト メニュー

TextBoxやIsEditable=TrueなComboBox内で右クリックしたときに出てくるおなじみの編集系コンテキスト メニューですが、Windows 7上でAeroLiteをApp.xamlにて適用しても、このコンテキスト メニューはAeroのままです。さらに、このAeroのメニュー項目にマウスオーバーしたときにハイライトが表示されません。ContextMenuを明示的に作成して、Windowにアタッチする場合や、Windows 8上のAeroLiteではまともに動作します。どうやらAeroLiteテーマにおけるTextBoxのバグのようです。


ここまでの説明がいったいどういうことなのか文章だけではよく分からない人は、実際にサンプルをビルドして動かしてみてからソースコードのコメントを読んでください。

結論

今回の話はものすごく些末な問題といえばそれまででしょう。
正直自分はComboBoxやTreeViewItemの背景色なんてカスタマイズしようとは思わないし、それに普段はModern UI for WPFを使っているのでそんなの知りません。
WPFテーマのバグというか仕様上の欠点といえばそうなんですが、特に大したことはない(致命的ではない)と思います。

……と言いたいところなんですが、一般論でいうと、たとえ(ある意味どうでもいい)些末な機能だったとしても、

  • どうしてもユーザーがそういった機能を要求しているが、ユーザーに対して「その機能を実装しない」ことを説明して納得させられるだけの材料や論拠・代替案を持ち合わせていない。
  • デザインチームが提案した機能だが、デザインチームに対して「その機能を実装しない」ことを説明して納得させられるだけの材料や論拠・代替案を持ち合わせていない。

という場合、じゃあプログラマーさんがんばってくださいね、という流れに行き着くのでしょう。

ちなみに今回の記事は個人的な興味で調査しただけのものです。些細だけれどWPFに存在するバグ・欠陥を明らかにする、またWPFカスタムControlTemplateを作成する以外の方法を模索する、という行為に技術的な興味があっただけです。
できるかできないかと言われればまあできるけどめんどくさいね。そんなところです。

しかしWin32/MFCWindows Formsと比べて、GUIの複雑なフルカスタマイズができるだけでなく、単純なカスタマイズも非常に簡単なのがWPFの良いところだったんですが、こういうworkaroundも含めると結局WPFでも苦労することになるのが残念ですね。