C# 使用WPF绘制图像网格

C# 使用WPF绘制图像网格,c#,wpf,grid,drawing,2d,C#,Wpf,Grid,Drawing,2d,我正在尝试用WPF绘制一个图像/图标的网格。网格尺寸会有所不同,但通常范围为10x10到200x200。用户应该能够点击单元格,有些单元格需要每秒更新(更改图像)10-20次。网格应该能够在所有四个方向上增长和收缩,并且应该能够切换到它所表示的三维结构的不同“切片”。我的目标是找到一种适当有效的方法,根据这些要求绘制网格 我当前的实现使用WPF网格。我在运行时生成行和列定义,并在适当的行/列处用行(对于网格线)和边框(对于单元格,因为它们当前刚刚打开/关闭)对象填充网格。(行对象横跨整个路径。)

我正在尝试用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;
            }
        }