WindowsForm では、Weifen Luo と呼ばれるフリーのドッキングウィンドウが良く使われています。
ネットには比較的情報も豊富で高機能であるため、私も好んで使っています。
ただ、残念なのはWPF版が存在しないことと、32ビット版限定であること。
一方、WPFでフリーのドッキングウィンドウを探すと、AvalonDockが候補に上がります。
こちらは64ビット版にも対応しているのですが、Weifen Luo と比べて情報が少なく、そのうちの半分はMVVMでの利用が前提となっています。
ということで、WindowsForm技術者向でWPFへの移行を考えている方々について、AvalonDockの使い方について解説したいと思います。
ちなみに、この記事は WPF +.NET Framework の組み合わせを前提としています。
WPF + .NET 6.0 の場合はエラーになりますので、予め こちら の記事を読んでから、本記事をお読みください。
AvalonDockの入手方法
Nuget を使う事で簡単にインストールが可能です。
Nugetの詳細については Visual Studio のパッケージ管理機能 NuGetとは? の記事をご覧ください。
まず、Nugetのパッケージ管理画面から次のキーワードで検索します。
Xceed.Products.wpf.Toolkit.AvalonDock
以下の様な確認画面が表示されますので、「適用」→「同意する」の順にクリックして下さい。
ちなみに、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行を追加して下さい。
xmlns:avalonDock="http://schemas.xceed.com/wpf/xaml/avalondock"
この行が無いとAvalonDockが見つからないというエラーになってしまいます。
では、実際のXamlは次の通りです。
<Window x:Class="AvalonDockText.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AvalonDockText"
xmlns:avalonDock="http://schemas.xceed.com/wpf/xaml/avalondock"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded" Closed="Window_Closed">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="22*"/>
<RowDefinition Height="397*"/>
</Grid.RowDefinitions>
<avalonDock:DockingManager x:Name="uxDockingManager" AllowMixedOrientation="True" Grid.Row="1">
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel Orientation="Vertical" >
<avalonDock:LayoutPanel Orientation="Horizontal">
<avalonDock:LayoutDocumentPane x:Name="uxDocumentPane">
<!-- ドキュメント ★ここから-->
<avalonDock:LayoutDocument Title="Document1" ContentId="Document1">
<TextBox/>
</avalonDock:LayoutDocument>
<!-- ドキュメント ★ここまで-->
</avalonDock:LayoutDocumentPane>
</avalonDock:LayoutPanel>
<avalonDock:LayoutAnchorablePane DockHeight="100" x:Name="uxAnchorablePane2">
<!-- ツールウィンドウ -->
<avalonDock:LayoutAnchorable Title="ToolWindow2" ContentId="ToolWindow2">
<TextBox/>
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
</Grid>
</Window>
ドッキングウィンドウの具体例
では、より理解を深めて頂くために、いくつかのパターンについて具体例を示してみたいと思います。
メインコンテンツにタブが2個ある場合
<Grid>
<avalonDock:DockingManager x:Name="uxDockingManager">
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel Orientation="Vertical">
<avalonDock:LayoutDocumentPane>
<!-- ドキュメント -->
<avalonDock:LayoutDocument Title="Document1">
<TextBox/>
</avalonDock:LayoutDocument>
<!-- ドキュメント -->
<avalonDock:LayoutDocument Title="Document2">
<TextBox/>
</avalonDock:LayoutDocument>
</avalonDock:LayoutDocumentPane>
<avalonDock:LayoutAnchorablePane DockHeight="100">
<!-- ツールウィンドウ -->
<avalonDock:LayoutAnchorable Title="ToolWindow2">
<TextBox/>
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
</Grid>
サブコンテンツ2の領域が2個ある場合
<Grid>
<avalonDock:DockingManager x:Name="uxDockingManager">
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel Orientation="Vertical">
<avalonDock:LayoutPanel Orientation="Horizontal">
<avalonDock:LayoutDocumentPane>
<!-- ドキュメント -->
<avalonDock:LayoutDocument Title="Document1">
<TextBox/>
</avalonDock:LayoutDocument>
</avalonDock:LayoutDocumentPane>
<avalonDock:LayoutAnchorablePane DockWidth="150">
<!-- ツールウィンドウ -->
<avalonDock:LayoutAnchorable Title="ToolWindow1">
<TextBox/>
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
<avalonDock:LayoutAnchorablePane DockHeight="100">
<!-- ツールウィンドウ -->
<avalonDock:LayoutAnchorable Title="ToolWindow2">
<TextBox/>
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
</Grid>
プログラムから動的にタブを追加する
通常は、メインコンテンツやサブコンテンツの表示レイアウトをXamlで作成し、プログラムからタブだけを追加していくことになると思います。
この場合、例えば下記の様にLayoutDocumentPaneに名前を付与しておいて、そこにプログラムから追加するのが一番簡単です。
<avalonDock:LayoutDocumentPane x:Name="uxDocumentPane">
<!-- ドキュメント ★ここから-->
<avalonDock:LayoutDocument Title="Document1" ContentId="Document1">
<TextBox/>
</avalonDock:LayoutDocument>
<!-- ドキュメント ★ここまで-->
</avalonDock:LayoutDocumentPane>
次は、タブを追加するプログラム例です。
冒頭の参照設定のところで using をしておいてください。
using Xceed.Wpf.AvalonDock.Layout;
次の様に LayoutDocument() のインスタンスを生成し、LayoutDocumentPane に追加します。
var layout = new LayoutDocument();
layout.Title = "タイトル";
layout.ContentId = "ID_0001";
uxDocumentPane.Children.Add(layout);
Title に設定した文字列は、タブに表示されます。
ContentIdはレイアウトをファイルに保存する際に必要となるもので、通常は設定しなくても問題ありません。
この layout クラスは中身が無いので、プログラムの仕様に応じて様々なコントロールを張り付ける必要があります。
var layout = new LayoutDocument();
layout.Title = title;
layout.ContentId = documentId;
//Layoutにコントロールを張り付けるコード
var panel = new StackPanel();
panel.Children.Add(new TextBox() { Width = 100, Height = 50, Text = "テキストボックス" });
panel.Children.Add(new Button() { Width = 100, Height = 50, Content = "ボタン" });
layout.Content = panel;
//DocumentPane にLayoutを追加
DocumentPane[0].Children.Add(layout);
複雑な機能の場合は、ユーザーコントロールを作っておいて、それを Content に登録する方法を使います。
サブコンテンツへのタブ追加も同様で、LayoutDocument() の代わりに LayoutAnchorable() を new して使います。
以下はそのサンプルです。
var layout = new LayoutAnchorable();
layout.Title = title;
layout.ContentId = documentId;
//Layoutにコントロールを張り付けるコード
var panel = new StackPanel();
panel.Children.Add(new TextBox() { Width = 100, Height = 50, Text = "テキストボックス" });
panel.Children.Add(new Button() { Width = 100, Height = 50, Content = "ボタン" });
layout.Content = panel;
//DocumentPane にLayoutを追加
uxAnchorablePane.Children.Add(layout);
プログラムから動的に Pane を追加する
メインコンテンツの表示エリアである LayoutDocumentPane、及びサブコンテンツの表示エリアである LayoutAnchorablePane についても、プログラムで動的に追加できます。
この場合も、Xaml上でLayoutPanel に名前を付けて、pane を追加していくところは同じですが、 Content ではなく Chiltrenに対してAddします。
<avalonDock:LayoutPanel x:Name="uxLayoutPanel1" Orientation="Horizontal">
//メインコンテンツ用のpane を追加する場合
var pane = new LayoutDocumentPane();
uxLayoutPanel1.Children.Add(pane);
//サブコンテンツ用のpaneを追加する場合
var pane = new LayoutAnchorablePane();
uxLayoutPanel1.Children.Add(pane);
レイアウトの保存と読み込み
レイアウトの保存と読み込みは AvalonDock の機能を使うので、冒頭の参照設定に次の1行を定義して下さい。
using Xceed.Wpf.AvalonDock.Layout.Serialization;
次に、AvalonDock に名前を付け、さらにメインコンテンツとサブコンテンツのタブの部分(LayoutDocument及びLayoutAnchorble)に、ユニークになるようなContentID を付与しておきます。
これをしておかないと、レイアウトを読み込んで直前の状態を復元することが出来ません。
<avalonDock:DockingManager x:Name="uxDockingManager" AllowMixedOrientation="True" Grid.Row="1">
・・・中略・・・・
<avalonDock:LayoutDocument Title="Document1" ContentId="Document1">
・・・中略・・・・
<avalonDock:LayoutAnchorable Title="ToolWindow1" ContentId="ToolWindow1">
以上の様な準備をしておけば以下の記述でレイアウトをファイルに保存できます。
XmlLayoutSerializer layoutSerializer = new XmlLayoutSerializer(uxDockingManager);
using (var writer = new StreamWriter("layout.xml"))
{
layoutSerializer.Serialize(writer);
}
保存したファイルは、以下の記述で復元できます。
XmlLayoutSerializer layoutSerializer = new XmlLayoutSerializer(uxDockingManager);
using (var reader = new StreamReader("layout.xml"))
{
layoutSerializer.Deserialize(reader);
}
保存されたレイアウト情報はXML形式で保存されています。
<?xml version="1.0" encoding="UTF-8"?>
-<LayoutRoot>
-<RootPanel Orientation="Vertical">
-<LayoutPanel Orientation="Horizontal">
-<LayoutDocumentPane>
<LayoutDocument LastActivationTimeStamp="08/12/2020 18:41:58" CanClose="True" ContentId="Document1" IsSelected="True" Title="Document1"/>
</LayoutDocumentPane>
-<LayoutAnchorablePane DockWidth="150">
<LayoutAnchorable CanClose="False" ContentId="ToolWindow1" IsSelected="True" Title="ToolWindow1" AutoHideMinHeight="100" AutoHideMinWidth="100"/>
</LayoutAnchorablePane>
</LayoutPanel>
-<LayoutAnchorablePane DockHeight="100">
<LayoutAnchorable CanClose="False" ContentId="ToolWindow2" IsSelected="True" Title="ToolWindow2" AutoHideMinHeight="100" AutoHideMinWidth="100"/>
</LayoutAnchorablePane>
-<LayoutDocumentPane>
<LayoutDocument LastActivationTimeStamp="08/12/2020 18:41:59" CanClose="True" IsSelected="True" Title="test" IsLastFocusedDocument="True"/>
</LayoutDocumentPane>
</RootPanel>
<TopSide/>
<RightSide/>
<LeftSide/>
<BottomSide/>
<FloatingWindows/>
<Hidden/>
</LayoutRoot>
保存できるのはXamlで定義したレイアウトのみ
保存できるのは、事前にXaml上に記述したレイアウトのみです。
ソースコードからメインコンテンツの表示エリアやタブ(LayoutDocumentPane、LayoutAnchorablePane、LayoutDocument、LayoutAnchorable)を追加しても、それはXMLに保存されるものの、復元が出来ません。
保存されたXMLファイルの中には、メインコンテンツとサブコンテンツのタブの内容(Title,ContentIDなど)も含まれていますが、やはりこれらも復元されません。
挙動としては、既に存在している(画面に表示されている)レイアウトに対して、XMLファイルからレイアウト情報(高さや幅)を取得し、ContentIDと突き合わせてレイアウトを復元しています。
従って、プログラムから追加したものに対しては、プログラム起動時は存在していないため、復元されないのです。
もし復元させようとするなら、直前にプログラムから追加した内容を、自力で復元してから、XMLファイルを読み込んであげれば復元は可能です。
どうしても子ウィンドウを復元したい場合
復元したい情報はXMLファイルの中に含まれているので、XMLファイルを直接読み込むという方法を使います。
これには、いくつかの方法がありますが、今回はXmlDocumentを使いました。
まず、以下の参照設定を追加しておいて下さい。
using System.Xml;
以下はXMLファイルから情報を読み取るサンプルです。
SelectNodesメソッドの引数に、XMLのルートからのパス(XPath)を指定します。
そして、そこに存在する全てのノードを取得すると共に、uxDockingManager から復元先のpane(下記例ではLayoutDocumentPane)を取得し、そこにLayoutDocumentを追加していきます。
XmlDocument xml = new XmlDocument();
xml.Load(filename);
var nodes = xml.SelectNodes("/LayoutRoot/RootPanel/LayoutPanel/LayoutDocumentPane/LayoutDocument");
var xmlNodes = nodes.Cast<XmlNode>().ToArray();
var pane = uxDockingManager.Layout.Descendents().OfType<LayoutDocumentPane>().ToArray();
foreach (var node in xmlNodes)
{
pane[0].Children.Add(new LayoutDocument() { Title = node.Attributes["Title"].Value });
}
LayoutDocumentPaneやLayoutAnchorablePane は1つの画面の中に複数存在する可能性があるので、配列で pane に返されます。
サンプルでは 決め打ちで配列の先頭(pane[0]) を指定しています。
XMLファイルで復元すると、付けた名前が消える
例えば、次の様に LayoutAnchorablePane に uxAnchorablePane1 という名前を付けていて、プログラムから動的にこのpane に対してタブ(LayoutAnchorable)を追加するようにしていたとします。
<avalonDock:LayoutAnchorablePane DockWidth="150" x:Name="uxAnchorablePane1">
<avalonDock:LayoutAnchorable Title="ToolWindow1" ContentId="ToolWindow1">
<TextBox/>
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
通常はこの方法でうまくタブ追加できますが、XMLファイルからレイアウトを読み込んだ途端、機能しなくなります。
実は、ContentID で突き合わせてレイアウトを復元する際、レイアウト情報を既存のものに反映するのではなく、全く別のものに置き換わってしまいます。
つまり、Xamlの定義で事前に決めておいた名前(今回の場合、uxAnchorablePane1)がレイアウト復元と同時に消滅してしまうのです。
このため、uxAnchorablePane1でタブを追加しようとしても、存在しないため追加できない=機能しなくなるのです。
これを回避するためには、AvalonDock から動的にLayoutPanel や pane を取得し、それに対して追加するようプログラムを変える必要があります。
具体的には下記の記述で直接 AvalonDock(下記例では uxDockingManager という名前を付与) から取得できますので、これを利用します。
LayoutPanel = uxDockingManager.Layout.Descendents().OfType<LayoutPanel>().ToArray();
DocumentPane = uxDockingManager.Layout.Descendents().OfType<LayoutDocumentPane>().ToArray();
AnchorablePane = uxDockingManager.Layout.Descendents().OfType<LayoutAnchorablePane>().ToArray();
pane の内容をクリアするには
プログラム起動直後は Xaml に記述した pane やタブが表示されているので、自力で復元する場合には邪魔になることがあります。
そんな場合、次のコードで pane だけを残して、タブをクリアすることができます。
var documentPane = uxDockingManager.Layout.Descendents().OfType<LayoutDocumentPane>().ToArray();
foreach (var pane in documentPane)
{
pane.Children.Clear();
}
var anchorablePane = uxDockingManager.Layout.Descendents().OfType<LayoutAnchorablePane>().ToArray();
foreach (var pane in anchorablePane)
{
pane.Children.Clear();
}
タブが閉じたことを検知する、又は閉じれないようにする
例えば、動的にタブを追加して、そのタブに対して操作をしたいことがあります。
もし、そのタブがユーザーで閉じられた場合、以降は操作ができなくなりますよね。
閉じれないようにするのは簡単で、LayoutDocument の CanCloseプロパティに falseを設定するだけです。
<avalonDock:LayoutDocument Title="KSASデータ抽出" CanClose="false">
<local:ProductInfoControl Width="Auto"/>
</avalonDock:LayoutDocument>
では、閉じたことを検知するにはどうすればよいでしょう。
閉じたタブがLayoutDocument の場合は、一律に DockingManager の DocumentClosed イベントハンドラを使うことで簡単に検知できます。
<avalonDock:DockingManager AllowMixedOrientation="True" Grid.Row="1" Margin="0,0,0,3" DocumentClosed="DockingManager_DocumentClosed">
イベントハンドラの引数に Document.Title というプロパティがあるので、そこでタブのタイトルが取得できます。
例えば、ContentListというDictionaryを作って、タブの名前で管理するとしましょう。
DockingManager には X:Name="uxDockingManager” という名前を付けていたとします。
下記ソースは、タブが閉じられた時に、ContentList から該当のLayoutDocumentを削除する例です。
//LaoutDocumentを保持するList
public Dictionary<string, LayoutDocument> ContentList{ get; set; } = new Dictionary<string, LayoutDocument>();
//イベントハンドラを定義
uxDockingManager.DocumentClosed += (s, e) =>
{
if (ContentList.ContainsKey(e.Document.Title)) Contents.Remove(e.Document.Title);
};
一方、閉じたタブが LayoutAnchorable の場合、このイベントハンドラは反応してくれません。
LayoutAnchorable の場合、個々の Layoutanchorble 毎に、 Closed イベント を捕捉する必要があります。
具体的には、次の様に LayoutAnchorable 生成時に、Closed イベントハンドラを設定します。
var doc = new LayoutAnchorable() { Title = name,CanClose=true };
doc.Closed += (s, e) =>
{
if (Contents.ContainsKey(((LayoutAnchorable)s).Title)) Contents.Remove(((LayoutAnchorable)s).Title);
};
タブやWindowのデザインを見栄え良くするには
AvalonDockを標準のまま使う場合、タブが白色の長方形であるため、パッと見たところ殺風景に感じるかもしれません。
これは、Nuget からテーマをインストールすることで、見栄えを良くできます。
例えば、AeroTheme を選ぶと次のようになります。
VS2010Theme を選ぶと、Visual Studioライクなデザインになります。
Nuget から次のキーワードを検索すると、使えるテーマがいくつか出てきますので、よさそうなものをインストールしてください。
Avalon Dock Theme
Xceed.Products.Wpf.Toolkit.AvalonDock.Themes はいくつかのテーマがパックされているので、もし迷ったらこれをインストールすれば良いかと思います。
使い方は、Xamlに3行を加えるだけです。
<avalonDock:DockingManager.Theme>
<avalonDock:AeroTheme />
</avalonDock:DockingManager.Theme>
AeroThemeの部分を好きなテーマに変更すると、レイアウトエディタに反映されます。
3行を入れる位置は次の通りです。
<avalonDock:DockingManager x:Name="uxDockingManager" AllowMixedOrientation="True" Grid.Row="1">
<avalonDock:DockingManager.Theme>
<avalonDock:AeroTheme />
</avalonDock:DockingManager.Theme>
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel Orientation="Vertical" >
<avalonDock:LayoutPanel Orientation="Horizontal">
・・・省略・・・
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
ドッキング解除と同時にタブの中身が消去される場合
ある時、LayoutDocument のドッキング状態をマウスのドラッグ&ドロップで解除したら、そのとたんにLayoutDocumentの中身が真っ白になることがあります。
もし、Themeを設定しているなら、その部分(3行)を削除してから、再度ドッキングを解除し、現象が発生するかお試しください。
<avalonDock:DockingManager.Theme>
<avalonDock:AeroTheme />
</avalonDock:DockingManager.Theme>
もし発生しなくなったら、Themeが原因だと思われます。
この場合の解決方法は2つです。
- 問題が発生しない別の Theme を使う
- LayoutDocument をやめて LayoutAnchorable を使う
私の場合は、LayoutAnchorable にすることで解決しました。
レイアウトを入れ子にしている箇所がエラーでビルドが通らない場合
ある時、ビルドしようとしたら、いきなりXamlでエラーになることがあります。
以下のようにレイアウトを入れ子にしていた時、入れ子側のLayoutPanel に波線が表示され、「2重に定義はできない」みたいな内容のエラーが表示されます。
<avalonDock:LayoutPanel Orientation="Vertical" >
<avalonDock:LayoutPanel Orientation="Horizontal">
・・・中略・・・
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
この場合の解決方法は、一旦 Theme の設定部分を削除して、一度ビルドを通してから、再度 Theme を設定するようにしてください。
私の場合、この方法で解決しました。
どうもThemeに関する部分はバグが潜んでいそうですね。
突然、何らかの不具合が発生したら、まずThemeを疑ってみるのが良いかもしれません。
まとめ
如何でしたでしょうか。
AvalonDockはMVVMを意識して作られているので、今回の様にソースコードべた書きで使う場合100%の機能を引き出せないかもしれません。
しかし、MVVMを使わなくても、レイアウトの保存と復元、プログラムからタブの追加や削除など必要最小限のことは可能です。
ちょっと癖はありますが十分実用的なので、WindowsForm から WPFでのプログラミングに移行しようと考えている方は、是非使ってみて下さい。