Reputation: 27575
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:
ref
parameter like this?CameraServiceClass
, listen to it in the CameraViewModel
class, and handle it by raising Frame
property changed? If yes, how would I do it?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
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
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
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