class ScrolledFrame(Pmw
.MegaWidget
):
def __init__(self
, parent
= None, **kw
):
# Define the megawidget options.
('borderframe', 1, INITOPT
),
('horizflex', 'fixed', self
._horizflex
),
('horizfraction', 0.05, INITOPT
),
('hscrollmode', 'dynamic', self
._hscrollMode
),
('labelmargin', 0, INITOPT
),
('labelpos', None, INITOPT
),
('scrollmargin', 2, INITOPT
),
('usehullsize', 0, INITOPT
),
('vertflex', 'fixed', self
._vertflex
),
('vertfraction', 0.05, INITOPT
),
('vscrollmode', 'dynamic', self
._vscrollMode
),
self
.defineoptions(kw
, optiondefs
)
# Initialise the base class (after defining the options).
Pmw
.MegaWidget
.__init
__(self
, parent
)
self
.origInterior
= Pmw
.MegaWidget
.interior(self
)
self
.origInterior
.grid_propagate(0)
# Create a frame widget to act as the border of the clipper.
self
._borderframe
= self
.createcomponent('borderframe',
Tkinter
.Frame
, (self
.origInterior
,),
self
._borderframe
.grid(row
= 2, column
= 2, sticky
= 'news')
# Create the clipping window.
self
._clipper
= self
.createcomponent('clipper',
Tkinter
.Frame
, (self
._borderframe
,),
self
._clipper
.pack(fill
= 'both', expand
= 1)
# Create the clipping window.
self
._clipper
= self
.createcomponent('clipper',
Tkinter
.Frame
, (self
.origInterior
,),
self
._clipper
.grid(row
= 2, column
= 2, sticky
= 'news')
self
.origInterior
.grid_rowconfigure(2, weight
= 1, minsize
= 0)
self
.origInterior
.grid_columnconfigure(2, weight
= 1, minsize
= 0)
# Create the horizontal scrollbar
self
._horizScrollbar
= self
.createcomponent('horizscrollbar',
Tkinter
.Scrollbar
, (self
.origInterior
,),
# Create the vertical scrollbar
self
._vertScrollbar
= self
.createcomponent('vertscrollbar',
Tkinter
.Scrollbar
, (self
.origInterior
,),
self
.createlabel(self
.origInterior
, childCols
= 3, childRows
= 3)
# Initialise instance variables.
self
._horizScrollbarOn
= 0
self
._vertScrollbarOn
= 0
self
._horizScrollbarNeeded
= 0
self
._vertScrollbarNeeded
= 0
self
._flexoptions
= ('fixed', 'expand', 'shrink', 'elastic')
# Create a frame in the clipper to contain the widgets to be
self
._frame
= self
.createcomponent('frame',
Tkinter
.Frame
, (self
._clipper
,)
# Whenever the clipping window or scrolled frame change size,
self
._frame
.bind('<Configure>', self
._reposition
)
self
._clipper
.bind('<Configure>', self
._reposition
)
# Work around a bug in Tk where the value returned by the
# scrollbar get() method is (0.0, 0.0, 0.0, 0.0) rather than
# the expected 2-tuple. This occurs if xview() is called soon
# after the Pmw.ScrolledFrame has been created.
self
._horizScrollbar
.set(0.0, 1.0)
self
._vertScrollbar
.set(0.0, 1.0)
# Check keywords and initialise options.
if self
.scrollTimer
is not None:
self
.after_cancel(self
.scrollTimer
)
Pmw
.MegaWidget
.destroy(self
)
# ======================================================================
# Set timer to call real reposition method, so that it is not
# called multiple times when many things are reconfigured at the
if self
.scrollTimer
is None:
self
.scrollTimer
= self
.after_idle(self
._scrollBothNow
)
# Called when the user clicks in the horizontal scrollbar.
# Calculates new position of frame then calls reposition() to
# update the frame and the scrollbar.
def xview(self
, mode
= None, value
= None, units
= None):
if type(value
) == types
.StringType
:
value
= string
.atof(value
)
return self
._horizScrollbar
.get()
frameWidth
= self
._frame
.winfo_reqwidth()
self
.startX
= value
* float(frameWidth
)
clipperWidth
= self
._clipper
.winfo_width()
jump
= int(clipperWidth
* self
['horizfraction'])
self
.startX
= self
.startX
+ value
* jump
# Called when the user clicks in the vertical scrollbar.
# Calculates new position of frame then calls reposition() to
# update the frame and the scrollbar.
def yview(self
, mode
= None, value
= None, units
= None):
if type(value
) == types
.StringType
:
value
= string
.atof(value
)
return self
._vertScrollbar
.get()
frameHeight
= self
._frame
.winfo_reqheight()
self
.startY
= value
* float(frameHeight
)
clipperHeight
= self
._clipper
.winfo_height()
jump
= int(clipperHeight
* self
['vertfraction'])
self
.startY
= self
.startY
+ value
* jump
# ======================================================================
# The horizontal scroll mode has been configured.
mode
= self
['hscrollmode']
if not self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
if self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
if self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
message
= 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
raise ValueError, message
# The vertical scroll mode has been configured.
mode
= self
['vscrollmode']
if not self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
if self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
if self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
message
= 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
raise ValueError, message
# The horizontal flex mode has been configured.
if flex
not in self
._flexoptions
:
message
= 'bad horizflex option "%s": should be one of %s' % \
(flex
, str(self
._flexoptions
))
raise ValueError, message
# The vertical flex mode has been configured.
if flex
not in self
._flexoptions
:
message
= 'bad vertflex option "%s": should be one of %s' % \
(flex
, str(self
._flexoptions
))
raise ValueError, message
# ======================================================================
def _reposition(self
, event
):
clipperWidth
= self
._clipper
.winfo_width()
frameWidth
= self
._frame
.winfo_reqwidth()
if frameWidth
<= clipperWidth
:
# The scrolled frame is smaller than the clipping window.
if self
['horizflex'] in ('expand', 'elastic'):
# The scrolled frame is larger than the clipping window.
if self
['horizflex'] in ('shrink', 'elastic'):
if self
.startX
+ clipperWidth
> frameWidth
:
self
.startX
= frameWidth
- clipperWidth
endScrollX
= (self
.startX
+ clipperWidth
) / float(frameWidth
)
# Position frame relative to clipper.
self
._frame
.place(x
= -self
.startX
, relwidth
= relwidth
)
return (self
.startX
/ float(frameWidth
), endScrollX
)
clipperHeight
= self
._clipper
.winfo_height()
frameHeight
= self
._frame
.winfo_reqheight()
if frameHeight
<= clipperHeight
:
# The scrolled frame is smaller than the clipping window.
if self
['vertflex'] in ('expand', 'elastic'):
# The scrolled frame is larger than the clipping window.
if self
['vertflex'] in ('shrink', 'elastic'):
if self
.startY
+ clipperHeight
> frameHeight
:
self
.startY
= frameHeight
- clipperHeight
endScrollY
= (self
.startY
+ clipperHeight
) / float(frameHeight
)
# Position frame relative to clipper.
self
._frame
.place(y
= -self
.startY
, relheight
= relheight
)
return (self
.startY
/ float(frameHeight
), endScrollY
)
# According to the relative geometries of the frame and the
# clipper, reposition the frame within the clipper and reset the
def _scrollBothNow(self
):
# Call update_idletasks to make sure that the containing frame
# has been resized before we attempt to set the scrollbars.
# Otherwise the scrollbars may be mapped/unmapped continuously.
self
._scrollRecurse
= self
._scrollRecurse
+ 1
self
._scrollRecurse
= self
._scrollRecurse
- 1
if self
._scrollRecurse
!= 0:
self
._horizScrollbar
.set(xview
[0], xview
[1])
self
._vertScrollbar
.set(yview
[0], yview
[1])
self
._horizScrollbarNeeded
= (xview
!= (0.0, 1.0))
self
._vertScrollbarNeeded
= (yview
!= (0.0, 1.0))
# If both horizontal and vertical scrollmodes are dynamic and
# currently only one scrollbar is mapped and both should be
# toggled, then unmap the mapped scrollbar. This prevents a
# continuous mapping and unmapping of the scrollbars.
if (self
['hscrollmode'] == self
['vscrollmode'] == 'dynamic' and
self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
and
self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
and
self
._vertScrollbarOn
!= self
._horizScrollbarOn
):
if self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
self
._toggleVertScrollbar
()
if self
['hscrollmode'] == 'dynamic':
if self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
if self
['vscrollmode'] == 'dynamic':
if self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
def _toggleHorizScrollbar(self
):
self
._horizScrollbarOn
= not self
._horizScrollbarOn
interior
= self
.origInterior
if self
._horizScrollbarOn
:
self
._horizScrollbar
.grid(row
= 4, column
= 2, sticky
= 'news')
interior
.grid_rowconfigure(3, minsize
= self
['scrollmargin'])
self
._horizScrollbar
.grid_forget()
interior
.grid_rowconfigure(3, minsize
= 0)
def _toggleVertScrollbar(self
):
self
._vertScrollbarOn
= not self
._vertScrollbarOn
interior
= self
.origInterior
if self
._vertScrollbarOn
:
self
._vertScrollbar
.grid(row
= 2, column
= 4, sticky
= 'news')
interior
.grid_columnconfigure(3, minsize
= self
['scrollmargin'])
self
._vertScrollbar
.grid_forget()
interior
.grid_columnconfigure(3, minsize
= 0)