Bookmark and Share

I use Python a lot. Python 2.5.1 to be specific. And inside Python is TkInter, which, with a little work, will give you a handy way to put a GUI together. But there are problems. To say that TkInter is poorly supported and poorly documented under OSX is to understate the case rather dramatically. So you’re left to Google for answers, and mostly, they aren’t to be found — or if they are, they aren’t obvious or easily found. So I’m going to provide some answers here that have taken me quite some time to collect, and hopefully keyword and title them so that a Google search will actually get you to the solution you need sooner rather than as much later as it did me!

The first thing is that if you launch a TkInter window from the command line, it will obstinately, and stupidly, show up behind your terminal window. Searching the net for an answer, all you find is “this is normal behavior.” You know what? It doesn’t matter that it’s normal — it’s bloody stupid. If you launch a GUI, it’s because you intend to interact with it. If it opens behind the window you launched it from… stupid. So here’s the answer you’re looking for. It’s not reasonable that you have to do this, but is easy, and it works. Just before you go to root.mainloop(), you want to add this happy little fix:

os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')

Python won’t do it. TkInter won’t do it. By by Darwin, the Finder will.

Ok, next, the TkInter Listbox. Most examples tell you to use the pack() method because pack is easy. Then there are all these general notes about using sticky=N+S+E+W to make the listbox stretch with the window. Yeah. Except pack() doesn’t support sticky. Nice, eh? So, the answer is, don’t use pack. Rather than explain it all, please let me gift you with some code; most of it came from elsewhere, but I’ve prodded it to do a little more — support double clicks on the list, have an OK button if you’re a one-click kind of person… anyway, it’s handy and I truly hope you’ll find it useful. Here you go:

import Tkinter as tk
import os

class ScrolledList(tk.Listbox):
	def __init__(self, master, **kw):
		self.sel=""
		self.frame = tk.Frame(master)
		self.frame.rowconfigure(0, weight=1)
		self.frame.columnconfigure(0, weight=1)
		self.hbar = tk.Scrollbar(self.frame, orient=tk.HORIZONTAL)
		self.block = tk.Frame(self.frame, width=18, height=18)
		self.block.grid(row=1, column=1)
		self.vbar = tk.Scrollbar(self.frame, orient=tk.VERTICAL)
		kw.setdefault('activestyle', 'none')
		kw.setdefault('highlightthickness', 0)
		if 'pack' in kw.keys() and kw.pop('pack') == 1:
			self.frame.pack(fill=tk.BOTH, expand=1)
		tk.Listbox.__init__(self, self.frame, **kw)
		self.bind("",self.pressok)
		self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
		self.hbar.configure(command=self.xview)
		self.vbar.configure(command=self.yview)
		self.config(yscrollcommand=self.vbar.set,xscrollcommand=self.hbar.set)
		self.button = tk.Button(self.frame,text="OK",command=self.pressok)
		self.button.grid()

		self.hbar.grid(row=1, column=0, sticky=tk.W+tk.E)
		self.vbar.grid(row=0, column=1, sticky=tk.N+tk.S)
		self.pack = lambda **kw: self.frame.pack(**kw)
		self.grid = lambda **kw: self.frame.grid(**kw)
		self.place = lambda **kw: self.frame.place(**kw)
		self.pack_config = lambda **kw: self.frame.pack_config(**kw)
		self.grid_config = lambda **kw: self.frame.grid_config(**kw)
		self.place_config = lambda **kw: self.frame.place_config(**kw)
		self.pack_configure = lambda **kw: self.frame.pack_config(**kw)
		self.grid_configure = lambda **kw: self.frame.grid_config(**kw)
		self.place_configure = lambda **kw: self.frame.place_config(**kw)

	def pressok(self,foo=3):
		self.sel = self.curselection()
		self.frame.quit()

	def gets(self):
		return self.get(0, tk.END)

	def sets(self, arg):
		self.delete(0, tk.END)
		try:
			arg = arg.strip('\n').splitlines()
		except AttributeError:
			pass
		if hasattr(arg, '__getitem__'):
			for item in arg:
				self.insert(tk.END, str(item))
		else:
			raise TypeError("Scrolledlist.sets() requires a string of iterable of strings")

Using that is really easy. It wants to be loaded with an iterable, such as a list. So you could set it up like this:

import TkInter as tk
from scrolledlistclass import * # (that's the above list widget)
list = ['santa claus','paul bunyan','john doe','jane doe']
root=tk.Tk()
lb = ScrolledList(root, width=50, height=5, fg='black', pack=1)
lb.sets(list)
os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
root.mainloop()
if (len(lb.sel) != 0):
	sel = int(lb.sel[0]) # here's your selection, an index into the above list
	sel = alist[sel]
else:
	sel = -1
print str(sel)

So, there you have it — and I hope it saves you the enormous amount of time I spent trying to figure this stuff out. TkInter. You can actually use it now.

If I find a way to pop up a TkInter treeview under Python 2.5.1 I’ll post about that, too. So far, no joy.