heltonbiker
heltonbiker

Reputation: 27575

How to make an event in the Model layer notify PropertyChanged in the ViewModel

I am trying to make a WPF window containing an Image display continuously the frames captured by a USB Camera.

In my code, the ViewModel instantiates a CameraServiceClass passing its _frame field as a ref parameter. Then, when the NewFrame event is triggered, the field is set, but I don't know how to notify of CameraViewModel.Frame property change, since the event is fired and handled inside _camera_service.

Questions are:

  1. Should I be using a ref parameter like this?
  2. Would it be a good idea to add an event to CameraServiceClass, listen to it in the CameraViewModel class, and handle it by raising Frame property changed? If yes, how would I do it?
  3. Should the CameraServiceClass itself notify a custom FrameReceived event and pass the Bitmap itself in the event args? If yes, how would I do it?

My classes are:

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

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

Upvotes: 0

Views: 305

Answers (3)

heltonbiker
heltonbiker

Reputation: 27575

After following the "How to: Implement Events in Your Class" tutorial in MSDN, following the second procedure (implementing events with custom data), I got this working.

The principle was to create a custom EventArgs which contains data, and also create an appropriate handler (delegate). I hadn't worked with events, delegates and handlers before, and that was one of the reasons things were not going forward here...

An alternative would be to use Peter Davidsen suggestion of having a public property in the service class and let the listener to get it itself, but I think this detail is a matter of preference (most probably easier to implement, using the first procedure of the linked MSDN tutorial).

New working code:

MainWindow.xaml (now with a valueconverter in codebehind (not shown)):

<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 file (now with a delegate, a handler and a custom "EventArgs" class)

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

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

}

Upvotes: 0

Peter Davidsen
Peter Davidsen

Reputation: 678

I would create an event in your CameraServiceClass and raise that whenever a new frame has been captured. Then in your viewmodel simply listen to that event and react to it.

Then when the event is triggered, you can set a property in your viewmodel to the frame from your model, which will call the PropertyChanged and update the UI.

Instead of the ref paramater, you're currently using

And for your third question, I wouldn't pass the frame in the argument, but instead let the listeners get it themselves.

Upvotes: 1

o_weisman
o_weisman

Reputation: 604

In your view class you have your image source bound to a property Frame, but I don' see that property in your view model class. Is it there and just not shown in your snippet? You should have a property and your view model should implement PropertyChanged. Then when you get the event, change the property and the view will pick up the change

Upvotes: 0

Related Questions