C# 慢速平移和放大WPF

C# 慢速平移和放大WPF,c#,wpf,wpf-controls,C#,Wpf,Wpf Controls,我在WPF的几张画布上画了很多线条和文字。我使用了WPF中最轻量级的元素:DrawingVisual 我在不同的画布上绘制了线条,并将其厚度绑定到缩放因子的倒数,以便在缩放时获得均匀的线条厚度。这意味着当我缩放时,我只是用线条重新绘制画布。包含文本的画布仅在程序开始时创建 现在我遇到了一个相当奇怪的问题。当我放大时,我的表现很慢。当我使用虚线样式时,性能会变得更差。慢,就像当你在word中加载一个大文本文件时,你滚动它时遇到问题 我的第一个想法是,也许创作台词花的时间太多了。由于我在每次缩放时(

我在WPF的几张画布上画了很多线条和文字。我使用了WPF中最轻量级的元素:
DrawingVisual

我在不同的画布上绘制了线条,并将其
厚度
绑定到缩放因子的倒数,以便在缩放时获得均匀的线条厚度。这意味着当我缩放时,我只是用线条重新绘制画布。包含文本的画布仅在程序开始时创建

现在我遇到了一个相当奇怪的问题。当我放大时,我的表现很慢。当我使用虚线样式时,性能会变得更差。慢,就像当你在word中加载一个大文本文件时,你滚动它时遇到问题

我的第一个想法是,也许创作台词花的时间太多了。由于我在每次缩放时(在鼠标滚轮上)都会创建这些线,所以我使用一个普通的秒表来测量从滚动鼠标滚轮到创建线结束之间的时间。令我惊讶的是,它只需要1毫秒!因此,线条的创建不是问题所在

经过进一步检查,我发现我在平移时的表现相当慢!就像你在windows中尝试平移非常非常大的图像时一样慢

那么问题出在哪里呢?我知道您需要一些代码,但由于代码很长,我将只显示
鼠标滚轮
事件中发生的事情:

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中处理鼠标按钮的方式有关?似乎有什么东西干扰了鼠标事件频率。非常感谢您的任何建议

更新:我试图实现游戏炼金术士在我的代码中所说的,但令人惊讶的是它仍然很慢

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

好的,我的建议是:在所有事件处理程序中,不要绘制或修改与渲染相关的任何内容。因为在屏幕刷新过程中事件可能会触发多次(=16ms,对于60Hz屏幕),因此您可能会多次重新绘制而没有任何结果。
所以,处理一些标志:在计算机图形学中,我们通常称它们为“脏”标志,但可以随意称呼它们。在这里,您可以使用两个可能是私有属性的bool,让我们将它们称为
linesneedsredaw
rendertransfermneedsupdate
(例如)。
例如,现在在mouseWheelHandler中,只需替换对
DrawZeroWidthDrawing(Scale)的调用
lineNeedsRedraw=true。在鼠标移动处理程序中,将RenderTransform更改替换为
RenderTransformMneedSupdate=true

第二件事:在
CompositionTarget.Rendering
事件上钩住一个方法。这种方法非常简单:

void repaintIfRequired() {
    if (linesNeedRedraw) {
       DrawZeroWidthDrawing(Scale);
       linesNeedRedraw = false;
    }
    if (renderTransformNeedsUpdate) {
       // ... do your transform change
       renderTransformNeedsUpdate = false;
    }
}
这样,每帧最多有一次更新

我将以一个问题结束:为什么不同时使用renderTransform进行缩放


祝你好运。

试试WPF的性能分析工具。每秒执行多少次?因为如果超过60倍,这是浪费时间。你可能会考虑只存储一些数据+在鼠标处理器中设置一个“脏”标志,并有一个30次/s的绘制方法,如果脏了,可以重新绘制。不幸的是,我不知道如何测量这个的帧率。我怎么能得到这个?你能帮我举个小例子作为回答吗。不久前我还问了一个类似的问题,一位用户给了我一些鼠标频率的提示,但我不明白。你可以在这里看到:@gamealchest顺便说一句,我在淘金时也会得到这个。你引用的答案很有趣。使用我建议的解决方案,在mouseWheel处理程序和其他鼠标处理程序中处理脏标志,然后在CompositionTarget.Rendering上挂起一个draw方法。只有当脏标志被明显设置时,它才应该重新绘制,然后在重新绘制后将其设置为false。非常感谢您为此投入的时间。我写了大部分程序,几个月来我一直在努力完成这部分。我实际上是在使用renderTransform进行缩放,但由于缩放时线条的厚度会增加,因此我别无选择,只能在每次缩放时使用新的厚度重新绘制线条。我尝试了这种方法,但不幸的是,我没有获得任何性能。它甚至看起来更慢。我会在一分钟内用我到目前为止所做的更新这个问题。我还没能解决这个问题。真令人沮丧。嗯。。。我看到了你的更新,我很困惑。。。我想知道更改转换是否不会使窗口变脏,因此需要在下一帧重新绘制。。。问题:它是越来越慢了,还是总是很慢?总是很慢,我能把源代码发给你吗?