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