WPFレイアウト:測定と配置





WPFレイアウトシステムの概要は、msdn( 1、2 )から取得できます。 コントロールはビジュアルツリーを形成し、各コントロールには独自の四角形があり、その中に描画されること、これらの四角形の定義はレイアウトシステムに割り当てられ、2段階(測定と配置)で実行され、WPFは通常の即時とは異なり、このモードの利点は何ですか?



ただし、msdnを読むと、ドキュメントで回答されていない多くの質問が発生し、何が起こっているのかを推測することしかできません。 たとえば、メジャーステージの子コントロールが、渡されたavailableSizeを超えるサイズを自身で要求した場合はどうなりますか? または-必要に応じて、 MeasureOverrideメソッドArrangeOverrideメソッドを正しく実装して、記述されたコードがMeasureおよびArrangeステップの実行方法に関する受け入れられている規則と矛盾しないようにする方法 Measureステージで得られた結果はArrangeステージとレンダリングに影響しますか、それともArrange呼び出しのみがレンダリングに影響し純粋に情報ステージを測定しますか?



舞台裏で何が起こっているのかをさらに詳しく理解してみましょう。



保持モードシステム




まず、WPFである保持モードのグラフィックシステムを思い出しましょう。 これは、グラフィックスを描画するためのアプローチであり、再描画が必要な領域を特定し、この再描画を実行する責任がグラフィックスシステムに移されます。 つまり、必要に応じてすべてのウィンドウとコントロールが再描画されることを保証するのはWPFです。 プログラマは、Win32 APIやWindowsFormsの場合のようにWM_PAINTなどのイベントの処理を心配しなくなりましたが、必要に応じてコントロールのレンダリングメソッドを設定するだけで、WPFはいつ、どのピースを再検証する必要があるかを判断します。 プログラマーは、次のようになります。再描画が必要な領域を定義し WM_PAINTメッセージが到着する たびにこの手順を実行する代わりに、プログラマーはコントロールを描画するために必要なコマンドのシーケンスを一度示します。 これは、 OnRenderメソッドで行われます。 OnRenderメソッド 、WPFシステムによって呼び出されます。





画像



OnRenderメソッドDrawingContext引数を使用して呼び出されます。この引数では、 DrawEllipseタイプなどの描画メソッドを取得します 。 伝えられるところによると、実際にはレンダリングは行われず、すべての呼び出しがコマンドキューに追加され、コントロールを再描画する必要があるとWPFが判断したときに使用されます。



実際、ここに共通の質問への答えがあります-何かが書かれたボタンがあり、このボタンのテキストを変更するとしましょう-しかし、どの時点でどのようにそれ自体を再配置して再描画する必要があると判断しますか? 結局、プロパティの値を変更しただけです。 また、次のことが起こります。ボタンテキストはレンダリングに影響し、対応するフラグでマークされます。したがって、このプロパティの値を変更すると、ボタンがダーティ、つまり再描画が必要なものとしてマークされます。 その後すぐに、WPFはレンダリングの更新を行います。 これはすぐには発生しませんが、WPFがこれを実行する時間がある場合に限ります。 つまり、置換の直後にRenderSizeを取得した場合、変更されません。 しかし、 UpdateLayout()メソッドを使用してマークアップを強制的に更新すると、 RenderSizeは変更されたテキストに対応するものになります。 ちなみに、 Dispatcher.Invokeの優先度もこのメカニズムに関連付けられています-Priority.Renderは利用可能な優先度の1つです。つまり、この優先度のデリゲートが呼び出されると、レンダリング要素に対して実行される手順と共に実行されます。



測定中




なぜこれがそんなに重要なのですか? 手順MeasureおよびArrangeは、要素の配置が発生するため、同様に機能します。 これらは一度呼び出され、 コントロールに MeasureIsValidフラグとArrangeIsValidフラグが設定されます。 この後、 MeasureArrangeの呼び出しはすぐに制御を返し、何もしません。 コントロールにこれらの事柄を再カウントさせるには、 AffectsMeasure / AffectsArrangeフラグを使用してDependencyPropertyを再度変更するか、InvalidateMeasure / InvalidateArrangeを呼び出してフラグを明示的にリセットする必要があります。 また、ドキュメントでは、 Measureの再検証はArrangeの再検証を自動的に伴うと述べていますが、これはすでに明らかです。 一般的に、最初の結論は次のとおりです。コントロールに、変更時に子コントロールのサイズや配置を変更できる特定のプロパティがある場合、 DependencyPropertyにしてAffectsMeasure / AffectsArrangeフラグを設定する必要があります。 このプロパティの値のすべての変更がコントロールの再検証を必要とするわけではない場合、これを行うのではなく、指定されたvalueChangedCallbackを使用してDependencyPropertyを実行します。



次に、ソフトウェア設計の観点からこれら2つの方法を検討します。 つまり、責任範囲と入力/出力データは何ですか。 これはおそらく、WPFレイアウトシステムの動作を理解する上で最も重要なことです。 個人的にこのトピックを味わうのに多くの時間がかかりました。 WPFのソースコードは入手可能であるため、ソースコードを詳しく調べる必要さえありました。



測定と配置




Measureのドキュメントは、これが単なる情報提供段階であり、表示に影響を与えないように書かれています。 すべてが論理的なようです-ペアレンタルコントロールは子供をポーリングし、子供からどれだけのスペースを占有したいかを判断し、誰にどのくらいのスペースを遮断するかを具体的に決定し、 Arrangeを呼び出します。 一般に、PresentationCoreレベルではこれが当てはまります(UIElementには空の仮想メソッドMeasureCoreとArrangeCoreが含まれます)が、より具体的な動作、FrameworkElementとその子孫の動作に興味があり、FrameworkElementはPresentationFrameworkアセンブリで既に定義されています。



私はできる限り明確かつ正確に定式化しようとします。 誤りや誤りがある場合は、修正し、変更します。



public void Measure(Size availableSize) -availableSizeを指定して、目的のサイズを決定し、this.DesiredSizeに設定するメソッド。 メソッドの説明では、結果のDesiredSizeがavailableSizeになる可能性があると記述されていますが、FrameworkElementの子孫には当てはまりません。

Measureメソッドの本質は、次のことを行うことです。

  1. すべてのVisualChild要素に対してMeasureを再帰的に呼び出します(そうしないと、IsMeasureValidフラグが設定されず、子要素を描画できません)。 メジャーは少なくとも1回呼び出す必要があります。 Measure繰り返し呼び出すことができます(たとえば、最初に引数Size = double.PositiveInfinityで Measureを呼び出してコントロールのフルサイズを決定できます) 。Measureへの最後の呼び出しは、この子コントロールを描画するために実際に使用されるディメンションで行う必要があります。
  2. availableSizeの寸法に収まるように、コントロールの状態を準備します。 これが、 Measureの最後の呼び出しが要素の実際の寸法を決定する理由です。 その理由は、コントロールがDesiredSizeをavailableSizeを超える値に設定すると、固定サイズのグリッドセルが何をすべきか分からなくなるためです。 サイズは固定されているようですが、子供はテーブルで拳を叩いているので、もっと欲しいです! したがって、availableSizeが「ソフト制約」であるというドキュメントフレーズは、 FrameworkElementのコンテキストでは厳密に言えば正しくありません。




2番目のポイントは非常に重要です。 実際、 測定段階では、描画の準備が行われます。 たとえば、 Measureを呼び出すときのリストボックスは、ディメンションに収まらないことがわかっている場合、スクロールバーが表示されると判断します。 そして、より大きなサイズでArrangeを呼び出しても、スクロールバーは残ります。 逆に、 MeasurePositiveInfinity 、およびArrangeで実行された場合、実際のディメンションでは、arrangeRectを超えるすべてのものが単純に切断されます。



ところで、なぜFrameworkElementなのか? 光はそれに収束しましたか? 実際に収束しており、 FrameworkElementは UIElement.MeasureCoreUIElement.ArrangeCoreをseal修飾子でオーバーライドします。つまり、FrameworkElementのすべての子孫(すべてのコントロール、ウィンドウなど)は、必要に応じてMeasureCoreとArrangeCoreの動作を変更できません。 彼らは願い事だけを残すことができます-このためにMeasureOverrideとArrangeOverrideメソッドが作成されます。 そして今、 MeasureOverrideでは availableSizeは実際にはソフト制約であり、引数の値を超える値を非常に合法的に返すことができます。



public void Arrange(Rect finalRect) -コントロールにその位置(x、y)と長方形のサイズを伝えるだけです。 これらの制限を超えるものはすべて割礼されます。



MeasureArrangeには次の関係があります-理想的には、 Measureの最後の呼び出しは、 Arrangeの次の呼び出しのサイズと一致するSizeを取る必要があります。 その場合、コントロールは完全に描画されます。 条件が満たされない場合、問題が発生する可能性があります。 つまり、すべてが正しくレンダリングされる可能性があり、完全ではない可能性があります。 たとえば、この状況では、リストボックス(arrangeSize <measureSizeの場合)はnuraを右に移動し(スクロールバーは境界線に沿って左にスライドしますが、トリミングされません)、下部はトリミングされます。



ここで、メソッドMeasureOverrideおよびArrangeOverrideを検討する必要があります



MeasureOverrideおよびArrangeOverride




保護された仮想サイズMeasureOverride(Size availableSize)は 、開発者が独自の配置ロジックで独自のコントロールパネルを作成できるように設計されています。 通常、 MeasureOverrideアルゴリズムは次の手順を実行する必要があります。

  1. パラメータSize.WidthおよびSize.Height = double.PositiveInfinityを使用してMeasureを再帰的に呼び出して、子のフルサイズを推定します。
  2. 子供のサイズに応じて自分のフルサイズを評価する
  3. availableSizeに入ると、独自のフルサイズの値を返します
  4. そうでない場合、割り当てられたavailableSizeに収まるように、PositiveInfinityではなく特定の値を使用して、子で再度Measureを呼び出す必要があります。 このステップの具体的な実装は、実装する配置ロジックによって異なります。
  5. availableSize内、またはavailableSizeを超える最小値に収まる場合は、availableSizeをDesiredSizeとして返します。これにより、コントロールを完全にレンダリングできます。


protected virtual Size ArrangeOverride(Size finalSize) -ここでは、対応する境界と位置を持つ各子要素に対してArrangeメソッドを呼び出すだけです。



availableSizeより大きい値をMeasureOverride 返すことができることに注意してください! ただし、これを実行してDesiredSizeコントロールを確認すると、 DesiredSize = availableSizeであることに驚かされます。 つまり、誰かが結果を無視して、 Measure引数の値をそこに書きました。 ただし、 ArrangeOverrideが引数としてさらに呼び出されると、再び魔法のように値を取得します。これはMeasureOverrideから返されました



何が起こっているの? そして、これが起こることです。



FrameworkElement.Measureが無限の定数で呼び出された場合、 MeasureOverrideが返すものに関係なく、FrameworkElement.MeasureCoreはそれを切り取り、DesiredSize <= availableSizeを設定します。 そして、DesiredSizeは自宅でキャッシュし、その後ArrangeOverrideでそれを渡します。 これは、FrameworkElementは、Measureを呼び出すときに、コンテンツをトリミングする必要がある場合でも、Measureを呼び出すときに割り当てられたピースに収まるようにするためです。



そうしないと、特定の幅/高さの値を持つグリッドセルが、 DesiredSize > availableSizeを返したコントロール上を回ります。 そして、 FrameworkElementはDesiredSizeの実際の要件を保持しており、 Arrangeの時間が来ると、 MeasureOverrideに返されたDesiredSize値を使用してArrangeOverrideメソッドを呼び出します。 そして、ArrangeOverrideでは、必要に応じて子を配置します。

その後、 ArrangeOverrideが呼び出されるコンテキストでFrameworkElement.ArrangeCoreがコンテンツをクリップし、コントロールの一部がグリッドに表示されます。 どの部分がHorizo​​ntal / VerticalAlignmentなどのプロパティに依存しますが、 DesiredSizeが保存されてArrangeOverrideに転送されたため、コンテンツは必要に応じてほぼ完全にレンダリングされます。



そして、 FrameworkElementの相続人を実装するとき、そのような状況でクリッピングを心配する必要はありません-彼は私たちのためにすべてを行います。 そして、 DesiredSize > availableSizeを処理するパネルが必要な場合は、何か間違ったことをしているか、 MeasureCoreArrangeCoreをファイナライズ(シール)しないUIElementに1レベル下がる必要があります



これをすべてテストするために、 Button継承を作成し、固定サイズをMeasureOverrideに返し 、ボタンをより小さいサイズ( 50x50など)のグリッドセルに配置できます。 ボタンがトリミングされます。



protected override Size MeasureOverride(Size constraint) {

return new Size(80, 80);

}








この機能については、 http://social.msdn.microsoft.com/で説明されています。



結論として、説明した資料に関連するWPFソース(UIElementおよびFrameworkElementクラス)のメソッドにコードを追加したいと思います。 エッセンスを詳細に詳述したコメント付きの作品。



WPFのソースコード全体は、ここからダウンロードできます (.Net-4をダウンロード)。



UPD: afshermanは、WPFソースがオプションであることを示します。 ReSharperをインストールしたユーザーは、組み込みオプションを使用して、Ctrlキーを押しながらクラス/メソッド/プロパティなどの名前をクリックしてソースをダウンロードできます。



All Articles