Binding GenAPI nodes to custom UI elements

I’ve started messing around in wpf a little bit, so it might be that my question is actually has a super easy answer but it really has me stumped!

I have a basic recorder application, but I don’t want the end user to have acces to all the genAPI nodes. Also, Iwould like some of them to be bound to different GUI elements (like sliders for exposure for example).

Two things go awry though which might be related to each other. I can’t seem to get the min, max and increment values of the slider bound to the node value to update dynamically. If for instance exposure decreases, it would be nice if the slider for framerate also changes, but for some reason it does not.

Something similar happens when I rediscover a camera; changing the sliders no longer seems to be linked to the orignial node?

So this would be what one of my sliders looks like in xaml;

<<DockPanel VerticalAlignment="Center">
            <Label Content="Exposure" Margin="10" VerticalAlignment="Center" Width="65"/>
            <TextBox Text="{Binding ElementName=ExposureSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}" DockPanel.Dock="Right" VerticalAlignment="Center" Width="40" Margin="10"/>
            <Slider Value="{Binding exposureTime.Value}" Name ="ExposureSlider" Maximum="{Binding exposureTime.Max, UpdateSourceTrigger=PropertyChanged}" TickPlacement="None" TickFrequency="{Binding exposureTime.Increment}" VerticalAlignment="Center" Minimum ="{Binding exposureTime.Min}" IsSnapToTickEnabled="True" Margin="10"/>
</DockPanel>

During initializing the camera i get my nodemap node

exposureTime = CameraNodemap["Std::ExposureTime"] as FloatNode;

It get’s interesting after my nice debug event;

        public MainWindow()
        {
            InitializeComponent();
            initCam();
            this.DataContext = this;
            exposureTime.Updated += node_Updated;

        }

the event node_updated no longer fires after the rediscover of the camera. Which makes me suspect that this entire problem lies somewhere in the nodes only being set once or something comparable?

Hi @CvK, I just see a tiny part of your code, but assume two things:

1. “Reset” the Camera

I assume you close (.Dispose()) the old camera if you rediscover it. Under which circumstances do you do that?

Regarding the .Net part: It is also safer in these circumstances to unregister (-=) the event you registered on any of the Device's objects.

2. MVVM

This is WPF’s Model, View, Controller pattern: Model, View, ViewModel. See Microsoft’s step by step introduction to it.

How does this apply here? Maybe you tried to bind directly to the model code (the Stemmer.Cvb.GenApi.Node object). So this works one time, because the property change you wrote in your XAML is not meaning an actual .Net property. If you set the slider position it also works fine as WPF then sets the value and knows it. If it is changed by the Device you are off.

The solution to that I would take is to write a view model for the Nodes. Something along the line of:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace YourProject
{
  abstract class ViewModelBase : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyOnPropertyChanged([CallerMemberName] string propertyName = null)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

Here an additional helper to reduce coding for various node types:

abstract class NodeViewModelBase<T> : ViewModelBase
  where T : Node
{
  protected T Node { get; private set;}
	
  protected NodeViewModelBase(T node)
  {
    Node = node ?? throw new ArgumentNullException(nameof(node));
		
    EventHandler updated = (s, e) => PropertyUpdated();
    node.Updated += updated;
		
    // clean this up when parent Device is disposed of
    NativeHandleEventDelegate disposing = null;
    disposing = s => 
    {
      node.Updated -= updated;
      node.ObjectDisposing -= disposing;
    };
  }
	
  protected abstract void PropertyUpdated();
}

And for an integer node like in your example:

class IntegerNodeViewModel : NodeViewModelBase<IntegerNode>
{
  public IntegerNodeViewModel(IntegerNode node)
    : base(node)
  {
  }
	
  public long Min => Node.Min;
	
  public long Max => Node.Max;
	
  public long Inc => Node.Increment;
	
  public long Value
  {
    get => Node.Value;
    set
    {
      if (value != Node.Value)
      {
        Node.Value = value;
        NotifyOnPropertyChanged();
      }
    }
  }
	
  protected override void PropertyUpdated()
  {
    NotifyOnPropertyChanged(nameof(Min));
    NotifyOnPropertyChanged(nameof(Max));
    NotifyOnPropertyChanged(nameof(Inc));
    NotifyOnPropertyChanged(nameof(Value));
  }
}

This is the property changed WPF is talking about. The cool part using this structure is that you can have different views per view model type. See the SystemBrowser tutorial’s MainWindow.xaml and look for DataTemplate in the ListBox.

3 Likes

That is indeed correct, i tried to directly bind to the Stemmer.Cvb.GenApi.IntegerNode and other nodetypes.

exposureTime = CameraNodemap["Std::ExposureTime"] as FloatNode;

I’ll try the updated event structure for the MVV; its not entirely clear for me just yet but I think thats because im not used to working with events in this particular way just yet!

Hmm, I must be overlooking something, but I keep getting errors that the node is disposed and cannot be accessed. Do I somehow need to tell the WPF element it should stop looking at the old node and update the binding? I thought this was the entire idea of the view model that you wouldn’t need to do this?

Even if I force the datacontext of the slider to be an instance of the IntergerNodeViewModel in the wpf element this happens. In this case for a float node (exposure) as that gives the most visual feeback when changed :wink:

<DockPanel VerticalAlignment="Center" >
                    <Label Content="Exposure" Margin="10" VerticalAlignment="Center" Width="65"/>
                    <TextBox Text="{Binding ElementName=ExposureSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}" DockPanel.Dock="Right" VerticalAlignment="Center" Width="40" Margin="10"/>
                    <Slider DataContext="{Binding exposureTime}" Value="{Binding Value, }" Name ="ExposureSlider" Maximum="{Binding Max}" TickPlacement="None" TickFrequency="{Binding Increment}" VerticalAlignment="Center" Minimum ="{Binding Min}" IsSnapToTickEnabled="True" Margin="10"/>
</DockPanel>

I’m not sure if I express myself completely correct or understandable, but it seems to keep looking at the wrong instance of the node, sticking to the old disposed. Hence my assumption is a silly event somewhere missing that notifies the slider of the source being changed?

And yep, that was it. The datacontext was the culprit. Setting that to null and then to the newly formed view did the trick. :slight_smile: