DockPanel SuiteでネストされたドッキングのProportionの自動設定

DockPanel Suiteでは、ネストドッキングをしたときに縦幅・横幅が自動調整されません。この自動調整(または動的に設定する方法)を考えたらとんでもなく面倒だったのでメモがてらに。

☆考える例
適当なドッキングのPaneに対して(DockLeft、DockRight等…)、自動調整したときの高さ(これはLeft, Rightの場合。Top, Bottomの場合は幅に読み替えてください)が100px, 200px, 300pxのウィンドウをドッキングし、高さを動的に調整したい。

☆ウィンドウの本当のサイズの取得
●問題点
通常、ウィンドウやフォームの高さを取りたいときは、DockContentのオブジェクトをdockContentとしたときに、dockContent.Size.HeightやdockContent.ClientSize.Heightなどのプロパティ参照する。いずれもSystem.Windows.Forms.Controlで実装されているプロパティなので、DockPanel Suiteオリジナルのプロパティではない(DockContentはFormを継承したものであるので)。
ところが、このプロパティでサイズを取ると、初期化時に適切なフォームサイズに変更しても、ドッキング時にドッキング相手のPaneのサイズにフォームサイズがリサイズされてしまう。例えば、本来なら100×100のサイズに自動調整されるべきで自動調整したはずのウィンドウが、200×500のPaneにドッキングした際に、200×500(ぐらいの)サイズに変更されてしまっているのだ。つまり、dockContent.Size.Heightで取った値は、初期化終了時には100であっても、ドッキング後には500に変更されてしまい値が保持されない。そのため、自動調整で用いるサイズを保持するサイズを返すプロパティをこちら側で実装する必要がある。

●解決例
あくまで下手っぴが書いた例。もっとうまいやり方があるかもしれない。

(1) 自動調整で用いるサイズを返すプロパティ(ここではこれをRealWidth, RealHeightとする)を実装するインターフェイスを用意する。仮にこのインターフェイス名をIMyDockWindowとする。
(2) 実装したいDockContentを継承する全てのフォームに対して、IMyDockWindowを継承させる(DockContentを継承したフォームにRealWidth, RealHeightを実装した自前のクラスを作って、そのクラスを継承させて個々のフォームを作るというのも良いかもしれないが試していない)
(3) ドッキングされているContentにアクセスする際に、オブジェクトをIMyDockWindowでキャストして、RealWidth, RealHeightの値を取り出して計算する。

さらにRealWidth, RealHeightの計算を容易にするために次のようにする。

(1) フォームに格納するコントロールを、一度ひとつのユーザーコントロールに格納しグループ化する。
(2) ウィンドウのメニューバーのサイズ(RealHeightを計算する際に必要)は、DockPanel Suiteから取得する方法がないので、スクリーンショットを撮って実測するなり、ライブラリのソースからぶっこ抜くなりして、定数項として記録しておく

ユーザーコントロールとしてグループ化しておけば、多数にわたるコントロールのサイズ計算もWindows.Forms側で勝手にやってくれるので、サイズ計算を省略したい場合に有効。ドッキングウィンドウ側から個々のコントロールにアクセスするのがちょっとめんどくなるけどね…。

このとき、RealWidth, RealHeightのフォーム側での実装はこのようになる。
    public partial class Form2 : DockContent, IMyDockWindow
{
//メニューバーの高さ
public static readonly int MenubarHeight = 21;

public Form2()
{
InitializeComponent();
}

public int RealHeight
{
get
{
return MenubarHeight + userControl11.ClientSize.Height;
}
}

public int RealWidth
{
get
{
return userControl11.ClientSize.Width;
}
}

}

public interface IMyDockWindow
{
int RealWidth { get; }
int RealHeight { get; }
}

ユーザーコントロールのLocationは0,0にあわせて、Dock(ライブラリ側のプロパティではなくForm側のプロパティ)はNoneにすればOK。本当はスプリッターのサイズとかも足さないといけないけどめんどいんで無視。

ここまででやっと自動調整のサイズを取れるようになったので、次はこのピクセルをどのようにProportionに変換するか。めんどくささはサイズの取得が4割ぐらい、Proportion変換が6割ぐらいなので、むしろここから先がめんどくさい。

☆Proportionへの変換
●Proportionの仕組み
ネストされたドッキングに対してウィンドウのサイズを設定する場合は次のようにする。
            foreach(DockContent x in dockPanel1.Contents)
{
x.Pane.SetNestedDockingProportion(0.2);
}

この場合、1つ目のウィンドウが全体の8割の高さになり、2つ目のウィンドウが全体の残り2割のうちの8割(16%)の高さとなり、3つ目がその残り2割の8割……となる。また、少し意地悪にコレクションへのアクセスを、
            for (int i = dockPanel1.Contents.Count - 1; i >= 0; i-- )
{
DockContent x = (DockContent)dockPanel1.Contents[i];
x.Pane.SetNestedDockingProportion(0.2);
}

このように逆順に行っても、同じ結果が得られる。また、Showメソッドでネストドッキングする際に、Proportionを設定して
            f0.Show(dockPanel1, DockState.DockLeft);
f1.Show(f21.Pane, DockAlignment.Bottom, 0.2);
f2.Show(f22.Pane, DockAlignment.Bottom, 0.2);

とやっても同じ結果になる。
SetNestedDockingProportionの値はスプリッターの位置をPanel全体の下を基準に設定しているので(これはDockAlignmentに依存?)、1-そのウィンドウの高さの比率で計算しないといけない。このへんからしてもういやな予感がするけど、加えて、この比率はPane全体の比率ではなく、それ以下のネストされているウィンドウ中での相対比であるので、確率でいうところの条件付き確率のような計算をしないといけない。また、ネストドッキングの場合のProportionは、DockPanelのDockLeftPortion(Left, Right, Top等…)と異なり、2以上の値を設定してもピクセル数で絶対固定はされないため、固定したくても相対比に変換しないといけない

●計算例(Paneに隙間がない場合)
最初に提示した、
 1つ目のウィンドウ(f0とする) : 100px
 2つ目のウィンドウ(f1とする) : 200px
 3つ目のウィンドウ(f2とする) : 300px
これらをDockLeftにネストドッキングしたときの、Proportionの値の計算方法を考える。答えから先に言ってしまうと次のように計算する。

d0 = 0
d1 = 100 / (100 + 200 + 300) = 1/6
d2 = 200 / (200 + 300) = 2/5
d3 = 300 / 300 = 1
-------------------
p0 = 1 - d0 = 1
p1 = 1 - d1 = 5/6
p2 = 1 - d2 = 3/5

ここでのp0, p1, p2がSetNestedDockingProportionやShowメソッドで設定するProportionの値である。一般的に書くなら、

pi = 1 - di
di = 0 (i = 0)
= h_(i-1) / {h_(i-1) + h_i + …… + h_n } (1 <= i <= n+1)

※hi = i番目のウィンドウのRealHeight

ただ、これはDockLeftの縦幅がドッキングしているウィンドウの縦幅の合計(h0 + … + hn)に一致する場合のみ。余りが出た場合は、このままの計算だとウィンドウとウィンドウの間がすかすかになる。↓イメージ:こんな感じ
2015-01-13 021113

●計算例(Paneに隙間がある場合)
より一般的な例として、ウィンドウのRealHeightの合計と、Paneの縦幅が一致しない場合にどういうProportion計算をするのか。あるいは、このような条件を満たすProportion計算でも、隙間がない場合の整合性を崩さないようにするにはどういう式を立てればいいのかを考える。
これは実は簡単で、隙間を1つのウィンドウとして考えれば隙間がない場合の式に適用することができて一件落着する。隙間の縦幅をR、Pane全体の縦幅をYとすると、

pi = 1 - di
di = 0 (i = 0)
= h_(i-1) / {h_(i-1) + h_i + …… + h_n + R} (1 <= i <= n+1)
R = max{Y - (h0 + … + hn), 0}


このような式でOKだ。隙間がない場合はR=0になるので、整合性を崩さない。これを先ほどの隙間ができてしまった例も、
2015-01-13 025708
このようにすっきりする。うまく簡単な例で表現できないが、これを実装しようとしているほっぽアルファではこの部分のコードは次のようになっている。
2015-01-13 160635


ここらへんを自前で実装するのはしんどすぎるんで、もっと簡単にできるライブラリ欲しいなと思った(小並感)
スポンサーサイト
プロフィール

こしあん

Author:こしあん
(:3[____]
【TwitterID : koshian2】
【ほしい物リスト】http://goo.gl/bDtvG2

Twitter
カウンター
天気予報

-天気予報コム- -FC2-
カテゴリ
月別アーカイブ
最新記事
最新トラックバック
検索フォーム
リンク