我正在开发一个自定义的WPF框架元素,该元素写入WriteableBitmap,然后在元素Onender()中显示该位图.

由于写入WriteableBitmap可能会有点慢(由于我当时正在计算的算法),而且我需要显示其中36个元素,因此我想在后台线程上完成更新WriteableBitmap的工作.

所以我想出了以下方法,大大提高了性能.问题是,如果我只创建8个或更少的这些元素,它可以正常工作,但如果我创建更多,例如请求的36个,当您调整窗口大小时,前8个之后的所有元素都会闪烁?

你知道是什么导致了这种情况吗?

渲染器元素:

public class Renderer : FrameworkElement
{
    private WriteableBitmap? _bitmap, _previousBitmap;
    private long _pBackBuffer = 0;
    private int _backBufferStride = 0, _backBufferWidth = 0, _backBufferHeight = 0;
    private SemaphoreSlim _semaphore = new(1, 1);

    private void ResizeBitmap()
    {
        int width = (int)ActualWidth;
        int height = (int)ActualHeight;

        if (width <= 0 || height <= 0) 
            return;
        
        if (_bitmap == null || width != _bitmap.PixelWidth || height != _bitmap.PixelHeight)
        {
            _previousBitmap = _bitmap;
            _bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
            _pBackBuffer = _bitmap.BackBuffer;
            _backBufferStride = _bitmap.BackBufferStride;
            _backBufferWidth = width;
            _backBufferHeight = height;

            // fill with blue for debugging purposes
            byte[] data = new byte[_bitmap.BackBufferStride * _bitmap.PixelHeight];
            for (int i = 0; i < _bitmap.PixelHeight; ++i)
            {
                for (int j = 0; j < _bitmap.PixelWidth; ++j)
                {
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 0] = 255;
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 1] = 0;
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 2] = 0;
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 3] = 255;
                }
            }

            _bitmap.WritePixels(new Int32Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), data, _bitmap.BackBufferStride, 0);
        }
    }

    public void InvalidateRender() => WriteToBitmapInWorkerThread();

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        if (_bitmap == null)
            return;

        _semaphore.Wait();

        _bitmap.Lock();
        _bitmap.AddDirtyRect(new Int32Rect(0, 0, (int)_bitmap.Width, (int)_bitmap.Height));
        _bitmap.Unlock();

        drawingContext.DrawImage(_bitmap, new Rect(0, 0, ActualWidth, ActualHeight));

        _semaphore.Release();
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);

        _semaphore.Wait();
        ResizeBitmap();
        _semaphore.Release();

        WriteToBitmapInWorkerThread();
    }

    private void WriteToBitmapInWorkerThread()
    {
        // do some writing to the bitmap that is slow (so run on a worker thread)
        Task.Run(() =>
        {
            _semaphore.Wait();

            unsafe
            {
                int* pPointer = (int*)_pBackBuffer;
                int stride = _backBufferStride / 4;

                // simulate slowness
                Thread.Sleep(10);

                // render a gradient for demo purposes
                for (int i = 0; i < _backBufferHeight; ++i)
                {
                    byte x = (byte)(255d / _backBufferHeight * i);
                    for (int j = 0; j < _backBufferWidth; ++j)
                    {
                        pPointer[i * stride + j] = 255 << 24 | x << 16 | x << 8 | 255;
                    }
                }
            }

            _semaphore.Release();

            Dispatcher.BeginInvoke(DispatcherPriority.Render, () => InvalidateVisual());
        });
    }
}

主窗口 :

 <Grid Background="Orange">
     <ItemsControl ItemsSource="{Binding Items}">
         <ItemsControl.ItemsPanel>
             <ItemsPanelTemplate>
                 <UniformGrid />
             </ItemsPanelTemplate>
         </ItemsControl.ItemsPanel>
         <ItemsControl.ItemTemplate>
             <DataTemplate>
                 <Border Margin="2"
                         Padding="2"
                         BorderThickness="1"
                         BorderBrush="Red">
                     <local:Renderer />
                 </Border>
             </DataTemplate>
         </ItemsControl.ItemTemplate>
     </ItemsControl>
 </Grid>
 public partial class MainWindow : Window
 {
     public List<string> Items { get; } = Enumerable.Range(0, 36).Select(e => e.ToString()).ToList();

     public MainWindow()
     {
         InitializeComponent();
         DataContext = this;
     }
 }

推荐答案

闪烁的主要原因似乎是您在创建后立即绘制了该位图,但在实际写入其缓冲区之前.这是因为每次大小更改后都会立即调用OnRender.

除此之外,在后台线程中创建新的比特图会更简单、更高效.您不需要WriteableBitmap,而是可以直接从像素缓冲区创建一个冻结的BitmapSource,如下所示,根本没有任何闪烁.

您还应该声明WriteToBitmapInWorkerThread方法async并等待其调用.您可以在SizeChanged事件处理程序中调用它,而不是重写的OnRenderSizeChanged方法,因为事件处理程序是唯一可以声明为async void的方法.

public class Renderer : FrameworkElement
{
    private BitmapSource _bitmap;

    public Renderer()
    {
        SizeChanged += async (s, e) => await WriteToBitmapInWorkerThread();
    }

    public Task InvalidateRender() => WriteToBitmapInWorkerThread();

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        if (_bitmap != null)
        {
            drawingContext.DrawImage(_bitmap, new Rect(0, 0, ActualWidth, ActualHeight));
        }
    }

    private async Task WriteToBitmapInWorkerThread()
    {
        // do some writing to the bitmap that is slow (so run on a worker thread)

        int width = (int)ActualWidth;
        int height = (int)ActualHeight;

        _bitmap = await Task.Run(() =>
        {
            int[] buffer = new int[width * height];

            // simulate slowness
            Thread.Sleep(10);

            // render a gradient for demo purposes
            for (int i = 0; i < height; ++i)
            {
                int x = (int)(255d / height * i);

                for (int j = 0; j < width; ++j)
                {
                    buffer[i * width + j] = (255 << 24) | (x << 16) | (x << 8) | 255;
                }
            }

            BitmapSource bitmap = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, buffer, width * 4);
            bitmap.Freeze();

            return bitmap;
        });

        InvalidateVisual();
    }
}

为了避免每次调用SizeChanged事件时多次调用WriteToBitmapInWorkerThread方法,您可以使用这样的计时器:

private readonly DispatcherTimer _updateTimer = new DispatcherTimer
{
    Interval = TimeSpan.FromMilliseconds(100)
};

public Renderer()
{
    _updateTimer.Tick += async (s, e) =>
    {
        _updateTimer.Stop();
        await WriteToBitmapInWorkerThread();
    };
}

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    _updateTimer.Stop();
    _updateTimer.Start();

    base.OnRenderSizeChanged(sizeInfo);
}

Csharp相关问答推荐

默认的PSHost实现是什么(用于RunspacePool)?

Selenium C#嵌套循环

ß != ss与ICU进行不区分大小写的比较

无法将blob发送到Azure -缺少HTTP标头异常

如何从顶部提取发票号作为单词发票后的第一个匹配

Serilog SQL服务器接收器使用UTC作为时间戳

如何使用C#中的图形API更新用户配置文件图像

AsNoTrackingWithIdentitySolutions()似乎不起作用?

如何注册类使用多级继承与接口

调用Task.Run()与DoSomethingAsync()有什么不同?

有空容错运算符的对立面吗?

如何向事件添加成员

ASP.NET Core MVC将值从视图传递到控制器时出现问题

在C#中,是否有与变量DISARD对应的C++类似功能?

具有类型识别的泛型方法

使用CollectionView时在.NET Maui中显示数据时出现问题

在';、';附近有错误的语法.必须声明标量变量";@Checkin";.';

在.NET8中如何反序列化为私有字段?

EF Core 7-忽略模型绑定中的虚拟属性

在使用.NET EF Core DbContext属性之前,是否应使用null判断