Friday, 17 June 2016

python addin - ArcGIS crashing with Tkinter?



When I run certain scripts (contains tkinter or similar python packages) ArcGIS crashes. When I load or copy/paste the same code and run it in a Python window it runs without error.


Is there a problem in ArcGIS? I am using 10.4.1.



Answer



I think the trick to using tkinter in Python is to run it from an external module, that way it isn't competing with the GUI components from the pythonaddins module. I do not take any credit for the tkinter GUI portion, as it was my colleague who figured it out, but here is a sample.


The GUI is dynamically created based on a list of text elements, and allows you to quickly update them all. In our templates, all of our header elements are prefixed with header_*. So to make this work, he saved out an external module called element_gui and I just imported it into my test add in and it works pretty smoothly. Here's the GUI code:


#-------------------------------------------------------------------------------
# Name: module1

# Purpose:
#
# Author: tylerjo
#
# Created: 08/04/2016
# Copyright: (c) tylerjo 2016
# Licence:
#-------------------------------------------------------------------------------
import sys
import os

import Tkinter as tk
import tkFileDialog, tkMessageBox
import ttk
import csv

N,S,E,W = tk.N,tk.S,tk.E,tk.W


def main(in_els=[]):
"""Main function. Calls GUI.


Required:
in_els -- List of element objects

"""

gui = GUI(els=in_els)
gui.master.title("Update Elements")
gui.master.minsize(width=500, height=150)
gui.mainloop()


return gui.outDict

class GUI(tk.Frame):

def __init__(self, master=None, els=[]):
tk.Frame.__init__(self,master)
self.grid(sticky=N+S+E+W)
tk.Grid.rowconfigure(self, 0, weight=1)
tk.Grid.columnconfigure(self, 0, weight=1)


self.elementList = els
self.elDict = {}
self.outDict = {}

self.__createWidgets()

def __createWidgets(self):

top = self.winfo_toplevel()

top.rowconfigure(0, weight=1)
top.columnconfigure(0,weight=1)
self.top = top

self.rowconfigure(1, weight=1)
self.columnconfigure(0, weight=1)

self.f1 = tk.Frame(self, height=500, width=500, bg='gray')
self.f1.grid(row=0, sticky=N+E+W+S, columnspan=2)
tk.Grid.rowconfigure(self.f1, 0, weight=1)


r1 = ['Element Name', 'Updated Value']
for ci,cLabel in enumerate(r1):
tk.Grid.columnconfigure(self.f1, ci, weight=1)
tk.Label(self.f1, text=cLabel, borderwidth=1, width=20, relief=tk.GROOVE).grid(column=ci, row=0, sticky=N+E+W)

for i, el in enumerate(self.elementList,1):
tk.Label(self.f1, text=el.name, borderwidth=2).grid(column=0, row=i, sticky=E+W)
self.elDict[el] = tk.StringVar()
tk.Entry(self.f1, textvariable=self.elDict[el], justify=tk.CENTER).grid(column=1,row=i, sticky=E+W)


self.saveButton = tk.Button(self, text="Send Updated Text Back To ArcMap", command=self.both_functions, width=30)
self.saveButton.grid(row=4, column=0)

def btnClick(self):
"""Get values from StringVar() objects and put into outDict
"""

for el, newval in self.elDict.items():
print newval.get()

self.outDict[el] = newval.get()

print self.outDict
return self.outDict

def _close(self):
"""Destroy GUI"""
self.top.destroy()

def both_functions(self):

"""For some reason I can't call _close within btnClick function.
So both are called here
"""

self.btnClick()
self._close()


if __name__ == '__main__':
main()


And here is how I consume it in my simple Add-In (just a toolbar with a button):


import arcpy
import pythonaddins
import os
import sys
sys.path.append(os.path.dirname(__file__))
import element_gui

class ButtonClass3(object):

"""Implementation for UpdateMapElmsTest_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = False
self.mxd = arcpy.mapping.MapDocument('current')
print os.path.dirname(__file__)

def onClick(self):
elms = arcpy.mapping.ListLayoutElements(self.mxd, 'TEXT_ELEMENT', 'Header*')
new_heads = element_gui.main(elms)

for elm,new_text in new_heads.iteritems():
elm.text = new_text
arcpy.RefreshActiveView()

And here is what it looks like:


Example


I am not sure how stable this will be. It did crash a couple times when I had the Python window open, so it may still be trying to compete with pythonaddins for UI resources.


No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...