A ScrolledFrame, with several bugs.
1 import Tkinter
2
3 GM_KEYS = set(
4 vars(Tkinter.Place).keys() +
5 vars(Tkinter.Pack).keys() +
6 vars(Tkinter.Grid).keys()
7 )
8
9 class ScrolledFrame(object):
10 _managed = False
11 # XXX These could be options
12 x_incr = 5
13 y_incr = 5
14
15 def __init__(self, master=None, **kw):
16 self.width = kw.pop('width', 200)
17 self.height = kw.pop('height', 200)
18
19 self._canvas = Tkinter.Canvas(master, **kw)
20 self.master = self._canvas.master
21 self._hsb = Tkinter.Scrollbar(orient='horizontal',
22 command=self._canvas.xview)
23 self._vsb = Tkinter.Scrollbar(orient='vertical',
24 command=self._canvas.yview)
25 self._canvas.configure(
26 xscrollcommand=self._hsb.set,
27 yscrollcommand=self._vsb.set)
28
29 self._placeholder = Tkinter.Frame(self._canvas)
30 self._canvas.create_window(0, 0, anchor='nw', window=self._placeholder)
31
32 self._placeholder.bind('<Map>', self._prepare_scroll)
33 for widget in (self._placeholder, self._canvas):
34 widget.bind('<Button-4>', self.scroll_up)
35 widget.bind('<Button-5>', self.scroll_down)
36
37
38
39 def __getattr__(self, attr):
40 if attr in GM_KEYS:
41 if not self._managed:
42 # Position the scrollbars now.
43 self._managed = True
44 if attr == 'pack':
45 self._hsb.pack(side='bottom', fill='x')
46 self._vsb.pack(side='right', fill='y')
47 elif attr == 'grid':
48 self._hsb.grid(row=1, column=0, sticky='ew')
49 self._vsb.grid(row=0, column=1, sticky='ns')
50 return getattr(self._canvas, attr)
51
52 else:
53 return getattr(self._placeholder, attr)
54
55
56 def yscroll(self, *args):
57 self._canvas.yview_scroll(*args)
58
59
60 def scroll_up(self, event=None):
61 self.yscroll(-self.y_incr, 'units')
62
63
64 def scroll_down(self, event=None):
65 self.yscroll(self.y_incr, 'units')
66
67
68 def see(self, event):
69 widget = event.widget
70 w_height = widget.winfo_reqheight()
71 c_height = self._canvas.winfo_height()
72 y_pos = widget.winfo_rooty()
73
74 if (y_pos - w_height) < 0:
75 # Widget focused is above the current view
76 while (y_pos - w_height) < self.y_incr:
77 self.scroll_up()
78 self._canvas.update_idletasks()
79 y_pos = widget.winfo_rooty()
80 elif (y_pos - w_height) > c_height:
81 # Widget focused is below the current view
82 while (y_pos - w_height - self.y_incr) > c_height:
83 self.scroll_down()
84 self._canvas.update_idletasks()
85 y_pos = widget.winfo_rooty()
86
87
88 def _prepare_scroll(self, event):
89 frame = self._placeholder
90 frame.unbind('<Map>')
91
92 if not frame.children:
93 # Nothing to scroll.
94 return
95
96 for child in frame.children.itervalues():
97 child.bind('<FocusIn>', self.see)
98
99 width, height = frame.winfo_reqwidth(), frame.winfo_reqheight()
100 self._canvas.configure(scrollregion=(0, 0, width, height),
101 yscrollincrement=self.y_incr, xscrollincrement=self.x_incr)
102
103 self._canvas.configure(width=self.width, height=self.height)
104
Usage example:
1 root = Tkinter.Tk()
2
3 sf = ScrolledFrame()
4 sf.grid(row=0, column=0, sticky='nsew')
5 sf.master.grid_columnconfigure(0, weight=1)
6 sf.master.grid_rowconfigure(0, weight=1)
7
8 for _ in range(10):
9 lbl = Tkinter.Label(sf, text="Hi")
10 lbl.pack()
11 btn = Tkinter.Button(sf, text="Buh")
12 btn.pack()
13 entry = Tkinter.Entry(sf)
14 entry.pack()
15
16 root.mainloop()
17