Handle/update the checked state of a menu item

What is the “best” way to handle/update the checked state of a menu item in python? For example, if you have multiple items in your dropdown menu, how can I display the check next to the item the user selects?

        try: 
            item1 = Grasshopper.Kernel.GH_Component.Menu_AppendGenericMenuItem(
                menu, "MenuItem 1", self.OnClicked, None, None, True, False);
            item1.Name = "Item 1";
            item2 = Grasshopper.Kernel.GH_Component.Menu_AppendGenericMenuItem(
                menu, "MenuItem 2", self.OnClicked, None, None, True, False);
            item2.Name = "Item 2";
            item3 = Grasshopper.Kernel.GH_Component.Menu_AppendGenericMenuItem(
                menu, "MenuItem 3", self.OnClicked, None, None, True, False);
            item3.Name = "Item 3";
        except Exception, ex:
            System.Windows.Forms.MessageBox.Show(str(ex))
            
    def OnClicked(self, sender, args):
        try:
            sender.Checked = True #how to get this "back" up to menuitem?
            System.Windows.Forms.MessageBox.Show("My name is {}...".format(sender.Name))
            System.Windows.Forms.MessageBox.Show("I am {}...".format(sender.Checked))
            self.ExpireSolution(True)
        except Exception, ex:
            System.Windows.Forms.MessageBox.Show(str(ex))

Hi @chanley

I am not sure what part is really giving trouble, because the original sample gave a very similar example with “checked”. The way is to store an attribute for each independent variable you want, then read it when menu items are appended.

def __init__(self):
   self.checked1 = True
   self.checked2 = True
   #if you have more, add self.checked3, self.checked4...

def AppendAdditionalMenuItems(self, menu):
    item = items.Items.Add("Use option 1, image, self.OnTextMenuClick1)
    item.Checked = self.checked1 
    item = items.Items.Add("Use option 2", image, self.OnTextMenuClick2)
    item.Checked = self.checked2

def OnTextMenuClick1(self, index, value):
    try:
        self.checked1 = not self.checked1
        System.Windows.Forms.MessageBox.Show("Option 1 is {}...".format(self.checked1 ))
        self.ExpireSolution(True)
    except Exception, ex:
        System.Windows.Forms.MessageBox.Show(str(ex))

def OnTextMenuClick2(self, index, value):
    pass #same, but referencing checked2, etc

Other option are:

  • differentiation based on sender.Text, or
  • adding the MenuItem to an attribute and check the sender
  • any other method that you can figure out and that will work for you (box values into a class, etc)…
3 Likes

Thanks! I was missing the step of storing an attribute for each independent variable and doing something in the event handler to change the menu items from checked to unchecked. Your samples allowed me to get my first custom context menu working! It’s good enough for what I was hoping to accomplish. Thank you again for all of your help!

Below is the code that worked for me. Note: Rhino 6, (6.7.18190.21071, 07/09/2018). GH python component in SDK mode.

from ghpythonlib.componentbase import executingcomponent as component
from System.Windows.Forms import ToolStripSeparator
import Grasshopper, GhPython
import System
import Rhino
import rhinoscriptsyntax as rs
import math
import fractions

class decFtConverter(component):
    def __init__(self):
        super(decFtConverter,self).__init__()
        self.factor = 1
        self.checked1 = False
        self.checked2 = False
        self.checked3 = False
    
    def RunScript(self,decIn):
        if self.factor == 1: 
            ftIn = decIn
            self.Message = "Make a Choice"
        else: ftIn = self.feet_inches(decIn)
        return ftIn
        
    def feet_inches(self,decimal):
        tol = round(decimal * self.factor)
        b = math.modf(tol / self.factor)
        feet = int(b[1] // 12)
        inch = int(b[1] % 12)
        fract = fractions.Fraction(b[0])
        if fract == 0:
            fract = ""
        fi= str(feet)+"'- "+str(inch)+" "+str(fract)+'"'
        return fi

    def AppendAdditionalMenuItems(self, items):
        try: #always everything inside try
            #context menu item 1
            component.AppendAdditionalMenuItems(self, items)
            image = None
            items.Items.Add(ToolStripSeparator())
            item = items.Items.Add("Round to 1/2", image, self.OnClicked1)
            item.ToolTipText = "Round 1/2";
            item.Name = "2";
            item.Checked = self.checked1
            #context menu item 2
            item = items.Items.Add("Round to 1/4", image, self.OnClicked2)
            item.ToolTipText = "Round 1/4 ";
            item.Name = "4";
            item.Checked = self.checked2
            #context menu item 3
            item = items.Items.Add("Round to 1/8", image, self.OnClicked3)
            item.ToolTipText = "Round 1/8";
            item.Name = "8";
            item.Checked = self.checked3
        except Exception, ex:
            System.Windows.Forms.MessageBox.Show(str(ex))
            
    def OnClicked1(self, index, value):
        try: #always everything inside try
            self.checked1 = not self.checked1
            self.checked2 = False
            self.checked3 = False
            self.factor = int(index.Name)
            self.Message = (index.ToolTipText)
            self.ExpireSolution(True)
        except Exception, ex:
            System.Windows.Forms.MessageBox.Show(str(ex))
    
    def OnClicked2(self, index, value):
        try: #always everything inside try
            self.checked2 = not self.checked2
            self.checked1 = False
            self.checked3 = False
            self.factor = int(index.Name)
            self.Message = (index.ToolTipText)
            self.ExpireSolution(True)
        except Exception, ex:
            System.Windows.Forms.MessageBox.Show(str(ex))
            
    def OnClicked3(self, index, value):
        try: #always everything inside try
            self.checked3 = not self.checked3
            self.checked1 = False
            self.checked2 = False
            self.factor = int(index.Name)
            self.Message = (index.ToolTipText)
            self.ExpireSolution(True)
        except Exception, ex:
            System.Windows.Forms.MessageBox.Show(str(ex))

References:
converter function from here: Dimensions help - python
initial samples and discussions here: Button and context menu options on component through GhPython

GHPy_SDKMode_contextMenu.gh (5.9 KB)

4 Likes

Dear Chris Hanley, with this method, is there a way to save the user’s selection in a gh/ghx file, next time when user open the file, it’s last chosen option rather than the default one.

I would guess you would want to look at something along the lines of using pickle or shelve to save a selected variable, (assuming you are using python). I don’t have any examples off hand, but that would be where I would start.

Thank you, the question is not which way to save and read the settings, any object and string conversion is possible.It is how to save the settings to the gh file, and read the settings from a specific component instance when user open the gh file.

Hello
How we can add the menu to the input instead of the component?