1. ホーム
  2. c#

[解決済み] WPFで画像のグリッドを描画する

2022-02-27 04:39:41

質問

WPFで画像やアイコンのグリッドを描画しようとしています。グリッドの寸法は様々ですが、通常10x10~200x200の範囲になると思います。ユーザーはセルをクリックすることができ、いくつかのセルは1秒間に10-20回更新(画像を変更)する必要があります。グリッドは4方向に拡大・縮小でき、それが表す3次元構造の別のquot;slice"に切り替えられる必要があります。私の目標は、これらの要件を満たした上で、グリッドを描画するための適切な効率的な方法を見つけることです。

現在の私の実装では、WPFの Grid . 実行時に行と列の定義を生成し、グリッドの入力に Line (グリッドライン用)と Border (セルは現在オン/オフだけなので) オブジェクトを適切な行/列に配置してください。(この Line オブジェクトは横方向に広がっています)。

<イグ

グリッドの拡大中(Num6を押しながら)、操作のたびに再描画するには描画が遅すぎることがわかったので、単純に新規に ColumnDefinition , Line のセットと Border オブジェクトを、成長の各列に対して作成しました。これによって成長の問題が解決されましたが、同じような戦術で縮小も高速に行うことができます。シミュレーションの途中で個々のセルを更新するには、単にセルオブジェクトへの参照を保存して、表示される画像を変更すればよいのです。新しいZレベルに変更する場合でも、グリッド全体を再構築するのではなく、セルの内容のみを更新することで改善されます。

しかし、こうした最適化を行う前に、別の問題が発生しました。グリッドにマウスオーバーすると(低速/通常速度でも)、アプリケーションのCPU使用率が急上昇するのです。グリッドの子要素のイベントハンドラをすべて削除してみましたが、効果はありませんでした。最終的に、CPU使用率を抑制する唯一の方法は IsHitTestVisible = false に対して Grid . (のすべての子要素に設定します)。 Grid は何もしなかった!)

私は、グリッドを構築するために個々のコントロールを使用することは集中的でこのアプリケーションには不適切であり、WPFの2D描画機構を使用することがより効率的であると信じています。しかし、私はWPFの初心者なので、これを達成するための最善の方法についてアドバイスを求めています。私が読んだ範囲では、私は DrawingGroup を使い、各セルの画像を1枚の画像に合成して表示します。そして、画像全体のクリックイベントハンドラーを使い、クリックされたセルの座標をマウスの位置で計算することができます。でも、これって面倒くさいし、もっといい方法がないかなぁ。

いかがでしょうか?

アップデート1:

友人のアドバイスで、"Space "を使用するように変更しました。 Canvas を使用し Rectangle を各セルのために使用します。最初にグリッドを描画するときに、すべての Rectangle を2次元配列に格納しておき、グリッドの内容を更新するときに、単純にその参照にアクセスするだけでよい。

private void UpdateGrid()
{
    for (int x = simGrid.Bounds.Lower.X; x <= simGrid.Bounds.Upper.X; x++)
    {
        for (int y = simGrid.Bounds.Lower.Y; y <= simGrid.Bounds.Upper.Y; y++)
        {
            CellRectangles[x, y].Fill = simGrid[x, y, ZLevel] ? Brushes.Yellow : Brushes.White;
        }
    }
}

グリッドの初期描画は高速化され、その後の更新も確実に高速化されているように見えますが、まだいくつかの問題が残っています。

  • マウスオーバーする領域がどんなに小さくても、グリッドが数百セル以上になると、マウスオーバーするたびにCPU使用率が急上昇します。

  • 更新がまだ遅すぎるため、上矢印キーを押しながらZレベルを変更すると(よくある使用例)、プログラムが数秒間フリーズし、その後一度に50Zレベルにジャンプするように見えます。

  • グリッドが5000セル程度になると、更新に1秒程度の時間がかかるようになります。これは法外に遅く、5000セルが典型的なユースケースに収まります。

をまだ試していません。 UniformGrid というのは、私がすでに遭遇したのと同じ問題が発生する可能性があると思うからです。しかし、もう少しオプションを使い切ったら、試してみるかもしれません。

どのように解決するのか?

ご質問の内容

質問を言い換えましょう。 これらは、あなたの問題の制約です。

  1. 動的な大きさのグリッドを描画したい
  2. 各セルのオン/オフが高速に変化
  3. グリッドサイズの高速変更
  4. セル数が多い(=グリッドの寸法が些細なものではない)
  5. 高速フレームレート(例:30fps)でこれらすべての変化を発生させたい場合
  6. グリッドとセルの位置とレイアウトは決定論的で、単純で、あまりインタラクティブではない

これらの制約から判断すると、間違ったアプローチをしていることがすぐにわかるでしょう。

Reqruiement。決定論的ポジションの高速リフレッシュと少ないインタラクション

高速更新フレームレート + フレームあたりの変更回数が多い + セル数が多い + セルごとにWPFオブジェクトが1つ = 不具合。

非常に高速なグラフィックハードウェアと非常に高速なCPUを持っていない限り、グリッドの寸法が大きくなると、フレームレートは常に低下します。

あなたの問題は、ビデオゲームやダイナミックズームを使用したCAD製図プログラムに近いものです。 通常のデスクトップアプリケーションのようなものではありません。

イミディエイトモードとリテインドモードの図面

つまり、quot;immediate mode"での描画が必要で、quot;retained mode"での描画は必要ない(WPFはretained mode)、ということです。 これは、あなたの制約が、各セルを個別のWPFオブジェクトとして扱うことで提供される機能をあまり必要としないからです。

例えば、各セルの位置は決定論的であるため、レイアウトのサポートは必要ないでしょう。 また、ヒットテストのサポートも必要ありません。 各セルは単純な矩形(または画像)なので、コンテナのサポートは必要ありません。 重なり合うものがないので、複雑な書式設定(透明度、丸みを帯びた枠線など)のサポートも不要です。 言い換えれば、Grid(またはUniformGrid)を使用して、セルごとに1つのWPFオブジェクトを使用するメリットはないのです。

バッファビットマップへのイミディエイトモード描画の考え方

必要なフレームレートを実現するために、基本的には大きなビットマップ(画面全体をカバーするもの)、つまりスクリーンバッファに描画することになります。 セルについては、このビットマップ/バッファに描画するだけです (おそらく GDI を使用します)。 セルの位置はすべて決定論的であるため、ヒットテストは簡単です。

この方法は、オブジェクトが1つ(スクリーンバッファのビットマップ)しかないため、高速に処理することができます。 フレームごとにビットマップ全体を更新するか、変化した画面位置のみを更新するか、あるいはこれらのインテリジェントな組み合わせが可能です。

ここではグリッドを描いていますが、グリッドエレメントを使っていないことに注意してください。 アルゴリズムとデータ構造は、問題の制約条件に基づいて選択します。 見た目 言い換えれば、quot;Grid"はquot;grid"を描くための正しい解決策ではないかもしれないのです。

WPFのイミディエイトモード描画

WPFはDirectXをベースにしているので、基本的にはすでにスクリーンバッファのビットマップ(バックバッファと呼ばれます)を裏で使っています。

WFPでイミディエイトモード描画を使うには、セルをGeometryDrawingのものとして作成します(リテインモードであるShapeのものではありません)。 GemoetryDrawing は通常、非常に高速です。GemoetryDrawing オブジェクトは DirectX プリミティブに直接マッピングされ、Framework Elements のように個別にレイアウトやトラッキングを行わないので、非常に軽量です。

このGeometryDrawingをDrawingImage(これは本質的にバックバッファです)に選択すると、画面上で高速に変化する画像を得ることができます。 裏側では、WPFはあなたが期待したとおりのことをします。つまり、変化するそれぞれの矩形をイメージバッファに描画するのです。

繰り返しになりますが、Shapeは使わないでください--これらはフレームワーク要素であり は、レイアウトに関与するため、大きなオーバーヘッドが発生します。 例えば 長方形は使用しないでください を使用しますが RectangleGeometry の代わりに

最適化

さらにいくつかの最適化を検討することができます。

  1. GeometryDrawingオブジェクトの再利用 -- 位置とサイズを変更するだけ
  2. グリッドに最大サイズがある場合、オブジェクトを事前に作成する
  3. 変更されたGeometryDrawingオブジェクトのみを修正し、WPFが不必要にリフレッシュしないようにする。
  4. つまり、異なるズームレベルに対して、常に前のグリッドよりはるかに大きいグリッドに更新し、それを縮小するためにスケーリングを使用します。 例えば、10x10のグリッドから直接20x20のグリッドに移動し、それを55%スケールバックして11x11の正方形を表示します。 この方法では、11x11 から 20x20 にズームしても、GeometryDrawing オブジェクトは変更されず、ビットマップのスケーリングのみが変更され、更新が非常に高速になります。

EDIT:フレーム・バイ・フレームレンダリングを行う

オーバーライド OnRender この質問に対する懸賞金の回答で提案されているように。 そうすると、基本的にシーン全体がキャンバスに描かれます。

DirectXを使った絶対制御

また、各フレームを絶対的に制御したい場合は、生のDirectXを使用することを検討してください。