[wxPython-users] a scrollbar problem with xrc loaded grid

Robin Dunn robin at alldunn.com
Sat Dec 1 12:16:46 PST 2007


Stuart wrote:
> I am having a problem with scrollbars in a grid window
> that was loaded from an xrc file.  The corresponding
> program that creates the controls directly does not show
> the problem.
> =

> I have the following layout:
> =

>    frame
>      panel1
>        vert box sizer
>          panel2
>            grid
>          horiz box sizer
>            button
> =

> The grid is wx.Grid subclass that can be reloaded with
> new data and will adjust it's number of rows accordingly.
> It is created with 50 rows and clicking the Chng Data
> button will reload it with a smaller, random number of
> rows.
> =

> When I run the version that constructs the controls
> manually, it works fine:  the scrollbars appear when
> expected.
> =

> When I run the version loaded from an xrc file (which
> I think is otherwise identical),

Not quite.  Try adding the widget inspection tool to your app and it's =

easy to see the difference in the widget and sizer hierarchies.  Another =

debugging trick I use is to set different background colors for the =

panels, then you can more easily see what is where.


> the vertical scrollbar
> only appears when the frame is made narrow enough for
> the horizontal scrollbar to appear.  If you slowly drag
> the frame narrower, you can see the vertical scrollbar
> start to appear as the frame border approaches the edge
> of the grid.
> =

> Seems like I am doing something wrong in my OnResizePanel2
> handler but I am new to wxpython and rather over my head
> here.  I also don't understand why it works fine in the
> non-xrc version.  =


There are a couple problems here that relate to using the unknown =

control feature of XRC:

* When you use an unknown control from XRC it actually creates a panel =

to contain the unknown, and when you call AttachUnknownControl it =

reparents the widget so it is parented by that panel, and puts it in a =

sizer so it fills the panel.  So when your EVT_SIZE handler is resizing =

the grid to be the same as PANEL2's client size, you are actually sizing =

it to be larger (initially) than the container panel, so it ends up =

clipping the grid.  When PANEL2 gets sized smaller than that initial =

size then the clipped portion of the grid becomes visible.  If you had =

put the unknown control in a sizer in the XRC instead of trying to =

manage its size yourself then it would not have had this problem because =

the sizer would change the size of the container, which would then =

change the size of the grid.

* Getting grid of the EVT_SIZE handler and putting the unknown in a =

sizer reveals the other problem.  This one is due to a weakness in the =

unknown control feature.  It works well with fixed sized items, but =

variable sized items or items that are intended to scroll sometimes do =

not.  The container panel calls SetSizerAndFit for the sizer that it =

puts your grid into.  This means that the container panel will be sized =

to the best size of the grid and set its minsize to that best size too. =

  With your grid initially having 50 rows that means that the panel will =

have a min size that is around 1000+ pixels high, and because of the =

sizer the grid will be made that size too.  This is obviously clipped as =

it should be by PANEL1, but it means that the since the grid thinks that =

it is large enough to not show its scrollbars then it won't do it.

You can work around this by setting the min size of the grid to some =

small value before it is passed to AttachUnknownControl.  This minsize =

will override the best size and things will work more like what is expected.

A probably better way to take care of this is to not use the unknown =

control feature at all.  You can use a normal grid class type in the =

XRC, and set the subclass attribute to the modulename.classname of your =

grid class.  That causes the subclass factory to create an instance of =

the class, but in all other ways it will be treated as a wx.grid.Grid. =

Then there won't be any mysterious container panel created behind the =

scenes for you to get in the way.  This takes a little tweaking of the =

class that will be loaded this way, see the demo for an example.



> The reason there is no sizer in panel2 is that in the
> real app, there is also an html control in there,
> overlapping the grid, and I will hide one of the other
> according to conditions, as per
> http://lists.wxwidgets.org/archive/wxPython-users/msg14336.html

> And as long as I'm here... :-)
> =

> I think a notebook control would also work well if I could
> get rid of the tabs and control page switching from buttons
> and menus.  Is getting rid of the tabs possible?
> =

> Would an "overlap sizer" be a useful addition to wxwidgets?

Most people do this simply by putting all of their page windows into a =

box sizer, and then hiding all but the one they want shown.  When you =

want to show a different page then just show it, hide the old one and =

call the sizer's Layout method.


-- =

Robin Dunn
Software Craftsman
http://wxPython.org  Java give you jitters?  Relax with wxPython!

-------------- next part --------------
A non-text attachment was scrubbed...
Name: gridsz2.xrc
Type: text/xml
Size: 956 bytes
Desc: not available
Url : http://lists.wxwidgets.org/pipermail/wxpython-users/attachments/20071=
201/c9216407/gridsz2.bin
-------------- next part --------------
#!/usr/bin/env python

import sys, random, pdb
import wx, wx.xrc, wx.grid
import wx.lib.mixins.inspection

def main():
	model =3D Model ()
	app =3D Application (model)
	app.MainLoop ()

class Application (wx.App, wx.lib.mixins.inspection.InspectionMixin):
    def __init__ (self, model):
	self.model =3D model
	wx.App.__init__(self, redirect=3D0)

    def OnInit (self):
        self.Init()
	xrcres =3D wx.xrc.XmlResource ("gridsz2.xrc")
	frame =3D Frame (None, -1, "Gridsz2", self.model, xrcres)
	self.SetTopWindow (frame)
	return True

class Frame (wx.Frame):
    def __init__ (self, parent, id, title, model, xrcres):
	self.model =3D model	# Database interface.
	wx.Frame.__init__ (self, parent, id, title)

	xrcres.LoadPanel (self, "PANEL1")
	self.grid =3D MyGrid (self, ('AAA','BBB','CCC'), 50)
	xrcres.AttachUnknownControl ('grid', self.grid, self)
	b =3D wx.xrc.XRCCTRL (self, 'data')
	p2 =3D wx.xrc.XRCCTRL (self, 'PANEL2')

	b.Bind (wx.EVT_BUTTON, self.chng_data)
##	p2.Bind (wx.EVT_SIZE, self.OnResizePanel2)

	self.Show (True)

    def OnResizePanel2 (self, evt):
	panel =3D evt.GetEventObject()
	sz =3D panel.GetClientSize()
	self.grid.SetSize(sz)
	#self.grid.ForceRefresh()
	#evt.Skip()

    def update_grid_contents (self):
	self.grid.loaddata (self.model.data)

    def chng_data (self, evt=3DNone):
	self.model.chng_data()
	self.update_grid_contents()

class MyGrid (wx.grid.Grid):

    '''Subclass of wx.Grid that provides methods for adjusting
    the number of rows, and for reloading grid data from an
    array with adjustment of the number of rows to match.''' =

 =

    def __init__ (self, parent, colheads, maxrows, data=3D[]):
	wx.grid.Grid.__init__(self, parent, -1) =

	self.CreateGrid (maxrows, len(colheads))
        self.SetMinSize((1,1))
	for r in range (maxrows):
	    for c in range (len(colheads)):
		self.SetCellValue (r, c, '')
	self.setcolheads (colheads)
	#self.loaddata (data)

    def setcolheads (self, colheads):
	for n,lbl in enumerate (colheads):
	    self.SetColLabelValue (n, lbl)

    def loaddata (self, data):
	self.adjustsize (len (data))
	for c in range(self.GetNumberCols()):
	    for r in range (len(data)):
		self.SetCellValue (r, c, str(data[r][c]))

    def adjustsize (self, nrows):
	#print "adjustsize entered, number rows is %d, wanted %d" \
	#	% (self.GetNumberRows(),nrows)
	delta =3D nrows - self.GetNumberRows()
	if delta > 0: =

	    if not self.InsertRows (0, delta):
		raise RuntimeError ("Failed to insert %d rows into grid" % delta)
	if delta < 0: =

	    if not self.DeleteRows (0, -delta):
		raise RuntimeError ("Failed to delete %d rows from grid" % delta)
	#print "adjustsize leave, number rows is %d" % (self.GetNumberRows(),)


class Model:
    def __init__(self):
	self.data =3D []

    def chng_data (self):
	# Create between 0 and 12 rows of 3-column random data...
	self.data =3D []
	chars =3D list('abcdefg')
	nrows =3D random.randint (0, 12)
	for i in range (nrows):
	    random.shuffle (chars)
	    self.data.append (
		(i * 10,
		 ''.join(chars[:random.randint (2,6)]),
		 random.randint (0, 9999)))

if __name__ =3D=3D '__main__':
	main()



More information about the wxpython-users mailing list