I just started learning Python last week, and I was having a few issues with doing a GUI app in Tk.

My main problem is this: I have a list (each member of the list is a frame with a picture and some text in it) that populates itself by going through a folder with a bunch of pictures in it and making an entry (not the entry widget, a frame) for each one. As things are, the program takes about 15 seconds after starting it before the window actually pops up because the list takes so long to populate. How would I go about populating this list after the window pops up, so that the user can start scrolling through it while it's on its way? (P.S., I might want to load something like ten members, then start the window, then load the rest if possible.)

Secondly, each picture has tags associated with it (like green, or hasPerson) that I'd like to be able to filter the list with. I can simply go through the list and destroy the members that don't have the required tag, but if I decide to look at pictures with another tag later, I'd rather not have to reload the list. In other words, I'd like to load the list once at the beginning of the program and not have to load it again. Is there a way I can temporarily hide widgets?

Ty in advance.

Recommended Answers

All 7 Replies

(like green, or hasPerson) that I'd like to be able to filter the list with.

Use a dictionary of lists, with the key being "green" or whatever, pointing to those pictures. You can then send all of the lists in the dictionary, or just the list for one key, to the Tk widget.

I might want to load something like ten members, then start the window, then load the rest if possible.

You would have to use threading or multiprocessing. That is read 10 pictures, start the Tk program in one thread and load the rest of the pictures in another process or thread. Note that the container that is holding the pictures would have to be common to both processes or threads.

Also, if you did not want to use threading/multiprocessing, you could use the "after" method.

Example:

def dothis():
    #Do something
    master.after(2000,dothis)       

master = Tk()
myapp.after(2000,dothis) 
master.mainloop()

Arguments:
The first argument is the time in milliseconds, the second is the function to run after the time set in the first argument has passed.

Thanks for the quick responses. :)

I was kind of aware of threading, but I didn't know if it'd solve my problems. Now, I''m trying to implement it, but I'm running into some problems. I wanted to make a scrollable frame, but since that wasn't possible directly, I had to make a canvas that could scroll. I looked up some code, and ended up making a class out of it:

class pScrollingFrame:
	def __init__(self, parent, argWidth, xscroll=0, yscroll=0, **argIFSpecs):
		self.oFrame = Frame(parent)
		self.canvas = Canvas(self.oFrame, width=argWidth)
		self.canvas.grid(row=0, column=0)
		if xscroll:
			self.xscrll = Scrollbar(self.oFrame, orient=HORIZONTAL, command=self.canvas.xview)
			self.canvas.configure(xscrollcommand=self.xscrll.set)
			self.xscrll.grid(row=1, column=0, sticky='we')
		if yscroll:
			self.yscrll = Scrollbar(self.oFrame, orient=VERTICAL, command=self.canvas.yview)
			self.canvas.configure(yscrollcommand=self.yscrll.set)
			self.yscrll.grid(row=0, column=1, sticky='ns')
		self.iFrame = Frame(self.canvas, **argIFSpecs)
		self.canvas.create_window(0, 0, window=self.iFrame, anchor='nw')
	def update(self):
		self.iFrame.update_idletasks()
		self.canvas.configure(scrollregion=(0, 0, self.iFrame.winfo_width(), self.iFrame.winfo_height()))

Where I looked up the code said I have to run the two lines in update whenever I put something in the scrollable frame, so I put them in their own separate function. It works when I normally run my code, but now that I do threading (first thread is the mainloop of the window, the second thread populates it), when it's done populating the list and reaches the update method, the program crashes. Why is this?

Btw, for the pScrollingFrame, its .iFrame is what I put stuff in, the .oFrame is what I grid into the actual window.

First off, I switched out that scrolling frame code with a more well formed class, so that worries me less, but I'm still having a few issues.

I tried both threading and the after method, but each time I ran into a similar problem. When I used threading, the window came up and the list started to get populated just fine, but when it was done, the little bar that you scroll with never appeared, it just stopped working. With the after method, the list was apparently populating (I have it output the names of the pictures to the console in the back, and it was doing that), but the list wasn't getting it's widgets added, and it also froze afterwards. What exactly is happening, here?

Here's a link to the scrollframe code I'm using: http://bruno.thoorens.free.fr/downloads/scrolled.py

And here's my code:

from Tkinter import *; from PIL import Image, ImageTk
import os; from functools import partial; import threading
from scrolled import Scrolledframe

def pThumbnail(argMaster, argWhere, argSize):
	mImage = Image.open(argWhere)
	mImage.thumbnail(argSize, Image.ANTIALIAS)
	mPhoto = ImageTk.PhotoImage(mImage)
	mPicLb = Label(argMaster, image=mPhoto)
	mPicLb.photo = mPhoto
	return mPicLb
	
def fillList(argList):
	path = r"C:\Users\Code\Everything\Graphics\Backgrounds"
	i = 0
	for aPic in os.listdir(path):
		if aPic == "desktop.ini": continue
		if i == 15: break
		thumbFrame = Frame(argList, relief=RAISED, borderwidth=1)
		thumbFrame.grid(row=i, column=0, sticky='we')
		thumbPic = pThumbnail(thumbFrame, path + "\\" + aPic, (56,56))
		thumbPic.grid(row=0, column=0, rowspan=2)
		thumbName = Label(thumbFrame, text=aPic)
		thumbName.grid(row=0, column=1, sticky='nswe')
		thumbTags = Label(thumbFrame, text="Tags to be placed")
		thumbTags.grid(row=1, column=1, sticky='nswe')
		i += 1; print i
	
def main():
	root = Tk(); root.title("Metadata Tagger: Picture Beta"); root.geometry('480x300')
	
	wViewer = Frame(root, width=250, height=300)
	wLister = Scrolledframe(root, stretch=True, width=180, height=300)
	wScroll = Scrollbar(root, orient="vertical", command=wLister.yview)
	wLister["yscrollcommand"] = wScroll.set
	
	wViewer.grid(row=0, column=0, sticky='nswe')
	wLister.grid(row=0, column=1, sticky='nswe')
	wScroll.grid(row=0, column=2, sticky='nswe')

	fillList(wLister())
	root.mainloop()

if __name__ == '__main__':
	main()

Also I was wondering how to make it so that when someone clicks on one of the entries in the list, a picture appears in the left frame along with some other things, but I'm having problems making the older frame with the stuff in it dissapear. Could I get some help with that, too?

"after" is just a timer, so it won't help you here as it will just stop the program for a period of time and then continue. I use multiprocessing/pyprocessing, not threading so can't help you there. To make the picture appear when someone clicks something, you would use a callback. I used the following program to test callbacks and it does something similiar to what you want. This program uses a checkbutton to copy a string to the right-hand box, and remove it if checked again which should give you a framework which you can adapt to your program. It also uses the Pmw extension to Tkinter becuase Pmw has scrolled widgets built in. If you don't want to install Pmw you can still see how a callback works when a specific button is pressed from the source code [the buttons() function]. http://www.pythonware.com/library/tkinter/introduction/events-and-bindings.htm

from Tkinter import *
import Pmw

class CB:
   def __init__(self, master):
      master.geometry("290x200+5+10" )
      self.master = master
      self.button_num = 0
      self.button_dic = {}
      self.maps_list = self.retrieve_maps()
      print self.maps_list
      self.var_list = []
      self.cb_list = []
      self.choice_list = []

      ##   frame to contain the list boxes
      self.frame = self.scrolled_frame('Available', 150)
      self.exit_button()
      self.label_box()
      self.buttons()


   ##-------------------------------------------------------------------
   def buttons(self):
      """ one checkbutton for each item in the list
      """
      for map_c in self.maps_list:
         self.button_dic[self.button_num] = map_c
         var = IntVar()
         var.set(0)          ## off=default
         self.var_list.append(var)

         b = Checkbutton(self.frame, text = map_c, variable=var)
         b.pack()
         self.cb_list.append ( b )
         def handler ( event, self=self, button_num=self.button_num ):
                return self.cb_handler( event, button_num )
         b.bind ( "<Button-1>", handler )

         self.button_num += 1


   ##-------------------------------------------------------------------
   def cb_handler( self, event, cb_number ):
##      print "cb_handler", cb_number, self.button_dic[cb_number]
      store_name = self.button_dic[cb_number]
      if store_name not in self.choice_list :
         self.choice_list.append(store_name)   ## add to selected
      else:
         self.choice_list.remove(store_name)   ## remove from selected
      self.choice_list.sort()

      text_label = "\n".join(self.choice_list)
      self.label_box.configure(text="---Print These---\n" + text_label)

   ##-------------------------------------------------------------------
   def exit_button(self):
      but_frame=Frame(self.master)
      prt =  Button(but_frame, text='PRINT',
             command=self.finished, bg='blue', fg='white' )
      prt.pack(side="left", fill=X, expand=1)

      exit=  Button(but_frame, text='CANCEL',
             command=self.master.quit, bg='red', fg='white' )
      exit.pack(side="right", fill=X, expand=1)

      but_frame.pack()

   ##-------------------------------------------------------------------
   def finished(self):
      ctr = 0
      for var in self.var_list:
         v = var.get()
         print ctr
         if v:
            print "button is on ", self.maps_list[ctr]
         else:
            print "OFF"
         ctr += 1
      self.master.quit()


   ##-------------------------------------------------------------------
   def label_box(self):
      # Create a label to display the selected items
      self.label_box = Label(self.frame,
                    text = '---Print These---',
                    relief = 'sunken', padx = 10, pady = 10)
      self.label_box.pack(side="right", fill = 'both', expand = 1, \
                          padx = 10, pady = 10)

   ##-------------------------------------------------------------------
   def retrieve_maps(self):
      maps_list = ["Los Angeles", "San Francisco", "Burbank", \
                   "Pasadena", "Hollywood", "Long Beach", \
                   "Catalina", "Alhambra", "Huntington" ]
      longest = max(maps_list)
      len_longest = len(longest)
      for map in maps_list:
         str_append = " "*(len_longest-len(map))
         map += str_append
      maps_list.sort()
      return maps_list

   ##-------------------------------------------------------------------
   def scrolled_frame(self, lit, height=150):
      self.sf = Pmw.ScrolledFrame(self.master,
                                 labelpos = 'n',
                                 label_text = lit,
                                 usehullsize = 1,
                                 hull_width = 200,
                                 hull_height = height,
                                 )

      self.sf.pack(padx=5, pady=3, fill='both', expand=1)
      frame = self.sf.interior()
      return frame

##======================================================================
if __name__ == "__main__":

   root = Tk()
   xx=CB(root)
   root.mainloop()

I use multiprocessing/pyprocessing, not threading

woooee: I've seen you mention this before. What makes you choose multi-process over multi-threads? I don't use either, so I'm just wondering if you see a particular advantage in one over the other.

woooee: I've seen you mention this before. What makes you choose multi-process over multi-threads? I don't use either, so I'm just wondering if you see a particular advantage in one over the other.

Laziness partly. I only wanted to learn one, and wanted a way to kill a process and be able to pass data back and forth. Multiprocessing/pyprocessing has a kill function and uses a list or dictionary to store common data, so game over from my perspective.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.