c# - 使用 WPF 绘制图像网格

标签 c# wpf grid drawing 2d

我正在尝试使用 WPF 绘制图像/图标网格。网格尺寸会有所不同,但通常范围从 10x10 到 200x200。用户应该能够点击单元格,有些单元格需要每秒更新(更改图像)10-20 次。网格应该能够在所有四个方向上增长和收缩,并且应该能够切换到它所代表的 3D 结构的不同“切片”。我的目标是根据这些要求找到一种适当有效的方法来绘制网格。

我当前的实现使用 WPF Grid .我在运行时生成行和列定义并用 Line 填充网格(对于网格线)和 Border (对于单元格,因为它们当前只是开/关)在适当的行/列的对象。 (Line 对象一直跨越。)

Current grid implementation

在扩展网格(按住 Num6)时,我发现它的绘制速度太慢而无法在每次操作时重新绘制,因此我对其进行了修改以简单地添加一个新的 ColumnDefinition , Line和一套 Border每个增长列的对象。这解决了我的增长问题,并且可以使用类似的策略来快速收缩。为了在模拟中更新单个单元格,我可以简单地存储对单元格对象的引用并更改显示的图像。即使更改为新的 Z 级别也可以通过仅更新单元格内容而不是重建整个网格来改进。

然而,在我进行所有这些优化之前,我遇到了另一个问题。每当我将鼠标悬停在网格上时(即使以低速/正常速度),应用程序的 CPU 使用率也会飙升。我从网格的子元素中删除了所有事件处理程序,但这没有任何效果。最后,控制 CPU 使用率的唯一方法是设置 IsHitTestVisible = falseGrid . (为 Grid 的每个子元素设置此项没有任何作用!)

我相信使用单个控件来构建我的网格过于密集且不适合此应用程序,并且使用 WPF 的 2D 绘图机制可能更有效。不过,我是 WPF 的初学者,所以我正在寻求有关如何最好地实现这一目标的建议。从我读过的一点点来看,我可能会使用 DrawingGroup将每个单元格的图像合成到单个图像上进行显示。然后我可以对整个图像使用单击事件处理程序,并通过鼠标位置计算单击单元格的坐标。不过,这看起来很困惑,我只是不知道是否有更好的方法。

想法?

更新 1:

我听取了 friend 的建议,转而使用 CanvasRectangle对于每个单元格。当我第一次绘制网格时,我存储对所有 Rectangle 的引用。在二维数组中,然后当我更新网格内容时,我只需访问这些引用。

private void UpdateGrid()
{
    for (int x = simGrid.Bounds.Lower.X; x <= simGrid.Bounds.Upper.X; x++)
    {
        for (int y = simGrid.Bounds.Lower.Y; y <= simGrid.Bounds.Upper.Y; y++)
        {
            CellRectangles[x, y].Fill = simGrid[x, y, ZLevel] ? Brushes.Yellow : Brushes.White;
        }
    }
}

绘制网格最初看起来更快,后续更新肯定更快,但仍然存在一些问题。
  • 无论我鼠标悬停的区域有多小,当我将鼠标悬停在有数百个单元格的网格上时,CPU 使用率仍然会飙升。
  • 更新仍然太慢,所以当我按住向上箭头键更改 Z 级(一个常见用例)时,程序一次卡住几秒钟,然后似乎一次跳跃 50 个 Z 级。
  • 一旦网格包含约 5000 个单元格,更新时间为一秒。这太慢了,5000 个单元格适合典型用例。

  • 我还没试过UniformGrid方法,因为我认为它可能会出现我已经遇到的相同问题。不过,一旦我用尽了更多选择,我可能会尝试一下。

    最佳答案

    你的问题

    让我们重新表述你的问题。这些是您的问题限制:

  • 您想绘制一个动态大小的网格
  • 每个单元格快速开/关
  • 网格大小快速变化
  • 有大量单元格(即网格尺寸不重要)
  • 您希望所有这些更改以快速帧速率(例如 30fps)发生
  • 网格和单元格的定位和布局是确定性的,简单且交互性不是很强

  • 从这些限制条件来看,您可以立即看出您使用了错误的方法。

    需求:快速刷新确定性位置,交互性很小

    快速刷新帧率+每帧许多变化+大量单元格+每个单元格一个WPF对象=灾难。

    除非您拥有非常快的图形硬件和非常快的 CPU,否则您的帧速率总是会随着网格尺寸的增加而受到影响。

    您的问题更像是视频游戏或具有动态缩放功能的 CAD 绘图程序。它不像一个普通的桌面应用程序。

    立即模式与保留模式绘图

    换句话说,您需要“立即模式”绘图,而不是“保留模式”绘图(WPF 是保留模式)。这是因为您的约束不需要通过将每个单元格视为单独的 WPF 对象来提供的许多功能。

    例如,您不需要布局支持,因为每个单元格的位置都是确定性的。您将不需要 HitTest 支持,因为同样,位置是确定性的。您不需要容器支持,因为每个单元格都是一个简单的矩形(或图像)。您不需要复杂的格式支持(例如透明度、圆角边框等),因为没有任何重叠。换句话说,每个单元格使用网格(或 UniformGrid)和一个 WPF 对象没有任何好处。

    立即模式绘制缓冲位图的概念

    为了达到您需要的帧速率,基本上您将绘制一个大位图(覆盖整个屏幕)——或“屏幕缓冲区”。对于您的单元格,只需绘制到此位图/缓冲区(可能使用 GDI)。 HitTest 很容易,因为单元格位置都是确定性的。

    这种方法会很快,因为只有一个对象(屏幕缓冲区位图)。您可以为每一帧刷新整个位图,或者只更新那些发生变化的屏幕位置,或者这些的智能组合。

    请注意,尽管您在此处绘制了“网格”,但并未使用“网格”元素。根据您的问题约束是什么来选择您的算法和数据结构,而不是看起来是显而易见的解决方案——换句话说,“网格”可能不是绘制“网格”的正确解决方案。

    WPF中的立即模式绘图

    WPF 基于 DirectX,因此本质上它已经在幕后使用屏幕缓冲区位图(称为后台缓冲区)。

    在 WFP 中使用即时模式绘图的方法是将单元格创建为 GeometryDrawing(而不是 Shape,这是保留模式)。 GemoetryDrawing 通常非常快,因为 GemoetryDrawing 对象直接映射到 DirectX 基元;它们不是作为框架元素单独布置和跟踪的,因此它们非常轻量级——您可以拥有大量它们而不会对性能产生不利影响。

    将 GeometryDrawing 选择到 DrawingImage(这实际上是您的后台缓冲区)中,您将获得一个快速变化的屏幕图像。在幕后,WPF 完全符合您的预期——即将每个矩形绘制到图像缓冲区上。

    同样,不要使用形状——这些是框架元素和 当他们参与布局时会产生大量的开销。例如,请勿使用矩形 ,但使用 矩形几何反而。

    优化

    您可能会考虑更多优化:
  • 重用 GeometryDrawing 对象——只需更改位置和大小
  • 如果网格有最大尺寸,则预先创建对象
  • 只修改那些改变的 GeometryDrawing 对象——这样 WPF 就不会不必要地刷新它们
  • 在“阶段”中填充位图——也就是说,对于不同的缩放级别,总是更新到比前一个大得多的网格,并使用缩放将其缩小。例如,从 10x10 网格直接移动到 20x20 网格,但将其缩小 55% 以显示 11x11 方块。这样,当从 11x11 一直缩放到 20x20 时,您的 GeometryDrawing 对象永远不会改变;仅更改位图上的缩放比例,因此更新速度非常快。

  • 编辑:逐帧渲染

    覆盖 OnRender正如答案中所建议的那样,为这个问题授予了赏金。然后你基本上在 Canvas 上绘制整个场景。

    使用 DirectX 进行绝对控制

    或者,如果您想对每一帧进行绝对控制,请考虑使用原始 DirectX。

    关于c# - 使用 WPF 绘制图像网格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5668181/

    相关文章:

    c# - 我应该在多个上下文中使用 ViewModels/模型吗?

    c# - 在不安装的情况下运行 Windows 服务应用程序

    c# - 呈现线程上发生未指定的错误。 (NotifyPartitionIsZombie)

    c# - 提交时 AjaxOption 中的字符串参数为 null 但在响应中显示

    c# - 动态设置网格列/行宽度/高度

    java - 网格以及 x 和 y 坐标

    wpf - 如何防止输入控件从 TextCompositionManager 窃取空格字符?

    c# - 如何在wpf应用程序中显示网页的信息

    c# - 组合框选择的操作已更改

    html - 内容长度可变的网格布局