简体   繁体   English

缓慢平移和放大WPF

[英]Slow pan and zoom in WPF

I have drawn a good deal of lines and texts on several canvases in WPF. 我在WPF的多个画布上绘制了很多线条和文字。 I have used the most lightweight element possible in WPF: DrawingVisual 我在WPF中使用了最轻量的元素: DrawingVisual

I have drawn lines on a different canvas and I have bound their thickness to the inverse of the zoom factor so that I can get a uniform line thickness while zooming. 我在不同的画布上绘制了线条,并将它们的thickness与缩放系数的倒数绑定在一起,以便在缩放时可以获得一致的线条粗细。 That means when I'm zooming I'm only redrawing the canvas with the lines. 这意味着在缩放时,我只会使用线条重新绘制画布。 The canvas with the texts is only created at the start of the program. 带有文本的画布仅在程序开始时创建。

Now I have encountered a rather odd problem. 现在我遇到了一个很奇怪的问题。 When I'm zooming I'm getting a slow performance. 缩放时,性能会变慢。 The performance gets worse when I use dashed line style. 当我使用虚线样式时,性能会变差。 Slow like when you load a big text file in a word and you have problems scrolling it! 速度很慢,就像在单词中加载大文本文件时,滚动时遇到问题!

My first thought was that maybe the creation of the lines is taking too much. 我首先想到的是,线条的创建可能花费了太多。 Since I'm creating those at each zoom (On mouse wheel), So I used a modest stopwatch to measure the time it takes between when I scroll mouse wheel till the creation of the lines ends. 由于我是在每次缩放时(在鼠标滚轮上)创建这些指针,因此我使用了适度的秒表来测量从滚动鼠标滚轮到线条创建结束之间的时间。 To my surprise it only takes 1 ms! 令我惊讶的是,仅需1毫秒! So the creation of the lines cannot be the issue. 因此,线的创建不是问题。

With further examination I figured out that I'm getting a rather slow performance while panning! 经过进一步的检查,我发现平移时的表现相当慢! Slow like when you a trying to pan in very very big image in windows! 就像您尝试在Windows中平移非常大的图像时一样缓慢!

So what could be the problem? 那么可能是什么问题呢? I know you will want some code but since the code is very long I'll only show what goes on inside Mousewheel event: 我知道您会需要一些代码,但是由于代码很长,所以我只会显示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);
}

I'm starting to think that maybe it has to do something with the way things get rendered in WPF, or maybe with how mouse buttons are getting handled in WPF? 我开始认为它可能与在WPF中呈现事物的方式有关,或者与在WPF中如何处理鼠标按钮有关? It seems like something is interfering with mouse event frequency . 似乎是某些因素在干扰鼠标事件的频率 Any suggestions are very appreciated. 任何建议都非常感谢。

Update: I have tried to implement what GameAlchemist said in my code to my surprise it is still slow. 更新:我试图实现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);
}

I figured out maybe redrawing the lines is the culprit but it seems not! 我发现也许重画线条是罪魁祸首,但事实并非如此! I removed the call to the function that actually redraws them with the new thicknesses and only left the scaling part. 我删除了对函数的调用,该函数实际上使用新的厚度重绘了它们,只保留了缩放部分。 The result didn't change! 结果没有改变! I'm still lacking performance. 我仍然缺乏表现。

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

在此处输入图片说明

I tried to measure the FPS using WPF performance tool, it falls back to 16~24 during zoom! 我尝试使用WPF性能工具来测量FPS,在缩放过程中它会降到16〜24!

Ok, Here's my advice : in all your events handler, do not draw or modify anything related to the render. 好的,这是我的建议:在所有事件处理程序中,请勿绘制或修改与渲染相关的任何内容。 Because the events might trigger more than once during a screen refresh (=16ms for 60Hz screen), so you might end up redrawing several times for nothing. 因为事件可能在屏幕刷新期间触发多次(对于60Hz屏幕= 16ms),所以您可能最终无缘无故地重画了几次。
So handle some flags : in computer graphics, we often call them 'dirty' flags, but call them as you want. 因此,请处理一些标志:在计算机图形学中,我们通常将它们称为“脏”标志,但可以根据需要调用它们。 Here you might use two bools that might be private properties, let's call them linesNeedsRedraw and renderTransformNeedsUpdate (for instance). 在这里,您可能会使用两个可能是私有属性的linesNeedsRedraw ,我们称它们为linesNeedsRedrawrenderTransformNeedsUpdate (例如)。
Now in the mouseWheelHandler, for instance, just replace your call to DrawZeroWidthDrawing(Scale); 例如,现在在mouseWheelHandler中,只需替换对DrawZeroWidthDrawing(Scale);调用即可DrawZeroWidthDrawing(Scale); by lineNeedsRedraw = true; 通过lineNeedsRedraw = true; . In the mouse move handler, replace your RenderTransform change by renderTransformNeedsUpdate=true; 在鼠标移动处理程序中,将您的RenderTransform更改替换为renderTransformNeedsUpdate=true; .

Second thing : hook a method on CompositionTarget.Rendering event. 第二件事:在CompositionTarget.Rendering事件上挂钩一个方法。 This method will be very simple : 此方法将非常简单:

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

This way you have a maximum of one update per frame. 这样,每帧最多可以更新一次。

I'll end by a question : why not also use the renderTransform to do the scaling ? 我将以一个问题结尾:为什么还不使用renderTransform进行缩放?

Good luck. 祝好运。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM