且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用 WPF 绘制图像网格

更新时间:2023-11-10 12:49:04

你的问题

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

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.