使用 WPF 绘制图像网格 [英] Drawing a grid of images with WPF

查看:162
本文介绍了使用 WPF 绘制图像网格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to draw a grid of images/icons with WPF. The grid dimensions will vary but will typically range from 10x10 to 200x200. The user should be able to click on cells, and some cells will need to update (change image) 10-20 times per second. The grid should be able to grow and shrink in all four directions, and it should be able to switch to a different "slice" of the 3D structure it represents. My goal is to find a suitably efficient method for drawing the grid given those requirements.

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

My current implementation uses a WPF Grid. I generate row and column definitions at runtime and populate the grid with Line (for the gridlines) and Border (for the cells, since they're currently just on/off) objects at the appropriate row/column. (The Line objects span all the way across.)

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

While expanding the grid (holding down Num6) I found that it draws too slowly to redraw on every operation, so I modified it to simply add a new ColumnDefinition, Line and set of Border objects for each column of growth. That solved my growth issue, and a similar tactic could be used to make shrinking fast as well. For updating individual cells mid-simulation, I could simply store references to the cell objects and change the displayed image. Even changing to a new Z-level could be improved by only updating cell contents instead of rebuilding the entire grid.

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

However, before I could make all of those optimizations, I ran into another problem. Whenever I mouse over the grid (even at slow/normal speeds) the application's CPU usage spikes. I removed all event handlers from the grid's child elements, but that had no effect. Finally, the only way to keep CPU usage in check was to set IsHitTestVisible = false for the Grid. (Setting this for every child element of the Grid did nothing!)

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

I believe that using individual controls to build my grid is too intensive and inappropriate for this application, and that using WPF's 2D drawing mechanisms might be more efficient. I'm a beginner to WPF, though, so I'm seeking advice on how to best achieve this. From what little I've read, I might use a DrawingGroup to compose each cell's image together onto a single image for display. I could then use a click event handler for the entire image and compute the coordinates of the clicked cell by the mouse location. That seems messy, though, and I just don't know if there's a better way.

想法?

更新 1:

我接受了一位朋友的建议,转而使用 CanvasRectangle 为每个单元格.当我第一次绘制网格时,我将所有 Rectangle 的引用存储在一个二维数组中,然后当我更新网格内容时,我只需访问这些引用.

I took a friend's advice and switched to using a Canvas with a Rectangle for each cell. When I first draw the grid, I store references to all the Rectangle in a two-dimensional array, and then when I update the grid contents, I simply access those references.

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;
        }
    }
}

刚开始画格子好像更快,后续更新肯定更快,但还是有一些问题.

Drawing the grid initially seems faster, and subsequent updates are definitely faster, but there are still a few problems.

  • 无论我鼠标悬停的区域有多小,当我将鼠标悬停在有数百个单元格的网格上时,CPU 使用率仍然会飙升.

  • No matter how small the area I mouse over is, CPU usage still spikes whenever I mouse over the grid when it has more than a few hundred cells.

更新仍然太慢,所以当我按住向上箭头键更改 Z 级(一个常见用例)时,程序一次冻结几秒钟,然后似乎跳了 50 个 Z 级立即.

Updates are still too slow, so when I hold down the up arrow key to change the Z-level (a common use case) the program freezes for seconds at a time and then appears to jump 50 Z-levels at once.

一旦网格包含约 5000 个单元格,更新将持续一秒钟.这太慢了,5000 个单元格适合典型用例.

Once the grid holds ~5000 cells, updates take on the order of one second. This is prohibitively slow, and 5000 cells fits within typical use cases.

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

I haven't yet tried the UniformGrid approach because I think it may exhibit the same problems I've already encountered. I might give it a try once I've exhausted a few more options, though.

推荐答案

你的问题

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

Your Question

Let's rephrase your question. These are your problem constraints:

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

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

Judging from these constraints, you can immediately see that you're using the wrong approach.

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

Fast refresh frame rate + many changes per frame + large number of cells + one WPF object per cell = dissaster.

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

Unless you have very fast graphics hardware and a very fast CPU, your frame rate is always going to suffer with increases in grid dimensions.

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

What your problem dictates is more like a video game or a CAD drawing program with dynamic zooming. It is lesss like a normal desktop application.

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

In other words, you want "immediate mode" drawing, not "retained mode" drawing (WPF is retained mode). That is because your constraints do not require much of the functionalities provided by treating each cell as a separate WPF object.

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

For example, you won't need layout support because each cell's position is deterministic. You won't need hit-testing support because, again, positions are deterministic. You won't need container support, because each cell is a simple rectangle (or an image). You won't need complex formatting support (e.g. transparency, rounded borders etc.) because nothing overlap. In other words, there is no benefit to use a Grid (or UniformGrid) and one WPF object per cell.

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

In order to achieve the frame rate you required, essentially you'll be drawing to a large bitmap (which covers the whole screen) -- or a "screen buffer". For your cells, simply draw to this bitmap/buffer (perhaps using GDI). Hit testing is easy as the cell positions are all deterministic.

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

This method will be fast because there is only one object (the screen buffer bitmap). You can either refresh the entire bitmap for each frame, or update only those screen positions that change, or an intelligent combination of these.

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

Notice that although you are drawing a "grid" here, you don't use a "Grid" element. Choose your algorithm and your data structures based on what your problem constraints are, not what it looks like to be the obvious solution -- in other words, a "Grid" may not be the right solution for drawing a "grid".

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

WPF is based on DirectX, so essentially it is already using a screen buffer bitmap (called the back-buffer) behind the scene.

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

The way you to use immediate mode drawing in WFP is to create the cells as GeometryDrawing's (not Shape's, which is retained mode). GemoetryDrawing is usually extremely fast because GemoetryDrawing objects map directly to DirectX primitives; they are not laid out and tracked individually as Framework Elements, so they are very light-weight -- you can have a large number of them without adversely affecting performance.

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

Select ths GeometryDrawing's into a DrawingImage (this is essentially your back-buffer) and you get a fast-changing image for your screen. Behind the scene, WPF does exactly what you expect it to do -- i.e. draw each rectangle that changes onto the image buffer.

再次强调,不要使用形状——这些是框架元素,并且在它们参与布局时产生大量的开销.例如,不要使用矩形,而是使用矩形几何.

Again, do not use Shape's -- these are Framework Elements and will incur significant overheads as they participate in layout. For instance, DO NOT USE Rectangle, but use RectangleGeometry instead.

您可以考虑的更多优化:

Several more optimizations you may consider:

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

逐帧渲染

覆盖 OnRender,如该问题的悬赏答案中所建议的那样.然后,您基本上将整个场景绘制在画布上.

Do Frame by Frame Rendering

Override OnRender as suggested in the answer awarded the bounty for this question. Then you essentially draw the entire scene on a canvas.

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

Alternatively, consider using raw DirectX if you want absolute control over each frame.

这篇关于使用 WPF 绘制图像网格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆