Reset GH canvas setting after closing Rhino

Hi everyone,

I’m developing a custom plugin in C# for Grasshopper. One of the components checks which packages are used in the GH canvas. If any disallowed packages are detected, the canvas background turns red to alert the user that something is wrong.

Here are the considerations I’m working on:

  1. When the checking component is removed, the canvas colors should automatically revert to the user’s original colors.

  2. When the GH document is closed or unloaded, the canvas colors should also return to the user’s original settings. (I’m currently handling this by subscribing to the ContextChanged event.)

private void OnDocumentContextChanged(object sender, GH_DocContextEventArgs e)
{
    // Restore canvas colors when document is closing
    if (e.Context == GH_DocumentContext.Close || e.Context == GH_DocumentContext.Unloaded)
    {
        RestoreOriginalCanvasColors();
    }
}

However, there’s an issue with the second case. Here’s the procedure I follow to reproduce the error:

  1. Open Rhino and Grasshopper.
  2. Load the component.
  3. Place a plugin that is not allowed on the canvas and the GH canvas turns red as expected.
  4. Close Rhino without closing Grasshopper.

After doing this, if I open Grasshopper again, the canvas colors are still red.

I tried handling the Rhino.Closing event, but it doesn’t seem to trigger — the same issue remains.

// ---------------------------------------------------------------------------         
// RhinoApp.Closing Event Handler
// ---------------------------------------------------------------------------

private void SubscribeToRhinoClosingEvent()
{
     if (_rhinoClosingEventSubscribed) return;
     RhinoApp.Closing += OnRhinoClosing;
      _rhinoClosingEventSubscribed = true;
}

private void OnRhinoClosing(object sender, EventArgs e)
{
     // Restore canvas colors before Rhino closes
     RestoreOriginalCanvasColors();
}

private void UnsubscribeFromRhinoClosingEvent()
{
     if (!_rhinoClosingEventSubscribed) return;
     RhinoApp.Closing -= OnRhinoClosing;
     _rhinoClosingEventSubscribed = false;
}

The idea is quite simple: change the GH canvas colors and restore them when closing Grasshopper or Rhino.

Does anyone have an idea what might be going wrong? Maybe @TomTom ?

Thanks in advance

Hi again,

I’m guessing here a bit… But two things:

first, you should never rely on events being fired under all circumstances. There are plenty of cases where e.g. „Closing“ is not being fired. Or other events are being fired multiple times for unexpected reasons.

Second, your eventhandler is not invoked on the main thread. If something is not implemented thread-safe, you might not see any reaction.Or in certain situations you cause a serious bug, maybe even a deadlock or a hard crash. So either use the GUI/Main thread dispatcher or use the RhinoApp.InvokeOnUiThread method which does the same.

I think the deeper problem is the unwanted persistence of the background color. Have you considered to use a different solution which will not override the user settings? If there is no (easy) way you can also consider drawing an overlay or signal it different to the user… it is good practice that your software is not modifying default behavior, but instead provide only additional features. This is also why I never publicly published code which does it. In worst cases your software can cause false bug reports for McNeel.

1 Like

And just because it fits this thread. I‘m a fan of defensive programming. I have once even worked on safety-critical code. Always see the usage of events/callbacks/function pointer and the connected observer/mediator patterns as a good method to decouple code, but with the drawback of much higher complexity in testing. On critical code potential recursion (and so events) are even prohibited. Its still a good thing to use, but be aware that its also a great place to introduce nasty bugs.

Therefore, write guards, assertions, apply logging and provide fallbacks if you deal with it.

1 Like

Thanks a lot for the comment @TomTom , I will see if there is another way of doing this without modifying the default settings :slight_smile:

Regarding placing the events in the main thread I suppose I did not showed you the full code but I have the events in the SolveInstance section:

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            // Clear previous state
            RhinoMessages.Clear();
            PackageMessages.Clear();
            HasErrors = false;

            // Initialize and check versions
            LoadVersionFile();
            SetEventHandlers();
            CheckRhinoVersion();
            CheckPackages();
        }

I suppose you are referring to that

Regarding drawing an overlay, I think that would be the best, thanks for the suggestion!