Getting Started with CVB.Net

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:

grafik

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.

1 Like