13

を使用してWPFで多数のテキストを作成し、DrawTextそれらを単一の.xmlに追加していCanvasます。

各イベントで画面を再描画する必要がありMouseWheel、パフォーマンスが少し遅いことに気付いたので、オブジェクトが作成される時間を測定したところ、1 ミリ秒未満でした!

では、何が問題になるのでしょうか?昔Rendering、ビジュアルを作成して追加するのではなく、実際に時間がかかるのはどこかで読んだことがあると思います。

テキストオブジェクトを作成するために使用しているコードは次のとおりです。重要な部分のみを含めました。

public class ColumnIdsInPlan : UIElement
    {
    private readonly VisualCollection _visuals;
    public ColumnIdsInPlan(BaseWorkspace space)
    {
        _visuals = new VisualCollection(this);

        foreach (var column in Building.ModelColumnsInTheElevation)
        {
            var drawingVisual = new DrawingVisual();
            using (var dc = drawingVisual.RenderOpen())
            {
                var text = "C" + Convert.ToString(column.GroupId);
                var ft = new FormattedText(text, cultureinfo, flowdirection,
                                           typeface, columntextsize, columntextcolor,
                                           null, TextFormattingMode.Display)
                {
                    TextAlignment = TextAlignment.Left
                };

                // Apply Transforms
                var st = new ScaleTransform(1 / scale, 1 / scale, x, space.FlipYAxis(y));
                dc.PushTransform(st);

                // Draw Text
                dc.DrawText(ft, space.FlipYAxis(x, y));
            }
            _visuals.Add(drawingVisual);
        }
    }

    protected override Visual GetVisualChild(int index)
    {
        return _visuals[index];
    }

    protected override int VisualChildrenCount
    {
        get
        {
            return _visuals.Count;
        }
    }
}

MouseWheelこのコードは、イベントが発生するたびに実行されます。

var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);

犯人は何ですか?

パン中にも問題があります。

    private void Workspace_MouseMove(object sender, MouseEventArgs e)
    {
        MousePos.Current = e.GetPosition(Window);
        if (!Window.IsMouseCaptured) return;
        var tt = GetTranslateTransform(Window);
        var v = Start - e.GetPosition(this);
        tt.X = Origin.X - v.X;
        tt.Y = Origin.Y - v.Y;
    }
4

3 に答える 3

19

私は現在、おそらく同じ問題に取り組んでおり、まったく予想外のことを発見しました。私は WriteableBitmap にレンダリングしており、ユーザーがスクロール (ズーム) およびパンして、レンダリングされる内容を変更できるようにしています。ズームとパンの両方で動きがぎくしゃくしているように見えたので、当然、レンダリングに時間がかかりすぎていると考えました。いくつかのインストルメンテーションの後、30 ~ 60 fps でレンダリングしていることを確認しました。ユーザーがどのようにズームやパンを行ってもレンダリング時間は増加しないため、途切れは別の場所から発生しているに違いありません。

代わりに、OnMouseMove イベント ハンドラーに注目しました。WriteableBitmap は 1 秒あたり 30 ~ 60 回更新されますが、MouseMove イベントは 1 秒あたり 1 ~ 2 回しか発生しません。WriteableBitmap のサイズを小さくすると、MouseMove イベントがより頻繁に発生し、パン操作がよりスムーズに表示されます。したがって、途切れは実際には、レンダリングではなく、MouseMove イベントが途切れ途切れになっている結果です (たとえば、WriteableBitmap は同じように見える 7 ~ 10 フレームをレンダリングし、MouseMove イベントが発生し、WriteableBitmap は新しくパンされた画像の 7 ~ 10 フレームをレンダリングします)。など)。

Mouse.GetPosition(this) を使用して WriteableBitmap が更新されるたびにマウスの位置をポーリングして、パン操作を追跡しようとしました。ただし、返されるマウスの位置は、新しい値に変更される前の 7 ~ 10 フレームで同じであるため、結果は同じでした。

次に、この SO の回答のように、PInvoke サービス GetCursorPos を使用してマウスの位置をポーリングしてみました。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

これは実際にトリックを行いました。GetCursorPos は、呼び出されるたびに (マウスが移動しているときに) 新しい位置を返すため、ユーザーがパンしている間、各フレームはわずかに異なる位置にレンダリングされます。同じ種類の途切れが MouseWheel イベントに影響を与えているようですが、それを回避する方法がわかりません。

したがって、ビジュアル ツリーを効率的に維持するための上記のアドバイスはすべて適切な方法ですが、パフォーマンスの問題は、何かがマウス イベントの頻度に干渉していることが原因であると思われます。私の場合、なんらかの理由で、レンダリングによって Mouse イベントが更新され、通常よりもはるかに遅く発生しているように見えます。この部分的な回避策ではなく、真の解決策が見つかった場合は、これを更新します。


編集:OK、私はこれをもう少し掘り下げて、何が起こっているのか理解したと思います. より詳細なコード サンプルで説明します。

この MSDN 記事で説明されているように、CompositionTarget.Rendering イベントを処理するために登録することで、フレームごとにビットマップにレンダリングしています。基本的には、UI がレンダリングされるたびにコードが呼び出されるので、ビットマップを更新できます。これは基本的に、実行しているレンダリングと同等です。視覚要素の設定方法に応じて、レンダリング コードが舞台裏で呼び出され、レンダリング コードが表示される場所にあるだけです。OnMouseMove イベントをオーバーライドして、マウスの位置に応じて変数を更新します。

public class MainWindow : Window
{
  private System.Windows.Point _mousePos;
  public Window()
  {
    InitializeComponent();
    CompositionTarget.Rendering += CompositionTarget_Rendering;
  }

  private void CompositionTarget_Rendering(object sender, EventArgs e)
  {
    // Update my WriteableBitmap here using the _mousePos variable
  }

  protected override void OnMouseMove(MouseEventArgs e)
  {
    _mousePos = e.GetPosition(this);
    base.OnMouseMove(e);
  }
}

問題は、レンダリングに時間がかかるため、MouseMove イベント (および実際にはすべてのマウス イベント) が呼び出される頻度が大幅に低下することです。レンダリング コードに 15 ミリ秒かかると、数ミリ秒ごとに MouseMove イベントが呼び出されます。レンダリング コードに 30 ミリ秒かかると、MouseMove イベントが数百回ごとに呼び出されます。ミリ秒。これが発生する理由についての私の理論は、WPF マウス システムがその値を更新してマウス イベントを発生させるのと同じスレッドでレンダリングが行われているというものです。このスレッドの WPF ループには、1 つのフレームでレンダリングに時間がかかりすぎる場合にマウスの更新をスキップする条件付きロジックが必要です。この問題は、レンダリング コードがすべてのフレームで「時間がかかりすぎる」場合に発生します。次に、レンダリングがフレームごとに 15 ミリ秒余分にかかるためにインターフェースが少し遅くなるように見える代わりに、インターフェースは大幅に途切れます。

前に述べた PInvoke の回避策は、本質的に WPF マウス入力システムをバイパスします。レンダリングが発生するたびに、ソースに直接送信されるため、WPF マウス入力システムが不足しても、ビットマップが正しく更新されなくなることはなくなりました。

public class MainWindow : Window
{
  private System.Windows.Point _mousePos;
  public Window()
  {
    InitializeComponent();
    CompositionTarget.Rendering += CompositionTarget_Rendering;
  }

  private void CompositionTarget_Rendering(object sender, EventArgs e)
  {
    POINT screenSpacePoint;
    GetCursorPos(out screenSpacePoint);

    // note that screenSpacePoint is in screen-space pixel coordinates, 
    // not the same WPF Units you get from the MouseMove event. 
    // You may want to convert to WPF units when using GetCursorPos.
    _mousePos = new System.Windows.Point(screenSpacePoint.X, 
                                         screenSpacePoint.Y);
    // Update my WriteableBitmap here using the _mousePos variable
  }

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  static extern bool GetCursorPos(out POINT lpPoint);

  [StructLayout(LayoutKind.Sequential)]
  public struct POINT
  {
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
      this.X = x;
      this.Y = y;
    }
  }
}

ただし、このアプローチでは残りのマウス イベント (MouseDown、MouseWheel など) は修正されませんでした。また、すべてのマウス入力に対してこの PInvoke アプローチを採用することに熱心ではなかったので、WPF を飢えさせるのをやめたほうがよいと判断しました。マウス入力システム。私がやったことは、本当に更新が必要なときにのみ WriteableBitmap を更新することでした。マウス入力が影響した場合にのみ更新する必要があります。その結果、マウス入力を 1 フレーム受信し、次のフレームでビットマップを更新しますが、更新に数ミリ秒かかりすぎるため、同じフレームでそれ以上のマウス入力を受信せず、次のフレームでさらに受信します。ビットマップを再度更新する必要がなかったため、マウス入力。

public class MainWindow : Window
{
  private System.Windows.Point _mousePos;
  private bool _bitmapNeedsUpdate;
  public Window()
  {
    InitializeComponent();
    CompositionTarget.Rendering += CompositionTarget_Rendering;
  }

  private void CompositionTarget_Rendering(object sender, EventArgs e)
  {
    if (!_bitmapNeedsUpdate) return;
    _bitmapNeedsUpdate = false;
    // Update my WriteableBitmap here using the _mousePos variable
  }

  protected override void OnMouseMove(MouseEventArgs e)
  {
    _mousePos = e.GetPosition(this);
    _bitmapNeedsUpdate = true;
    base.OnMouseMove(e);
  }
}

この同じ知識をあなた自身の特定の状況に変換します。パフォーマンスの問題につながる複雑なジオメトリについては、ある種のキャッシュを試してみます。たとえば、ジオメトリ自体がまったく変更されない場合、または頻繁に変更されない場合は、それらをRenderTargetBitmapにレンダリングしてから、ジオメトリ自体を追加する代わりに RenderTargetBitmap をビジュアル ツリーに追加してみてください。そうすれば、WPF がそのレンダリング パスを実行しているときに、生のジオメトリ データからピクセル データを再構築するのではなく、これらのビットマップをブリットするだけで済みます。

于 2014-06-24T17:29:02.533 に答える
4

考えられる原因は、ホイール イベントごとにビジュアル ツリーを消去して再構築しているという事実です。あなた自身の投稿によると、そのツリーには「多数の」テキスト要素が含まれています。入ってくるイベントごとに、これらの各テキスト要素を再作成、再フォーマット、測定し、最終的にレンダリングする必要があります。これは、単純なテキストのスケーリングを実現する方法ではありません。

ScaleTransformFormattedText要素にa を設定するのではなく、テキストを含む要素に a を設定します。必要に応じて、RenderTransformまたはを設定できますLayoutTransform。次に、ホイール イベントを受け取ったら、Scaleそれに応じてプロパティを調整します。イベントごとにテキストを再構築しないでください。

ItemsControlまた、他の人が推奨していることを行い、列のリストにバインドして、そのようにテキストを生成します。これを手動で行う必要がある理由はありません。

于 2014-06-09T14:36:27.787 に答える