WinForms でドッキングコントロール(DockPanelSuite)を扱う

WPF/AvalonDock の WinForms 版みたいなやつね。以下備忘録。コード見てね。

イメージ

f:id:sutefu7:20200219235012p:plain

f:id:sutefu7:20200219235024p:plain

サンプルコード

using System;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;

/*
 * 本体
 * nuget: DockPanelSuite
 * author: Weifen Luo
 * 
 * スキン(見た目のテーマ)
 * nuget: DockPanelSuite.ThemeVS2015 とか
 * author: Weifen Luo
 * 
 * 
 * 
 */

namespace WindowsFormsApp29
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();


            //Test1();
            //Test2();
            Test3();
        }

        private void Test1()
        {
            // DockPanel コンテナーコントロールを配置
            var dockPanel1 = new DockPanel();
            dockPanel1.ShowDocumentIcon = true;
            dockPanel1.Dock = DockStyle.Fill;
            dockPanel1.DocumentStyle = DocumentStyle.DockingWindow;
            Controls.Add(dockPanel1);

            // DockPanelの中に、子コントロールの DockContent コントロールを配置
            // Form2 の継承元クラスを DockContent に変えて、(一応いったんリビルドした後、デザイナー開いて)Button を配置している
            // Form2 : WeifenLuo.WinFormsUI.Docking.DockContent

            // 左ペイン
            var left1 = new Form2();
            left1.TabText = nameof(left1);
            left1.Show(dockPanel1, DockState.DockLeft);
            
            // 右ペイン
            var right1 = new Form2();
            right1.TabText = nameof(right1);
            right1.Show(dockPanel1, DockState.DockRight);

            // 下ペイン
            var bottom1 = new Form2();
            bottom1.TabText = nameof(bottom1);
            bottom1.Show(dockPanel1, DockState.DockBottom);

            // ドキュメントペイン
            var center1 = new Form2();
            center1.TabText = nameof(center1);
            center1.Show(dockPanel1, DockState.Document);

            var center2 = new Form2();
            center2.TabText = nameof(center2);
            center2.Show(dockPanel1, DockState.Document);


            // 下側ペインよりも左側ペインを手前に表示したい問題の対応
            dockPanel1.UpdateDockWindowZOrder(DockStyle.Left, true);
        }

        private void Test2()
        {
            // DockPanel コンテナーコントロールを配置
            var dockPanel1 = new DockPanel();
            dockPanel1.ShowDocumentIcon = true;
            dockPanel1.Dock = DockStyle.Fill;
            dockPanel1.DocumentStyle = DocumentStyle.DockingWindow;
            Controls.Add(dockPanel1);

            // DockPanelの中に、子コントロールの DockContent コントロールを配置
            // Form2 の継承元クラスを DockContent に変えて、(一応いったんリビルドした後、デザイナー開いて)Button を配置している
            // Form2 : WeifenLuo.WinFormsUI.Docking.DockContent

            // 左ペイン
            var left1 = new Form2();
            left1.TabText = nameof(left1);
            left1.Show(dockPanel1, DockState.DockLeft);

            // 指定ペインの前に挿入
            var left2 = new Form2();
            left2.TabText = nameof(left2);
            left2.Show(left1.Pane, left1);

            var left3 = new Form2();
            left3.TabText = nameof(left3);
            left3.Show(left2.Pane, DockAlignment.Bottom, 0.5);

            var left4 = new Form2();
            left4.TabText = nameof(left4);
            left4.Show(dockPanel1, DockState.DockLeft);



            // 右ペイン
            var right1 = new Form2();
            right1.TabText = nameof(right1);
            right1.Show(dockPanel1, DockState.DockRight);

            var right2 = new Form2();
            right2.TabText = nameof(right2);
            right2.Show(dockPanel1, DockState.DockRight);



            // 下ペイン
            var bottom1 = new Form2();
            bottom1.TabText = nameof(bottom1);
            bottom1.Show(dockPanel1, DockState.DockBottom);

            var bottom2 = new Form2();
            bottom2.TabText = nameof(bottom2);
            bottom2.Show(dockPanel1, DockState.DockBottom);



            // ドキュメントペイン
            var center1 = new Form2();
            center1.TabText = nameof(center1);
            center1.Show(dockPanel1, DockState.Document);

            var center2 = new Form2();
            center2.TabText = nameof(center2);
            center2.Show(dockPanel1, DockState.Document);


            // 下側ペインよりも左側ペインを手前に表示したい問題の対応
            dockPanel1.UpdateDockWindowZOrder(DockStyle.Left, true);



            // 多分、各画面同士で、データのやり取りがあると思う、その操作方法
            // シングルトンクラスとかで、あちこちから呼び出せれば楽かな?
            // 上下左右のペイン
            //var items1a = dockPanel1.DockWindows;
            //var items1b = dockPanel1.DockWindows.OfType<DockWindow>().ToList();
            //var items1c = dockPanel1.DockWindows.OfType<Form2>().ToList();

            // 上下左右、ドキュメントの全ペイン1
            var items1d = dockPanel1.DockWindows.OfType<DockWindow>()
                .SelectMany(x => x.Controls.OfType<DockPane>())
                .SelectMany(x => x.Controls.OfType<Form2>())
                .Select(x => x.TabText)
                .ToList();

            // 上下左右、ドキュメントの全ペイン2
            var items1e = dockPanel1.DockWindows.OfType<DockWindow>()
                .SelectMany(x => x.NestedPanes.OfType<DockPane>())
                .SelectMany(x => x.Contents.OfType<Form2>())
                .Select(x => x.TabText)
                .ToList();

            // 上下左右のペイン
            var items1f = dockPanel1.DockWindows.OfType<DockWindow>()
                .SelectMany(x => x.NestedPanes.OfType<DockPane>())
                .SelectMany(x => x.Contents.OfType<Form2>())
                .Where(x => x.DockState != DockState.Document)
                .Select(x => x.TabText)
                .ToList();



            // 独立画面中のフロートペイン
            //var items2a = dockPanel1.FloatWindows;
            var items2b = dockPanel1.FloatWindows.OfType<FloatWindow>()
                .SelectMany(x => x.Controls.OfType<DockPane>())
                .SelectMany(x => x.Controls.OfType<Form2>())
                .Select(x => x.TabText)
                .ToList();



            // 中央のドキュメントペイン
            //var items3a = dockPanel1.Documents;
            var items3b = dockPanel1.Documents.OfType<DockContent>().ToList();
            var items3c = dockPanel1.Documents.OfType<Form2>().ToList();
            var items3d = dockPanel1.Documents.OfType<DockContent>()
                .Select(x => x.TabText)
                .ToList();

            Console.WriteLine("");
        }

        private void Test3()
        {
            // DockPanel コンテナーコントロールを配置
            var dockPanel1 = new DockPanel();
            dockPanel1.ShowDocumentIcon = true;
            dockPanel1.Dock = DockStyle.Fill;
            dockPanel1.DocumentStyle = DocumentStyle.DockingWindow;

            // 下ペインよりも左ペインを手前にする
            dockPanel1.UpdateDockWindowZOrder(DockStyle.Left, true);

            // 右ペイン幅を固定幅にする(小数点の場合はパーセント扱い。整数の場合は固定値)
            dockPanel1.DockRightPortion = 250;

            //dockPanel1.Theme = new VS2015LightTheme();
            dockPanel1.Theme = new VS2015BlueTheme();

            Controls.Add(dockPanel1);

            // DockPanelの中に、子コントロールの DockContent コントロールを配置
            // Form2 の継承元クラスを DockContent に変えて、(一応いったんリビルドした後、デザイナー開いて)Button を配置している
            // Form2 : WeifenLuo.WinFormsUI.Docking.DockContent

            // 左ペイン
            var left1 = new Form2();
            left1.TabText = nameof(left1);
            left1.Show(dockPanel1, DockState.DockLeft);

            // 右ペイン
            var right1 = new Form2();
            right1.TabText = nameof(right1);
            right1.Show(dockPanel1, DockState.DockRight);

            // 下ペイン
            var bottom1 = new Form2();
            bottom1.TabText = nameof(bottom1);
            bottom1.Show(dockPanel1, DockState.DockBottom);

            // ドキュメントペイン
            var center1 = new Form2();
            center1.TabText = nameof(center1);
            center1.Show(dockPanel1, DockState.Document);

            var center2 = new Form2();
            center2.TabText = nameof(center2);
            center2.Show(dockPanel1, DockState.Document);


            // 下側ペインよりも左側ペインを手前に表示したい問題の対応
            dockPanel1.UpdateDockWindowZOrder(DockStyle.Left, true);
        }

    }
}

その他

・階層関係

DockPanel(コンテナーコントロール)
  + DockWindows プロパティ(DockWindowCollection / ReadOnlyCollection<DockWindow>)
      + DockWindow.NestedPanes プロパティ(NestedPaneCollection / ReadOnlyCollection<DockPane>)
          + DockPane.Contents プロパティ(DockContentCollection / ReadOnlyCollection<IDockContent>)

  + Documents プロパティ(IEnumerable<IDockContent>)

  + FloatWindows プロパティ(FloatWindowCollection / ReadOnlyCollection<FloatWindow>)
      + FloatWindow.NestedPanes プロパティ(NestedPaneCollection / ReadOnlyCollection<DockPane>)
          + DockPane.Contents プロパティ(DockContentCollection / ReadOnlyCollection<IDockContent>)

つまり、

DockWindow / FloatWindow
  + DockPane
      + IDockContent

直接扱うのは、DockPanel コントロールと DockContent コントロールを継承した画面だが、各画面同士でやり取りする際は継承関係を知る必要あるかも。他にもたくさんプロパティやメソッドがあるが、とりあえずは基本の上記。

・各画面(DockContent 継承先の画面)の管理 DockContent.TextよりもDockContent.TabTextの方が表示的に優先されるみたい。

インスタンス生成時、DockContent.HideOnClose = trueしておいて、閉じてもインスタンス破棄されないようにしておく。そうすると閉じた際、DockContent.IsHidden = trueになるので、DockContent.Show()DockContent.Activate()を呼び出す。

・(2020/03/22追記)ShowDialog() しているDockPanel を使った画面を閉じた際、呼び出し元画面が不安定になる現象の対応

画面を閉じる際に(FormClosing/FormClosed)、自前でペインを閉じたら直った。

// 例えばドキュメントペイン。他にもサイドのペインも。
var panes = dockPanel1.Documents.OfType<DockContent>().ToList();

for (var i = panes.Count - 1; i >= 0; i--)
{
    panes[i].HideOnClose = false;
    panes[i].Close()
}