如何使模型层中的事件通知视图模型中的属性已更改

本文关键字:模型 属性 视图 事件 何使 通知 | 更新日期: 2023-09-27 18:36:42

我正在尝试使包含图像的WPF窗口连续显示USB相机捕获的帧。

在我的代码中,ViewModel 实例化CameraServiceClass将其_frame字段作为ref参数传递。然后,当触发NewFrame事件时,设置了该字段,但我不知道如何通知CameraViewModel.Frame属性更改,因为该事件是在_camera_service内部触发和处理的。

问题是:

  1. 我应该使用这样的ref参数吗?
  2. 事件添加到CameraServiceClass,在CameraViewModel类中侦听它,并通过提高属性更改来处理它Frame是一个好主意吗?如果是,我该怎么做?
  3. CameraServiceClass本身是否应该通知自定义 FrameReceived 事件并在事件参数中传递位图本身?如果是,我该怎么做?

我的课程是:

<Window x:Class="CameraGUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ap="clr-namespace:CameraGUI"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ap:CameraViewModel/>
    </Window.DataContext>
    <Grid>
        <Viewbox Stretch="Uniform">
            <Image Source="{Binding Frame, Mode=OneWay}" />     
        </Viewbox>
    </Grid>
</Window>

相机视图型号:

class CameraViewModel : ViewModelBase {
    System.Drawing.Bitmap _frame_camera;
    public System.Windows.Media.Imaging.BitmapImage Frame {
        get {
            if (_frame_camera != null) {
                using(MemoryStream ms = new MemoryStream())
                {
                    _frame_camera.Save(ms, ImageFormat.Bmp);
                    ms.Position = 0;
                    BitmapImage bitmapImage = new BitmapImage();
                    bitmapImage.BeginInit();
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.StreamSource = ms;
                    bitmapImage.EndInit();
                    bitmapImage.Freeze();
                    return bitmapImage;
                }
            } else return null;
        }
    }

    CameraServiceClass _camera_service;
    // CONSTRUTOR
    public CameraViewModel() {
        _camera_service = new CameraServiceClass(ref _frame_camera);         
    }
}

和相机服务类:

public class CameraServiceClass
{
    System.Drawing.Bitmap _frame;
    VideoCaptureDevice videoSource;
    // CONSTRUTOR
    public CameraServiceClass(ref System.Drawing.Bitmap bitmap) {
        _frame = bitmap;
        var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
        videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
        videoSource.NewFrame += new NewFrameEventHandler(video_NewFrame);
        videoSource.Start();
    }

    private void video_NewFrame (object sender, NewFrameEventArgs eventArgs) {
        System.Drawing.Bitmap bmp = (System.Drawing.Bitmap)eventArgs.Frame.Clone();
        _frame = bmp;
    }
}

如何使模型层中的事件通知视图模型中的属性已更改

我会在你的 CameraServiceClass 中创建一个事件,并在捕获新帧时引发它。然后在您的视图中,只需侦听该事件并对其进行响应即可。

然后,当触发事件时,可以将视图模型中的属性设置为模型中的帧,这将调用 PropertyChanged 并更新 UI。

而不是引用参数,您当前正在使用

对于你的第三个问题,我不会在论证中传递框架,而是让听众自己理解。

在您的视图类中,您将图像源绑定到属性 Frame,但我在您的视图模型类中看不到该属性。它在那里并且没有显示在您的代码段中吗? 你应该有一个属性,你的视图模型应该实现属性更改。然后,当您获取事件时,更改属性,视图将选取更改

按照 MSDN 中的"如何:在类中实现事件"教程进行操作后,按照第二个过程(使用自定义数据实现事件),我开始工作。

原则是创建一个包含数据的自定义 EventArgs,并创建一个适当的处理程序(委托)。我以前没有与事件、代表和处理程序合作过,这就是这里没有进展的原因之一......

另一种方法是使用 Peter Davidsen 的建议,即在服务类中拥有一个公共属性,并让侦听器自己获取它,但我认为这个细节是一个偏好问题(使用链接的 MSDN 教程的第一个过程,很可能更容易实现)。

新的工作代码:

MainWindow.xaml(现在在代码隐藏中有一个值转换器(未显示)):

<Window x:Class="CameraGUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ap="clr-namespace:CameraGUI"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ap:CameraViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <ap:BitmapToSource x:Key="BitmapToSource"/>
    </Window.Resources>
    <Grid>
        <Viewbox Stretch="Uniform">
            <Image Source="{Binding Frame, Mode=OneWay, Converter={StaticResource BitmapToSource}}" />     
        </Viewbox>
    </Grid>
</Window>

CameraService.cs 文件(现在带有委托、处理程序和自定义"EventArgs"类)

public class CameraServiceClass
{
    VideoCaptureDevice videoSource;
    // CONSTRUTOR
    public void Start() {
        var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
        videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
        videoSource.NewFrame += new NewFrameEventHandler(video_NewFrame);
        videoSource.Start();
    }

    private void video_NewFrame (object sender, NewFrameEventArgs eventArgs) {
        System.Drawing.Bitmap bmp = (System.Drawing.Bitmap)eventArgs.Frame.Clone();
        OnNovoFrame(new NovoFrameArgs(bmp));
    }

    public event NovoFrameEventHandler NovoFrame;
    protected void OnNovoFrame(NovoFrameArgs e) {
        if (NovoFrame != null)
            NovoFrame(this, e);
    }
}
public delegate void NovoFrameEventHandler(object sender, NovoFrameArgs e);
public class NovoFrameArgs : EventArgs {
    System.Drawing.Bitmap _frame;
    public NovoFrameArgs(System.Drawing.Bitmap fr) {
        this._frame = fr;
    }
    public System.Drawing.Bitmap Frame {
        get { return _frame; }
        set { _frame = value; }
    }
}

视图模型:

class CameraViewModel : ViewModelBase {
    CameraServiceClass _camera_service;
    public System.Drawing.Bitmap Frame {
        get { return _frame; }
        set {
            _frame = value;
            RaisePropertyChanged(() => Frame);
        }
    }
    System.Drawing.Bitmap _frame;

    // CONSTRUTOR
    public CameraViewModel() {
        _camera_service = new CameraServiceClass();
        _camera_service.NovoFrame += new NovoFrameEventHandler(_camera_service_NovoFrame);
        _camera_service.Start();          
    }
    void _camera_service_NovoFrame(object sender, NovoFrameArgs e) {
        Frame = e.Frame;
    }
}