Getting Deep into .net

May 11, 2013

Transforming WinForms Camera control to WPF in MVVM style

Filed under: .NET,WPF — goldytech @ 1:46 pm
Tags:

 

WHY MVVM

Winforms technology enjoyed its dominance until Windows Presentation Foundation (WPF) made an entry and completely over took this legacy technology. It is now the preferred choice for developing the enterprise scale apps for desktops. It was an easy decision for companies to migrate their existing winforms code by doing simply copy and paste option. As most of the code behind written in winforms would just run in WPF, obviously the UI needs to be redeveloped. But that would defy the purpose of using the rich technology of WPF which supports data binding and eye catching glossy UI. Moreover if you are developing in WPF and are not adapting to the MVVM pattern then you are committing a serious crime. Eventually your app will grow and it would get nightmares for maintaining it.

In this blog post I talk about how I converted existing Winform Camera Control and encapsulated its entire functionality in the WPF User control so it can be reused across the project.

GETTING STARTED

Just let me introduce to you about the functionality of this project. There should be live streaming from the webcam in your application and the user should be able to take the snapshot of the on going frame. The image should be saved in users Pictures library folder. Quite a simple requirement and there are tons of samples out there which can help us to nail this problem, but all those solutions violates the MVVM pattern. My solution follows the MVVM pattern religiously and still achieves the desired functionality.

I’m using Aforge framework to capture the camera output stream. This is quite extensive framework which does more than image processing. You can get more info by reading its documentation.

Create a new WPF application. Download MVVM Light toolkit libraries from the Nuget. Now add the Aforge libraries. You would require the following

  • AForge.Controls
  • AForge.Video
  • AForge.Video.DirectShow

Please note that all these above Aforge libraries can also be obtained from the Nuget.

Now comes the two important assemblies that you would require to reference in your project and they are

  • WindowsFormsIntegration
  • System.Windows.Forms

These two assemblies allow us to integrate winform controls in xaml. You can virtually use any winform control via these assemblies. Just nest it under the WindowsFormHost and you are done.

I LOVE XAML

XAML and MVVM are made for each other. One of the cool features of any XAML based technologies is rich data binding. This feature allows us to bind any visual element. The only prerequisite for data binding is that the property to which you are binding should be a dependency property.

You can also create your own dependency properties of the user control and program it to the way you want. Create a new folder in your project and name it to ‘Controls’. Add a new UserControl to this folder and name it AForgeWebCam.xaml. Copy the below code in it.

  1. <UserControl x:Class="WPFWebCamMVVM.Controls.AForgeWebCam"
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008&quot;
  6.              xmlns:aforge="clr-namespace:AForge.Controls;assembly=AForge.Controls"
  7.              mc:Ignorable="d"
  8.              d:DesignHeight="300" d:DesignWidth="300">
  9.     <StackPanel>
  10.         <WindowsFormsHost Margin="10" Name="WinFormHost" Background="{x:Null}" Width="640" Height="480">
  11.             <aforge:VideoSourcePlayer x:Name="VideoPlayer" Size="640,480" />
  12.         </WindowsFormsHost>
  13.         </StackPanel>
  14.         
  15. </UserControl>

Switch to the code behind of this user control , where we would create all the required dependency properties. We would require following dependency properties.

1)AvailableCameraSources : This will list down all the attached camera with the device name and unique id. This property will be of type IList<DeviceInfo>. The DeviceInfo is a simple class with two properties.

  1. public class DeviceInfo
  2.     {
  3.         /// <summary>
  4.         /// Gets or sets the display name.
  5.         /// </summary>
  6.         /// <value>The display name.</value>
  7.         public string DisplayName
  8.         {
  9.             get;
  10.             set;
  11.         }
  12.  
  13.         /// <summary>
  14.         /// Gets or sets the usb string by which OS identifies the hardware.
  15.         /// </summary>
  16.         /// <value>The usb string.</value>
  17.         public string UsbString
  18.         {
  19.             get;
  20.             set;
  21.         }
  22.     }

 

  1. public static readonly DependencyProperty AvailableCameraSourcesProperty =
  2.             DependencyProperty.Register(
  3.                 "AvailableCameraSources",
  4.                 typeof(IList<DeviceInfo>),
  5.                 typeof(AForgeWebCam),
  6.                 new PropertyMetadata(GetCameraSources())); //set the default value

Have a look at line number 6 in the above code , the last argument of dependency property is PropertyMetaData , whose one constructor accepts one parameter Object type. Using this parameter the developer can set the default value of the dependency property. So here I initialize this property by calling private static method GetCameraSources. This constructor also accepts the other arguments which I will explain later.

  1. private static IList<DeviceInfo> GetCameraSources()
  2.         {
  3.             availableCameraSources = new List<DeviceInfo>();
  4.             var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
  5.             foreach (FilterInfo filterInfo in videoDevices)
  6.             {
  7.                 availableCameraSources.Add(
  8.                     new DeviceInfo { DisplayName = filterInfo.Name, UsbString = filterInfo.MonikerString });
  9.             }
  10.             return availableCameraSources;
  11.         }

2)CameraId : This is of string type . The value is basically the unique id of the camera by which the operating system identifies it. This is used by AForge VideoSourcePlayer control to render the video streaming.

  1. /// <summary>
  2.       /// The camera id property.
  3.       /// </summary>
  4.       public static readonly DependencyProperty CameraIdProperty =
  5.           DependencyProperty.Register("CameraId", typeof(string), typeof(AForgeWebCam), new PropertyMetadata(string.Empty, OnCameraValueChanged, OnCameraCoherceValueChanged));

Notice the two call-back methods OnCameraValueChanged and OnCameraCoherceValueChanged.The first call back methods handles the starting and stopping of the camera.The coerce call-back method allows us to adjust the value if of the property if it is not within the required range and throwing exception.

  1. private static void OnCameraValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
  2.       {
  3.           host = (AForgeWebCam)dependencyObject;
  4.           if (e.NewValue == null || string.IsNullOrEmpty(e.NewValue.ToString()))
  5.           {
  6.  
  7.               StopVideoDevice();
  8.               return;
  9.           }
  10.  
  11.  
  12.           if (e.NewValue != e.OldValue)
  13.           {
  14.               if (videoDevice != null)
  15.               {
  16.                   StopVideoDevice();
  17.                   InitVideoDevice(e.NewValue.ToString(), (AForgeWebCam)dependencyObject);
  18.               }
  19.               else
  20.               {
  21.                   InitVideoDevice(e.NewValue.ToString(), (AForgeWebCam)dependencyObject);
  22.               }
  23.           }
  24.       }

 

  1. private static object OnCameraCoherceValueChanged(DependencyObject dependencyObject, object basevalue)
  2.         {
  3.             host = (AForgeWebCam)dependencyObject;
  4.             if (string.IsNullOrEmpty(basevalue.ToString()))
  5.             {
  6.                 return null; // if basevalue is null then return
  7.             }
  8.  
  9.             if (availableCameraSources.FirstOrDefault(c => c.UsbString.Equals(basevalue.ToString())) == null)
  10.             {
  11.                 // check if the basevalue exists in the availableCamerasources , if not then assign the first value of the availableCameraSources
  12.                 if (availableCameraSources != null)
  13.                 {
  14.                     var firstOrDefault = availableCameraSources.FirstOrDefault();
  15.                     if (firstOrDefault != null)
  16.                     {
  17.                         return firstOrDefault.UsbString;
  18.                     }
  19.                 }
  20.             }
  21.  
  22.             return basevalue;
  23.         }

3)TakePictureCommand : This property is of ICommand type. And its default value is set to TakeInteralPictureCommand private property. The action of this command encapsulates all the logic of taking the picture. A thing to note over here is that Aforge framework does not provide this functionality out of the box. I have written the custom code to achieve this. I capture the position of the control and using the apis available in System.Drawing namespace I create new image and save it to the pictures folder. See the code below

  1. public static readonly DependencyProperty TakePictureCommandProperty =
  2.             DependencyProperty.Register(
  3.                 "TakePictureCommand",
  4.                 typeof(ICommand),
  5.                 typeof(AForgeWebCam),
  6.                 new PropertyMetadata(TakeInternalPictureCommand));

 

  1. private static void TakePicture()
  2.         {
  3.             try
  4.             {
  5.                 const int PanelWidth = 640;
  6.                 const int PanelHeight = 480;
  7.                 string fileName;
  8.  
  9.                 System.Drawing.Point pnlPoint =
  10.                     host.VideoPlayer.PointToScreen(
  11.                         new System.Drawing.Point(host.VideoPlayer.ClientRectangle.X, host.VideoPlayer.ClientRectangle.Y)); // get the position of the VideoPlayer
  12.                 using (var bmp = new Bitmap(PanelWidth, PanelHeight))
  13.                 {
  14.                     using (var g = Graphics.FromImage(bmp))
  15.                     {
  16.                         // generate the image
  17.                         g.CopyFromScreen(
  18.                             pnlPoint, System.Drawing.Point.Empty, new System.Drawing.Size(PanelWidth, PanelHeight));
  19.                     }
  20.  
  21.                     // Get MyPictures folder path
  22.                     fileName = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) + "\\"
  23.                                + DateTime.Now.ToString().Replace(":", string.Empty).Replace("/", string.Empty) + ".jpg";
  24.  
  25.                     // save the file
  26.                     bmp.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
  27.                 }
  28.  
  29.                 MessageBox.Show("Picture saved in Pictures library with filename=" + fileName, "Success");
  30.             }
  31.             catch (Exception exception)
  32.             {
  33.  
  34.                 MessageBox.Show(exception.Message);
  35.             }
  36.         }

 

CODE REUSE

The whole idea of wrapping up the entire camera functionality in the UserControl was the code reuse. This control can be now dropped into any xaml window, page or even other user control.

 

  1. <Window xmlns:Controls="clr-namespace:WPFWebCamMVVM.Controls"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
  4.         xmlns:Helpers="clr-namespace:WPFWebCamMVVM.Helpers" x:Class="WPFWebCamMVVM.MainWindow"
  5.         Title="MainWindow" Height="350" Width="525">
  6.     
  7.     <Window.Resources>
  8.       
  9.         <Helpers:DeviceInfoConverter x:Key="DeviceInfoConverter"/>
  10.         </Window.Resources>
  11.     <Window.DataContext>
  12.         <Binding Mode="OneWay" Path="Main" Source="{StaticResource Locator}"/>
  13.     </Window.DataContext>
  14.     <Grid>
  15.         <Grid.RowDefinitions>
  16.             <RowDefinition Height="Auto"/>
  17.             <RowDefinition Height="Auto"/>
  18.             <RowDefinition Height="Auto"/>
  19.             <RowDefinition Height="Auto"/>
  20.             <RowDefinition Height="Auto"/>
  21.             <RowDefinition Height="Auto"/>
  22.         </Grid.RowDefinitions>
  23.         <ComboBox x:Name="ComboBox" Grid.Row="0" Margin="10" ItemsSource="{Binding AvailableCameraSources,ElementName=WebCam}" DisplayMemberPath="DisplayName" SelectedItem="{Binding SelectedDevice}"/>
  24.         <!–Displaying the camera stream using the element binding–>
  25.         <TextBlock Grid.Row="1" Text="{Binding SelectedItem, Converter={StaticResource DeviceInfoConverter}, ElementName=ComboBox}" x:Name="UsbString" Margin="10"></TextBlock>
  26.         <Controls:AForgeWebCam x:Name="WebCam" Grid.Row="2" Margin="10" CameraId="{Binding SelectedItem, Converter={StaticResource DeviceInfoConverter}, ElementName=ComboBox}" />
  27.         <!–Comment the above two lines and Uncomment the below line if you want to assign the cameraUSB property from viewModel–>
  28.         <!–<Controls:AForgeWebCam x:Name="WebCam" Grid.Row="2" Margin="10" CameraId="{Binding CameraUsbId}" />–>
  29.         <Button Margin="10" Grid.Row="3" Command="{Binding ConnectCommand}">Connect</Button>
  30.         <Button Margin="10" Grid.Row="4" Command="{Binding TakePictureCommand, ElementName=WebCam}">Take Picture</Button>
  31.         <Button Margin="10" Grid.Row="5" Command="{Binding DisconnectCommand}">Disconnect</Button>
  32.     </Grid>
  33. </Window>

Using the element data binding capabilities I have binded the Combo box. So as a developer who is consuming your control does not have to bother about getting the camera sources because your control already exposes that as a dependency property.

The api of the usercontrol is quite simple , to connect to the camera set the CamerId property to the valid connection string, to disconnect it make it null or set the property to empty string.

 

CLOSURE

Using WinFormHost control in WPF lot of existing controls can be reused. The only challenge lies how you ensure that your MVVM design in not getting compromised with this implementation. If planned smartly then this can be taken care off. I hope you must have enjoyed the reading this article. Please leave your valuable feedback in the comments. Download the sample code from here.

About these ads

4 Comments »

  1. How can I do if I want to show two cameras at the same time? I tried, but the second one stops the first one

    Comment by Panzer — April 8, 2014 @ 3:06 pm | Reply

    • Hi
      It should work as both the cameras should have different USB ids.

      Comment by goldytech — April 10, 2014 @ 5:17 pm | Reply

  2. Very Good post.
    How can we make it take picture auto after like 5 seconds.

    Comment by tle — May 22, 2014 @ 3:24 pm | Reply

  3. Very good posts. Thank you
    How to make the control/use the control so that it will take picture auto like after 5 seconds.
    Thanks

    Comment by tle — May 22, 2014 @ 5:18 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Rubric Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 73 other followers

%d bloggers like this: