我正在使用MVVM社区工具包在Avalonia UI中构建一个应用程序. 简而言之,它主要是一个工具,用户可以在其中将数据输入特定的字段. 用户还可以使用图像控件从文件系统加载图像,或者使用FlashCap库从网络摄像头捕获图像.
我有一个主视图,其中包含几个SKImageView,这是一个基于Skia-Sharp的图像控件,用于Avalonia 11.
MainView还包含几个按钮,按下这些按钮(使用RelayCommand)时,会打开一个新窗口. 此窗口有两个组合框,用于获取和更改FlashCap设备或特征,以及CaptureImage按钮(使用RelayCommand),用于将当前流的快照保存到SKBitmap. 一旦SKBitmap存储了其Bitmap数据,ImageCaptureWindow将关闭.
现在,MainView中的SKImageViews源应该更新并显示捕获的图像,但实际上并非如此.
我正在努力提供所有必要的代码信息.
MainView.axaml(缩短):
<UserControl x:Class="ArtexControlValveRepCardUI.MainView"
xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ArtexControlValveRepCardUI.ViewModels"
xmlns:bh="using:ArtexControlValveRepCardUI.Styles"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:siv="clr-namespace:SkiaImageView;assembly=SkiaImageView"
xmlns:views="clr-namespace:ArtexControlValveRepCardUI.Views"
Width="1600" Height="1050"
MinWidth="1600" MinHeight="1050"
d:DesignHeight="1050" d:DesignWidth="1600"
x:DataType="vm:MainViewModel"
x:CompileBindings="False"
mc:Ignorable="d">
<Grid>
...
<siv:SKImageView Width="600" Height="600"
x:Name="SkImageView1"
HorizontalAlignment="Center" VerticalAlignment="Center"
Source="{Binding CurrentImage11, Mode=TwoWay}"
Stretch="Uniform" />
...
<Button Name="CaptureImageButton1"
Width="130" Height="35"
Content="Bild aufnehmen"
Margin="10"
HorizontalContentAlignment="Center"
FontSize="15"
Command="{Binding CaptureImageButtonPressedCommand, FallbackValue=null}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type views:MainWindow}}}"/>
...
</Grid>
</UserControl>
MainViewModel.cs(缩短):
namespace ArtexControlValveRepCardUI.ViewModels
{
public partial class MainViewModel : ObservableObject
{
...
[ObservableProperty] private SKBitmap? _currentImage11;
...
public MainViewModel()
{
...
var fileStream = new SKFileStream(_baseImagePath);
CurrentImage11 = SKBitmap.Decode(fileStream);
...
}
...
[RelayCommand]
private async Task CaptureImageButtonPressed(MainWindow ownerWindow)
{
await new ImageCaptureWindow().ShowDialog(ownerWindow);
}
...
}
}
ImageCaptureWindow.axaml(缩写):
<Window x:Class="ArtexControlValveRepCardUI.Views.ImageCaptureWindow"
xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:siv="clr-namespace:SkiaImageView;assembly=SkiaImageView"
xmlns:views="clr-namespace:ArtexControlValveRepCardUI.Views"
xmlns:vm="clr-namespace:ArtexControlValveRepCardUI.ViewModels"
Title="Kamera"
Width="800" Height="900"
MinWidth="800" MinHeight="900"
d:DesignHeight="900" d:DesignWidth="800"
x:CompileBindings="False" CanResize="False"
CornerRadius="5" ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="0" ExtendClientAreaToDecorationsHint="True"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid ...
...
<siv:SKImageView Width="750" Height="750"
HorizontalAlignment="Center" VerticalAlignment="Center"
Source="{Binding Image, Mode=TwoWay}"
Stretch="Uniform" />
...
</Grid>
<Grid ...
...
<Button Grid.Row="1" Grid.RowSpan="2"
Grid.Column="2"
x:Name="CaptureImageButton"
Click="CaptureImageButton_OnClick"
Width="100"
Margin="100,0,100,0" HorizontalAlignment="Center"
VerticalAlignment="Center" HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Classes="capture"
Command="{Binding CaptureImageButtonPressedCommand, FallbackValue=null}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type views:ImageCaptureWindow}}}">
<Svg Path="/Assets/Icons/CaptureButtonIcon.svg" />
</Button>
...
</Grid>
</Window>
ImageCaptureWindowViewModel.cs(简称:
namespace ArtexControlValveRepCardUI.ViewModels
{
public partial class ImageCaptureWindowViewModel : ObservableObject
{
...
private long frameCount;
private ImageCaptureWindow? _currentWindow;
private MainViewModel _mainViewModel;
private CaptureDevice? _captureDevice;
[ObservableProperty] private string? _statistics1;
[ObservableProperty] private string? _statistics2;
[ObservableProperty] private string? _statistics3;
[ObservableProperty] private bool? _isEnabled;
[ObservableProperty] private bool? _windowIsOpen;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(DeviceDescriptor))] private ObservableCollection<CaptureDeviceDescriptor>? _devices = new();
[ObservableProperty] [NotifyPropertyChangedFor(nameof(Characteristic))] private ObservableCollection<VideoCharacteristics>? _characteristics = new();
[ObservableProperty] private CaptureDeviceDescriptor? _deviceDescriptor;
[ObservableProperty] private VideoCharacteristics? _characteristic;
[ObservableProperty] private SKBitmap? _image;
public ImageCaptureWindowViewModel()
{
_mainViewModel = new MainViewModel();
// Enumerate capture devices:
var devices = new CaptureDevices();
// Store device list into combobox:
Devices?.Clear();
// Get device descriptor
foreach (var descriptor in devices.EnumerateDescriptors().
Where(d => d.DeviceType == DeviceTypes.DirectShow))
{
Devices?.Add(descriptor);
}
IsEnabled = true;
}
// Partial Methods for auto-generated observables
partial void OnDeviceDescriptorChanged(CaptureDeviceDescriptor? descriptor)
{
OnDeviceListChangedAsync(descriptor);
}
partial void OnCharacteristicChanged(VideoCharacteristics? characteristic)
{
OnCharacteristicsChangedAsync(characteristic);
}
[RelayCommand]
private Task OnDeviceListChangedAsync(CaptureDeviceDescriptor? descriptor)
{
if (descriptor is {})
{
Characteristics?.Clear();
foreach (var characteristic in descriptor.Characteristics)
{
if (characteristic.PixelFormat != PixelFormats.Unknown)
{
Characteristics?.Add(characteristic);
}
}
Characteristic = Characteristics?.FirstOrDefault();
}
else
{
Characteristics?.Clear();
}
DeviceDescriptor = descriptor;
return default;
}
[RelayCommand]
private async Task OnCharacteristicsChangedAsync(VideoCharacteristics? characteristics)
{
IsEnabled = false;
try
{
// Close and dispose when already opened:
if (_captureDevice is {} captureDevice)
{
captureDevice = null;
await captureDevice.StopAsync();
await captureDevice.DisposeAsync();
}
// Delete preview:
Image = null;
Statistics1 = null;
Statistics2 = null;
Statistics3 = null;
frameCount = 0;
// Descriptor gets assigned and valid characteristics are being set:
if (DeviceDescriptor is {} descriptor &&
characteristics is {})
{
// Open capture device:
captureDevice = await descriptor.OpenAsync(
characteristics,
OnPixelBufferArrivedAsync);
// Start capturing:
await captureDevice.StartAsync();
_captureDevice = captureDevice;
}
}
finally
{
IsEnabled = true;
}
}
private async Task OnPixelBufferArrivedAsync(PixelBufferScope bufferScope)
{
// Pixel buffer has arrived:
// <<NOTE: Possibly the thread context is NOT UI thread save>>
// Get image data binary:
byte[] image = bufferScope.Buffer.ExtractImage();
// Decode/convert binary image data to bitmap:
var bitmap = SKBitmap.Decode(image);
// Capture statistics:
var frameCount = Interlocked.Increment(ref this.frameCount);
var frameIndex = bufferScope.Buffer.FrameIndex;
var timeStamp = bufferScope.Buffer.Timestamp;
// PixelBuffer is no longer needed - bitmap has been copied.
bufferScope.ReleaseNow();
// Switch to UI Thread:
Dispatcher.UIThread.InvokeAsync(() =>
{
// Update bitmap:
Image = bitmap;
// Update statistics:
var realFps = frameCount / timeStamp.TotalSeconds;
var fpsByIndex = frameIndex / timeStamp.TotalSeconds;
Statistics1 = $"Frame={frameCount}/{frameIndex}";
Statistics1 = $"FPS={realFps:F3}/{fpsByIndex:F3}";
Statistics1 = $"SKBitmap={bitmap.Width}x{bitmap.Height} [{bitmap.ColorType}]";
});
}
[RelayCommand]
private Task CloseWindowButtonPressed(ImageCaptureWindow captureWindow)
{
_currentWindow = captureWindow;
_currentWindow.Close();
return Task.CompletedTask;
}
[RelayCommand]
private async Task CaptureImageButtonPressed(ImageCaptureWindow captureWindow)
{
_currentWindow = captureWindow;
var captureDevice = _captureDevice;
captureDevice.StopAsync();
_mainViewModel.CurrentImage11 = Image;
_currentWindow.Close();
}
...
}
}
当我将ImageCaptureViewModel的ObservableProperty Image存储在来自MainViewModel的ObservableProperty CurrentImage11中时,.
_mainViewModel.CurrentImage11 = Image;
...SKBitmap正确存储在该变量中,但它不会触发MainView更新其SKImageView源代码以显示更改后的结果.
我还有很多关于MVVM的知识需要学习,我显然缺少一些东西,但我找不到问题所在.有没有人能看一下我的代码,或许能帮我找出哪里出了问题?
任何帮助都将不胜感激.