[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