我试图用WPF绘制一个图像/图标的网格。网格的尺寸会有所不同,但通常从10x10到200x200不等。用户应该能够单击单元格,一些单元格将需要每秒更新(更改图像) 10-20次。网格应该能够在所有四个方向上生长和收缩,并且应该能够切换到它所代表的3D结构的一个不同的“部分”。我的目标是找到一种在这些需求下绘制网格的适当有效的方法。
我的当前实现使用WPF Grid。我在运行时生成行和列定义,并使用Line (用于网格线)和Border (针对单元格,因为它们目前只是开/关)在适当的行/列填充网格。( Line对象贯穿整个过程。)

在扩展网格(按住Num6)时,我发现它绘制得太慢,无法在每个操作上重新绘制,因此我对其进行了修改,以便为每一列增长添加一个新的ColumnDefinition、Line和一组Border对象。这解决了我的成长问题,类似的策略也可以用来使经济迅速萎缩。为了更新单个单元格的中期模拟,我可以简单地存储对单元对象的引用并更改显示的图像。即使更改为新的Z级,也可以通过只更新单元格内容而不是重建整个网格来改进。
然而,在进行所有这些优化之前,我遇到了另一个问题。每当我鼠标移动到网格上(即使在缓慢/正常的速度下),应用程序的CPU使用量就会激增。我从网格的子元素中删除了所有事件处理程序,但没有任何效果。最后,检查CPU使用情况的唯一方法是为IsHitTestVisible = false设置Grid。(为Grid的每个子元素设置它没有做任何事情!)
我认为使用单独的控件来构建我的网格对于这个应用程序来说是过于密集和不合适的,并且使用WPF的2D绘图机制可能更有效。不过,我是WPF的初学者,所以我正在寻求如何最好地实现这一目标的建议。从我所读到的内容来看,我可以使用一个DrawingGroup将每个单元格的图像组合到一个单独的图像上以供显示。然后,我可以对整个图像使用一个单击事件处理程序,并通过鼠标位置计算单击的单元格的坐标。不过,这听起来很混乱,我只是不知道有没有更好的办法。
有什么想法?
更新1:
我听取了一个朋友的建议,转而使用Canvas,每个单元格都有一个Rectangle。当我第一次绘制网格时,我将对所有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;
}
}
}最初绘制网格似乎更快,随后的更新肯定更快,但仍然存在一些问题。
我还没有尝试过UniformGrid方法,因为我认为它可能会显示出我已经遇到的同样的问题。不过,一旦我用尽了几个选择,我可能会试一试。
发布于 2011-04-26 02:22:04
你的问题
让我们重新表述一下你的问题。以下是问题的制约因素:
H 111网格和单元格的位置和布局是确定的、简单的和不交互的H 212G 213从这些约束判断,您可以立即看到您使用了错误的方法。
Reqruiement:具有小交互性的确定性位置的快速刷新
快速刷新帧速率+每帧许多变化+大量单元格+每个单元格一个WPF对象= dissaster。
除非你有非常快的图形硬件和一个非常快的CPU,你的帧速率总是会受到网格尺寸增加的影响。
你的问题更像是一个视频游戏或一个动态缩放的CAD绘图程序。它不像普通的桌面应用程序。
即时模式与保留模式绘图
换句话说,您需要“立即模式”绘图,而不是“保留模式”绘图(WPF是保留模式)。这是因为您的约束不需要通过将每个单元格作为一个单独的WPF对象来提供的许多功能。
例如,您不需要布局支持,因为每个单元格的位置都是确定性的。您将不需要命中测试支持,因为,同样,位置是确定性的。您不需要容器支持,因为每个单元格都是一个简单的矩形(或图像)。您不需要复杂的格式化支持(例如透明度、圆角边框等)。因为没有重叠。换句话说,每个单元格使用一个网格(或UniformGrid)和一个WPF对象是没有好处的。
缓冲位图的即时模式绘制的概念
为了达到所需的帧速率,基本上您将绘制到一个大位图(覆盖整个屏幕) --或一个“屏幕缓冲区”。对于单元格,只需绘制此位图/缓冲区(可能使用GDI)。命中测试很容易,因为单元格位置都是确定性的。
此方法将是快速的,因为只有一个对象(屏幕缓冲区位图)。您可以刷新每个帧的整个位图,或者只更新那些更改的屏幕位置,或者智能地组合这些位置。
注意,虽然您在这里绘制一个“网格”,但您没有使用"grid“元素。选择您的算法和数据结构是基于您的问题约束是什么,而不是它看起来是什么明显的解决方案-换句话说,“网格”可能不是绘制“网格”的正确解决方案。
WPF中的即时模式绘制
WPF是基于DirectX的,因此实际上它已经使用了场景后面的屏幕缓冲区位图(称为后台缓冲区)。
在WFP中使用即时模式绘图的方法是将单元格创建为几何图形(不是Shape's,即保留模式)。GemoetryDrawing通常非常快,因为GemoetryDrawing对象直接映射到DirectX原语;它们不是作为Framework单独布局和跟踪的,所以它们的重量非常轻--您可以在不影响性能的情况下拥有大量这些对象。
选择该几何绘图到一个DrawingImage (这本质上是您的后台缓冲区),您将得到一个快速变化的图像为您的屏幕。在场景的后面,WPF做的和你期望的完全一样--也就是把每个改变的矩形画到图像缓冲区上。
同样,不要使用形状的--这些都是框架元素,在参与布局时会产生大量的管理费用。例如,不使用矩形,而是使用。
优化
您可能会考虑其他几种优化:
重用GeometryDrawing对象--如果网格有最大大小,只需更改位置和大小,预先创建对象只修改更改过的GeometryDrawing对象--这样WPF就不会不必要地刷新它们,在“阶段”中填充位图--也就是说,对于不同的缩放级别,总是更新到一个比前一个大得多的网格,并使用缩放来缩小它。例如,从10x10网格直接移动到20x20网格,但将其缩小55%以显示11x11方格。这样,当从11x11放大到20x20时,您的GeometryDrawing对象就永远不会改变;只会改变位图上的缩放,使得更新起来非常快。。
编辑:逐帧呈现
正如答案中所建议的那样,重写OnRender为这个问题授予了赏金。然后你在画布上画出整个场景。
使用DirectX进行绝对控制
或者,如果您想要对每个帧进行绝对控制,可以考虑使用原始DirectX。
发布于 2011-04-26 21:19:08
您可以编写自己的自定义控件(基于画布、面板等)并覆盖OnRender,如下所示:
public class BigGrid : Canvas
{
private const int size = 3; // do something less hardcoded
public BigGrid()
{
}
protected override void OnRender(DrawingContext dc)
{
Pen pen = new Pen(Brushes.Black, 0.1);
// vertical lines
double pos = 0;
int count = 0;
do
{
dc.DrawLine(pen, new Point(pos, 0), new Point(pos, DesiredSize.Height));
pos += size;
count++;
}
while (pos < DesiredSize.Width);
string title = count.ToString();
// horizontal lines
pos = 0;
count = 0;
do
{
dc.DrawLine(pen, new Point(0, pos), new Point(DesiredSize.Width, pos));
pos += size;
count++;
}
while (pos < DesiredSize.Height);
// display the grid size (debug mode only!)
title += "x" + count;
dc.DrawText(new FormattedText(title, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Arial"), 20, Brushes.White), new Point(0, 0));
}
protected override Size MeasureOverride(Size availableSize)
{
return availableSize;
}
}我可以在Y笔记本电脑上成功地绘制和调整400x400网格的大小(不是竞争机器.)。
有更多花哨和更好的方法(在StreamGeometry上使用DrawingContext),但这至少是一个很好的测试工作台。
当然,您必须重写HitTestXXX方法。
发布于 2011-04-24 07:03:06
通过继续使用Canvas方法,看起来,如果您能够快速绘制网格线,那么您可以省略所有的空方块,并根据您正在做的事情的密度,大幅度减少屏幕上元素的总数。无论如何,要快速绘制网格线,可以使用如下所示的DrawingBrush:
<Grid>
<Grid.Background>
<DrawingBrush x:Name="GridBrush" Viewport="0,0,20,20" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#CCCCCC">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0 20,1"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#CCCCCC">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0 1,20"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Grid.Background>
</Grid>其结果是:

https://stackoverflow.com/questions/5668181
复制相似问题