我的
_myBitmap
类中有一个 WritableBitmap 对象 MainWindowViewModel
,我想用它来显示来自网络摄像头的实时镜头。但本质上,我要做的就是定期向 WritableBitmap 的帧缓冲区写入一些新值,如方法_changeMyBitmapColor
所示。
预期的应用程序行为:位图的灰度级每 500 毫秒从 0 更改为 50、100、150...。
实际应用程序行为:仅当调整应用程序窗口大小或按下虚拟组合框时,位图颜色的更改才会反映在 UI 上。 颜色从 0 变为 50,并且似乎永远保持在 50。此外,仅当调整窗口大小或按下
MainWindow.axaml
我不知道为什么调整窗口大小或在 UI 上点击虚拟组合框会更新显示的位图。我也不明白为什么显示的位图在应用程序的整个生命周期中似乎只改变一次颜色。 从控制台日志中,可以确认 WritableBitmap
_myBitmap
的帧缓冲区每 500 毫秒成功修改一次。
这是一个可重现的示例。我实现了 ReactiveUI 方法,如本教程中所述。由于项目中有不安全的方法,所以
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
中有一个TryUpdateBitmap.csproj
。该项目是使用默认的 Avalonia MVVM 项目模板创建的,运行:
dotnet new avalonia.mvvm -o TryUpdateBitmap
文件:MainWindowViewModel
namespace TryUpdateBitmap.ViewModels;
using Avalonia.Media.Imaging;
using Avalonia;
using Avalonia.Platform;
using System.Threading;
using System;
using System.Threading.Tasks;
using ReactiveUI;
public class MainWindowViewModel : ReactiveObject
{
public MainWindowViewModel()
{
// Assume the bitmap is of shape 640x480 with 4 channels
_myBitmap = new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
Console.WriteLine("Start app");
Task.Run(_updateMyBitmapColorPeriodicallyAsync);
}
public WriteableBitmap MyBitmap
{
get => _myBitmap;
set => this.RaiseAndSetIfChanged(ref _myBitmap, value);
}
private WriteableBitmap _myBitmap;
private async Task _updateMyBitmapColorPeriodicallyAsync()
{
PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500)); // Color of my bitmap pixels changes every 500ms
int colorOption = 0;
while (await timer.WaitForNextTickAsync())
{
_changeMyBitmapColor(colorOption);
colorOption = (colorOption + 1) % 5; // there will be 3 different color options
Console.WriteLine("Bitmap modified!");
}
}
private unsafe void _changeMyBitmapColor(int colorOption)
{
byte[] pixelValueOptions = [50, 100, 150, 200, 225];
using ILockedFramebuffer frameBuffer = MyBitmap.Lock();
void* dataStart = (void*)frameBuffer.Address;
Span<byte> buffer = new Span<byte>(dataStart, 640 * 480 * 4); // assume each pixel is 1 byte in size and my image has 4 channels
buffer.Fill(pixelValueOptions[colorOption]); // fill the bitmap memory buffer with some values
}
}
文件:MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:TryUpdateBitmap.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="TryUpdateBitmap.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="IHaveGivenUpIHaveLetMyselfDownIWillRunAroundAndDesertMyself">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid ColumnDefinitions="640" RowDefinitions="500">
<Image Source="{Binding MyBitmap, Mode=TwoWay}" Grid.Column="0" Grid.Row="0"/>
<ComboBox Grid.Column="0" Grid.Row="1"/>
</Grid>
</Window>
非常感谢任何帮助!新年快乐。
我找到了两个可行的解决方案,都实现了
INotifyPropertyChange
接口,而不是使用 本指南中的
ReactiveUI
。
所以这种方法的关键是改变属性
_myBitmap
引用的writeablebitmap对象。销毁并重新分配新的位图实例并将每个帧分配给 _myBitmap
对于性能而言可能并不理想,因此将像素以字节形式写入其中更为可取。但在这种情况下,_myBitmap
引用的实例始终保持不变,事情就无法正常工作。
INotifyPropertyChange
文件:MainWindowViewModel
namespace TryUpdateBitmap.ViewModels;
using Avalonia.Media.Imaging;
using Avalonia;
using Avalonia.Platform;
using System.Threading;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.ComponentModel;
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
// Assume the bitmap is of shape 640x480 with 4 channels
_myBitmap = new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
_currentBitmapIndex = 0;
_scratchBoards = [];
_scratchBoards.Add(new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque));
_scratchBoards.Add(new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque));
Console.WriteLine("Start app");
Task.Run(_updateMyBitmapColorPeriodicallyAsync);
}
public WriteableBitmap MyBitmap
{
get => _myBitmap;
set
{
_myBitmap = value;
OnPropertyChanged(nameof(MyBitmap));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private WriteableBitmap _myBitmap;
private int _currentBitmapIndex;
private List<WriteableBitmap> _scratchBoards;
private async Task _updateMyBitmapColorPeriodicallyAsync()
{
PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500)); // Color of my bitmap pixels changes every 500ms
int colorOption = 0;
while (await timer.WaitForNextTickAsync())
{
_changeMyBitmapColor(colorOption);
colorOption = (colorOption + 1) % 5; // there will be 3 different color options
Console.WriteLine("Bitmap modified!");
_currentBitmapIndex = (_currentBitmapIndex + 1) % 2;
}
}
private unsafe void _changeMyBitmapColor(int colorOption)
{
byte[] pixelValueOptions = [50, 100, 150, 200, 225];
using ILockedFramebuffer frameBuffer = _scratchBoards[_currentBitmapIndex].Lock();
void* dataStart = (void*)frameBuffer.Address;
Span<byte> buffer = new Span<byte>(dataStart, 640 * 480 * 4); // assume each pixel is 1 byte in size and my image has 4 channels
buffer.Fill(pixelValueOptions[colorOption]); // fill the bitmap memory buffer with some values
MyBitmap = _scratchBoards[_currentBitmapIndex];
}
}
通过在
MyBitmap
中的两个元素之间交替 _scratchBoards
引用的 writablebitmap 实例(注意不要修改私有成员 _myBitmap
),保证每帧都会触发 set
的 MyBitmap
访问器,这通过 PropertyChanged
引发 OnPropertyChanged
事件,然后通知视图重新渲染显示的图像。
CommunityToolkit
文件:MainWindowViewModel
namespace TryUpdateBitmap.ViewModels;
using Avalonia.Media.Imaging;
using Avalonia;
using Avalonia.Platform;
using System.Threading;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
public partial class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel()
{
// Assume the bitmap is of shape 640x480 with 4 channels
_myBitmap = new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
_currentBitmapIndex = 0;
_scratchBoards = [];
_scratchBoards.Add(new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque));
_scratchBoards.Add(new WriteableBitmap(new PixelSize(640, 480), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque));
Console.WriteLine("Start app");
Task.Run(_updateMyBitmapColorPeriodicallyAsync);
}
[ObservableProperty]
private WriteableBitmap _myBitmap;
private int _currentBitmapIndex;
private List<WriteableBitmap> _scratchBoards;
private async Task _updateMyBitmapColorPeriodicallyAsync()
{
PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500)); // Color of my bitmap pixels changes every 500ms
int colorOption = 0;
while (await timer.WaitForNextTickAsync())
{
_changeMyBitmapColor(colorOption);
colorOption = (colorOption + 1) % 5; // there will be 3 different color options
Console.WriteLine("Bitmap modified!");
_currentBitmapIndex = (_currentBitmapIndex + 1) % 2;
}
}
private unsafe void _changeMyBitmapColor(int colorOption)
{
byte[] pixelValueOptions = [50, 100, 150, 200, 225];
using ILockedFramebuffer frameBuffer = _scratchBoards[_currentBitmapIndex].Lock();
void* dataStart = (void*)frameBuffer.Address;
Span<byte> buffer = new Span<byte>(dataStart, 640 * 480 * 4); // assume each pixel is 1 byte in size and my image has 4 channels
buffer.Fill(pixelValueOptions[colorOption]); // fill the bitmap memory buffer with some values
MyBitmap = _scratchBoards[_currentBitmapIndex]; // note that MyBitmap is implemented by community toolkit since _myBitmap is marked as ObservableProperty
}
}
非常感谢CommunityToolkit,所需的代码显着减少。据我所知,这种方法在底层做了与解决方案 1 类似的事情,但它抽象了很多样板文件。我们要做的就是将
MainWindowViewModel
类标记为partial,继承自ObservableObject
并将私有属性_myBitmap
标记为ObservableProperty
。它将生成公共字段MyBitmap
,并通过更改MyBitmap
(而不是_myBitmap
)引用的对象,它通知属性更改,并且将重新渲染显示的位图。
这两种提议的解决方案都有效,但可能不是最佳方法。分配了两个额外的 writeablebitmap 实例,并且在列表中的元素之间交替对象引用似乎有点有趣。 总的来说,我对 Avalonia、MVVM 或 C# 还很陌生。也许使用 ReactiveUI 方法或使用
Avalonia.Threading.Dispatch.UIThread
更简单,但我还没有让它发挥作用。还没有。