在WinForms应用程序中绘制数千条线的最快方法是什么

本文关键字:千条 是什么 方法 应用程序 WinForms 绘制 | 更新日期: 2023-09-27 18:37:18

我有WinForms应用程序。我创建了一个用户控件,它从大约 10k 行的坐标绘制地图。实际上,并非所有线都是直线,但是当地图完全缩小时,贝塞尔曲线无关紧要,并被直线取代。

当地图缩放时,我的直线和曲线数量较少,因此绘制速度足够快(低于 15 毫秒)。但是当它完全缩小时 - 我需要绘制所有线条(因为所有线条都适合视口)。这是非常缓慢的。在我的非常快的机器上,它大约需要 1000 毫秒,所以在速度较慢的机器上,这将是一种矫枉过正。

有没有一种简单的方法来加快绘图速度?我使用Graphics对象进行绘制Graphics.Scale并将属性设置为适合我的控件的地图。这会减慢速度吗?我使用Graphics.TranslateTransform()来确保整个地图可见。缩放和转换在 OnPaint() 事件处理程序中仅设置一次。

然后有一个循环绘制大约 10k 条线。我只是看到他们在屏幕上画画。

也许 WPF 容器会有所帮助?

好吧,我可能会简化地图以合并一些线条,但我想知道这是否值得付出努力。这将使代码大大复杂化,会引入更多的计算,使用额外的内存,我不知道在一天结束时它是否会快得多。

顺便说一句,我测试了所有行的处理(通过一些额外的计算从一种结构转换为另一种结构)在我的机器上需要大约 10 毫秒。所以 - 仅绘图就花费了 100 倍的时间。

编辑:现在这是新问题。我已经打开了双重缓冲:

SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);

这是我凌乱的OnPaint()处理程序:

protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    if (Splines == null) return;
    var pens = new[] {
        new Pen(TrackColor),
        new Pen(TrackColor),
        new Pen(RoadColor),
        new Pen(RiverColor),
        new Pen(CrossColor)
    };
    var b = Splines.Bounds;
    Graphics g = e.Graphics;
    g.PageScale = _CurrentScale;
    g.TranslateTransform(-b.Left, -b.Top);
    int i = 0;
    foreach (var s in Splines) {
        g.DrawLine(pens[s.T], s.A, s.D);
        if (++i > 100) break;
        //if (s.L) g.DrawLine(pens[s.T], s.A, s.D);
        //else g.DrawBezier(pens[s.T], s.A, s.B, s.C, s.D);
    }
    foreach (var p in pens) p.Dispose();
}

相信我的话,如果我只从样式中删除OptimizedDoubleBuffer,代码就可以工作。当双缓冲在处理程序上正确执行时,每个DrawLine都使用正确的参数执行。但不显示图形。调整大小期间的 CPU 使用率接近于零。像所有DrawLine电话一样被忽略了。这是怎么回事?

在WinForms应用程序中绘制数千条线的最快方法是什么

在我最近看到但找不到的一篇相关帖子中,OP 声称在切换他的控件以使用双缓冲时看到了很大的加速。 显然,将东西吸引到屏幕上有很大的好处。

您可以尝试的另一件事是在缩小时删除绘制的线条中的点列表。 每次更改缩放时,您只能执行一次,而不是每帧进行抽取。

尝试双缓冲作为可能的解决方案或尝试减少行数。只有测试才能为您的应用程序提供答案。

Winforms双缓冲

带面板的双缓冲

这样做的可行性实际上取决于你是否使用抗锯齿,物体是否可以旋转,厚度是否必须非常精确,等等。

但是,您始终可以将所有线条绘制到位图中,然后只需重新绘制位图,除非地图数据本身已实际更改。当然,然后你会为不同的缩放级别使用不同的位图,隐藏和显示它们,网格中的多个位图以获得高细节等。

这绝对不理想,但如果你真的需要在20毫秒的刷新中绘制数千条线......这可能是你唯一真正的选择。

或者,您可以在 GDI+ 之外使用较低级别的绘图。 一个这样的例子是SlimDX。此包装器允许您从窗口控件和窗体创建 directX 设备写入。一旦 DirectX 投入使用,速度最多可以提高数倍。

第二,即使启用了双缓冲,在win面板上绘图时,您始终必须使面板无效,该面板要求环境调用OnPaint事件,该事件实际使用系统提供的图形对象进行绘制。这种无效通常需要一个射速超过30到5的计时器,一种流畅播放的感觉。现在,当负载增加时,后续计时器事件会延迟,因为一切都在单个线程下发生。并且计时器必须在每次触发后产生线程大约 25 毫秒(Windows 操作系统限制)。不允许跨线程访问,使用它可以防止这种抖动。

有关我尝试将现有 GDI 代码传输到 DirectX 的示例,请参阅此链接。该代码使用了许多图形属性,我已经将这些属性合并到包装器中,这些包装器可以在GDI和DirectX上绘制。

https://drive.google.com/file/d/1DsoQl62x2YeZIKFxf252OTH4HCyEorsO/view?usp=drivesdk