Wednesday, July 24, 2013

Python Add-Ins and Tkinter

ArcGIS for Desktop does not support any python GUIs out of the box, but let's say we want to have a form pop-up anyway. As shown below


One way to do this is to create a wx python instance at start up, which is create before the desktop python loop is created.  You would then reference the wx loop instead of the ArcGIS python loop.. but it's complicated...  You can also see tons of forum posts like this, that describe how the in process causes python to crash with GUIs: http://gis.stackexchange.com/questions/36848/crashing-arcgis-10-1-add-ins-using-multiprocessing

Let's assume though you want to use Tkinter because it's core, it comes with the python install.  Tkinter is the out of the box GUI that comes with python, and if you want to learn more about it, you can check it out here.  

Since ArcGIS for Desktop runs python add-ins 'in process'.  We cannot use multiprocessing or subprocessing to launch another instance python and display the code.  This is essentially the thing hindering python GUI development in the 10.x framework.  To get around this issue, code must be executed out of process.  Luckily for python people, we can create toolboxes and reference those toolboxes through the ImportToolbox().  Toolboxes allow you to run code 'out of process', which means multiprocessing!  Pretty sweet.

Let's take a look at the code to generate the form:
import Tkinter
from Tkinter import *
import multiprocessing
import sys, os
import arcpy
def show_form():

    root =Tk()
    root.title('Button')
    Label(text='I am a button').pack(pady=15)

    #button are labels that react to mouse and keyboard events.
    #in this case the button is packed to be at the bottom of the root or
    #toplevel widget.

    Button( text='Button') .pack(side=BOTTOM)
    root.mainloop()
    
if __name__ == "__main__":
    pythonExe = os.path.join(sys.exec_prefix, 'python.exe')
    multiprocessing.set_executable(pythonExe)
    multiprocessing.freeze_support = True
    jobs = []
    pool = multiprocessing.Pool(1)
    jobs.append(pool.apply_async(show_form,[]))
    pool.close()
    pool.join()
    del pool
    del jobs
    arcpy.SetParameterAsText(0, True)   


Now that we have the code to create the form, create a new python toolbox. Add this script and make sure you UNCHECK 'Run python script in process'

Next create your python add-in toolbar and add a button.  Copy the python script and toolbox from above into your 'install' folder.  Begin editing the python add-in script as follows:
import arcpy
import pythonaddins
import os
class btnShowForm(object):
    """Implementation for tkinterTester_addin.button (Button)"""
    def __init__(self):
        self.enabled = True
        self.checked = False
    def onClick(self):
        tbx = arcpy.ImportToolbox(os.path.join(os.path.dirname(__file__), "scripts.tbx"))
        tbx.showform() # name of script in toolbox is showform

Finally all you have to do is create the .esriaddin file. Just double click on the makeaddin.py and install the addin.

When you run the add-in from ArcMap, you should see a pop-up appear.  Pretty cool!  The one down side is that ArcPy needs to reload, so it can cause a slight delay in showing the form.


Enjoy

Also, if you want Python GUI support in ArcGIS for Desktop, please vote up this (http://ideas.arcgis.com/ideaView?id=087E00000004SmHIAU).