首页 > 解决方案 > WPF Canvas 在大量绘制时冻结

问题描述

我是初学者,正在做一些 C# 练习。我发现了 Forest Fire Model 并尝试使用 WPF 和绘图来做到这一点,我通过为每个像素创建一个矩形来使用画布。我遇到的问题是程序冻结并且画布不绘制任何东西(使用 while(true) 循环)。此外,我在迭代后删除了所有子项,但程序仍在收集 GB 的 RAM。

用于测试的简化代码:

public partial class TestDrawing : Window
{
    public TestDrawing()
    {
        InitializeComponent();
    }
    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
        DrawForestFire();
    }
    private void DrawForestFire()
    {

        Random rand = new Random();

        while (true)
        {
            for (int y = 0; y < 100; y++)
            {
                for (int x = 0; x < 100; x++)
                {
                    Rectangle rectangle = new Rectangle();

                    Color color = Color.FromRgb((byte)rand.Next(200), 
                              (byte)rand.Next(200), (byte)rand.Next(200));

                    rectangle.Fill = new SolidColorBrush(color);
                    rectangle.Width = 4;
                    rectangle.Height = 4;

                    Canvas.SetTop(rectangle, y * 4);
                    Canvas.SetLeft(rectangle, x * 4);

                    canvas.Children.Add(rectangle);
                }
            }
            canvas.Children.Clear();
        }
    }
}

我还尝试在线程中绘制运行“DrawForestFire()”,画布对象位于“this.Dispatcher.Invoke(() => { ... });”中 但这对我没有任何影响。出了什么问题?

而且,对于这种操作,还有比 Canvas 更好的东西吗?

标签: c#wpfcanvasfreeze

解决方案


与其将 10000 个 Rectangle 元素添加到 Canvas 中,不如将其绘制到单个WriteableBitmap.

Image在 XAML 中声明一个元素

<Image x:Name="image"/>

并将 WriteableBitmap 分配给它的 Source 属性。然后使用 DispatcherTimer 更新位图像素:

public partial class MainWindow : Window
{
    private const int width = 100;
    private const int height = 100;

    private readonly Random random = new Random();
    private readonly byte[] buffer = new byte[3 * width * height];
    private readonly WriteableBitmap bitmap =
        new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, null);

    public MainWindow()
    {
        InitializeComponent();

        image.Source = bitmap;

        var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) };
        timer.Tick += OnTimerTick;
        timer.Start();
    }

    private void UpdateBuffer()
    {
        for (var y = 0; y < height; y++)
        {
            for (var x = 0; x < width; x++)
            {
                var i = 3 * (width * y + x);
                buffer[i++] = (byte)random.Next(200);
                buffer[i++] = (byte)random.Next(200);
                buffer[i++] = (byte)random.Next(200);
            }
        }
    }

    private async void OnTimerTick(object sender, EventArgs e)
    {
        await Task.Run(() => UpdateBuffer());

        bitmap.WritePixels(new Int32Rect(0, 0, width, height), buffer, 3 * width, 0);
    }
}

推荐阅读