MapInfo Pro Developers User Group

 View Only
  • 1.  Dynamically adding commands to buttons

    Posted 09-21-2023 13:15
    Edited by Peter Møller 09-22-2023 06:05
      |   view attached

    Hi,

    I am trying to write a Python addin that creates tabs and populates them with groups and controls based on the structure of a JSON file. Part of this is adding commands to the button which should be achieved by supplying the software with a command ID to an existing MapInfo function or a path to a .py or .MBX script for custom functionality. The relevant code is shown below, I've tried to do this by attaching a lambda function which just calls commonutil.do() on the "run application" mapbasic command for the supplied path. This adds the tab, group, and button and configures it mostly correctly, but nothing is called when I select the button. 

    #Open json file
    with open("C:/Users/romano/Documents/MapBasic/ModuleLoader/modules.json", 'r') as j:
         tabs = loads(j.read())
     
    #Add tabs, groups, and buttons as outlined by JSON hierarchy
    for tab in tabs["tabs"]:
         newTab = self.AddNewTabToRibbon(tab["progName"], tab["dispName"])
         for group in tab["groups"]:
              newGroup = self.AddNewGroupToTab(newTab, group["progName"], group["dispName"])
              for control in group["controls"]:
                   #Translate control type from JSON spec to MapInfo internal enumeration
                   controlType = buttSwitch.get(control["type"], ControlType.Button)
                   #Add new control and get handle to it
                   newControl = self.AddNewControlToGroup(newGroup, control["progName"], control["dispName"], controlType)
                   #Set control large if specified
                   newControl.IsLarge = control["large"]
                   #if control["large"]:
                   #     newControl.LargeIcon = control["img"]
                   #else:
                   #     newControl.SmallIcon = control["img"]
     
                   #Assign anonymous function that calls a "run application" on the MBX specified in the command
                   newControl.Command = AddinUtil.create_command(lambda self, sender: CommonUtil.do("run application \"{}\"".format(control["command"])))
                   #newControl.Command = AddinUtil.create_command(self.test())
    Why does the code not call the appropriate function on click? Is there a more elegant way of doing this? I've looked into the source for addinutil.create_command which calls CommandAdapters.ViewToContractAdapter() which seems to be undocumented.
    I've attached my code as a .zip file if you need to look at the other parts of the addin.
    Thanks,
    Romano



    ------------------------------
    Romano Agostini
    Knowledge Community Shared Account
    ------------------------------

    Attachment(s)

    zip
    ModuleLoader.zip   28 KB 1 version


  • 2.  RE: Dynamically adding commands to buttons

    Posted 09-22-2023 08:54
    Edited by Romano Agostini 09-22-2023 08:54

    Hiya,

    I've managed to figure out something that works, the issue seems to be that anonymous functions or their references are probably deleted at some point in the loading process. So the method needs to be stored in memory with a constant reference.

    I therefore created a small holder class to hold the path and call run application on when needed.

    #Small object, holds path to script and calls later on when assigned button is clicked
    class custCommand():
    
        def __init__(self, path):
            self.path = path
    
        def call(self, sender):
            CommonUtil.do("run application \"{}\"".format(self.path))

    When the JSON parser gets to the command keyword, it creates a new instance of the class and adds the script path to the object's path variable, places the object in a list held within the addin object and assigns that object's call function to the button's command.

        #Set up addin, called when MapInfo loads addin
        def __init__(self, imapinfopro, thisApplication):
            
            try:
                self._pro = imapinfopro
                self._thisApplication = thisApplication
    
                print("Loading modules...")
    
                #Create list to hold tab references
                self._newTab = []
    
                #Create list to hold custom command class references
                self._custCommands = []
    
                #Open json file
                with open("C:/Users/romano/Documents/MapBasic/ModuleLoader/modules.json", 'r') as j:
                     tabs = loads(j.read())
    
                #Add tabs, groups, and buttons as outlined by JSON hierarchy
                for tab in tabs["tabs"]:
                    newTab = self.AddNewTabToRibbon(tab["progName"], tab["dispName"])
                    for group in tab["groups"]:
                        newGroup = self.AddNewGroupToTab(newTab, group["progName"], group["dispName"])
                        for control in group["controls"]:
                            #Translate control type from JSON spec to MapInfo internal enumeration
                            controlType = buttSwitch.get(control["type"], ControlType.Button)
                            #Add new control and get handle to it
                            newControl = self.AddNewControlToGroup(newGroup, control["progName"], control["dispName"], controlType)
                            #Set control large if specified
                            newControl.IsLarge = control["large"]
                            #if control["large"]:
                            #    newControl.LargeIcon = control["img"]
                            #else:
                            #    newControl.SmallIcon = control["img"]
    
                            #Create new custom command object and append it to custCommands list for dereferencing later on
                            self._custCommands.append(custCommand(control["command"]))                        
                            newControl.Command = AddinUtil.create_command(self._custCommands[-1].call)

    This works well for me, if anyone can find a better implementation or can see some issues with my code please let me know.



    ------------------------------
    Romano Agostini
    Knowledge Community Shared Account
    ------------------------------