#! /usr/bin/python # (tested on Windows XP, with Python 2.7 and Tcl/Tk 8.5.2 built entirely # from sources; on Linux using Ubuntu 9.04 distributed packages -- Python # 2.6.2) # Example of a scrolled list bulit from a custom widget # # type1: an Entry field with a StringVar built-in # type2: another Entry field, without the built-in StringVar # frm: a Frame build from two type1's and one type2, with some Labels # testlist: a scrolling list of frm's # testcase: build a 3 high testlist with 10 frm's (scrollable) # Issues: # # 1. Moving the slider slightly gives jerky motion due to # redisplaying. To reduce this I check for a small "moveto" # that doesn't change the displayed row and only set the # scrollbar (slider) without redisplaying the list elements. # # 2. on Linux, moving the slider on the scroll bar to the top # sometimes gives a negative "moveto", which I force to 0, or a # "moveto" greater than 1.0, which I force to 1.0. This # doesn't happen on Windows. # # 3. on Windows, using "pack" as shown causes the root window to flash # quite a bit when scrolling (presumably due to the "grid_forget"s # briefly redisplaying the window with fewer rows. This isn't # visible on Linux. As a workaround on Windows I use a Canvas # which hides the flashing. # # 4. (MOST SEVERE) On Windows, with or without the Canvas widget, # moving the slider slowly hits a glitch when row #7 would be # the top row displayed. The slider jumps between a pair of # values (sometimes 0.56 and 0.7, sometimes other values), # causing the window to flash rapidly. I cannot get the slider # to position row #7 at the top -- it jumps to row #8 or row #6 # (or even lower). This does not happen on Linux -- scrolling # is smooth, no glitches. from Tkinter import * import sys class type1(Entry): def __init__(self,master, v='xxx'): self.v = StringVar() self.v.set(v) Entry.__init__(self, master, textvariable=self.v) class type2(Entry): def __init__(self,master, **kw): Entry.__init__(self,master,**kw) class frm(Frame): def __init__(self, master, **kw): Frame.__init__(self) self.x1 = type1(self,**kw) self.x1.pack(side=LEFT) self.fx2 = Frame(self) Label(self, text=' X2').pack(in_=self.fx2,side=LEFT) self.v2 = StringVar() self.v2.set('yy') self.x2 = type2(self,width=5,textvariable=self.v2) self.x2.pack(in_=self.fx2,side=LEFT) self.fx2.pack(side=LEFT) self.fx3 = Frame(self) Label(self,text=' X3').pack(in_=self.fx3,side=LEFT) self.x3 = type1(self, v='zzz') self.x3.pack(in_=self.fx3,side=LEFT) self.fx3.pack(side=LEFT) class testlist(Frame): def __init__(self, master, maxdisp=3, **kw): Frame.__init__(self,master) self.sb = Scrollbar(self, command=self.scroll) self.sb.grid(in_=self,row=0,column=1,rowspan=maxdisp,sticky=N+S) self.maxdisp = maxdisp self.frms = [frm(self,**kw)] self.active = 0 self.frms[0].grid(in_=self,row=0,column=0) def setsb(self,start,mv): dispfrac = self.maxdisp / float(len(self.frms)) startfrac = float(start) / len(self.frms) if mv == None: self.sb.set(startfrac, min(1, startfrac+dispfrac)) else: self.sb.set(mv, min(1, mv+dispfrac)) def redisp(self, start, mv=None): for xx in self.frms[self.active:self.maxdisp+self.active]: xx.grid_forget() yy = 0 for xx in self.frms[start:self.maxdisp+start]: xx.grid(in_=self,row=yy,column=0) yy += 1 self.setsb(start,mv) # print start, startfrac, min(1, startfrac+dispfrac) self.active = start def addentry(self, **kw): self.frms.append(frm(self,**kw)) # perhaps move to the end of the list ? self.redisp(self.active) def scroll(self,*xx): op, cnt = xx[0], xx[1] mv = None if op == "scroll": if xx[2] == 'pages': amt = self.maxdisp * int(xx[1]) else: amt = int(xx[1]) newstart = self.active + amt else: mv = min(1.0,max(0.0,float(cnt))) newstart = int(len(self.frms) * mv) print cnt, mv, newstart newstart = min(newstart, len(self.frms) - self.maxdisp) newstart = max(0, newstart) if newstart <> self.active: self.redisp(newstart,mv=mv) else: self.setsb(newstart,mv) def testcase(master): tt = testlist(master,v='1') for xx in range(2,11): tt.addentry(v=str(xx)) return tt if (__name__ == '__main__'): rr = Tk() try: arg1=sys.argv[1] except IndexError: arg1=None if arg1 == 'c': # use Canvas cc=Canvas(rr,height=300,width=600) cc.pack() tt = testcase(cc) tt.place(x=10, y=10) else: # just pack into root tt = testcase(rr) tt.pack() rr.mainloop()