C# 使用WPF绘制图像网格
我正在尝试用WPF绘制一个图像/图标的网格。网格尺寸会有所不同,但通常范围为10x10到200x200。用户应该能够点击单元格,有些单元格需要每秒更新(更改图像)10-20次。网格应该能够在所有四个方向上增长和收缩,并且应该能够切换到它所表示的三维结构的不同“切片”。我的目标是找到一种适当有效的方法,根据这些要求绘制网格 我当前的实现使用WPFC# 使用WPF绘制图像网格,c#,wpf,grid,drawing,2d,C#,Wpf,Grid,Drawing,2d,我正在尝试用WPF绘制一个图像/图标的网格。网格尺寸会有所不同,但通常范围为10x10到200x200。用户应该能够点击单元格,有些单元格需要每秒更新(更改图像)10-20次。网格应该能够在所有四个方向上增长和收缩,并且应该能够切换到它所表示的三维结构的不同“切片”。我的目标是找到一种适当有效的方法,根据这些要求绘制网格 我当前的实现使用WPF网格。我在运行时生成行和列定义,并在适当的行/列处用行(对于网格线)和边框(对于单元格,因为它们当前刚刚打开/关闭)对象填充网格。(行对象横跨整个路径。)
网格
。我在运行时生成行和列定义,并在适当的行/列处用行
(对于网格线)和边框
(对于单元格,因为它们当前刚刚打开/关闭)对象填充网格。(行
对象横跨整个路径。)
在扩展网格(按住Num6键)时,我发现它的绘制速度太慢,无法在每次操作中重新绘制,因此我对它进行了修改,只需为每列增长添加一个新的列定义
,行
和一组边框
对象。这就解决了我的增长问题,类似的策略也可以用于快速收缩。为了在模拟过程中更新单个单元,我可以简单地存储对单元对象的引用并更改显示的图像。即使更改为新的Z级别,也可以通过只更新单元格内容而不是重建整个网格来改进
然而,在我进行所有这些优化之前,我遇到了另一个问题。每当我将鼠标移到网格上时(即使是在慢速/正常速度下),应用程序的CPU使用率就会出现峰值。我从网格的子元素中删除了所有事件处理程序,但没有任何效果。最后,检查CPU使用情况的唯一方法是为网格设置ishitestvisible=false
。(为网格的每个子元素设置此选项没有任何作用!)
我认为使用单个控件来构建网格过于密集,不适合此应用程序,并且使用WPF的2D绘图机制可能更有效。不过,我是WPF的初学者,所以我正在寻求如何最好地实现这一点的建议。据我所知,我可能会使用DrawingGroup
将每个单元格的图像组合到一个单独的图像上进行显示。然后,我可以对整个图像使用单击事件处理程序,并通过鼠标位置计算单击单元格的坐标。这看起来很混乱,我只是不知道是否有更好的方法
想法
更新1:
我听取了一位朋友的建议,转而为每个单元格使用带有矩形的画布。当我第一次绘制网格时,我将对所有矩形的引用存储在二维数组中,然后当我更新网格内容时,我只访问这些引用
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;
}
}
}
private void UpdateGrid()
{
对于(int x=simGrid.Bounds.Lower.x;x如果希望单元格大小相同,我认为UniformGrid可能最合适。这样,您就不必担心在代码中设置大小
如果你实施,我会对结果非常感兴趣。通过继续使用画布
方法,看起来如果你可以快速绘制网格线,你可以忽略所有的空方块,并根据你正在做的事情的密度,大幅度减少屏幕上的元素总数。无论如何,要绘制gr快速绘制id线您可以像这样使用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>
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;
}
}
其结果是:
我建议您为此编写一个自定义面板,编写此面板应该很简单,因为您只需要覆盖MeasureOverride和ArrangeOverride方法。根据行/列的数量,您可以为每个单元格分配可用的大小。这将使您的性能优于网格,如果您想进一步优化它,您可以o在面板中实现虚拟化
当我必须创建一个滚动矩阵时,我就是这样做的,它必须显示一些文本信息,而不是图像,并且行/列的数量是不同的
如果您想让我与您分享我编写的代码,请告诉我。这里我想做一些猜测:
使用画布方法
在画布上禁用命中测试以
不要让鼠标越过CPU而发疯
将您的更改与其他更改分开跟踪
用户界面。仅更改填充
具有
自上次更新以来已更改。我
猜测缓慢的更新是
由于更新了数千个用户界面
要素及其后的发展
重新渲染所有内容
我想你会很难处理这么多的元素,如果只有一小部分是可见的,虚拟画布控件可能会有帮助,但这只会有助于滚动。要同时看到这么多单元格,你可能需要以某种方式绘制位图
下面是一个示例,其中一个单元格的VisualBrush被平铺,然后每个单元格都使用OpacityMask进行切换。下面的方法非常简洁,因为每个单元格只需要一个像素;元素可以是任意大小,您不需要复杂的代码将单元格内容快速切换到位图
该示例创建了一个1000*1000网格,有3种单元格类型(如果您只需要两种),代码可以进一步简化并删除许多循环。更新速度很快(200*200为3ms,1k*1k为100ms),滚动效果如预期,添加缩放应该不会太难
<Window ... >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25*" />
<RowDefinition Height="286*" />
</Grid.RowDefinitions>
<Button Click="Button_Click" Content="Change Cells" />
<ScrollViewer Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Auto">
<Grid x:Name="root" MouseDown="root_MouseDown" />
</ScrollViewer>
</Grid>
</Window>
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;
}
}