読者です 読者をやめる 読者になる 読者になる

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

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

WPFアプリの外観をストア アプリ風に ―Modern UI for WPF―

プログラミングTips C# WPF .NET Framework

(これは2013-05-13に書いた故OCNブログの記事を加筆修正したものです)

Windowsストア アプリ(Windowsランタイム アプリ、WinRTアプリ)や、Outlook.com、OneDrive(旧SkyDrive)などで採用されている、Modern UI(旧称Metro UI)は、他のMS製デスクトップ アプリでもスタイルとして採用されていて、Visual Studio 2012/2013やOffice 2013はかなりすっきりしたデザインになっています。タイルUIとかフラットUIとかいう呼称もあります。Windows 8のデスクトップ用Win32コモン コントロール自体も、XP/Vista/7のグラデーションや半透明効果を多用した比較的リッチで立体的なLuna/Aeroコントロールとは趣を変えて、シンプルな外観のコントロール(内部名はAero2らしい)になってます。実はIE 10 for Windows 7でも、Webフォームのコントロールや[お気に入りバー]など一部のUIはWindows 8のコモン コントロールと同じAero2のシックな外観になっていることに気付いてる人も多いと思います。
いまやWebデザインやモバイルの世界にも広がっている、グラデーションレスもしくはごくわずかなグラデーションを適用したシンプルで控えめなUIというのは2014年現在のトレンドといっても良いでしょう*1

WPF 4.5アプリもWindows 8上で実行すると、ネイティブWin32/MFCあるいはWinFormsアプリケーション同様にAero2のスキンが適用されてシンプルな外観になるのですが、Visual Studio 2012やOffice 2013ほどModern UI寄りのスッキリしたデザインではありません。しかもネイティブWin32のAero2デザイン(ごくわずかなグラデーションがかかっていて、Office 2010のデザインに近い)とは微妙に違う部分があり、特にWPF版Aero2のボタンデザインは非常にダサいです*2。また、タッチUI用途にボタン面積を大きくしようと思うと、自分でMarginやPaddingを調整したりする手間が必要になります。

今回はそのModern UIスタイルを比較的簡単にWPFアプリケーションに適用するためのオープンソースサードパーティ製ライブラリ「Modern UI for WPF(以下MUI4WPF)」および「Elysium」の導入手順を紹介します。

……と思ったんですが、導入手順をいちいちブログに書くのはダルいので、MUI4WPFをメインにしてElysiumを補助的に使ったサンプルコードを下記にアップしました。詳細はソースコードのコメントを参考にしてください。ビルドにはVisual Studio 2012/2013が、実行には.NET 4.5が必要となります。

2015-01-14追記:
Elysium 2.0 SP4、MUI4WPF 1.0.6に対応しました。MUI4WPF 1.0.6は前バージョン1.0.5と互換性のない変更が一部加えられているようです。
2015-09-19追記:
Visual Studio 2015にも対応しました。といってもソリューションファイルを追加してビルド&実行を確認しただけです。
2016-06-24追記:
MUI4WPF 1.0.9に対応しました。また、ソースコードGitHubに移管する際、ライセンスもMITとして明記しました。

MUI4WPFはCodePlex(のちにGitHub)にてMicrosoft Public Licenseで公開されています。また、Elysiumはどうやらロシアの個人開発者が作成したものらしく、MITライセンスで公開されています。

サンプルの実行結果は下記のような感じになります。

https://sygh-jp.github.io/content_hosting/my_program_ss/MyWpfModernUIApp1_ss_2016_09_29a.png

https://sygh-jp.github.io/content_hosting/my_program_ss/MyWpfModernUIApp1_ss_2016_09_29b.png

実はVisual StudioへMUI4WPFのアプリケーションプロジェクトテンプレートを追加するVSIXも公開されているのですが、今回のサンプルはそのテンプレートをベースにしています。このテンプレートが面白いのは、MFC Feature Packのようにデザインテーマカラーを実行時に変更する機能が自動的に組み込まれることです。上の画像はそれぞれcyan+light、mauve+darkという組み合わせを適用してスクリーンショットを撮っただけです。なかなかきれいだと思いませんか?
サンプルでどういったコードを追加したのか知りたい場合は、WinMergeなどで上記テンプレートが出力するデフォルトのコード群と比較すれば分かるでしょう。

ちなみにElysiumも単体でModern UIアプリケーションを構築できるフレームワークで、プログレス リングやアプリ バーまで使えるという(ある意味どうでもいい)機能があるんですが、

  • Elysiumスタイルを適用したScrollBarは逆に使いづらくなってしまう。幅がやたら細くなるし、両端のリピートボタンがなくなってしまう。ScrollViewerやComboBox内のScrollBarも同様
  • Elysium.Controls.Windowの右上システム コマンド ボタン群がキーボード フォーカスを受け取ってしまう
  • Elysium.Controls.Windowのリサイズ時の動きが見苦しい

などの不都合があるため、今回は補助に回ってもらいました。コンテキスト メニューは、Elysiumのデザインテーマを個別に適用するのもアリだと思います(Elysiumスタイルのコンテキスト メニューは、行間が広くとってあってタッチ操作がしやすくなっています)。CheckBox、RadioButton、ToggleButtonは、MUI4WPFとElysiumを比較できるようにしてみました。

Modern UI for WPFへの不満、そしてハック

MUI4WPFにはVer.1.0.5時点でいくつかのバグや不足機能・不満点があります。ほとんどがこれまでのWPF標準動作やWin32標準動作からの逸脱に関するもので、現状見つけたものをざっと列挙するだけでも結構あるのですが、ハックする(MUI4WPF自体を書き換えずに対処する処理を注入する)にはそれなりの量のコードを書かなくてはいけない模様。ちなみにElysiumにも同様の不具合がいくつかあります。

  • ModernWindowが実はレイヤードウィンドウではないのでリサイズ時にちらつく(実はVisual Studio 2012 IDEにも同じ不具合がある)
  • 一部のコントロール(TextBoxやComboBox)でテキストのアンチエイリアスClearTypeでなくグレースケールになっている
  • ModernWindowのコンテンツを管理する最下層パネル、それとシステム コマンド ボタン(最小化、最大化/復元、クローズ ボタン)のパネルがキーボード フォーカスを受け取ってしまう
  • ModernDialogのキーボード フォーカスまわりがおかしい(不必要にフォーカスを受け取るパネルが存在する)
  • ModernWindowのシステム コマンド ボタンのツールヒントやModernDialogがローカライズされていない
  • ModernDialogにアイコンが指定できない(アイコンに応じたシステムサウンドも当然鳴らない)
  • ModernDialogをサブスレッドから直接表示できない(Dispatcherが必要)
  • ModernDialogなどで使われているBBCodeBlockはエスケープをサポートしない
  • ToggleButtonの外観がIsCheckedによって変動しない
  • 中間状態になっているCheckBoxのForegroundがなぜかグレーになる(Disabledになっているように見えてしまう)
  • RadioButtonがなぜか正方形状でラジオボタンっぽくない(IsThreeState="True"でIsChecked="{x:Null}"な中間状態のCheckBoxに見えてしまう)
  • TextBoxやComboBoxのコンテキスト メニューにModernスタイルが正しく適用されない(ハイライト色が変わるだけ)。また、テキストのインデント位置が微妙におかしい(ずれている)
  • TabControlにはスタイルが適用されない(代替としてLinkGroup、Link、ModernTabが用意されている)
  • Expanderにはスタイルが適用されない
  • ListViewがIsEnabled="False"のとき、背景色が強制的に#F4F4F4になってしまう
  • Windows 7でAero Glassを無効化すると、ModernWindowの角が丸くなってしまう

今回紹介したサンプルでは、上記の不具合にほぼ対処しています。詳しくはこれもソースコードを参照してください。MUI4WPF自体をいじらずに不具合修正するために結局P/Invokeを多用する羽目になり、また一部ではリフレクションによるカプセル化の破壊も行なっていますが、そういったアドホックというか泥臭いコードは別のヘルパーアセンブリに押し込みました。
またサンプルでは、MUI4WPFのModernDialogをハックして下記のようにアイコンを指定する機能を付加してみました。アイコンはModernUIIcons.comで公開されているXAML形式のベクトルデータ(Geometry)を使っています。利用規約は付属のテキストを参照してください。

https://sygh-jp.github.io/content_hosting/my_program_ss/MyWpfModernUIApp1_ss_2014_06_20c.png

https://sygh-jp.github.io/content_hosting/my_program_ss/MyWpfModernUIApp1_ss_2014_06_20d.png

MUI4WPFデフォルトのModernDialogは実際かなり貧弱でただの見かけ倒しなので、修正が面倒だったらWPF標準のMessageBoxやTaskDialog(Windows API Code PackWPF TaskDialog Wrapper and Emulatorなど)を使ったほうがよいです。

Modern UIのすすめ

WPFの現行機能がMFCWindows Formsと比べてまだ十分とはいえないにもかかわらず、Visual Studio 2013ではWPFに目立った機能が追加されず、WPFの将来を悲観する声も上がっているのは事実です。しかし、WPFで使われているXAMLは、Silverlightやストア アプリでも採用されていて、今後のMSプラットフォームUIを支えていく根幹技術だと確信しています。Windowsプログラマーだったら修得しておいて損はしないでしょう。
また、業務用途などのリッチクライアントを必要とする環境では、依然としてWebアプリやストアアプリよりも、WPFアプリを選択するほうが正解と言えます。であれば、Modern UIを使ってWebアプリやストアアプリとのデザインの統一感を出した上で、機能性を重視したリッチクライアントを構築するのもひとつの差別化だと思います。

それとこれはアプリケーションUIを作るときのコツなんですが、をあまり無計画に使いすぎないようにしましょう。下記ではPowerPointスライドを作るときの色の使い方に関して言及されていますが、基本はベース/メイン/アクセントの3色です。WPF = Windows Presentation Foundationの名の通り、アプリケーションのUIもいわばプレゼンテーションのひとつの形ですので、せっかくひかえめなModern UIの外観を使っていても、ユーザーの集中力を散漫させるような色使いをしてしまうと台無しになってしまうので注意しましょう。

ちなみにオープンソースWPFライブラリといえばAvalonDock、WPF Toolkit、Extended WPF Toolkitあたりが有名なんですが、こいつらに関してもまた機会があればいずれ紹介しましょう……

2015-05-17追記:
MSの.NETチーム公式ブログにて、2014年末にWPFの今後のロードマップに関する記事が掲載されました。高DPI対応のデスクトップアプリ用APIであるWPFに対して、今後も改良のための投資が行なわれるようです。MS謹製のWPFリボンは作り込みが甘く、細かい部分で多数の問題を抱えていて、正直VS 2012/2013時点でのWPFリボンは使い物にならない(自分でフルスクラッチしたほうがマシなレベル)と自分も思っていましたが、一応改善する気はあるようです。個人的にはDirectX 11/DirectX 12との相互運用を実現して欲しいところです。

ちなみにWPFのリボンは、batzen氏によって開発された「Fluent Ribbon Control Suite」と呼ばれるオープンソース版も存在するようです。MS公式版よりもこちらのほうが完成度が高そうな雰囲気です。

Modern UI(フラットデザイン)における注意点

ユーザーエクスペリエンス/ユーザビリティの第一人者であるヤコブ・ニールセン博士が、流行りのフラットデザインに対する問題点を指摘しています。

確かにWindows XPのLuna、Vista/7のAero、Mac OS XのAquaなどにおける立体的なデザインのウィジェットと比べて、立体感に乏しいフラットデザインを安直に使うと、「一見しただけではどれがクリック/タップ/入力可能なインタラクティブパーツで、どれがラベル/コンテンツのような非インタラクティブパーツなのか分からない」というジレンマに陥ることになります。たとえば今回提示したサンプルのスクリーンショットですが、Darkテーマでは複数並べているはずのボタンの境界線が見えず、くっついてしまっているように見えます。もはや一見では個々のボタンとして認識できないものになってしまっており、これはマズいデザインといえるでしょう。また、ボタンとエディットボックスは、内部テキストのアライメントが異なるだけで、そのほかの明確な違いがありません。ユーザーの誤認識をできるかぎり解消するためにも、パーツ同士のマージン(余白)を十分にとる、ボタンは太いボーダー(境界線)を明示的に指定するような追加のスタイルを適用する、などの地道な対処を検討する必要があります。

下記のような記事も、UI設計者・デザイナーは一読しておくべきでしょう。

余談:AeroLite

WPF 4.5には、Windows 8の隠しテーマであるAeroLite用のテーマアセンブリも同様に含まれています。AeroLiteもAero2同様シンプルな外観ですが、どちらかというとAeroとAero2の中間に位置するようなデザインです。

WPF 4.5アプリにおいては、Windows 7でもScrollBarなどをAeroではなくWindows 8風の外観(AeroLite)にすることができます。
参照設定にPresentationFramework.AeroLite.dllを追加し、[ローカル コピー]プロパティをTrueに設定して、さらにScrollBar.Resourcesなどに以下のようなコードを追加します。

<ResourceDictionary Source="/presentationframework.aerolite;component/themes/aerolite.normalcolor.xaml"/>

親WindowのResourcesとMergedDictionariesとStyleを使って、特定の型のすべての子コントロールのテーマだけをAeroLiteに暗黙的一括設定する方法はちょっと分からないです……コード ビハインドしかなさげ。現在調査中。

Win8デフォルトのスキンは、厳密にはAeroLiteではなくAero2であり(Win8 OS設定におけるネイティブAeroLiteは隠しテーマ扱い)、またWPF 4.5にはPresentationFramework.Aero2.dllというアセンブリも存在し、Win8ではAero2のほうがデフォルトで使用されるのですが、なぜかWindows 7上のWPF 4.5ではAero2が使用不可能でした。でもまぁAero2よりもAeroLiteのほうが見やすい気がします。AeroのScrollBarはマウスオーバーすればリピートボタンとレールが分離して見えるようになってますし、Track (Thumb + RepeatButton) の部分がはっきりとした部品的なデザインになってるんですが、Aero2のScrollBarはリピートボタンとレールの色がまったく同じでくっついて見えるのがダメだと思う……
ちなみにWPFのAero2/AeroLiteテーマにはいろいろ外観上の問題があります。例えばAero2ではComboBoxのBackGroundが指定できない(AeroLiteも少し挙動がおかしい)、AeroLiteではTreeViewItem選択時のハイライト色がAero/Aero2と違ってSystemColors.HighlightBrushKeyだけで指定できない、などなど。ComboBoxに関してはControlTemplateを書き換えるか、コードビハインドでイベントハンドラーを挿入してしまえば一応対処することはできるし、TreeViewItemのほうは空Styleの追加で対処できるんですが、そこまでしてComboBoxの背景色やTreeViewItemのハイライト色を変えたりする必要性が果たしてあるのかどうか。

なお、ToggleButtonはXP以降の外観が個人的に好かない(マウスオーバーしたときの外観が気に入らない)というかどう考えても押し込み状態が判別しづらいと思うのでなるべく使わないほうがいいと思います。ユーザーエクスペリエンス的には最悪の部類です。普通にRadioButtonやCheckBoxでいいやん。

*1:かつて(Office 2007あたりまでの時代)は、いわゆるグラススタイルグラデーションなどでボタンを立体的に見せるというのが至るところで流行っていました。

*2:WPF版Aero2はどちらかというとWindows 10デスクトップのビジュアルスタイルに近いようです。