【问题标题】:WPF Canvas freezes when drawing a lotWPF Canvas 在大量绘制时冻结
【发布时间】:2018-06-19 12:24:42
【问题描述】:

我是初学者,正在做一些 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 更好的东西吗?

【问题讨论】:

  • 永远不要在应用程序的 UI 线程中使用 while (true)。使用计时器,例如而是 DispatcherTimer。
  • 您正在连续循环中制作 10000 个黑色矩形。难怪它变得反应迟钝。
  • 而不是绘制 10000 个矩形,考虑使用 WriteableBitmap。
  • 你见过stackoverflow.com/questions/1644874/…>吗?
  • 我尝试创建一个间隔为 2 秒的 DispatcherTimer,我确实让 DrawForestFire() 每个 Tick 运行,但它只冻结了一秒钟,什么也没发生。我想让它在画布上工作,之后我会用位图尝试一下。

标签: c# wpf canvas freeze


【解决方案1】:

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

在 XAML 中声明 Image 元素

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

【讨论】:

  • 感谢您的帮助,我用 Bitmap 实现了这个,因为我无法让它与 Canvas 一起工作。无论如何,我对您的解决方案有疑问,每个像素不是一个接一个地绘制,而是在两个循环的迭代之后绘制的。在维基百科页面中:en.wikipedia.org/wiki/Forest-fire_model 你可以看到每个像素都是一个接一个地绘制的,我应该在第二个 for 循环中使用 WritePixels 吗?如果有,具体是怎样的?
  • 模型规则同时应用于网格的所有像素。因此,您应该在每个计时器刻度上为每个像素计算一个新值。用代表实际状态的三种颜色之一替换随机颜色 - 空的、被树占据或燃烧。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2019-02-10
    • 1970-01-01
    • 1970-01-01
    • 2015-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-07
    • 1970-01-01
    相关资源
    最近更新 更多