如何在Model层中创建一个事件,在ViewModel中通知PropertyChanged

时间:2013-10-24 16:43:07

标签: c# events mvvm

我正在尝试制作一个包含图像的WPF窗口,连续显示USB摄像头拍摄的帧。

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

问题是:

  1. 我应该使用这样的ref参数吗?
  2. 将事件添加到CameraServiceClass,在CameraViewModel类中收听,并通过提升Frame属性来处理它是不是一个好主意?如果是的话,我该怎么做?
  3. CameraServiceClass本身是否应该通知自定义FrameReceived事件并在事件args中传递Bitmap?如果是的话,我该怎么做?
  4. 我的课程是:

    <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>
    

    Camera ViewModel:

    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);         
        }
    }
    

    和CameraServiceClass:

    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;
        }
    }
    

3 个答案:

答案 0 :(得分:1)

我会在CameraServiceClass中创建一个事件,并在捕获新帧时提高该事件。然后在你的viewmodel中只需监听该事件并对其做出反应。

然后,当触发事件时,您可以在viewmodel中将属性设置为模型中的框架,该框架将调用PropertyChanged并更新UI。

您正在使用

,而不是参考参数

对于你的第三个问题,我不会在参数中传递帧,而是让听众自己得到它。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

按照MSDN中的“如何:在类中实现事件”教程后,按照第二个过程(使用自定义数据实现事件),我完成了这项工作。

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

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

新工作代码:

MainWindow.xaml(现在代码隐藏中有一个valueconverter(未显示)):

<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; }
    }
}

ViewModel:

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;
    }

}