# Event Watchers

This guide covers how to synchronize a control's appearance with what is going on in Rhino using event watchers.

Applications can not update any user interface controls from any thread other than the main UI thread. Changing a property on a windows control typically causes the control to immediately update its display. But if the call is made from another thread then the UI thread windows will crash the application.

If you are updating a control from inside your event handler, you should check to see if CheckAccess() on the Dispatcher associated with your control evaluates to True. If this is the case, you must invoke a delegate that handles the UI updating and make sure that this delegate is called from the UI thread. This is actually easier than it sounds.

Here is a C# sample of RhinoDoc.AddRhinoObject event handler that handles the thrown exception when called from a thread other than the UI thread…

private void rhinoObjectAdded(Object sender, Rhino.DocObjects.RhinoObjectEventArgs e)
{
var msg = String.Format("thread id = {0}, obj id = {1}",
e.ObjectId.ToString());

RhinoApp.WriteLine(msg);

try {
// when a sphere is added from a secondary thread this line will
// throw an exception because UI controls can only be accessed from
_label.Content = msg;
} catch (InvalidOperationException ioe) {RhinoApp.WriteLine(ioe.Message);}
}


But… I really do need to update my control’s state, even when in the wrong thread!

This is where Dispatcher.Invoke or Dispatcher.BeginInvoke comes into play. You can force the code that modifies the control to run from the UI thread. Here is a C# sample of how to do this…

private void rhinoObjectAddedSafe(Object sender, Rhino.DocObjects.RhinoObjectEventArgs e)
{
var msg = String.Format("thread id = {0}, obj id = {1}",
e.ObjectId.ToString());

RhinoApp.WriteLine(msg);

// checks if the calling thread is the thread the dispatcher is associated with.
// In other words, checks if the calling thread is the UI thread
if (_label.Dispatcher.CheckAccess())
// if we're on the UI thread then just update the component
_label.Content = msg;
else
{
// invoke the setLabelTextDelegate on the thread the dispatcher is associated with, i.e., the UI thread
var setLabelTextDelegate = new Action<string>(txt => _label.Content = txt);
_label.Dispatcher.Invoke(setLabelTextDelegate, new String[] { msg });
}
}


The above function could be rewritten to just always call the delegate…

private void rhinoObjectAddedSafe(Object sender, Rhino.DocObjects.RhinoObjectEventArgs e)
{
var msg = String.Format("thread id = {0}, obj id = {1}",
e.ObjectId.ToString());

RhinoApp.WriteLine(msg);

// invoke the setLabelTextDelegate on the thread the dispatcher is associated with, i.e., the UI thread
var setLabelTextDelegate = new Action<string>(txt => _label.Content = txt);
_label.Dispatcher.Invoke(setLabelTextDelegate, new String[] { msg });
}


NOTE: If Windows Forms (on Windows) is used instead of WPF then _label.Dispatcher.CheckAccess() becomes _label.InvokeRequired and _label.Dispatcher.Invoke(...) becomes _label.Invoke(...).

Here is the complete example:

using System;
using Rhino;
using Rhino.Commands;
using Rhino.DocObjects;
using Rhino.Geometry;
using System.Windows;
using System.Windows.Controls;

namespace examples_cs
{
[System.Runtime.InteropServices.Guid("E4A93905-6E61-43BB-9FF0-4D5F6AF76704")]
{
public override string EnglishName { get { return "csChangeUIFromDifferentThread"; } }
private RhinoDoc _doc;
private Label _label;
private Window _window;

protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
_doc = doc;

_window = new Window {Title = "Object ID and Thread ID", Width = 500, Height = 75};
_label = new Label();
_window.Content = _label;
new System.Windows.Interop.WindowInteropHelper(_window).Owner = Rhino.RhinoApp.MainWindowHandle();
_window.Show();

// add a sphere from the main UI thread.  All is good

// doesn't work well when called from another thread
addSphereDelegate.BeginInvoke(new Point3d(0, 10, 0), null, null);

// desgined to work no matter which thread the call is comming from.

// try again adding a sphere from a secondary thread.  All is good!
addSphereDelegate.BeginInvoke(new Point3d(0, 20, 0), null, null);

doc.Views.Redraw();

return Result.Success;
}

}

private void RhinoObjectAdded(Object sender, RhinoObjectEventArgs e)
{
var message = String.Format("thread id = {0}, obj id = {1}",
e.ObjectId.ToString());

RhinoApp.WriteLine(message);

try {
// when a sphere is added from a secondary thread this line will
// throw an exception because UI controls can only be accessed from
_label.Content = message;
} catch (InvalidOperationException ioe) {RhinoApp.WriteLine(ioe.Message);}
}

private void RhinoObjectAddedSafe(Object sender, RhinoObjectEventArgs e)
{
var message = String.Format("thread id = {0}, obj id = {1}",
e.ObjectId.ToString());

RhinoApp.WriteLine(message);

// checks if the calling thread is the thread the dispatcher is associated with.
// In other words, checks if the calling thread is the UI thread
if (_label.Dispatcher.CheckAccess())
// if we're on the UI thread then just update the component
_label.Content = message;
else
{
// invoke the setLabelTextDelegate on the thread the dispatcher is associated with, i.e., the UI thread
var setLabelTextDelegate = new Action<string>(txt => _label.Content = txt);
_label.Dispatcher.BeginInvoke(setLabelTextDelegate, new String[] { message });
}
}
}
}