I need to display n Cameras using CVB, how can I do that?
As this questions pops up every now and then, and most of the approaches to solve this task are lacking any design pattern “for the sake of keeping the example simple” I will here describe how to tackle this problem.
What I will show you here will be split into 2 parts.
After part 1 you have a simple WPF application that is able to display a fix (known at design time) number of camera streams.
Part 2 will then make the solution a little bit more dynamic, as we are going to add displays to the view depending on the number of cameras we have loaded (not fixed at design time).
We will do this in WPF, reasons pro and con WPF will not be part of this tutorial, just take it or leave it
Another decision already made is, that this example will be designed after the MVVM pattern.
This is a bit of overhead looking at the simplicity of what we want to achieve but experience shows, that most of you guys here will use the examples and tutorials as starting point and then add further functionality.
Having a clean separation between Logic (Models), displaying this data (View) and finally having a layer to connect the view with data provided by the model classes which is done in our ViewModels therefore is essential.
So lets dive right into it and start a new WPF application in Visual Studio:
- Create a new project:
Just a screenshot of which button to hit in case you have never done this
- Select WPF App (.Net Framework)
And finally hit the create button on the bottom right.
First thing to do now is to navigate through the Menustrip of VS and make one important change:
PROJECT → “YourProjectName” Properties → Build
Here, uncheck the Prefer32bit checkbox for all configurations.
Save!
Ready? Ok lets Start with the View then.
First of all, we are going to rename our MainWindow into MainView (to stick to MVVM naming conventions only). As this sometimes can be a bit tricky we will do this by deleting the MainWindow from the solution explorer (right click on MainWindow → delete) and then adding a new View:
Just name this “MainView”
Designing the UI
We want to have a way to load a new Camea and display, lets say 2 Cameras on our MainView.
So we will divide our MainView into a small strip on top where a MenuStrip will go and then vertically divide the rest of the View into 2 parts where our Display will go.
You do this by creating Grid definitions in your MainView.xaml:
Necessary Grid definitions
Now its time to add our displays.
In order to have access to the controls, we have to add to our project references:
So on the right in the solution explorer right click on References and add Stemmer.Cvb and Stemmer.Cvb.Wpf from this path: “Your:cvb:Installation folder\ Lib\ Net”:
And we have to add Cvb to the list of namespaces in our xaml code:
Add namespace to xaml
Then simply add 2 display controls to the MainView and align them next to each other by defining which column and row of the grid they are in:
Add displays in Xaml
Now we have to use our first data binding, to tell the display, which property to get the data from, that they should display:
Data Binding
Implement the ViewModel
Now that we have our View almost set up, its time to get our ViewModel running, so that we are able to actually bind to our properties from the View.
So just add a new class to your project and call it MainViewModel.
Now in this class we have to creat our Image1 and Image2 property that we need for the display.
Also we will have a property for every Device we load.
As they will be returning a Stemmer.Cvb.Image, add Stemmer.Cvb to the usings.
Make sure that the class is public and in the classes constructor, just load the CVMock.vin for now to Mock a camera and assing its Image to our image properties.
The code for now will look like this:
MainViewModel so far
using Stemmer.Cvb;
namespace WpfApp3
{
class MainViewModel
{
public MainViewModel()
{
Device1 = DeviceFactory.Open("CVMock.vin", 0, 0);
Device2 = DeviceFactory.Open("CVMock.vin", 0, 1);
Image1 = Device1.DeviceImage;
Image2 = Device2.DeviceImage;
}
public Image Image1
{
get
{
return _image1;
}
set
{
_image1 = value;
}
}
private Image _image1;
public Image Image2
{
get
{
return _image2;
}
set
{
_image2 = value;
}
}
private Image _image2;
public Device Device1
{
get
{
return _device1;
}
set
{
_device1 = value;
}
}
private Device _device1;
public Device Device2
{
get
{
return _device2;
}
set
{
_device2 = value;
}
}
private Device _device2;
}
}
The properties are a little unhandy as you could write them shorter BUT we will need this structure later on.
Now you can start the application the first time, if you get the message, that the MainWindow can not be found, you probably messed up and renamed the MainWindow class that you should have deleted right away. If this is the case for you just change the App.xaml accordingly:
If everything works, you will have an empty window, that we are going to fill now.
There are two reasons why this Window is still empty, one of which is, that we have not started to acquire images yet.
So lets just do this in an endless loop for now:
Just add the following Method to your MainViewModel and call it from the ctor for now.
Additional usings needed
using Stemmer.Cvb.Async;
using Stemmer.Cvb.Driver;
using System.Threading.Tasks
Acquisition logic
public async Task StartPlayingAsync(Device toStartStreamingFrom)
{
toStartStreamingFrom.Stream.Start();
try
{
while (true)
{
using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
{
}
}
}
finally
{
if (toStartStreamingFrom.Stream.IsRunning)
toStartStreamingFrom.Stream.Abort();
}
}
Now we can start the Application once again aaaaaaand… still see nothing.
Sorry for this one
But, we are very close to finally seeing our camera images.
Yet we are missing some way to tell the View, that actually something in the ViewModel has changed.
To do this we have to add another class (the last one really, I promise) called ViewModelBase, with the following content (and let our MainViewModel derive from this class):
usings needed
using System.ComponentModel;
using System.Runtime.CompilerServices;
ViewModelBase implementation
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in every property we have the possibility to tell the UI, when there were changes made from the ViewModel by calling OnPropertyChanged().
This will trigger an Update on the UI and with the following ViewModel code, we should finally be able to see our cameras running.
Also we have to tell our MainView, where to look for the properties we just created, by adding the MainViewModel as datacontext to the window:
The final code so far for the MainView:
MainView.xaml
<Window x:Class="WpfApp3.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp3"
xmlns:cvb="http://www.commonvisionblox.com/wpf"
mc:Ignorable="d"
Title="MainView" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<cvb:Display Grid.Row="1" Grid.Column="0" Image="{Binding Image1}"/>
<cvb:Display Grid.Row="1" Grid.Column="1" Image="{Binding Image2}"/>
</Grid>
</Window>
And the ViewModel:
MainViewModel.cs
using Stemmer.Cvb;
using Stemmer.Cvb.Async;
using Stemmer.Cvb.Driver;
using System.Threading.Tasks;
namespace WpfApp3
{
class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Device1 = DeviceFactory.Open("CVMock.vin", 0, 0);
Device2 = DeviceFactory.Open("CVMock.vin", 0, 1);
Image1 = Device1.DeviceImage;
Image2 = Device2.DeviceImage;
StartPlayingAsync(Device1);
StartPlayingAsync(Device2);
}
public Image Image1
{
get
{
return _image1;
}
set
{
_image1 = value;
OnPropertyChanged();
}
}
private Image _image1;
public Image Image2
{
get
{
return _image2;
}
set
{
_image2 = value;
OnPropertyChanged();
}
}
private Image _image2;
public Device Device1
{
get
{
return _device1;
}
set
{
_device1 = value;
OnPropertyChanged();
}
}
private Device _device1;
public Device Device2
{
get
{
return _device2;
}
set
{
_device2 = value;
OnPropertyChanged();
}
}
private Device _device2;
public async Task StartPlayingAsync(Device toStartStreamingFrom)
{
toStartStreamingFrom.Stream.Start();
try
{
while (true)
{
using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
{
}
}
}
finally
{
if (toStartStreamingFrom.Stream.IsRunning)
toStartStreamingFrom.Stream.Abort();
}
}
}
}
Just debug the application and you should have your two displays doing their job and displaying whatever you configured in the CVMock.vin.
In my case, the final result looks like this:
What about the Model?
You might have noticed at this point, that we are not having any Model classes in our MVVM approach.
This is just because we are not doing any processing or data handling until this point. Any processing of the images we are displaying should be done in a corresponing Model class.
So guys, thats it for now.
In the next post here on this thread we will finally add our Menu strip to decide which driver to load and have a Checkbox to start and stop streaming.
Happy coding!
Chris