【WPF】AvalonDockの使い方(for WindowsForm技術者)

この記事は約15分で読めます。

WindowsForm では、Weifen Luo と呼ばれるフリーのドッキングウィンドウが良く使われています。

ネットには比較的情報も豊富で高機能であるため、私も好んで使っています。

ただ、残念なのはWPF版が存在しないことと、32ビット版限定であること。

一方、WPFでフリーのドッキングウィンドウを探すと、AvalonDockが候補に上がります。

こちらは64ビット版にも対応しているのですが、Weifen Luo と比べて情報が少なく、そのうちの半分はMVVMでの利用が前提となっています。

ということで、WindowsForm技術者向でWPFへの移行を考えている方々について、AvalonDockの使い方について解説したいと思います。

AvalonDockの入手方法

Nuget を使う事で簡単にインストールが可能です。

Nugetの詳細については こちら の記事をご覧ください。

まず、Nugetのパッケージ管理画面から、AvalonDock で検索します。

すると、いくつか候補が表示されますので

  1. Xceed.Products.Wpf.ToolKit.AvalonDock をクリック
  2. インストールしたいプロジェクトにチェック
  3. インストールボタンをクリック

の3手順を行って下さい。

以下の様な確認画面が表示されますので、「OK」ボタンをクリックして下さい。

ちなみに、AvalonDockText というのは、これからAvalonDockをインストールしようとしているプロジェクトの名前です。

インストールが完了したら、ソリューションエクスプローラーの参照に下記の2つが表示されます。

  • Xceed.Wpf.AvalonDock
  • Xceed.Wpf.Toolkit

AvalonDockの概要

AvalonDockはアプリケーションの1つの画面を複数のブロックで区切り、そのブロックの中で子ウィンドウを複数持たせ、また子ウィンドウを切り離したり、別の位置にドッキングさせることが出来ます。

AvalonDockを使ってドッキング可能な画面を作るには、レイアウト構成の仕組みを知っておく必要があります。

下図は、レイアウトを作成する際に必要となるパーツと、実際非表示される画面の例です。

LayoutPane

LayoutPanelは画面を複数の区画に区切るためのものです。

LayoutPanelの中には、更に複数のLayoutPanelを入れ子にできますので、より複雑なレイアウトを作ることが可能です。

LayoutPanelの中には、LayoutDocumentPane もしくは LayoutAnchorablePane という2つのPane(表示領域)を持たせることが出来ます。

LayoutDocumentPane

LayoutDocumentPaneは、主要となるメインコンテンツ(編集画面、登録画面、設定画面など)を表示するエリアで、Visual Studioでいうソースコードエディタやレイアウトエディタをタブ表示する部分に相当します。

LayoutAnchorablePane

LayoutAnchorblePane は、サブコンテンツ(設定、属性など)を表示するエリアで、Visual Studioでいうプロパティやソシューションエクスプローラーの表示部分に相当します。

メインコンテンツを表示するためのLayoutDocumentPaneは、画面の中央に大きく表示されるのに対して、サブコンテンツはメインコンテンツを取り巻く形で、上下左右のいずれかに細長く表示されます。

LayoutDocument

LayoutDocument は、主要となるコンテンツの表示エリア(LayoutDocumentPane)に表示するタブ領域のことです。

LayoutDocumentPane1つにつき1つのタブが表示されます。

LayoutAnchorble

LayoutAnchorble は、サブコンテンツに表示の表示エリア(LayoutAnchorablePane)に表示されるタブ領域になります。

見た目の違いとして、タブの耳が下に来ています。

尚、LayoutDocument はメインコンテンツ表示エリアにのみドッキングすることができますが、このLayoutAnchorbleは、メインコンテンツ、サブコンテンツのどちらにもドッキングが可能です。

また、メインコンテンツ表示エリアにドッキングした時は、LayoutDocument と同様にタブの耳が上に表示されるようになります。

Xamlの例

では、メインコンテンツ1つ、サブコンテンツ2つという単純な構成(下図)を例に、実際のXamlを見ていきましょう。

ここで1つだけ注意があります。

Xamlで記述する場合、必ず冒頭の参照設定のところに次の1行を追加して下さい。

この行が無いとAvalonDockが見つからないというエラーになってしまいます。

では、実際のXamlは次の通りです。

ドッキングウィンドウの具体例

では、より理解を深めて頂くために、いくつかのパターンについて具体例を示してみたいと思います。

メインコンテンツにタブが2個ある場合

サブコンテンツ2の領域が2個ある場合

プログラムから動的にタブを追加する

通常は、メインコンテンツやサブコンテンツの表示レイアウトをXamlで作成し、プログラムからタブだけを追加していくことになると思います。

この場合、例えば下記の様にLayoutDocumentPaneに名前を付与しておいて、そこにプログラムから追加するのが一番簡単です。

次は、タブを追加するプログラム例です。

冒頭の参照設定のところで using をしておいてください。

次の様に LayoutDocument() のインスタンスを生成し、LayoutDocumentPane に追加します。

Title に設定した文字列は、タブに表示されます。

ContentIdはレイアウトをファイルに保存する際に必要となるもので、通常は設定しなくても問題ありません。

この layout クラスは中身が無いので、プログラムの仕様に応じて様々なコントロールを張り付ける必要があります。

複雑な機能の場合は、ユーザーコントロールを作っておいて、それを Content に登録する方法を使います。

サブコンテンツへのタブ追加も同様で、LayoutDocument() の代わりに LayoutAnchorable() を new して使います。

以下はそのサンプルです。

プログラムから動的に Pane を追加する

メインコンテンツの表示エリアである LayoutDocumentPane、及びサブコンテンツの表示エリアである LayoutAnchorablePane についても、プログラムで動的に追加できます。

この場合も、Xaml上でLayoutPanel に名前を付けて、pane を追加していくところは同じですが、 Content ではなく Chiltrenに対してAddします。

レイアウトの保存と読み込み

レイアウトの保存と読み込みは AvalonDock の機能を使うので、冒頭の参照設定に次の1行を定義して下さい。

次に、AvalonDock に名前を付け、さらにメインコンテンツとサブコンテンツのタブの部分(LayoutDocument及びLayoutAnchorble)に、ユニークになるようなContentID を付与しておきます。

これをしておかないと、レイアウトを読み込んで直前の状態を復元することが出来ません。

以上の様な準備をしておけば以下の記述でレイアウトをファイルに保存できます。

保存したファイルは、以下の記述で復元できます。

保存されたレイアウト情報はXML形式で保存されています。

保存できるのはXamlで定義したレイアウトのみ

保存できるのは、事前にXaml上に記述したレイアウトのみです。

ソースコードからメインコンテンツの表示エリアやタブ(LayoutDocumentPane、LayoutAnchorablePane、LayoutDocument、LayoutAnchorable)を追加しても、それはXMLに保存されるものの、復元が出来ません。

保存されたXMLファイルの中には、メインコンテンツとサブコンテンツのタブの内容(Title,ContentIDなど)も含まれていますが、やはりこれらも復元されません。

挙動としては、既に存在している(画面に表示されている)レイアウトに対して、XMLファイルからレイアウト情報(高さや幅)を取得し、ContentIDと突き合わせてレイアウトを復元しています。

従って、プログラムから追加したものに対しては、プログラム起動時は存在していないため、復元されないのです。

もし復元させようとするなら、直前にプログラムから追加した内容を、自力で復元してから、XMLファイルを読み込んであげれば復元は可能です。

どうしても子ウィンドウを復元したい場合

復元したい情報はXMLファイルの中に含まれているので、XMLファイルを直接読み込むという方法を使います。

これには、いくつかの方法がありますが、今回はXmlDocumentを使いました。

まず、以下の参照設定を追加しておいて下さい。

以下はXMLファイルから情報を読み取るサンプルです。

SelectNodesメソッドの引数に、XMLのルートからのパス(XPath)を指定します。

そして、そこに存在する全てのノードを取得すると共に、uxDockingManager から復元先のpane(下記例ではLayoutDocumentPane)を取得し、そこにLayoutDocumentを追加していきます。

LayoutDocumentPaneやLayoutAnchorablePane は1つの画面の中に複数存在する可能性があるので、配列で pane に返されます。

サンプルでは 決め打ちで配列の先頭(pane[0]) を指定しています。

XMLファイルで復元すると、付けた名前が消える

例えば、次の様に LayoutAnchorablePane に uxAnchorablePane1 という名前を付けていて、プログラムから動的にこのpane に対してタブ(LayoutAnchorable)を追加するようにしていたとします。

通常はこの方法でうまくタブ追加できますが、XMLファイルからレイアウトを読み込んだ途端、機能しなくなります。

実は、ContentID で突き合わせてレイアウトを復元する際、レイアウト情報を既存のものに反映するのではなく、全く別のものに置き換わってしまいます。

つまり、Xamlの定義で事前に決めておいた名前(今回の場合、uxAnchorablePane1)がレイアウト復元と同時に消滅してしまうのです。

このため、uxAnchorablePane1でタブを追加しようとしても、存在しないため追加できない=機能しなくなるのです。

これを回避するためには、AvalonDock から動的にLayoutPanel や pane を取得し、それに対して追加するようプログラムを変える必要があります。

具体的には下記の記述で直接 AvalonDock(下記例では uxDockingManager という名前を付与) から取得できますので、これを利用します。

pane の内容をクリアするには

プログラム起動直後は Xaml に記述した pane やタブが表示されているので、自力で復元する場合には邪魔になることがあります。

そんな場合、次のコードで pane だけを残して、タブをクリアすることができます。

タブが閉じたことを検知する、又は閉じれないようにする

例えば、動的にタブを追加して、そのタブに対して操作をしたいことがあります。

もし、そのタブがユーザーで閉じられた場合、以降は操作ができなくなりますよね。

閉じれないようにするのは簡単で、LayoutDocument の CanCloseプロパティに falseを設定するだけです。

では、閉じたことを検知するにはどうすればよいでしょう。

閉じたタブがLayoutDocument の場合は、一律に DockingManager の DocumentClosed イベントハンドラを使うことで簡単に検知できます。

イベントハンドラの引数に Document.Title というプロパティがあるので、そこでタブのタイトルが取得できます。

例えば、ContentListというDictionaryを作って、タブの名前で管理するとしましょう。

DockingManager には X:Name=”uxDockingManager” という名前を付けていたとします。

下記ソースは、タブが閉じられた時に、ContentList から該当のLayoutDocumentを削除する例です。

一方、閉じたタブが LayoutAnchorable の場合、このイベントハンドラは反応してくれません。

LayoutAnchorable の場合、個々の Layoutanchorble 毎に、 Closed イベント を捕捉する必要があります。

具体的には、次の様に LayoutAnchorable 生成時に、Closed イベントハンドラを設定します。

タブやWindowのデザインを見栄え良くするには

AvalonDockを標準のまま使う場合、タブが白色の長方形であるため、パッと見たところ殺風景に感じるかもしれません。

これは、Nuget からテーマをインストールすることで、見栄えを良くできます。

例えば、AeroTheme を選ぶと次のようになります。

VS2010Theme を選ぶと、Visual Studioライクなデザインになります。

Nuget で Avalon Dock Theme で検索すると使えるテーマが出てきますので、インストールして下さい。

Xceed.Products.Wpf.Toolkit.AvalonDock.Themes を選ぶと、いくつかのテーマがまとめてインストールできます。

使い方は、Xamlに3行を加えるだけです。

AeroThemeの部分を好きなテーマに変更すると、レイアウトエディタに反映されます。

3行を入れる位置は次の通りです。

ドッキング解除と同時にタブの中身が消去される場合

ある時、LayoutDocument のドッキング状態をマウスのドラッグ&ドロップで解除したら、そのとたんにLayoutDocumentの中身が真っ白になることがあります。

もし、Themeを設定しているなら、その部分(3行)を削除してから、再度ドッキングを解除し、現象が発生するかお試しください。

もし発生しなくなったら、Themeが原因だと思われます。

この場合の解決方法は2つです。

  1. 問題が発生しない別の Theme を使う
  2. LayoutDocument をやめて LayoutAnchorable を使う

私の場合は、LayoutAnchorable にすることで解決しました。

レイアウトを入れ子にしている箇所がエラーでビルドが通らない場合

ある時、ビルドしようとしたら、いきなりXamlでエラーになることがあります。

以下のようにレイアウトを入れ子にしていた時、入れ子側のLayoutPanel に波線が表示され、「2重に定義はできない」みたいな内容のエラーが表示されます。

この場合の解決方法は、一旦 Theme の設定部分を削除して、一度ビルドを通してから、再度 Theme を設定するようにしてください。

私の場合、この方法で解決しました。

どうもThemeに関する部分はバグが潜んでいそうですね。

突然、何らかの不具合が発生したら、まずThemeを疑ってみるのが良いかもしれません。

まとめ

如何でしたでしょうか。

AvalonDockはMVVMを意識して作られているので、今回の様にソースコードべた書きで使う場合100%の機能を引き出せないかもしれません。

しかし、MVVMを使わなくても、レイアウトの保存と復元、プログラムからタブの追加や削除など必要最小限のことは可能です。

ちょっと癖はありますが十分実用的なので、WindowsForm から WPFでのプログラミングに移行しようと考えている方は、是非使ってみて下さい。

タイトルとURLをコピーしました