# Getting started: Displaying multiple Cameras

**URL:** <https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508>\
**Category:** .Net API\
**Created:** [September 21, 2021, 8:10am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508 "2021-09-21T08:10:17Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Chris](https://yyz1.discourse-cdn.com/flex027/user_avatar/forum.commonvisionblox.com/chris/32/1154_2.png) [@Chris](https://forum.commonvisionblox.com/u/Chris)\
**Post date:** [September 21, 2021, 8:10am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/1 "2021-09-21T08:10:17Z")

</div>

**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 ( **M** odels), displaying this data ( **V** iew) and finally having a layer to connect the view with data provided by the model classes which is done in our **V** iew **M** odels therefore is essential.

So lets dive right into it and start a new WPF application in Visual Studio:

1. Create a new project:

> **Just a screenshot of which button to hit in case you have never done this**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/148b8b81dcc0b74627c137c05d008cab88ce6c99.png)

1. Select WPF App (.Net Framework)

> **Framework to select**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/c6c3929f9abb5997b9789d3bebceadcc5a4d6101.png)

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:

> **How to add new View screenshot**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/d82e85b78ca27cf5bbec816e79b9ec6128f2893e.png)

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**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/ed5396afe454f6ad4509e311efa78caca62ea92e.png)

> **And the resulting view**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/4dbca7683720259ed9f9675094c66aed8710b28b.png)

Now its time to add our displays.

In order to have access to the :cvb: controls, we have to add :cvb: 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”:

> **References to add:**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/1d7381e325165205c7df63ed4767665478924eff.png)

And we have to add Cvb to the list of namespaces in our xaml code:

> **Add namespace to xaml**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/6d176d8385fa4c021b1ddbcafc030f2eecfd0675.png)

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**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/1c998dfa57c7958c75eaedd08ced2b7ad39aa3bf.png)

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**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/7ea8b4a0a8ab508fa6b807d85922edef6e77d9db.png)

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

> **Adding a new class**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/c6cd00fe3a7802d00aa3bb4b315527e931c635e1.png)

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**
>
> ```cs
> 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:

> **Yes, I have messed up also :sweat\_smile:**
>
> ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/6303b09d8de8888649534990299994dbf87012cb.png)

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**
>
> ```cs
> 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**
>
> ```cs
> 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:

![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/134092c923df215a0bc39974d7af5b194383b542.png)

**The final code so far for the MainView:**

> **MainView.xaml**
>
> ```auto
> <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**
>
> ```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:

 ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/d24a9618e581fe246d58b06a270fe7cb062d36ec.jpeg)

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

---

<div class="post-metadata">

**Author:** ![Lukas](https://avatars.discourse-cdn.com/v4/letter/l/e9a140/32.png) [@Lukas](https://forum.commonvisionblox.com/u/Lukas)\
**Post date:** [September 21, 2021, 1:52pm UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/2 "2021-09-21T13:52:44Z")

</div>

As a smaller second step, before the larger changes to dynamically add displays, we’re going to add a menu item to start and stop the stream.

**Adding the menu**

For our purpose we only need one menu item that can be checked and unchecked. So we’re gonna create a menu with the “Aquisition” submenu containing the checkable “Grab” Item.

> **Adding menu to Xaml**
>
> ```auto
> <Menu Grid.ColumnSpan="2">
> <MenuItem Height="30" Header="Aquisition">
> <MenuItem
> Header="Grab"
> IsCheckable="True"/>
> </MenuItem>
> </Menu>
> 
> ```

**Adding grabbing property**

Now we need a property that binds to our checkbox and starts and stops the image aquisition. We start with a basic bool property. (Plus a check if the value actually changed before calling OnPropertyChanged(), which should always be done.)

> **Basic bool property**
>
> ```cs
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> ```

Then we add a call to our StartPlayingAsync() method to start the stream, when the checkbox gets checked:

> **Stream start**
>
> ```cs
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {          
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync(Device1);
> StartPlayingAsync(Device2);
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> ```

To stop the stream when the checkbox gets unchecked we only need to change the while condition in StartPlayingAsync() to stop the aquisition when the Grabbing property changes to false:

> **Stream stop**
>
> ```cs
> public async Task StartPlayingAsync(Device toStartStreamingFrom)
> {
> toStartStreamingFrom.Stream.Start();
> try
> {
> while (Grabbing)
> {
> using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
> {
> 
> }
> }
> }
> finally
> {
> if (toStartStreamingFrom.Stream.IsRunning)
> toStartStreamingFrom.Stream.Abort();
> }
> }
> 
> ```

The last step is binding the property to the checkbox, just like the images were bound to the display.

> **Binding**
>
> ```auto
> <Menu Grid.ColumnSpan="2">
> <MenuItem Height="30" Header="Aquisition">
> <MenuItem
> Header="Grab"
> IsCheckable="True"
> IsChecked="{Binding Grabbing}" />
> </MenuItem>
> </Menu>
> 
> ```

**Summary**

Just to sync codes, here is the final version:

> **MainView.xaml**
>
> ```auto
> <Window
> x:Class="WpfApp1.MainView"
> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
> xmlns:cvb="http://www.commonvisionblox.com/wpf"
> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
> xmlns:local="clr-namespace:WpfApp1"
> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
> Title="MainView"
> Width="800"
> Height="450"
> mc:Ignorable="d">
> <Window.DataContext>
> <local:MainViewModel />
> </Window.DataContext>
> <Grid>
> <Grid.RowDefinitions>
> <RowDefinition Height="30" />
> <RowDefinition />
> </Grid.RowDefinitions>
> <Grid.ColumnDefinitions>
> <ColumnDefinition />
> <ColumnDefinition />
> </Grid.ColumnDefinitions>
> 
> <Menu Grid.ColumnSpan="2">
> <MenuItem Header="Aquisition" Height="30">
> <MenuItem Header="Grab" IsCheckable="True" IsChecked="{Binding Grabbing}"/>
> </MenuItem>
> </Menu>
> 
> <cvb:Display
> Grid.Row="1"
> Grid.Column="0"
> Image="{Binding Image1}" />
> <cvb:Display
> Grid.Row="1"
> Grid.Column="1"
> Image="{Binding Image2}" />
> </Grid>
> </Window>
> 
> ```

> **MainViewModel.cs**
>
> ```cs
> using Stemmer.Cvb;
> using Stemmer.Cvb.Async;
> using Stemmer.Cvb.Driver;
> using System.Threading.Tasks;
> 
> namespace WpfApp1
> {
> public 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 bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync(Device1);
> StartPlayingAsync(Device2);
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> 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 (Grabbing)
> {
> using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
> {
> 
> }
> }
> }
> finally
> {
> if (toStartStreamingFrom.Stream.IsRunning)
> toStartStreamingFrom.Stream.Abort();
> }
> }
> }
> }
> 
> ```

See you soon,  
Lukas

---

<div class="post-metadata">

**Author:** ![Chris](https://yyz1.discourse-cdn.com/flex027/user_avatar/forum.commonvisionblox.com/chris/32/1154_2.png) [@Chris](https://forum.commonvisionblox.com/u/Chris)\
**Post date:** [September 22, 2021, 2:09pm UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/3 "2021-09-22T14:09:39Z")

</div>

Thank you @Lukas for pushing this topic forward.

Now, lets use the code we have at this point and do some processing.  
Until now we only had 2 Displays, that did nothing else than display the image that was currently acquired.  
After this little session we will have one display that shows an image that we manipulated in some way. We did some processing with the image if you will.

So… lets remove everything that had to do with the second device and only keep the Image2 property.

All that is left now should be this:

> **MainViewModel.cs**
>
> ```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);
> Image1 = Device1.DeviceImage;
> }
> 
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync(Device1);
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> 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 async Task StartPlayingAsync(Device toStartStreamingFrom)
> {
> toStartStreamingFrom.Stream.Start();
> try
> {
> while (Grabbing)
> 
> {
> using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
> {
> 
> }
> }
> }
> finally
> {
> if (toStartStreamingFrom.Stream.IsRunning)
> toStartStreamingFrom.Stream.Abort();
> }
> }
> }
> }
> 
> ```

Now, the Image2 property still exists, our second display on the View also still exists and still binds to this property.  
What we do now is, in the constructor we set the Image2 to a black image with the same size as Image1, just to have something else than blank white space on startup:

```cs
Image2 = new Image(new Size2D(Image1.Width, Image1.Height));
Image2.Initialize(0);

```

Bevore we can now use any filters on the image, we need to add a new reference and use it.  
So go to your references, add **Stemmer.Cvb.Foundation** and dont forget to add it to the usings:

```cs
using Stemmer.Cvb.Foundation;

```

Short sync:

> **This is what the ctor should look like now**
>
> ```cs
> public MainViewModel()
> {
> Device1 = DeviceFactory.Open("CVMock.vin", 0, 0);
> Image1 = Device1.DeviceImage;
> Image2 = new Image(new Size2D(Image1.Width, Image1.Height));
> Image2.Initialize(0);
> }
> 
> ```

Now, lets go into the StartPlayingAsync(Device toStartStreamingFrom) and add some filtering on our camera images and display them on the second display:

> **StartPlayingAsync implementation with filter**
>
> ```cs
> public async Task StartPlayingAsync(Device toStartStreamingFrom)
> {
> toStartStreamingFrom.Stream.Start();
> try
> {
> while (Grabbing)
> {
> using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
> {
> Image2 = Filter.Sobel(image, FilterOrientation.Horizontal, FixedFilterSize.Kernel3x3);
> }
> }
> }
> finally
> {
> if (toStartStreamingFrom.Stream.IsRunning)
> toStartStreamingFrom.Stream.Abort();
> }
> }
> 
> ```

Thats it… if you run your example right now and hit ‘Grab’ you should see something like this:

 ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/ea045bc7bda727249ee103e98f62bfec017764b7.jpeg)

Now, that was simple, wasn’t it?

Well there is a little problem with this, that you might stumble upon in the future if you are doing some high speed processing or really complex image manipulation but you dont need to worry about this for now. If you are interested feel free to reveal the spoiler below.

There is just one thing we have to keep in mind whenever we do any processing.  
Currently the Filtermethod takes 2ms-5ms (on my machine and in debug mode).  
My CVMock.vin`s FrameRate is set to 5, this means, I get 5 images per second.  
Thus I have 200ms from acquiring one image until the next image arrives.  
So we are all good here.

If you aim at higher framerates or have more complex processing tasks, your processing might eventually take longer than the time until the next image is being acquired.  
To handle this situations and prevent you from loosing images, :cvb: has a nice little RingBuffer interface, that by default is set to 3.  
So, by default you are able to buffer 3 images for processing without data getting lost. Depending on how often your processing takes longer than the acquisition this might still not be enough.

For now, dont worry about this as it wont affect this tutorial, but if you like have a look at this explanation here:  
[https://forum.commonvisionblox.com/t/getting-started-with-cvb-net/246/14](https://forum.commonvisionblox.com/t/getting-started-with-cvb-net/246/14)

Or just search the forum for RingBuffer.

**Summary**  
As always, here is the MainViewModel to sync codes, we did not touch the MainView this time.

> **MainViewModel.cs**
>
> ```cs
> using Stemmer.Cvb;
> using Stemmer.Cvb.Async;
> using Stemmer.Cvb.Driver;
> using System.Threading.Tasks;
> using Stemmer.Cvb.Foundation;
> 
> namespace WpfApp3
> {
> class MainViewModel : ViewModelBase
> {
> public MainViewModel()
> {
> Device1 = DeviceFactory.Open("CVMock.vin", 0, 0);
> Image1 = Device1.DeviceImage;
> Image2 = new Image(new Size2D(Image1.Width, Image1.Height));
> Image2.Initialize(0);
> }
> 
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync(Device1);
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> 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 async Task StartPlayingAsync(Device toStartStreamingFrom)
> {
> toStartStreamingFrom.Stream.Start();
> try
> {
> while (Grabbing)
> {
> using (StreamImage image = await toStartStreamingFrom.Stream.WaitAsync())
> {
> Image2 = Filter.Sobel(image, FilterOrientation.Horizontal, FixedFilterSize.Kernel3x3);
> }
> }
> }
> finally
> {
> if (toStartStreamingFrom.Stream.IsRunning)
> toStartStreamingFrom.Stream.Abort();
> }
> }
> }
> }
> 
> ```

Happy coding and see you soon!  
Cheers!  
Chris

---

<div class="post-metadata">

**Author:** ![Lukas](https://avatars.discourse-cdn.com/v4/letter/l/e9a140/32.png) [@Lukas](https://forum.commonvisionblox.com/u/Lukas)\
**Post date:** [September 23, 2021, 8:12am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/4 "2021-09-23T08:12:16Z")

</div>

**Part 2: Dynamically adding displays**

In this part we’re going to change our previous application, so we can open any number of devices and display their streams.

To start we need to restructure the ViewModels a bit. First we create a new DisplayViewModel, just like the MainViewModel was created. (Add a new class called DisplayViewModel, that derives from the ViewModelBase.)  
The DisplayViewModel will be responsible for handling a single device and the  
MainViewModel will then manage a collection of DisplayViewModels.

**DisplayViewModel**

We need all the current properties and the method from the MainViewModel, but with a few changes. We’ll start with the basically unchanged Device and Image properties:

> **Device and Image**
>
> ```cs
> public Image Image
> {
> get
> {
> return _image;
> }
> set
> {
> _image = value;
> OnPropertyChanged();
> }
> }
> private Image _image;
> 
> public Device Device
> {
> get
> {
> return _device;
> }
> set
> {
> _device = value;
> OnPropertyChanged();
> }
> }
> private Device _device;
> 
> ```

Again we’ll need to add Stemmer.Cvb to the usings, for the Stemmer.Cvb.Image.

The StartPlayingAsync() method can be simplified a bit. As each DisplayViewModel only has one Device, we don’t need to give the method a device it can just use the property. We still need the same usings as last time:

> **Usings**
>
> ```auto
> using Stemmer.Cvb.Async;
> using Stemmer.Cvb.Driver;
> using System.Threading.Tasks;
> 
> ```

> **StartPlayingAsync()**
>
> ```cs
> public async Task StartPlayingAsync()
> {
> Device.Stream.Start();
> try
> {
> while (Grabbing)
> {
> using (StreamImage image = await Device.Stream.WaitAsync())
> {
> 
> }
> }
> }
> finally
> {
> if (Device.Stream.IsRunning)
> Device.Stream.Abort();
> }
> }
> 
> ```

That also simplifies the Grabbing property:

> **Grabbing**
>
> ```cs
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync();
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> ```

Now the only thing still missing is the constructor, where we set our Device and Image. Then the whole class should look like this:

> **Constructor**
>
> ```cs
> public DisplayViewModel(Device device)
> {
> Device = device;
> Image = device.DeviceImage;
> }
> 
> ```

> **Full DisplayViewModel**
>
> ```cs
> using Stemmer.Cvb;
> using Stemmer.Cvb.Async;
> using Stemmer.Cvb.Driver;
> using System.Threading.Tasks;
> 
> namespace WpfApp3
> {
> public class DisplayViewModel : ViewModelBase
> {
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync();
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> public Image Image
> {
> get
> {
> return _image;
> }
> set
> {
> _image = value;
> OnPropertyChanged();
> }
> }
> private Image _image;
> 
> public Device Device
> {
> get
> {
> return _device;
> }
> set
> {
> _device = value;
> OnPropertyChanged();
> }
> }
> private Device _device;
> 
> public DisplayViewModel(Device device)
> {
> Device = device;
> Image = device.DeviceImage;
> }
> 
> public async Task StartPlayingAsync()
> {
> Device.Stream.Start();
> try
> {
> while (Grabbing)
> {
> using (StreamImage image = await Device.Stream.WaitAsync())
> {
> 
> }
> }
> }
> finally
> {
> if (Device.Stream.IsRunning)
> Device.Stream.Abort();
> }
> }
> }
> }
> 
> ```

**DisplayView**

Next we need a corresponding View for our new DisplayViewModel. Since the ViewModel contains only a single device, the View is very simple: A single display.

For the new DisplayView we’ll create a User Control this time because it won’t be its own window:

Right click the project → Add → User Control (WPF)

Then we need to again add the Cvb namespace, add a display and bind it to Image. The finished View should look like this:

> **Full DisplayView**
>
> ```auto
> <UserControl
> x:Class="WpfApp3.DisplayView"
> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
> xmlns:cvb="http://www.commonvisionblox.com/wpf"
> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
> xmlns:local="clr-namespace:WpfApp3"
> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
> d:DesignHeight="450"
> d:DesignWidth="800"
> mc:Ignorable="d">
> <Grid>
> <cvb:Display Image="{Binding Image}" />
> </Grid>
> </UserControl>
> 
> ```

**ObservableCollection**

After those boring preparations we finally get to something new. The ObservableCollection is a collection that provides notifications when changes are made to it. Since we’ve been doing that job manually for our properties, you can probably see why it’s great for data bindings.

But first we need to get rid of all the old stuff in our MainViewModel since that’s all being done in the DisplayViewModel now. (Except the Grabbing property we can use that later)

Then we’ll add and initialize the ObservableCollection containing our DisplayViewModels. To use it we’ll need to add another using:

```auto
using System.Collections.ObjectModel;

```

```cs
public ObservableCollection<DisplayViewModel> Displays { get; } = new ObservableCollection<DisplayViewModel>();

```

Now we switch to the MainView to bind this Collection to a fitting control. But before that we’ll again need to remove some old stuff.  
We no longer need the two displays and the ColumnDefinitions. Those only split the window for our two displays, and our new control will automatically fit as many displays as we need.

Then we can add our ItemsControl:

> **ItemsControl**
>
> ```auto
> <ItemsControl Grid.Row="1" ItemsSource="{Binding Displays}">
> <ItemsControl.ItemsPanel>
> <ItemsPanelTemplate>
> <UniformGrid />
> </ItemsPanelTemplate>
> </ItemsControl.ItemsPanel>
> <ItemsControl.ItemTemplate>
> <DataTemplate>
> <local:DisplayView />
> </DataTemplate>
> </ItemsControl.ItemTemplate>
> </ItemsControl>
> 
> ```

This Control allows us to bind a collection of items (our ObservableCollection Displays) and show them with the given DataTemplate (our DisplayView).

**OpenDevice Command**

Now that the basic structure is complete we can finally add the functionality to open a device in the MainViewModel. Since we only had property bindings until now. We’ll also learn how to bind methods with commands.

First we need the actual method to open a device and add a new DisplayViewModel with that device to our ObservableCollection:

> **OpenDevice method**
>
> ```cs
> public void OpenDevice()
> {
> try
> {
> var dev = FileDialogs.LoadByDialog(path => DeviceFactory.Open(path), "CVB Devices|*.vin;*.emu;*.avi");
> if (dev == null)
> return;
> 
> Displays.Add(new DisplayViewModel(dev));
> }
> catch (IOException ex)
> {
> MessageBox.Show(ex.Message, "Error loading device", MessageBoxButton.OK, MessageBoxImage.Error);
> }
> }
> 
> ```

To bind that method to a menu item we’ll need to create a command. To make it easier, we first add a new class called DelegateCommand:

> **DelegateCommand**
>
> ```cs
> using System;
> using System.Windows.Input;
> 
> namespace WpfApp3
> {
> /// <summary>
> /// Object for executing an <see cref="Action{T}"/> with optional
> /// check if the <see cref="Action{T}"/> can be executed.
> /// </summary>
> public class DelegateCommand : ICommand
> {
> private readonly Predicate<object> _canExecute;
> private readonly Action<object> _execute;
> 
> /// <summary>
> /// Eventhandler 
> /// </summary>
> public event EventHandler CanExecuteChanged;
> 
> /// <summary>
> /// Constructor.
> /// </summary>
> /// <param name="execute"></param>
> public DelegateCommand(Action<object> execute)
> : this(execute, null)
> {
> }
> 
> /// <summary>
> /// Constructor with condition for executing action.
> /// </summary>
> /// <param name="execute">Action to execute.</param>
> /// <param name="canExecute">Check before executing.</param>
> public DelegateCommand(Action<object> execute,
> Predicate<object> canExecute)
> {
> _execute = execute;
> _canExecute = canExecute;
> }
> 
> /// <summary>
> /// Condition for executing.
> /// </summary>
> /// <param name="parameter"></param>
> /// <returns>True if null or condition is complied.</returns>
> public bool CanExecute(object parameter)
> {
> return _canExecute == null ? true : _canExecute(parameter);
> }
> 
> /// <summary>
> /// Conducts the Action when command is called.
> /// </summary>
> /// <param name="parameter"></param>
> public void Execute(object parameter)
> {
> _execute(parameter);
> }
> 
> /// <summary>
> /// Invokes action when event is raised.
> /// </summary>
> public void RaiseCanExecuteChanged()
> {
> CanExecuteChanged?.Invoke(this, EventArgs.Empty);
> }
> }
> }
> 
> ```

Then we add this using to the MainViewModel:

```cs
using System.Windows.Input;

```

After that we just create a ICommand property and initialize it in the constructor with the help of our DelegateCommand.

> **Creating the Command**
>
> ```cs
> public ICommand OpenDeviceCommand { get; }
> 
> public MainViewModel()
> {
> OpenDeviceCommand = new DelegateCommand((o) => OpenDevice());
> }
> 
> ```

As a last step we create a new MenuItem in the MainView and bind the OpenDeviceCommand to it:

> **New MenuItem**
>
> ```auto
> <Menu Grid.ColumnSpan="2">
> <MenuItem Height="30" Header="Aquisition">
> <MenuItem
> Header="Grab"
> IsCheckable="True"
> IsChecked="{Binding Grabbing}" />
> </MenuItem>
> <MenuItem Header="File">
> <MenuItem Command="{Binding OpenDeviceCommand}" Header="Open" />
> </MenuItem>
> </Menu>
> 
> ```

We can finally see some progress:Try opening the CVMock.vin!

**Starting/Stopping the stream on all displays**

Now we just have to reenable the Grab button to work on all the displays. Since we didn’t delete the Grabbing property or its binding, we just have to change it a bit:

> **New Grabbing property**
>
> ```cs
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> 
> foreach (var display in Displays)
> display.Grabbing = value;
> 
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> ```

Now it relays the changes made to it to the Grabbing properties of all the DisplayViewModels in our collection.

That’s it! Now you can test the Application.

To have the finished code in one place:

> **MainViewModel**
>
> ```cs
> using Stemmer.Cvb;
> using Stemmer.Cvb.Wpf;
> using System.Collections.ObjectModel;
> using System.IO;
> using System.Windows;
> using System.Windows.Input;
> 
> namespace WpfApp3
> {
> class MainViewModel : ViewModelBase
> {
> public ObservableCollection<DisplayViewModel> Displays { get; } = new ObservableCollection<DisplayViewModel>();
> 
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> 
> foreach (var display in Displays)
> display.Grabbing = value;
> 
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> public ICommand OpenDeviceCommand { get; }
> 
> public MainViewModel()
> {
> OpenDeviceCommand = new DelegateCommand((o) => OpenDevice());
> }
> 
> public void OpenDevice()
> {
> try
> {
> var dev = FileDialogs.LoadByDialog(path => DeviceFactory.Open(path), "CVB Devices|*.vin;*.emu;*.avi");
> if (dev == null)
> return;
> 
> Displays.Add(new DisplayViewModel(dev));
> }
> catch (IOException ex)
> {
> MessageBox.Show(ex.Message, "Error loading device", MessageBoxButton.OK, MessageBoxImage.Error);
> }
> }
> 
> }
> }
> 
> ```

> **MainView**
>
> ```auto
> <Window
> x:Class="WpfApp3.MainView"
> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
> xmlns:cvb="http://www.commonvisionblox.com/wpf"
> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
> xmlns:local="clr-namespace:WpfApp3"
> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
> Title="MainView"
> Width="800"
> Height="450"
> mc:Ignorable="d">
> <Window.DataContext>
> <local:MainViewModel />
> </Window.DataContext>
> <Grid>
> <Grid.RowDefinitions>
> <RowDefinition Height="30" />
> <RowDefinition />
> </Grid.RowDefinitions>
> 
> <Menu Grid.ColumnSpan="2">
> <MenuItem Height="30" Header="Aquisition">
> <MenuItem
> Header="Grab"
> IsCheckable="True"
> IsChecked="{Binding Grabbing}" />
> </MenuItem>
> <MenuItem Header="File">
> <MenuItem Command="{Binding OpenDeviceCommand}" Header="Open" />
> </MenuItem>
> </Menu>
> 
> <ItemsControl Grid.Row="1" ItemsSource="{Binding Displays}">
> <ItemsControl.ItemsPanel>
> <ItemsPanelTemplate>
> <UniformGrid />
> </ItemsPanelTemplate>
> </ItemsControl.ItemsPanel>
> <ItemsControl.ItemTemplate>
> <DataTemplate>
> <local:DisplayView />
> </DataTemplate>
> </ItemsControl.ItemTemplate>
> </ItemsControl>
> </Grid>
> </Window>
> 
> ```

> **DisplayViewModel**
>
> ```cs
> using Stemmer.Cvb;
> using Stemmer.Cvb.Async;
> using Stemmer.Cvb.Driver;
> using System.Threading.Tasks;
> 
> namespace WpfApp3
> {
> public class DisplayViewModel : ViewModelBase
> {
> public bool Grabbing
> {
> get => _grabbing;
> set
> {
> if (value != _grabbing)
> {
> _grabbing = value;
> if (value)
> {
> StartPlayingAsync();
> }
> OnPropertyChanged();
> }
> }
> }
> private bool _grabbing;
> 
> public Image Image
> {
> get
> {
> return _image;
> }
> set
> {
> _image = value;
> OnPropertyChanged();
> }
> }
> private Image _image;
> 
> public Device Device
> {
> get
> {
> return _device;
> }
> set
> {
> _device = value;
> OnPropertyChanged();
> }
> }
> private Device _device;
> 
> public DisplayViewModel(Device device)
> {
> Device = device;
> Image = device.DeviceImage;
> }
> 
> public async Task StartPlayingAsync()
> {
> Device.Stream.Start();
> try
> {
> while (Grabbing)
> {
> using (StreamImage image = await Device.Stream.WaitAsync())
> {
> 
> }
> }
> }
> finally
> {
> if (Device.Stream.IsRunning)
> Device.Stream.Abort();
> }
> }
> }
> }
> 
> ```

> **DisplayView**
>
> ```auto
> <UserControl
> x:Class="WpfApp3.DisplayView"
> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
> xmlns:cvb="http://www.commonvisionblox.com/wpf"
> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
> xmlns:local="clr-namespace:WpfApp3"
> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
> d:DesignHeight="450"
> d:DesignWidth="800"
> mc:Ignorable="d">
> <Grid>
> <cvb:Display Image="{Binding Image}" />
> </Grid>
> </UserControl>
> 
> ```

> **DelegateCommand**
>
> ```cs
> using System;
> using System.Windows.Input;
> 
> namespace WpfApp3
> {
> /// <summary>
> /// Object for executing an <see cref="Action{T}"/> with optional
> /// check if the <see cref="Action{T}"/> can be executed.
> /// </summary>
> public class DelegateCommand : ICommand
> {
> private readonly Predicate<object> _canExecute;
> private readonly Action<object> _execute;
> 
> /// <summary>
> /// Eventhandler 
> /// </summary>
> public event EventHandler CanExecuteChanged;
> 
> /// <summary>
> /// Constructor.
> /// </summary>
> /// <param name="execute"></param>
> public DelegateCommand(Action<object> execute)
> : this(execute, null)
> {
> }
> 
> /// <summary>
> /// Constructor with condition for executing action.
> /// </summary>
> /// <param name="execute">Action to execute.</param>
> /// <param name="canExecute">Check before executing.</param>
> public DelegateCommand(Action<object> execute,
> Predicate<object> canExecute)
> {
> _execute = execute;
> _canExecute = canExecute;
> }
> 
> /// <summary>
> /// Condition for executing.
> /// </summary>
> /// <param name="parameter"></param>
> /// <returns>True if null or condition is complied.</returns>
> public bool CanExecute(object parameter)
> {
> return _canExecute == null ? true : _canExecute(parameter);
> }
> 
> /// <summary>
> /// Conducts the Action when command is called.
> /// </summary>
> /// <param name="parameter"></param>
> public void Execute(object parameter)
> {
> _execute(parameter);
> }
> 
> /// <summary>
> /// Invokes action when event is raised.
> /// </summary>
> public void RaiseCanExecuteChanged()
> {
> CanExecuteChanged?.Invoke(this, EventArgs.Empty);
> }
> }
> }
> 
> ```

> **ViewModelBase**
>
> ```cs
> using System.ComponentModel;
> using System.Runtime.CompilerServices;
> 
> namespace WpfApp3
> {
> public abstract class ViewModelBase : INotifyPropertyChanged
> {
> public event PropertyChangedEventHandler PropertyChanged;
> 
> protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
> {
> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
> }
> }
> }
> 
> ```

Hopefully it was possible to follow this tutorial. Feel free to ask if you have any questions or problems.

Lukas

---

<div class="post-metadata">

**Author:** ![Chris](https://yyz1.discourse-cdn.com/flex027/user_avatar/forum.commonvisionblox.com/chris/32/1154_2.png) [@Chris](https://forum.commonvisionblox.com/u/Chris)\
**Post date:** [September 23, 2021, 8:16am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/5 "2021-09-23T08:16:00Z")

</div>

As @Lukas cant post images yet, this is what your application should look like with 4 dynamically created Displays:

 ![image](https://canada1.discourse-cdn.com/flex027/uploads/commonvisionblox/original/1X/bea71e6d036f03328fd5045ed30a32374627d9d0.png)

---

<div class="post-metadata">

**Author:** ![Gautier](https://avatars.discourse-cdn.com/v4/letter/g/7c8e57/32.png) [@Gautier](https://forum.commonvisionblox.com/u/Gautier)\
**Post date:** [March 11, 2025, 9:24am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/6 "2025-03-11T09:24:06Z")

</div>

Hello,  
First of all, thank you for this wonderful tutorial.

By doing Image = Device.DeviceImage, we try to display all the images in the WPF component. Which is not a good thing with a high framerate.

How to reduce the display frequency in the WPF component?

Thanking you in advance.

Gautier

---

<div class="post-metadata">

**Author:** ![TStadler](https://yyz1.discourse-cdn.com/flex027/user_avatar/forum.commonvisionblox.com/tstadler/32/308_2.png) [@TStadler](https://forum.commonvisionblox.com/u/TStadler)\
**Post date:** [March 11, 2025, 9:26am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/7 "2025-03-11T09:26:02Z")

</div>

Hello @Gautier,

the WPF Display has a property **FrameTimeMin** which you can use to reduce the **display framerate**.

Cheers,  
Tim

---

<div class="post-metadata">

**Author:** ![Gautier](https://avatars.discourse-cdn.com/v4/letter/g/7c8e57/32.png) [@Gautier](https://forum.commonvisionblox.com/u/Gautier)\
**Post date:** [March 12, 2025, 10:51am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/8 "2025-03-12T10:51:28Z")

</div>

Hello @TStadler

Thank you for your answer.  
FrameTimeMin=“hh:mm:ss”  
It seems this time is given in hours, minutes and seconds.  
I will need to refresh every 4 milliseconds.  
How can I do this ?  
Thanks in advance.

Cheers,  
Gautier

---

<div class="post-metadata">

**Author:** ![TStadler](https://yyz1.discourse-cdn.com/flex027/user_avatar/forum.commonvisionblox.com/tstadler/32/308_2.png) [@TStadler](https://forum.commonvisionblox.com/u/TStadler)\
**Post date:** [March 12, 2025, 11:56am UTC](https://forum.commonvisionblox.com/t/getting-started-displaying-multiple-cameras/1508/9 "2025-03-12T11:56:20Z")

</div>

Hello @Gautier

you would specify milliseconds like this:  
FrameTimeMin=“0:0:0.004”

Cheers,  
Tim
