Display a Stream (WPF)
We do a non-MVVM demo of a WPF app for :cvb:. You can create a MVVM app as our controls support binding (see my answer here for an example). Let’s create a new WPF App (Visual C#) and call it CvbWpfViewer:
After the wizard finishes you should see an empty MainWindow.xaml.
Don’t forget to disable the Prefer 32-bit check-box.
For this app we need the Stemmer.Cvb.dll, Stemmer.Cvb.Wpf.dll, and Stemmer.Cvb.Extensions.dll as references. To be able to use our :cvb: UI controls, we need to add our namespace to the <Window ...>
:
xmlns:cvb="http://www.commonvisionblox.com/wpf"
UI Design
First we add to rows to the grid:
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
The first row is for the Display
and the second row for the controls. Let’s add them:
<cvb:Display x:Name="display" Grid.Row="0" />
<StackPanel Grid.Row="1"
Orientation="Horizontal" Margin="0,4,0,0">
<Button x:Name="openButton" Content="Open..."
MinWidth="75" Margin="0, 0, 4, 0" />
<CheckBox x:Name="grabCheckBox" Content="Grab"
VerticalAlignment="Center" />
</StackPanel>
I also gave the Grid
a Margin="8"
. Yes, this is just for quickness sake. In a real app you should put the visual stuff in <Style>
s.
Adding Behavior
We click on the Button
and open the events in the Properties:
Double-click in the text box right to the Click event to create the event handler. Enter the following code:
private void openButton_Click(object sender, RoutedEventArgs e)
{
try
{
Device device = FileDialogs.LoadByDialog(path => DeviceFactory.Open(path), "CVB Devices|*.vin;*.emu;*.avi");
if (device == null)
return; // canceled
Device = device;
}
catch (IOException ex)
{
MessageBox.Show(ex.Message, "Error loading device", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
and
private Device Device
{
get { return _device; }
set
{
if (!ReferenceEquals(value, _device))
{
display.Image = value?.DeviceImage;
_device?.Dispose(); // old one is not needed anymore
_device = value;
}
}
}
private Device _device;
To make this compile add the following namespaces:
using Stemmer.Cvb;
using Stemmer.Cvb.Wpf;
using System.IO;
We use the helper FileDialogs.LoadByDialog
to easily open a FileDialog
that returns the opened Device
or null
if canceled. This dialog supports avi- and emu-files as well as preconfigured vin-drivers.
We also utilize a Device MainWindow.Device
property to manage life-time of prior loaded devices and to assign the DeviceImage
to the display
. (If ?.
looks unfamiliar: that is automatic null
handling: null
is assigned if value == null
.)
When we run the app and load e.g. the GenICam.vin, we see a black image in the Display
. This is normal as we don’t do “Auto Snap” and no data has been acquired, yet.
Finally we connect the grabCheckBox
’s Checked and Unchecked events by double-clicking in the text-boxes right from them. We add the following code:
private async void grabCheckBox_Checked(object sender, RoutedEventArgs e)
{
Device.Stream.Start();
openButton.IsEnabled = false;
try
{
while (grabCheckBox.IsChecked ?? false)
{
using (StreamImage image = await Device.Stream.WaitAsync())
{
// processing
}
}
}
catch (OperationCanceledException)
{
// acquisition was aborted
}
finally
{
openButton.IsEnabled = true;
}
}
(note the async
in the event handler’s signature)
and
private void grabCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (Device != null && Device.Stream.IsRunning)
Device.Stream.Abort();
}
For this code to compile you need these namespaces:
using Stemmer.Cvb.Async;
using Stemmer.Cvb.Driver;
Ok, this is a lot. But let me point you to the previous post as this is nearly the same except the adjustments for WPF:
Exit Handling
As described in the previous post, exit handling is important. Sadly WPF Windows are not derived from IDisposable
. As we store IDisposable
types (the Device
) we better implement that interface. But this doesn’t help us here as nobody calls .Dispose()
on our MainWindow
. The correct solution would be to implement the IDisposable
interface and .Abort()
the acquisition in the Window
’s Closing event. For briefness sake I put the exit handling directly into the event handler (go back to the designer, select the Window
and double-click in the text-box right to the Closing event):
private void Window_Closing(object sender, CancelEventArgs e)
{
grabCheckBox.IsChecked = false;
Device?.Dispose();
}
(We use the Closing event because at this point in time the MainWindow
is still alive.)
This is how the correct way looks like
The Closing event has also to be registered and the MainWindow
must implement IDisposable
:
public partial class MainWindow : Window, IDisposable
Then we need a finalizer:
~MainWindow()
{
Dispose(false);
}
, the .Dispose()
method:
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
, the actual dispose handler:
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Device?.Dispose();
}
}
, and last but not least our Closing event handler:
private void Window_Closing(object sender, CancelEventArgs e)
{
grabCheckBox.IsChecked = false;
}
In this case we defer clean-up to the user of our MainWindow
. If it is opened manually, the implementer is responsible for disposing it. In case the framework opens the MainWindow
, the process is exited anyways and the garbage collector will clean-up the Device
.