Table of Contents

Observers and messages

An object invokes events (ModifiedEvent, ProgressEvent, ErrorEvent, ...), and its observers are called on the thread that invokes them. C# handlers are observers.

Handlers

// a handler for one of VTK's events; the tag removes it
ulong tag = array.AddObserver(vtkCommand_EventIds.ModifiedEvent,
                              (sender, e) => calls.Add(((vtkObject)sender!).GetClassName()));
array.Modified(); // calls it: "vtkDoubleArray"
array.RemoveObserver(tag);
array.Modified(); // doesn't

sender is the object that invoked the event, e.EventId the event, e.CallData its data: e.GetDouble() for ProgressEvent, e.GetText() for ErrorEvent and WarningEvent. VTK holds the handler until it's removed or the object goes.

// observed until disposed
using (sphere.Observe(vtkCommand_EventIds.EndEvent, (_, _) => runs++))
{
  sphere.Update(); // runs: 1
}

sphere.SetRadius(2);
sphere.Update(); // runs again, unobserved

Events

// C# events for the common ones: StartEvent, ProgressEvent (0 to 1), EndEvent on algorithms;
// ModifiedEvent, ErrorEvent, WarningEvent on every vtkObject
sphere.StartEvent += (_, _) => seen.Add("start");
sphere.ProgressEvent += (_, e) => seen.Add($"{e.GetDouble():P0}");
sphere.EndEvent += (_, _) => seen.Add("end");
sphere.Update();

Remove a handler (-=) through the proxy that added it.

vtkCommand subclasses

// a vtkCommand of our own: VTK calls Execute with the object that invoked the event
public sealed class NameRecorder(List<string>? names = null) : vtkCommand
{
  public List<string> Names { get; } = names ?? [];

  public override void Execute(vtkObject caller, ulong eventId, IntPtr callData)
  {
    using (caller)
    {
      if (vtkAbstractArray.SafeDownCast(caller) is { } array)
      {
        using (array)
        {
          Names.Add(array.GetName());
        }
      }
    }
  }
}
using var command = new NameRecorder();
array.AddObserver((ulong)vtkCommand_EventIds.ModifiedEvent, command);
array.Modified();

AddObserver takes the event id as ulong here. VTK keeps a command it holds alive, after its last C# reference too. caller is a new proxy, holding a reference: dispose it.

Messages

VTK writes errors, warnings and text to its output window: on Windows a window of its own, elsewhere the console. VtkOutput routes them to C#:

// VTK's errors, warnings and text: to this handler, not to a window or the console
EventHandler<VtkOutputEventArgs> handler = (_, e) =>
{
  if (e.Kind == VtkOutputKind.Error)
  {
    errors.Add(e.Text);
  }
};
VtkOutput.Message += handler;
try
{
  reader.Update(); // the file isn't there: VTK reports an error and goes on
}
finally
{
  VtkOutput.Message -= handler;
}

Adding a handler installs the routing, VtkOutput.Install() does it without one: messages then go to System.Diagnostics.Trace while no handler is attached. Install it at startup; Uninstall() gives VTK its window back.

An object with an ErrorEvent handler reports its errors there instead:

// one object's errors, which then don't reach VtkOutput; its executive's report of the
// failure still does
using var reader = new vtkXMLUnstructuredGridReader();
reader.SetFileName(Missing);
reader.ErrorEvent += (_, e) => errors.Add(e.GetText()!);
reader.Update();

Exceptions in callbacks

// VTK isn't written for exceptions: the handler's stops at the callback, VTK goes on, and the
// call that led to it throws a VtkException carrying it
array.ModifiedEvent += (_, _) => throw new InvalidOperationException("no");
try
{
  array.Modified();
}
catch (VtkException e)
{
  caught = e.InnerException; // the InvalidOperationException
}
A handler's exception comes back from the call A handler's exception comes back from the call

The same holds for vtkCommand overrides and VtkOutput.Message handlers. A C++ exception from a VTK call (std::bad_alloc, any std::exception) arrives as a VtkException too, its type in NativeType, its message in NativeMessage.