Custom Component Options (VB) |
This article discusses how to add custom options to a component and have them included in *.gh/*.ghx (de)serialization. It skips over some portions of Component design which have already been handled in previous topics, so do not read this article before familiarizing yourself with the My First Component topic.
The component we'll create in this article will sort a list of numbers and have the custom option to convert those numbers to absolute values prior to sorting. However, rather than providing this option as a boolean input parameter, we'll allow people to set it via the Component context menu. We'll need to do four special things to achieve this, to wit:
Before you start with this topic, create a new class that derives from GH_Component, as outlined in the My First Component topic.
This component will require one input parameter and one output parameter, both of type Number with list access:
... Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) pManager.AddNumberParameter("Values", "V", "Values to sort", GH_ParamAccess.list) End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As Kernel.GH_Component.GH_OutputParamManager) pManager.AddNumberParameter("Values", "V", "Sorted values", GH_ParamAccess.list) End Sub ...
... Protected Overrides Sub SolveInstance(ByVal DA As Kernel.IGH_DataAccess) Dim values As New List(Of Double) If (Not DA.GetDataList(0, values)) Then Return If (values.Count = 0) Then Return 'Don't worry about where the Absolute property comes from, we'll get to it soon. If (Absolute) Then For i As Int32 = 0 To values.Count - 1 values(i) = Math.Abs(values(i)) Next End If values.Sort() DA.SetDataList(0, values) End Sub ...
The 'Absolute' option for this component applies to the entire object, but not to other instances of this component. Since it needs to survive (i.e. retain its value) for as long as the component lives, it has to be declared as a class level variable:
... Private m_absolute As Boolean = False Public Property Absolute() As Boolean Get Return m_absolute End Get Set(ByVal value As Boolean) m_absolute = value If (m_absolute) Then Message = "Absolute" Else Message = "Standard" End If End Set End Property ...
It is of course possible to add any number of custom fields to a component, but you can only attach a single message, if you have more than one field you want to make the user aware of, you'll need to get creative.
When you add options or states to your component which need to be 'sticky', you'll also need to (de)serialize them correctly. (De)serialization is used when saving and opening files, when copying and pasting objects and during undo/redo actions. In this particular case, we only need to add a single boolean to the standard file archive. Serialization in Grasshopper happens using the GH_IO.dll methods and types, not via standard framework mechanisms such as the SerializableAttribute.
Override the Write and Read methods on GH_Component and be sure to always call the base implementation.
... Public Overrides Function Write(ByVal writer As GH_IO.Serialization.GH_IWriter) As Boolean 'First add our own field. writer.SetBoolean("Absolute", Absolute) 'Then call the base class implementation. Return MyBase.Write(writer) End Function Public Overrides Function Read(ByVal reader As GH_IO.Serialization.GH_IReader) As Boolean 'First read our own field. Absolute = reader.GetBoolean("Absolute") 'Then call the base class implementation. Return MyBase.Read(reader) End Function ...
We'll also need to add an additional menu item to the component context menu, then handle the click event for that item. Adding items to a context menu is best done via the AppendAdditionalComponentMenuItems method. It allows you to insert anu number of item in between the Bake and the Help items. The easiest way to add menu items is to use the Shared methods on GH_DocumentObject such as Menu_AppendItem or one of the overloads. In this case we also want to assign a tooltip text to the item which cannot be done from inside Menu_AppendItem().
... Protected Overrides Sub AppendAdditionalComponentMenuItems(ByVal menu As System.Windows.Forms.ToolStripDropDown) 'Append the item to the menu, making sure it's always enabled and checked if Absolute is True. Dim item As ToolStripMenuItem = Menu_AppendItem(menu, "Absolute", AddressOf Menu_AbsoluteClicked, True, Absolute) 'Specifically assign a tooltip text to the menu item. item.ToolTipText = "When checked, values are made absolute prior to sorting." End Sub ...
When this menu item is clicked, the delegate assigned inside the Menu_AppendItem() method will be invoked. It is here that we must handle a click event. There are usually three steps involved in handling clicks; Record the current state as an undo event, change the state, trigger a new solution:
... Private Sub Menu_AbsoluteClicked(ByVal sender As Object, ByVal e As EventArgs) RecordUndoEvent("Absolute") Absolute = Not Absolute ExpireSolution(True) End Sub ...