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
------------------------------
Original Message:
Sent: 09-21-2023 13:14
From: Romano Agostini
Subject: Dynamically adding commands to buttons
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 filewith 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 hierarchyfor 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
------------------------------