Eto.Forms in Grasshopper
Eto.Forms is the cross-platform(Mac or PC) library that comes with Rhino that you can access through API. This library provides the basic building blocks of a graphic user interface(GUI). If you ever need to customize a window/dialog to interact with the Rhino user, this is the library you are looking for. For example, when you type "rebuild" in rhino and call up the command, a window pops up instead the command line sequence of inputs. This way you see all the parameters in one place and have the opportunity to adjust every one of them before committing the change. Eto.Forms would allow you to make your own windows just like the rebuild window.
To clarify, Eto.Forms belongs to a larger library called Eto and it also includes Eto.Drawing, which is used in tandem with Eto.Forms often. The API doc is here. McNeel's website also has helpful contents for you to get started. I'm linking the Rhino Python page of McNeel because I code in Python and I still think it's concise and easy to write. But Eto library itself is a .NET resource, and therefore can be used with C#.
Rhinoceros is composed of many layers - written in many languages - all stacked on top of each other. The most foundational are on the bottom
Why GhPython? Two main reasons. One is that GhPython offers this simple compiling mechanism that can quickly turn the GhPython component into a "plugin". This will not only protect your source code, but also slightly increase computation speed. Two is that the Grasshopper environment has a lot of existing interactions with the Rhino document. For example, the use of API to change how objects are previewed in the viewport is a bit of a pain but Grasshopper has the nifty preview component that takes care of that. We can use many ready made GH components as the back end of the customized window.
This image is taken straight from McNeel's website explaining Eto. The dialog form is really a base box. The layout grid is a smaller box. The controls are buttons, check boxes and other trinkets you can interact with. You place the trinkets in small boxes, which are then arranged and placed in the large base box. This is fundamentally the building process of a GUI with Eto.Forms and it's a process repeated over and over. In comparison to back end algorithms, front end scripting often is tedious. 20 lines of code here only gets the ugliest window to barely show any "dead" interactive elements whereas 20 line of algorithmic codes may be able to compute some complex geometries for you.
Styling the form and the features
When an Eto.Forms window is run, each control (a button, a slider, a text box etc.) starts a number of "event listeners" in your computer. If the listener "hears" an event happening, it would trigger one or many functions or methods. A click of the mouse would be an event. So the task is telling the button that when the click event happens do the following. Here is the one line of code to do that.
eventName += whatMustBeDone
The window itself is very plain and ugly when you start of, so it's best done to do some styling to make it more user friendly.
We start of by doing an initial sketch of how the layout should look like and what features should be included in the window.
We also need to do a UX flow map to determine the structure of the dataflow and what the user should be allowed to control.
Grasshopper scripts are run linearly from left to right and once it's done it would not recompute unless told to. Once your window is fired up, the script on GH canvas has finished computing. The window itself, however, is a live running program. If you try to return any value out of the GH component from which the window is launched, nothing will happen because the canvas does not refresh anymore. The twist here is to somehow pass references of some of the component instances on the canvas into the window object. That way it can manually be expired by calling the
To find and retain the "pointers" to objects on GH canvas, the straightforward way is through GH API. You can connect the host component downstream to another GhPython. By accessing
Component.Params.Output[i].Recipients[i], you can have a reference to that GhPython. Using in tandem with
scriptcontext.sticky library, you can change values in sticky and call the
ExpireSolution(True) on the downstream GhPy. In this case that connection between host component and downstream doesn't even have to carry data.
The final form
Utimately, this is what I ended up with. A more user friendly form which you can interact with Grasshopper scripts through.