我正在使用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的知识需要学习,我显然缺少一些东西,但我找不到问题所在.有没有人能看一下我的代码,或许能帮我找出哪里出了问题?

任何帮助都将不胜感激.

推荐答案

我发现微软的MVVM社区工具包有messaging (sender, recipients)个.为了解决我的问题,我通过创建如下信使类来实现此消息传递系统:

using CommunityToolkit.Mvvm.Messaging.Messages;

namespace ArtexControlValveRepCardUI.Messages
{
    public class UpdateImageMessage : ValueChangedMessage<byte[]>
    {
        public UpdateImageMessage(byte[] value) : base(value)
        {
        }
    }
}

然后,My ViewModel使用IRecipient接口实现这些Messenger类:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;

namespace ArtexControlValveRepCardUI.ViewModels
{
    public partial class MainViewModel : ObservableObject, IRecipient<UpdateImageMessage>

}

在接收的ViewModel的类构造函数中,我使用WeakRefereneMessenger注册消息:

public MainViewModel()
{
    WeakReferenceMessenger.Default.Register<UpdateImageMessage>(this);
}

现在,我可以使用来自IRecipient接口的接收方法从另一个视图模型接收数据并处理我的代码:

public void Receive(UpdateImageMessage message)
{
    _receivedImageData = message.Value;

    DecodeAndUpdateBitmap(_receivedImageData);
    LoadImageBasedOnPressedCaptureButton();
}

要发送消息,我只需在希望从中发送数据的ViewModel中使用WeakReferenceMessengers Send方法:

using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;

namespace ArtexControlValveRepCardUI.ViewModels
{
    public partial class ImageCaptureWindowViewModel : ObservableObject
    {
        [RelayCommand]
        public async Task CaptureImageButtonPressed(byte[] value)
        {
            if (_captureDevice != null)
            {
                value = _imageData;
                WeakReferenceMessenger.Default.Send(new UpdateImageMessage(value));
            }    
           
            await _captureDevice.StopAsync();
            CurrentImageCaptureWindow?.Close();
        }
    }
}

我希望这对任何可能与我有过同样问题的人有所帮助.

如果任何人对如何更有效地完成这项工作有一些建议,或者如果我的解释不够清楚,请让我知道!

Csharp相关问答推荐

获取Windows和Linux上的下载文件夹

当MD5被废弃时,如何在Blazor WASM中使用它?

Microsoft.AspNetCore.Mvc. Controller Base.用户:属性或索引器Controller Base.用户无法分配给--它是只读的

为什么Blazor值在更改后没有立即呈现?

在一个模拟上设置一个方法,该模拟具有一个参数,该参数是一个numc函数表达式

如何将Kafka消息时间戳转换为C#中的日期和时间格式?

REST API端点中异步后台代码执行的类型

Azure函数中实体框架核心的依赖注入

如何将此方法参数化并使其更灵活?

如何在C#中实现非抛出`MinBy`?

自定义列表按字符串的部分排序

我什么时候应该在Dapper中使用Connection.OpenAsync?

如何在.NET MAUI中最大化GraphicsView的大小?

C#中类库项目的源代码生成器

使用Blazor WebAssembly提高初始页面加载时间的性能

在C#/ASP.NET Core 7中,什么可能导致POST请求作为GET请求发送

如何在.NET8中使用Blazor Web App(WebAssembly)托管服务器端控制器?

如何在flutter dart中使用publicKey.xml文件进行rsa加密,我遇到了问题Exception:Could not parse BigInt

将ValueTask发送给调用者

在SQL中删除少于24小时的令牌