【问题标题】:Slow pan and zoom in WPF在 WPF 中缓慢平移和放大
【发布时间】:2014-12-20 21:06:01
【问题描述】:

我在 WPF 的几个画布上绘制了大量的线条和文本。我在 WPF 中使用了最轻量级的元素:DrawingVisual

我在不同的画布上绘制了线条,并将它们的thickness 绑定到缩放因子的倒数,以便在缩放时获得均匀的线条粗细。这意味着当我缩放时,我只是用线条重绘画布。带有文本的画布仅在程序开始时创建。

现在我遇到了一个相当奇怪的问题。当我缩放时,我的表现很慢。当我使用虚线样式时,性能会变差。速度很慢,就像您在一个单词中加载一个大文本文件并且滚动它时遇到问题!

我的第一个想法是,线条的创建可能需要太多时间。由于我在每次缩放时创建这些(在鼠标滚轮上),所以我使用了一个适度的秒表来测量从滚动鼠标滚轮到线条创建结束之间的时间。令我惊讶的是,它只需要 1 毫秒!所以线条的创建不是问题。

经过进一步检查,我发现我在平移时的性能相当缓慢!当您尝试在 Windows 中平移非常非常大的图像时,速度会很慢!

那么可能是什么问题?我知道你会想要一些代码,但由于代码很长,我只会展示 Mousewheel 事件中发生的事情:

private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
    var sw = new Stopwatch();
    sw.Start();

    var st = GetScaleTransform(Window);
    var tt = GetTranslateTransform(Window);

    var absoluteX = MousePos.Current.X * st.ScaleX + tt.X;
    var absoluteY = MousePos.Current.Y * st.ScaleY + tt.Y;

    const double zoomfactorforwheel = 1.3;
    if (e.Delta > 0)
    {
        st.ScaleX = Math.Min(st.ScaleX * zoomfactorforwheel, Scalemax);
        st.ScaleY = Math.Min(st.ScaleY * zoomfactorforwheel, Scalemax);
    }
    else
    {
        st.ScaleX = Math.Max(st.ScaleX / zoomfactorforwheel, Scalemin);
        st.ScaleY = Math.Max(st.ScaleY / zoomfactorforwheel, Scalemin);
    }
    tt.X = absoluteX - MousePos.Current.X * st.ScaleX;
    tt.Y = absoluteY - MousePos.Current.Y * st.ScaleY;

    Scale = st.ScaleX;

    // Inside this function I'm drawing the lines on drawingvisual
    // Then I'm adding the drawingvisual to a canvas
    DrawZeroWidthDrawing(Scale);

    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

我开始认为这可能与 WPF 中呈现事物的方式有关,或者可能与 WPF 中鼠标按钮的处理方式有关? 似乎有什么东西干扰了鼠标事件频率。任何建议都非常感谢。

更新:我尝试实现 GameAlchemist 在我的代码中所说的内容,但令我惊讶的是它仍然很慢。

private MouseWheelEventArgs e;
private bool zoomNeeded;

CompositionTarget.Rendering += CompositionTargetOnRendering;

private void CompositionTargetOnRendering(object sender, EventArgs eventArgs)
{
    if (zoomNeeded)
    {
        zoomNeeded = false;
        DoZoom();

        DrawZeroWidthDrawing();              
    }
}

private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
    this.e = e;
    zoomNeeded = true;
}

private void DoZoom()
{
    var st = GetScaleTransform(Window);
    var tt = GetTranslateTransform(Window);

    var absoluteX = MousePos.Current.X * st.ScaleX + tt.X;
    var absoluteY = MousePos.Current.Y * st.ScaleY + tt.Y;

    const double zoomfactorforwheel = 1.3;
    if (e.Delta > 0)
    {
        st.ScaleX = Math.Min(st.ScaleX * zoomfactorforwheel, Scalemax);
        st.ScaleY = Math.Min(st.ScaleY * zoomfactorforwheel, Scalemax);
    }
    else
    {
        st.ScaleX = Math.Max(st.ScaleX / zoomfactorforwheel, Scalemin);
        st.ScaleY = Math.Max(st.ScaleY / zoomfactorforwheel, Scalemin);
    }
    tt.X = absoluteX - MousePos.Current.X * st.ScaleX;
    tt.Y = absoluteY - MousePos.Current.Y * st.ScaleY;

    Scale = st.ScaleX;            
}

private static TranslateTransform GetTranslateTransform(UIElement element)
{
    return (TranslateTransform)((TransformGroup)element.RenderTransform)
      .Children.First(tr => tr is TranslateTransform);
}

private static ScaleTransform GetScaleTransform(UIElement element)
{
    return (ScaleTransform)((TransformGroup)element.RenderTransform)
      .Children.First(tr => tr is ScaleTransform);
}

我发现重画线条可能是罪魁祸首,但似乎不是!我删除了对实际使用新厚度重绘它们的函数的调用,只留下了缩放部分。结果没变!我仍然缺乏表现。

private void CompositionTargetOnRendering(object sender, EventArgs eventArgs)
{
    if (zoomNeeded)
    {
        zoomNeeded = false;
        DoZoom();
    }
}

我尝试使用 WPF 性能工具测量 FPS,它在缩放期间回落到 16~24!

【问题讨论】:

  • 试用 WPF 的性能分析工具。
  • 每秒执行多少次?因为如果超过60次,那就是浪费时间。您可能会考虑只存储一些数据 + 在鼠标处理程序中设置一个“脏”标志,并有一个名为 30 次/秒的绘制方法,如果脏了则重绘。
  • @GameAlchemist 感谢您关注我几个月来一直坚持这个问题。不幸的是,我不知道如何测量它的帧率。我怎么能得到那个?你能帮我举个小例子作为答案吗?前段时间我也问了一个类似的问题,一位用户给了我一些关于鼠标频率的提示,但我无法理解。你可以在这里看到它:stackoverflow.com/questions/24119311/…
  • @GameAlchemist 顺便说一句,我在平移时也遇到了这个问题。
  • 你引用的答案很有趣。通过在 mouseWheel 处理程序和其他鼠标处理程序中处理脏标志来寻求我建议的解决方案,然后在 CompositionTarget.Rendering 上挂钩单个绘制方法。只有在明显设置了脏标志的情况下才应该重绘,然后在重绘后将其设置为false。

标签: c# wpf wpf-controls


【解决方案1】:

好的,这是我的建议:在所有事件处理程序中,不要绘制或修改与渲染相关的任何内容。因为在屏幕刷新期间事件可能会触发多次(对于 60Hz 屏幕 = 16 毫秒),因此您可能最终会重绘几次而一无所获。
所以处理一些标志:在计算机图形学中,我们经常称它们为“脏”标志,但你可以随意调用它们。在这里,您可能会使用两个可能是私有属性的布尔值,我们称它们为 linesNeedsRedrawrenderTransformNeedsUpdate(例如)。
例如,现在在 mouseWheelHandler 中,只需将您对 DrawZeroWidthDrawing(Scale); 的调用替换为 lineNeedsRedraw = true;。在鼠标移动处理程序中,将您的 RenderTransform 更改替换为 renderTransformNeedsUpdate=true;

第二件事:在CompositionTarget.Rendering 事件上挂钩一个方法。这个方法会很简单:

void repaintIfRequired() {
    if (linesNeedRedraw) {
       DrawZeroWidthDrawing(Scale);
       linesNeedRedraw = false;
    }
    if (renderTransformNeedsUpdate) {
       // ... do your transform change
       renderTransformNeedsUpdate = false;
    }
}

这样您每帧最多可以更新一次。

最后我会问一个问题:为什么不也使用 renderTransform 来进行缩放?

祝你好运。

【讨论】:

  • 非常感谢您为此付出的时间。我已经编写了大部分程序,并且几个月来我一直在努力解决这部分问题。我实际上是使用 renderTransform 来进行缩放,但是由于缩放时线条的粗细会增加,所以我别无选择,只能在每次缩放时用新的粗细重新绘制它们。
  • 我试过这个,但不幸的是我没有得到任何性能。它甚至看起来更慢。我会在一分钟内用我目前所做的更新这个问题。
  • 我没能解决这个问题。真令人沮丧。
  • 嗯...我已经看到您的更新,我很困惑...我想知道更改转换是否不会使窗口“变脏”,因此需要在下一帧重新绘制。 .. 问题:它是越来越慢,还是总是很慢?
  • 总是很慢,我可以把源代码发给你吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 2018-08-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多