WPF Canvas在绘制很多时冻结

时间:2021-12-09 16:53:15

Iam an beginner and doing some C# exercises. I found out about Forest Fire Model and tried to do this with WPF and for drawing iam using canvas by creating a rectange for every pixel. The problem iam getting is that the Program freezes and canvas doesnt draw anything (with the while(true) loop). Also iam deleting all childrens after an iteration, still the program is collecting GBs of RAM.

我是一名初学者并做了一些C#练习。我发现了森林火灾模型并试图用WPF做这个,并通过为每个像素创建一个矩形来使用画布绘制iam。我得到的问题是程序冻结,画布没有绘制任何东西(使用while(true)循环)。我也在迭代后删除所有的孩子,程序仍在收集GB的RAM。

Simplified Code for testing:

简化的测试代码:

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();
        }
    }
}

I also tried to draw run the "DrawForestFire()" in a Thread, with the canvas Object in a "this.Dispatcher.Invoke(() => { ... });" but it didnt made any difference for me. What is it iam doing wrong?

我还尝试在线程中绘制“DrawForestFire()”,其中canvas对象位于“this.Dispatcher.Invoke(()=> {...});”但它对我没有任何影响。我做错了什么?

And is there something better than Canvas for this kind of operations?

对于这种操作,有没有比Canvas更好的东西?

2 个解决方案

#1


4  

Instead of adding 10000 Rectangle elements to a Canvas, better draw into a single WriteableBitmap.

不是向Canvas添加10000个Rectangle元素,而是更好地绘制到单个WriteableBitmap中。

Declare an Image element in XAML

在XAML中声明一个Image元素

<Image x:Name="image"/>

and assign a WriteableBitmap to its Source property. Then use a DispatcherTimer to update the bitmap pixels:

并将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);
    }
}

#2


0  

Just for fun, here is a working forest fire implementation. I enjoyed to play around with the self-ignitition and new-tree probabilities.

只是为了好玩,这是一个有效的森林火灾实施。我喜欢玩自我点燃和新树的概率。

public partial class MainWindow : Window
{
    private enum CellState
    {
        Empty, Tree, Burning
    }

    private const int width = 400;
    private const int height = 400;

    private readonly WriteableBitmap bitmap =
        new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, null);

    private readonly byte[] buffer = new byte[3 * width * height];
    private readonly Random random = new Random();

    private readonly Dictionary<CellState, Color> stateColors =
        new Dictionary<CellState, Color>
        {
            { CellState.Empty, Colors.Black },
            { CellState.Tree, Colors.Green },
            { CellState.Burning, Colors.Yellow }
        };

    private CellState[,] cells = new CellState[height, width];

    private double ignitionProbability = 0.0001;
    private double newTreeProbability = 0.01;

    public MainWindow()
    {
        InitializeComponent();

        image.Source = bitmap;

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

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

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

    private bool IsBurning(int y, int x)
    {
        return x >= 0 && x < width && y >= 0 && y < height
            && cells[y, x] == CellState.Burning;
    }

    private bool StartsBurning(int y, int x)
    {
        return IsBurning(y - 1, x - 1)
            || IsBurning(y - 1, x)
            || IsBurning(y - 1, x + 1)
            || IsBurning(y, x - 1)
            || IsBurning(y, x + 1)
            || IsBurning(y + 1, x - 1)
            || IsBurning(y + 1, x)
            || IsBurning(y + 1, x + 1)
            || random.NextDouble() <= ignitionProbability;
    }

    private CellState GetNewState(int y, int x)
    {
        var state = cells[y, x];

        switch (state)
        {
            case CellState.Burning:
                state = CellState.Empty;
                break;

            case CellState.Empty:
                if (random.NextDouble() <= newTreeProbability)
                {
                    state = CellState.Tree;
                }
                break;

            case CellState.Tree:
                if (StartsBurning(y, x))
                {
                    state = CellState.Burning;
                }
                break;
        }

        return state;
    }

    private void UpdateCells()
    {
        var newCells = new CellState[height, width];

        for (var y = 0; y < height; y++)
        {
            for (var x = 0; x < width; x++)
            {
                newCells[y, x] = GetNewState(y, x);

                var color = stateColors[newCells[y, x]];
                var i = 3 * (width * y + x);

                buffer[i++] = color.B;
                buffer[i++] = color.G;
                buffer[i++] = color.R;
            }
        }

        cells = newCells;
    }
}

#1


4  

Instead of adding 10000 Rectangle elements to a Canvas, better draw into a single WriteableBitmap.

不是向Canvas添加10000个Rectangle元素,而是更好地绘制到单个WriteableBitmap中。

Declare an Image element in XAML

在XAML中声明一个Image元素

<Image x:Name="image"/>

and assign a WriteableBitmap to its Source property. Then use a DispatcherTimer to update the bitmap pixels:

并将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);
    }
}

#2


0  

Just for fun, here is a working forest fire implementation. I enjoyed to play around with the self-ignitition and new-tree probabilities.

只是为了好玩,这是一个有效的森林火灾实施。我喜欢玩自我点燃和新树的概率。

public partial class MainWindow : Window
{
    private enum CellState
    {
        Empty, Tree, Burning
    }

    private const int width = 400;
    private const int height = 400;

    private readonly WriteableBitmap bitmap =
        new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, null);

    private readonly byte[] buffer = new byte[3 * width * height];
    private readonly Random random = new Random();

    private readonly Dictionary<CellState, Color> stateColors =
        new Dictionary<CellState, Color>
        {
            { CellState.Empty, Colors.Black },
            { CellState.Tree, Colors.Green },
            { CellState.Burning, Colors.Yellow }
        };

    private CellState[,] cells = new CellState[height, width];

    private double ignitionProbability = 0.0001;
    private double newTreeProbability = 0.01;

    public MainWindow()
    {
        InitializeComponent();

        image.Source = bitmap;

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

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

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

    private bool IsBurning(int y, int x)
    {
        return x >= 0 && x < width && y >= 0 && y < height
            && cells[y, x] == CellState.Burning;
    }

    private bool StartsBurning(int y, int x)
    {
        return IsBurning(y - 1, x - 1)
            || IsBurning(y - 1, x)
            || IsBurning(y - 1, x + 1)
            || IsBurning(y, x - 1)
            || IsBurning(y, x + 1)
            || IsBurning(y + 1, x - 1)
            || IsBurning(y + 1, x)
            || IsBurning(y + 1, x + 1)
            || random.NextDouble() <= ignitionProbability;
    }

    private CellState GetNewState(int y, int x)
    {
        var state = cells[y, x];

        switch (state)
        {
            case CellState.Burning:
                state = CellState.Empty;
                break;

            case CellState.Empty:
                if (random.NextDouble() <= newTreeProbability)
                {
                    state = CellState.Tree;
                }
                break;

            case CellState.Tree:
                if (StartsBurning(y, x))
                {
                    state = CellState.Burning;
                }
                break;
        }

        return state;
    }

    private void UpdateCells()
    {
        var newCells = new CellState[height, width];

        for (var y = 0; y < height; y++)
        {
            for (var x = 0; x < width; x++)
            {
                newCells[y, x] = GetNewState(y, x);

                var color = stateColors[newCells[y, x]];
                var i = 3 * (width * y + x);

                buffer[i++] = color.B;
                buffer[i++] = color.G;
                buffer[i++] = color.R;
            }
        }

        cells = newCells;
    }
}