Tkinter GUI Disconnect with ArcGIS Script Interface

2867
5
06-06-2011 12:43 PM
GraceCai
New Contributor
I'm working on a script that allows the user to set various parameters on multiple datasets and does some geoprocessing on the data based on said user defined parameters. I've created a simple GUI for taking in the parameters using Tkinter. Things work fine when testing in IDLE but when I import the script into a tool box and try to run from there, the interface falls apart. By that I mean, what is suppose to be a multi-paged widget (using "back" and "next" buttons to flip through) comes up separately. But more importantly, it won't allow any text inputs. The text boxes are there but it seems that the widgets are competing against the processes run by Arc in running the script.  The cursor never stays long enough for text to be entered. Check-boxes work.

Here's the code I'm not sure if it'll help.

import os, sys, arcpy
from Tkinter import *

if len(sys.argv) > 1:
    vdata = sys.argv[1]# get list of datasets from the script UI
    rdata = sys.argv[2]
    outDir = sys.argv[3]

else:
    vdata = [...]
    rdata = [...]
    outDir = "C:\Users\..."
    
taskList={} #params for all the datasets are stored here, with datapath as key
task = {} #params for each dataset is stored 
log = "C:\Users\...\log.txt"
file = open(log, 'w')

class VectorHandle(Frame):
    
    def __init__(self, root, source):
        Frame.__init__(self, root)
       
        Label(self, text="Data source \n"+ source).pack()

        Label(self, text="Weight").pack()
        weight = Entry(self)
        weight.pack()

        Label(self, text="Season (if left empty, this constraint is applicable all year)").pack()
        season = []
        spring = Checkbutton(self, text='spring', command= lambda: self.season('spring', season))
        summer = Checkbutton(self, text='summer', command= lambda: self.season('summer', season))
        fall = Checkbutton(self, text='fall', command= lambda: self.season('fall', season))
        winter = Checkbutton(self, text='winter', command= lambda: self.season('winter', season))
        spring.pack() 
        summer.pack()
        fall.pack()
        winter.pack()
            
        Label(self, text="Feature membership").pack()
        featMem = Entry(self)
        featMem.pack()

        Label(self, text="Function").pack()
        var = IntVar()
        var.set("buffer")
        funcDropdown = OptionMenu(self, var, "buffer", "ring", command = self.setFunction)
        funcDropdown.pack()
        Button(self, text="Edit", command = lambda: self.setFunction).pack()
                
        Button(self, text="Commit", command = lambda: self.commit(source, temp)).pack(side = BOTTOM)

    def season(self, s, season):
       ......

    def setFunction(self, event):
        ....
                    
    def commit(self, source, temp): #updates the updates the params of a task
        task["weight"] = weight.get()
        task["season"] = season
        task["type"] = 'v'
        task.update(temp)
        taskList[source] = str(task)       
        task.clear


class RasterHandle(Frame):
 #same idea as VectorHandle
    
class Application(Frame):
    def __init__(self, root):
        Frame.__init__(self, root)
        self.root = root
        self.page = 0
        self.pages = [VectorHandle(self, source) for source in vdata]
        self.pages_raster = [RasterHandle(self, source) for source in rdata]
        self.pages.extend(self.pages_raster)
        self.pages[self.page].pack(side=TOP)
        Button(self, text="Next", command= self.next).pack(side=RIGHT)
        Button(self, text="Back", command= self.back).pack(side=LEFT)
        Button(self, text="Go", command= lambda: self.main(root)).pack(side=BOTTOM)
        

    def next(self):
        self.pages[self.page].pack_forget() #remove the current page
        self.page += 1
        if self.page >= len(self.pages): 
            self.page = 0
        self.pages[self.page].pack(side=TOP) #add the next one

    def back(self):
        self.pages[self.page].pack_forget() #remove the current page
        self.page -= 1
        if self.page < 0: 
            self.page = 4
        self.pages[self.page].pack(side=TOP) #add the next one

    def main(self, root):
        self.destroy
        for dataset in taskList:
            logMsg = str(dataset) + "\n" + str(taskList[dataset]) +"\n"
            file.write(logMsg)
        file.close()



if __name__ == "__main__":
    root = Tk()
    root.title("Cost Surface Generator")
    app = Application(root)
    app.mainloop()

Tags (2)
0 Kudos
5 Replies
KimOllivier
Occasional Contributor III
That is the problem, it does not use the same event queue to detect mouse actions.

I don't see anything that you could not do using the tool interface. Have you looked at the validation tab? You can add in Python scripts there that can react to filling in the form before the script is run.

You can interactively add in an input there and do some preprocessing depending on the input.
0 Kudos
LukeWebb
Occasional Contributor III
Same here - I like a Custom Branded etc UI to match my Arc9 VBA tools that everyone is used to using.
0 Kudos
LukeWebb
Occasional Contributor III
Just seen this post from ESRI in a different topic:

You can mark the GP tool to run out-of-process in the script tool configuration dialog (uncheck run in process) and the focus contention issue should go away. And I believe PyQT behaves pretty well when run in process, but I have not done extensive testing.


I have not got round to testing it though and wont be for a while!


Source: http://forums.arcgis.com/threads/4106-Python-GUI-Arc10?p=108684&viewfull=1#post108684
0 Kudos
KimOllivier
Occasional Contributor III
But hold your code! ArcGIS 10.1 will have AddIns and support for windows events.
As seen in this video
http://video.esri.com/watch/234/the-power-of-python
0 Kudos
TimLeach
New Contributor II
This may be a workaround (apparently until 10.1).  I simply wanted to get a Tkinter form to work with an entry box called from a script tool from ArcToolbox.  I had interface problems as mentioned earlier in the thread.  Focus wouldn't allow me to enter anything in the entry box. 

I tried a few things and this seemed to be a fix for now (I only tested on one form).  The object of my script was to update a database table in a filegeodatabase.  I wasn't doing anything with TOC layers, so this solution may not work for you.  Also, this could be bad practice, but I was more concerned with the form working to update the database table.

My solution was to create a separate python script that is accessed by a tool, that in turn calls my real tool with the tkinter form.  The script launches my real tool from the operating system using os.system.  This seems to provide a buffer from interference with ArcMap's GUI.

Here's the code of a separate python program called PyLauncher.py.  PyLauncher.py runs my real tool with tkinter forms within the script called UpdtProjectDB.py from the operating system via a seperate process or shell:

import os
runin = os.system("C:\\Python26\\ArcGIS10.0\\python C:\\Tim\\ArcGIS\\TableRelate\\py\\UpdtProjectDB.py")

I add the launcher to my ArcToolbox as a script tool and am then able to interact with the Tkinter form from UpdtProjectDB.py.  I have not done any research to see if this might be problematic, so use this technique at your own risk.  It's not fast, but in my case it works and I can look forward to the improvements in 10.1.
0 Kudos