johnmdye

pythonaddins gui functions -- without the pythonaddins

Blog Post created by johnmdye on Dec 3, 2014

So, I'm a big fan of ESRI's pythonaddins module. Especially the GUI functions in it. Granted, there's not much there. Just a Messagebox and an Open and Save Dialog function but when leveraged properly, those are three functions that actually go a really long way in improving the end user experience.

 

Unfortunately, ESRI decided to restrict use of the pythonaddins module to the ArcGIS Desktop environment which is really a shame because that means we can't use any of those nifty little GUI functions in standlone scripts or script tools. Now I'm a guy that has quite a few of these standalone scripts that really run the gamut from basic data QA/QC to simple end user scipts to perform data extracts or ETL processes.

 

So since it didn't look like ESRI was going to make this functionality available to me outside of ArcGIS for Desktop, I decided to take a shot at recreating the functions using native python libraries. I should caveat that I'm only interested in the MessageBox, OpenDialog and SaveDialog functions. It seemed to me that there would really be no utility in trying to recreate the GetSelectedTOCLayerOrDataFrame function since you would have to be in an ArcMap session already for that to be of any use. As for the GPToolDialog function, I seriously doubt it's even possible to raise a GPTool Dialog outside of the ArcGIS for Desktop environment. If someone knows of some wizardry to accomplish that feat, please let me know. I would love to see it.

 

pythonaddins.MessageBox() = MessageBox

I'm actually rather proud of this one. Not only does it behave exactly as the pythonaddins.MessageBox function, but I was also able to add an additional parameter to allow for the inclusion of MessageBox icons to give a better visual indicator of the message's nature.

 

def MessageBox(Title, Message, ButtonStyle=0, MessageboxType="NOICON"):
    """
    Raises a custom messagebox with the given title and message
    with the specified ButtonStyle and MessageboxType

    Value                    ButtonType
    0                        OK (Default)
    1                        OK | Cancel
    2                        Abort | Retry | Ignore
    3                        Yes | No | Cancel
    4                        Yes | No
    5                        Retry | No
    6                        Cancel | Try Again | Continue

    Value                    MessageboxType
    NOICON                   No Icon (Default)
    INFO                     Information Icon
    QUESTION                 Question Mark Icon
    WARNING                  Warning Exclamation
    ERROR                    Error Icon

    Returns an integer value indicating the button in the Messagebox pressed by the user

    Return Value             Button Pressed
    1                        OK
    2                        Cancel
    3                        Abort
    4                        Retry
    5                        Ignore
    6                        Yes
    7                        No
    10                       Try Again
    11                       Continue

    Usage:
        MessageBox("RUIN DAY?", "Would you ArcGIS to ruin your day?", 4, "QUESTION")
    """
    if MessageboxType == "NOICON":
        MB_STYLE = 0x00
    elif MessageboxType == "INFO":
        MB_STYLE = 0x40
    elif MessageboxType == "QUESTION":
        MB_STYLE = 0x20
    elif MessageboxType == "WARNING":
        MB_STYLE = 0x30
    elif MessageboxType == "ERROR":
        MB_STYLE = 0x10
    else:
        raise Exception("Value given for 'MessageboxType' parameter is not valid")


    if ButtonStyle not in range(0,7):
        raise Exception("Value given for 'ButtonStyle' parameter is not valid")


    import ctypes
    messagebox = ctypes.windll.user32.MessageBoxA(0, Message, Title, ButtonStyle | MB_STYLE)
    return messagebox

MB.pngPlease, no.

 

pythonaddins.OpenDialog = selectFileDialog

This one was a bit trickier but not really that bad. The nuance is the file filter. You have to pass in a tupled list of filetype descriptions and extensions which in turn, populates the combobox to the right of the FileName text box. If you don't provide an input for the InitialDirectory, it defaults to the user's My Documents folder. I would have preferred to use ctypes here as well but couldn't find any examples that were less than 100 lines so I switched to Tk. No crashes yet, so fingers crossed!

def selectFileDialog(FileTypes, InitialDirectory, DialogTitle):
    """
    Raises a standard File Browser dialog and returns the absolute path of the file
    selected by the user in the dialog.
    Files displayed in the dialog can be filtered by passing a tuple of lists into the
    FileTypes parameter where the value at index 0 of each list is the filetype name and
    the value at index 1 of each list is the filetype extension.

    Usage:
        selectFileDialog([("Layer Package", "*lpk"), ("Shapefile", "*.shp")], r"C:\Temp", "Select a file")
    """
    import os
    if InitialDirectory == "":
        InitialDirectory = os.path.expanduser('~\Documents')
    if DialogTitle == "":
        DialogTitle = "Select a file"
    import Tkinter, tkFileDialog
    root = Tkinter.Tk()
    root.withdraw()
    FileToOpen = tkFileDialog.askopenfilename(filetypes=FileTypes, initialdir=InitialDirectory, title=DialogTitle)
    return FileToOpen

OpenDialog.png

I'd like to figure out how to get rid of that stupid Tk icon in the title bar and browse inside the Geodatabase.

 

pythonaddins.SaveDialog = saveFileDialog

This one is almost identical to OpenDialog except that instead of filtering files, we have the ability to automatically append a particular extension to the user's input. It's also possible to pass in a tupled list of filetype descriptions and extension which would then populate the Save As Filetype Dropdown, but again the only thing that is doing is filtering the files displayed. It's actually the FileExtension parameter that is appending the extension to the return value.

def saveFileDialog(FileExtension, InitialDirectory, DialogTitle):
    """
    Raises a standard File Save dialog and returns the absolute path of the file
    given by the user in the dialog.
    An extension can automatically be appended to the end of the return value by specifying
    the extension type in the 'FileExtension' parameter.

    Usage:
        selectFileDialog(".lyr", r"C:\Temp", "Save a file")
    """
    if not FileExtension:
        raise Exception("File extension is a required parameter")
    if InitialDirectory == "":
        import os
        InitialDirectory = os.path.expanduser('~\Documents')
    if DialogTitle == "":
        DialogTitle = "Save As"
    import Tkinter, tkFileDialog
    root = Tkinter.Tk()
    root.withdraw()
    FileToSave = tkFileDialog.asksaveasfilename(defaultextension=FileExtension, initialdir=InitialDirectory, title=DialogTitle)
    return FileToSave

SaveFile.png

So now you're probably thinking to yourself, that's neat but the pythonaddins module already does all of this.

 

That's true. The catch is the pythonaddins module will only do these things inside of the ArcGIS for Desktop environment. You cannot use it in Standalone Scripts or Script Tools. These custom functions however, should work just fine outside of ArcGIS without any third party libraries whatsoever.

Outcomes