Select to view content in your preferred language

Creating an Addin Extension that Responds to a Python Script

3449
19
08-23-2012 04:27 PM
RichardFairhurst
MVP Honored Contributor
I want to know if it is possible to create an Add-in extension that listens for map refresh events to specifically respond to the actions of a python script?

I envision adding a hidden text object into a specific kind of map that will at first indicate to the Add-in Extension that nothing needs to be done.  When the Python script runs it will write to that text object to set a flag telling the Extension that it is processing a change to the map, so ignore any map refresh actions during the script processing.  Just prior to completion of the Python script, the Python script will update the Text Object to say that it just finished and needs the Extension to do its thing.  Then the Python script will refresh the map.  I assume the Add-in Extension can detect the map refresh event triggered by the Python script, read the text object to detect that python wants the extension to run and then the extension can fire its action on the map.  When the extension finishes it will reset the text object back to the state indicating that nothing needs to be done by the extension.

Does this sound like it will work?

I want the Add-in to detect when arcpy.mapping finishes updating everything that it can in the map, and then have the extension change the data frame clipping geometry to match a feature's shape according to the current definition query on a layer that the python script set.  I do not want the user to have to push another button after the Python script finishes to do things that Python seemingly cannot do, and I do not want the Add-in to do the Python script actions.  I do not see any other way to have Python trigger an ArcObjects action within the currently open map, since the ctypes functionality says it is limited to basic C++ types and I do not see a way to pass a hook for the current map to an ArcObjects DLL method from Python.

Please correct me if I am wrong about any of this or if there is a better approach to do what I want.
0 Kudos
19 Replies
JochenManegold
Esri Contributor
What about that: embed your Python script into a Python script tool inside a custom toolbox. Extent your add-in with a button. If the user clicks the button, the add-in runs the script tool (synchron) and after it finished, it does the other stuff with ArcObjects. For deployment (to the assembly cache) you can add the custom toolbox as a resource to the add-in.  The code for using an embedded toolbox is something like that:

// Initialize the geoprocessor.
IGeoProcessor2 gp = new GeoProcessorClass();
// Add the toolbox.
string strPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string strToolbox = strPath + "\\PythonTools.tbx";
gp.AddToolbox(strToolbox);
// Generate the array of parameters.
IVariantArray parameters = new VarArrayClass();
parameters.Add(inString);
// Execute the model tool by name.
IGeoProcessorResult result = gp.Execute("Reply", parameters, null);
// Ergebnis zurückliefern
string reply = result.GetOutput(0).GetAsText();
0 Kudos
RichardFairhurst
MVP Honored Contributor
What about that: embed your Python script into a Python script tool inside a custom toolbox. Extent your add-in with a button. If the user clicks the button, the add-in runs the script tool (synchron) and after it finished, it does the other stuff with ArcObjects. For deployment (to the assembly cache) you can add the custom toolbox as a resource to the add-in.  The code for using an embedded toolbox is something like that:

// Initialize the geoprocessor.
IGeoProcessor2 gp = new GeoProcessorClass();
// Add the toolbox.
string strPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string strToolbox = strPath + "\\PythonTools.tbx";
gp.AddToolbox(strToolbox);
// Generate the array of parameters.
IVariantArray parameters = new VarArrayClass();
parameters.Add(inString);
// Execute the model tool by name.
IGeoProcessorResult result = gp.Execute("Reply", parameters, null);
// Ergebnis zurückliefern
string reply = result.GetOutput(0).GetAsText();


I do not think this will work for me.  My python script tool is unconventional and is not merely designed to act as a geoprocessing tool.  It acts like a form object with user interface capabilities.  I do not want the user to simply feed parameters to the tool.  I intend for them to interact with the tool.

I have adapted the script tool's ToolValidator to responds to each action by the user so that as they make choices, that action changes the options that work for other parameters according to the databases the user is querying.  The interface ensures that the user ends up with inputs that will return valid results when the actual geoprocessing takes place.  For example, if a user inputs a date range, the ToolValidator reads the database to make sure that there is data that extends to both limits of the range, and corrects the range if it does not.  It also responds to the date range change by altering the list of Primary Roads that the user can choose to only be those roads that actually had events within the date range.  When the user chooses a Primary Road, the validator makes sure it is in the list, and then updates the lists of intersecting roads that connect to the chosen Primary road.  As the user chooses intersecting roads and sets offsets from that intersection that will ultimately create point events or a road segment event when the actual georocessing takes place, the interface makes sure that any offsets will remain on the road and corrects the user's entries if they do not.

This interactive experience is essential to the tool's use and converting to it a mere geoprocessing tool called by ArcObjects would defeat my purpose.  I am aware that with .Net I could design a form using ArcObjects, but I have no interest in doing that.

Therefore the python script tool dialog must actually be opened by the user and only the user can determine when they are happy with the parameters values they have chosen within the dialog to trigger the actual geoprocessing actions of the tool.

Given my objectives and restrictions, do you still think your suggestion could somehow be adapted to work?
0 Kudos
LeoDonahue
Deactivated User
I want to know if it is possible to create an Add-in extension that listens for map refresh events to specifically respond to the actions of a python script?

I envision adding a hidden text object into a specific kind of map that will at first indicate to the Add-in Extension that nothing needs to be done.  When the Python script runs it will write to that text object to set a flag telling the Extension that it is processing a change to the map, so ignore any map refresh actions during the script processing.  Just prior to completion of the Python script, the Python script will update the Text Object to say that it just finished and needs the Extension to do its thing.  Then the Python script will refresh the map.  I assume the Add-in Extension can detect the map refresh event triggered by the Python script, read the text object to detect that python wants the extension to run and then the extension can fire its action on the map.  When the extension finishes it will reset the text object back to the state indicating that nothing needs to be done by the extension.

Does this sound like it will work?

I want the Add-in to detect when arcpy.mapping finishes updating everything that it can in the map, and then have the extension change the data frame clipping geometry to match a feature's shape according to the current definition query on a layer that the python script set.  I do not want the user to have to push another button after the Python script finishes to do things that Python seemingly cannot do, and I do not want the Add-in to do the Python script actions.  I do not see any other way to have Python trigger an ArcObjects action within the currently open map, since the ctypes functionality says it is limited to basic C++ types and I do not see a way to pass a hook for the current map to an ArcObjects DLL method from Python.

Please correct me if I am wrong about any of this or if there is a better approach to do what I want.


At first thought, your pattern sounds like the observer pattern, notify registered observers when an event occurs, except you're using two different languages, so I'm not sure how you would connect the two.

At second thought, there is: com.esri.arcgis.carto.IActiveViewEventsFocusMapChangedEvent  Which might work.

Lastly, Add-ins respond to user initiated events, so I'm not sure how you would initiate the tool, button, menu, utility object or extension that should be listening for the events from python.
0 Kudos
LeoDonahue
Deactivated User
My python script tool is unconventional and is not merely designed to  act as a geoprocessing tool.  It acts like a form object with user  interface capabilities.  I do not want the user to simply feed  parameters to the tool.  I intend for them to interact with the tool.  I am aware that with .Net I could design a form using ArcObjects, but I have no interest in doing that.

Can I interest you in writing a Java add-in?  😄
0 Kudos
RichardFairhurst
MVP Honored Contributor
At first thought, your pattern sounds like the observer pattern, notify registered observers when an event occurs, except you're using two different languages, so I'm not sure how you would connect the two.

At second thought, there is: com.esri.arcgis.carto.IActiveViewEventsFocusMapChangedEvent  Which might work.

Lastly, Add-ins respond to user initiated events, so I'm not sure how you would initiate the tool, button, menu, utility object or extension that should be listening for the events from python.


Thanks for the suggestions.  I was afraid of the last thing you said.  The notification process I outlined would ensure that any user triggered refresh would cause the listener to respond, detect the update by Python from the text object, and perform its action.  A refresh is harder for a user to avoid as a trigger than pressing another button.  That approach may be my best option, but it would introduce a delay to the full completion of the update that I would rather avoid and would not be anywhere close to fully reliable.

It would be a nice enhancement of the Python arcpy interface to have it be able to call buttons or activate tools on a toolbar, but I also see the potential dangers in allowing that.  I am at 10.0, so I know it cannot do that.  With 10.1 Python add-ins I did not hear about anything like that.

Without being able to pass an application object hook or map to a DLL from Python, I also do not see a way to do this from the Python side.  The hook would be required to get ArcObjects to trigger any button or toolbar I might build designed to work within an open ArcMap session.  I would wrap the script tool inside of an ArcObjects call if ArcObjects could actually trigger the tool to actually open it's dialog and continue when the dialog closes, but I have not seen anything like that either.  I also see dangers in that approach as well, since the program notification processes of the two languages would remain incompatible.

I do not think the FocusMapChanged Event helps, since I believe that only occurs when a user opens another map, not while continuing to work within a single map.  But the help description is not very clear about its intended behavior.
0 Kudos
LeoDonahue
Deactivated User
What if you wrote a console ArcObjects application that you execute from python?  I believe you can get a list of current ArcMap application references from the IAppROT interface.  That might work.
0 Kudos
RichardFairhurst
MVP Honored Contributor
Can I interest you in writing a Java add-in?  😄


Sarcasm aside, the python script tool dialog is doing the essential things that a form in ArcObjects does, in that it listens to actions done on the form and allows me to respond.  The debug time of validating user input on an ArcObjects form is nearly unending when text boxes have to take care of virtually everything and I have to write all of the validation code to restrict it to providing valid dates, numbers, and combobox lists, or knowing when to cross check each interface object at precisely the right moment during an onchange event for each interface object, etc.  Interface design in ArcObjects is full of undocumented pitfalls that have wasted weeks of my time that I simply don't have to deal with on the script tool form.  I am utterly tired of ArcObjects expecting me to take control of everything rather than being able to rely on well designed built-in code that takes care of at least 50% of my validation needs.
0 Kudos
LeoDonahue
Deactivated User
In .NET maybe it is hard to capture every possible user "oops".

In Java, it's straight-forward.

DecimalFormat.. DateFormat..  fun stuff.

If you enter junk into this text field, it will default to 0.00

        // Create an instance of DecimalFormat to limit the number of integers the user can type into certain fields.
        DecimalFormat temperatureFormat = new DecimalFormat("##0.00");

        ftfTemperature = new JFormattedTextField(new DefaultFormatterFactory(new NumberFormatter(temperatureFormat),new NumberFormatter(temperatureFormat),new NumberFormatter(temperatureFormat)));
        ftfTemperature.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                SwingUtilities.invokeLater(new Runnable(){
                    @Override
                    public void run() {
                        ftfTemperature.selectAll();
                    }
                });
            }
        });
        ftfTemperature.setBounds(120, 8, 85, 20);
        panel.add(ftfTemperature);
0 Kudos
RichardFairhurst
MVP Honored Contributor
What if you wrote a console ArcObjects application that you execute from python?  I believe you can get a list of current ArcMap application references from the IAppROT interface.  That might work.


The IAppROT interface seems promising for connecting to any running ESRI application.  Hopefully I could have Python send a text string naming the Map and test that string against the IAppROT list of running applications (realizing that only ArcMap applications would be valid).

From the help on the AppRef returned by the Item method of IAppROT, it says "Note you can only use the AppRef object if your code is running inside one of the ArcGIS application processes".  I assume a console ArcObjects application would meet that requirement, but I have not designed one before, so I am not sure.  In any case, so far this looks like a viable option.
0 Kudos