加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – WPF中性能低下的原因

发布时间:2020-12-15 03:56:16 所属栏目:百科 来源:网络整理
导读:我正在使用DrawText在 WPF中创建大量文本,然后将它们添加到单个Canvas. 我需要在每个MouseWheel事件中重画屏幕,我意识到性能有点慢,所以我测量了创建对象的时间,不到1毫秒! 那么可能是什么问题呢?很久以前,我想我读到某个地方,实际上是渲染,花费时间,而不
我正在使用DrawText在 WPF中创建大量文本,然后将它们添加到单个Canvas.

我需要在每个MouseWheel事件中重画屏幕,我意识到性能有点慢,所以我测量了创建对象的时间,不到1毫秒!

那么可能是什么问题呢?很久以前,我想我读到某个地方,实际上是渲染,花费时间,而不是创建和添加视觉效果.

这里是我用来创建文本对象的代码,我只包括了基本部分:

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;
    }

解决方法

我正在处理可能是同样的问题,我发现了一些非常意想不到的事情.我正在渲染到一个WriteableBitmap,并允许用户滚动(缩放)和平移来更改所渲染的内容.这个动作对于缩放和平移来说似乎都是不稳定的,所以我自然认为渲染花费的时间太长.经过一些测试,我验证了我以30-60 fps渲染.渲染时间没有增加,无论用户如何缩放或平移,所以choppiness必须来自其他地方.

我看着OnMouseMove事件处理程序.虽然WriteableBitmap每秒更新30-60次,但是MouseMove事件每秒只能触发1-2次.如果我减小WriteableBitmap的大小,MouseMove事件更频繁地触发,并且平移操作看起来更平滑.所以,chopiness实际上是由于MouseMove事件被破坏的结果,而不是渲染(例如WriteableBitmap渲染7-10帧,看起来相同,MouseMove事件触发,然后WriteableBitmap渲染新的图像的7-10帧,等等).

每当WriteableBitmap使用Mouse.GetPosition(this)更新时,通过轮询鼠标位置,我尝试跟踪平移操作.然而,这样做的结果相同,因为在更改为新值之前,返回的鼠标位置将与7-10帧相同.

然后我尝试使用PInvoke服务GetCursorPos like in this SO answer轮询鼠标位置,例如:

[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每次调用时都会返回一个新的位置(当鼠标移动时),所以每个帧在用户平移的同时呈现在稍微不同的位置.同样的choppiness似乎影响了MouseWheel事件,我不知道如何解决这个问题.

所以,尽管上述关于有效维护视觉树的建议是很好的做法,但我怀疑你的性能问题可能是某些干扰鼠标事件频率的结果.在我的情况下,似乎由于某些原因,渲染导致鼠标事件更新并且比平常更慢.如果我找到一个真正的解决方案,而不是这个部分解决方法,我会更新这个.

编辑:好的,我再多挖一点,我想我现在明白了.我将用更详细的代码示例来解释:

我按照每帧渲染到我的位图,通过注册来处理如this MSDN article.所述的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事件(和所有的鼠标事件真的)被调用的频率要低得多.当渲染代码需要15ms时,MouseMove事件将每隔几ms调用一次.当渲染代码需要30ms时,MouseMove事件将每几百毫秒被调用.我的理论为什么会发生这样的情况:渲染发生在同一个线程上,WPF鼠标系统更新其值并触发鼠标事件.该线程上的WPF循环必须具有一些条件逻辑,如果在一帧中渲染需要太长时间,它将跳过执行鼠标更新.当我的渲染代码在每一帧都需要“太长”时,会出现问题.然后,由于渲染每帧需要15个额外的ms,因此界面看起来慢了一点点,因此界面很大程度上是因为额外的15ms的渲染时间在鼠标更新之间引入了数百毫秒的滞后.

之前提到的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,当它真的需要更新.只有当鼠标输入受到影响时才需要进行更新.所以结果是我接收到鼠标输入一帧,更新下一帧的位图,但不会在同一帧上接收更多的鼠标输入,因为更新需要几毫秒太长时间,然后下一帧我会收到更多鼠标输入,因为位图不需要再次更新.随着渲染时间的增加,这会产生更线性(和合理)的性能下降,因为可变长度帧只是平均值的一半.

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执行它的渲染路径时,它所需要做的就是将这些位图blit,而不是从原始几何数据重构像素数据.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读