WPFでドッキングウィンドウ(AvalonDock) 使い方 その2 - AvalonDockのアーキテクチャ

AvalonDock2.0 のソースコードを実際に追ってみました。

さて、前回の最後の例のように、ドキュメント1つと、その右にドッキングウィンドウを持つウィンドウのXAML 要素は以下のような感じになります。

・DockingManager
 ・LayoutRoot
  ・LayoutPanel
   ・LayoutDocumentPane
    ・LayoutDocument
   ・LayoutAnchorablePane
    ・LayoutAnchorable

ここで 「なるほど、じゃあ LayoutAnchorable が Window コントロールだな」と思って、例えばウィンドウタイトルのテキストにデータバインドをかけようとして
<LayoutAnchorable Title={Binding Name}>
なんて書いても、バインドされません。

クラス定義を追ってみるとわかりますが、Layout~ 要素は FrameworkElement ではありません。DataContext を持たないため、バインドできません。
これらは UI要素ではなく、静的な階層構造(つまり、初期状態)を定義するただのデータクラスです。(AvalonDock 内では LayoutElement というクラスのサブクラスです)


じゃあ Window コントロールはどこ?ウィンドウタイトルにバインドしたいときはどうするの?

AvalonDock の構成要素

AvalonDock の中核は以下の3つの要素で構成されています。
・LayoutElement
・LayoutItem
・LayoutControl

・LayoutElement

Document や Anchorable 等の静的な位置関係・階層構造を定義するためのデータクラスです。
通常、XAML には LayoutElement を記述します。
↑で例に挙げた XAML 要素も全て LayoutElement のサブクラスです。

・LayoutItem

DockingManager は LayoutElement の構造を元に、LayoutItem を生成します。

LayoutItem ドッキング要素が表示するべき情報を握っているオブジェクトです。
・タイトル文字列
・アクティブであるか
・閉じることができるか
・要素内部に表示するコントロール
等々…
ドッキングウィンドウコントロールに対する ViewModel とみなすことができます。

また、FrameworkElement を継承しており、バインドターゲットにすることができます。
例えばドッキングウィンドウのタイトル文字列にバインドしたい場合は LayoutItem.Title プロパティにバインドすることになります。

LayoutItem は、元になる LayoutElement と対になるインスタンスがつくられます。
LayoutDocument → LayoutDocumentItem
LayoutAnchorable → LayoutAnchorableItem


なお、LayoutItem は動的に生成されるため XAML から直接アクセスすることはできません。
LayoutItem のプロパティにバインドするときは Style や Template を駆使することになります。

・LayoutControl (ILayoutControl)

DockingManager は LayoutItem の状態を元に、LayoutControl を生成します。

LayoutControl は LayoutItem に対する View です。
LayoutItem の状態 (ドッキングしているか、していないか等) によって様々なコントロールが作られます。
LayoutDocument
 フローティング中 → LayoutDocumentFloatingWindowControl (Window を継承)
 ドッキング中   → LayoutDocumentPaneControl (TabControl を継承)

※あくまでイメージ。実際はさらに細かく子コントロールが作られ、かなり複雑です。

このため、LayoutControl の寿命は保証できません。
これは普通のListViewItem 等と同じですね。



いきなり AvalonDock のソースコードに突撃してもその複雑さに面くらいますが、
こうして一歩引いて眺めてみると割とシンプルで、ちゃんとMVVMに則っています。

非 MVVM アプリでできるのはここまでです

ここまでで LayoutItem が表示に必用な情報を握っていることがわかったので、
「じゃあ LayoutItem の Style を 作って、Title プロパティにバインドすれば行けるのでは?」とか小細工にトライしたくなるのですが、LayoutItem は UI ツリーからは見えないところにいるので無理です。

コードビハインドでガシガシ潜っていく処理を書けば何とかたどり着けはします。
しかし、AvalonDockに限らずそういう回りくどいコードを書くときはライブラリの使い方を間違っていることが多いです。こと MVVM パターンが絡む問題については。
取得したつもりの View が実はまだ作成されていなかった等、何が起こるかわかりませんので危険です。


ということで、次からは MVVM アプリとしての実装に移っていきます。

[おまけ] レイアウトの保存なら非 MVVM でも何とか・・・


AvalonDockのサンプルアプリのコードがそのまま使えます。
詳しくは AvalonDock.MVVMTestApp の以下のメソッドを。
・AvalonDock.TestApp.MainWindow.OnSaveLayout()
・AvalonDock.TestApp.MainWindow.OnLoadLayout()

ポイントは、 XAML で LayoutAnchorable.ContentID に適当な一意の文字列を入れておくこと。
ロード時にこの値をキーとして、復元対象のドッキングウィンドウを探します。