Grids, Sizers, and Scrollbars

I would like to use a grid as a subwindow within a frame using sizers,
but in my attempts to do this, I can't get both the sizing and the
scrollbars to work together. Specifically, I would like the grid
window to be sized to exactly fit the cells in the horizontal
direction, and then to scroll in the vertical direction.

I've tried many combinations of Fit, FitInside, Layout, SetScrollbars,
SetVirtualSize, EXPAND, VSCROLL, etc, but just can't seem to get this
right. For example, the attempt below fits the grid correctly in the
horizontal direction but doesn't scroll in the vertical.

(Here, I'm using a Mac, Python 2.5, and wxPython 2.8.10.1)

import wx
import wx.grid as gridlib

class MyFrame(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "Grid Test", size=(700,
450))

        grid_window = MyGrid(self)
        btn_ok = wx.Button(self, -1, "OK")

        svert_sizer = wx.BoxSizer(wx.VERTICAL)
        svert_sizer.Add(grid_window, 0)

        rvert_sizer = wx.BoxSizer(wx.VERTICAL)
        rvert_sizer.Add(btn_ok, 1, wx.EXPAND)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(rvert_sizer, 1)
        sizer.Add(svert_sizer, 0, wx.EXPAND)
        sizer.Add((20,20))
        self.SetSizer(sizer)

class MyGrid(gridlib.Grid):

    def __init__(self, parent):
        gridlib.Grid.__init__(self, parent, -1, style=wx.VSCROLL)

        self.SetDefaultColSize(60)
        self.SetDefaultRowSize(30)

        self.CreateGrid(50, 7)

if __name__=="__main__":

    class MyApp(wx.App):

        def OnInit(self):
            frame = MyFrame(None)
            frame.Show(True)
            self.SetTopWindow(frame)
            return True

    app = MyApp(0)
    app.MainLoop()

If you add the WIT to your sample you'll see that the calculated best size of the grid has a height of something like 1532 pixels. So the reason that the grid is not showing the scrollbar is because it doesn't need one, it is being sized to that height and then being truncated by its parent window so you can't see the whole thing.

http://wiki.wxpython.org/Widget_Inspection_Tool

If you set the grid's minsize with something like self.SetMinSize((-1, 250)) then it will override the calculated best size and use that height instead. Then you'll just need to fiddle with proportions and sizer flags a little bit to get that item to fill the available extra space and it should behave the way you want.

···

On 3/31/10 8:51 AM, Tom wrote:

I would like to use a grid as a subwindow within a frame using sizers,
but in my attempts to do this, I can't get both the sizing and the
scrollbars to work together. Specifically, I would like the grid
window to be sized to exactly fit the cells in the horizontal
direction, and then to scroll in the vertical direction.

I've tried many combinations of Fit, FitInside, Layout, SetScrollbars,
SetVirtualSize, EXPAND, VSCROLL, etc, but just can't seem to get this
right. For example, the attempt below fits the grid correctly in the
horizontal direction but doesn't scroll in the vertical.

(Here, I'm using a Mac, Python 2.5, and wxPython 2.8.10.1)

--
Robin Dunn
Software Craftsman

If you add the WIT to your sample you'll see that the calculated best
size of the grid has a height of something like 1532 pixels.

Thanks. WIT seems to be really useful, both for fixing problems and
also learning wx since now I can easily inspect what I was simply
guessing at before. I'm still having some trouble though...

If you set the grid's minsize with something like self.SetMinSize((-1,
250)) then it will override the calculated best size and use that height
instead. Then you'll just need to fiddle with proportions and sizer
flags a little bit to get that item to fill the available extra space
and it should behave the way you want.

The problem I run into after adding SetMinSize is that when the
vertical scrollbar is added, a horizontal one, which I don't want, is
also added. I think this is because the sizer fails to account for
the vertical scrollbar's width, so the scrollbar draws over the
rightmost grid element, and a horizontal scrollbar is added to account
for this. Is there a way to fix this?

With wx.ScrolledWindows (which the Grid class derives from) it is possible to have a virtual size that is not an exact multiple of the scroll rate, and so it has to round up the sizes in order to make it possible to scroll the whole thing into view. This means that it's possible to have it showing some extra space (or think that it needs to) that you don't want there. I'm guessing that this is, or is related to, the problem that you are seeing.

To workaround you can either make sure that the horizontal virtual size is always an exact multiple of the scroll rate (GetScrollLineX()) including leaving room for the scrollbar. (Adjusting column widths, etc.) Or you can just calculate the horizontal size needed yourself and use that in your SetMinSIze too.

Personally I would probably just let the grid expand to fill the available horizontal space instead of trying to make the container resize itself to exactly fit the grid. Then I'd probably do something like resize the last column in the grid to fill whatever leftover space is available (leaving room for the scrollbar) so it looks a little better.

···

On 3/31/10 11:47 AM, Tom wrote:

If you add the WIT to your sample you'll see that the calculated best
size of the grid has a height of something like 1532 pixels.

Thanks. WIT seems to be really useful, both for fixing problems and
also learning wx since now I can easily inspect what I was simply
guessing at before. I'm still having some trouble though...

If you set the grid's minsize with something like self.SetMinSize((-1,
250)) then it will override the calculated best size and use that height
instead. Then you'll just need to fiddle with proportions and sizer
flags a little bit to get that item to fill the available extra space
and it should behave the way you want.

The problem I run into after adding SetMinSize is that when the
vertical scrollbar is added, a horizontal one, which I don't want, is
also added. I think this is because the sizer fails to account for
the vertical scrollbar's width, so the scrollbar draws over the
rightmost grid element, and a horizontal scrollbar is added to account
for this. Is there a way to fix this?

--
Robin Dunn
Software Craftsman

Thanks, Robin. I've learned a lot here, but still having trouble, so
I'll rephrase the question this way...

Is it possible to have a grid (placed within a window that has other
widgets where all are positioned with sizers) that has only a vertical
scrollbar but no horizontal scrollbar?

What I keep on finding is that no matter what my dimensions, etc, that
once the vertical scroolbar is added the horizontal one always
appears. I've tried to play with and inspect all the numbers and I'm
quite sure that the reason for this is that the vertical scrollbar is
set so that it covers a small part of the grid (rather than to the
side of the grid), so that the horizontal scrollbar is then required
to allow viewing of this tiny side bit that's covered by the vertical
scrollbar.

The grid's best size calculation (pasted below) doesn't take into account the scrollbar size. So you can probably workaround it by setting the MinSize width to BestSize width + scrollbar width, which you can get from wx.SystemSettings.

wxSize wxGrid::DoGetBestSize() const
{
     wxGrid *self = (wxGrid *)this; // const_cast

     // we do the same as in AutoSize() here with the exception that we don't
     // change the column/row sizes, only calculate them
     wxSize size(self->SetOrCalcColumnSizes(true) - m_rowLabelWidth + m_extraWidth,
                 self->SetOrCalcRowSizes(true) - m_colLabelHeight + m_extraHeight);
     wxSize sizeFit(GetScrollX(size.x) * GetScrollLineX(),
                    GetScrollY(size.y) * GetScrollLineY());

     // NOTE: This size should be cached, but first we need to add calls to
     // InvalidateBestSize everywhere that could change the results of this
     // calculation.
     // CacheBestSize(size);

     return wxSize(sizeFit.x + m_rowLabelWidth, sizeFit.y + m_colLabelHeight)
             + GetWindowBorderSize();
}

···

On 4/1/10 9:58 AM, Tom wrote:

Thanks, Robin. I've learned a lot here, but still having trouble, so
I'll rephrase the question this way...

Is it possible to have a grid (placed within a window that has other
widgets where all are positioned with sizers) that has only a vertical
scrollbar but no horizontal scrollbar?

What I keep on finding is that no matter what my dimensions, etc, that
once the vertical scroolbar is added the horizontal one always
appears. I've tried to play with and inspect all the numbers and I'm
quite sure that the reason for this is that the vertical scrollbar is
set so that it covers a small part of the grid (rather than to the
side of the grid), so that the horizontal scrollbar is then required
to allow viewing of this tiny side bit that's covered by the vertical
scrollbar.

--
Robin Dunn
Software Craftsman

This almost works for me. Is there a way to force the horizontal
scrollbar to not appear? (I've tried EnableScrolling(False, True) but
it doesn't stop the horizontal scrollbar.)

···

On Apr 1, 1:35 pm, Robin Dunn <ro...@alldunn.com> wrote:

On 4/1/10 9:58 AM, Tom wrote:

> Thanks, Robin. I've learned a lot here, but still having trouble, so
> I'll rephrase the question this way...

> Is it possible to have a grid (placed within a window that has other
> widgets where all are positioned with sizers) that has only a vertical
> scrollbar but no horizontal scrollbar?

> What I keep on finding is that no matter what my dimensions, etc, that
> once the vertical scroolbar is added the horizontal one always
> appears. I've tried to play with and inspect all the numbers and I'm
> quite sure that the reason for this is that the vertical scrollbar is
> set so that it covers a small part of the grid (rather than to the
> side of the grid), so that the horizontal scrollbar is then required
> to allow viewing of this tiny side bit that's covered by the vertical
> scrollbar.

The grid's best size calculation (pasted below) doesn't take into
account the scrollbar size. So you can probably workaround it by
setting the MinSize width to BestSize width + scrollbar width, which you
can get from wx.SystemSettings.

wxSize wxGrid::DoGetBestSize() const
{
wxGrid *self = (wxGrid *)this; // const_cast

 // we do the same as in AutoSize\(\) here with the exception that we

don't
// change the column/row sizes, only calculate them
wxSize size(self->SetOrCalcColumnSizes(true) - m_rowLabelWidth +
m_extraWidth,
self->SetOrCalcRowSizes(true) - m_colLabelHeight +
m_extraHeight);
wxSize sizeFit(GetScrollX(size.x) * GetScrollLineX(),
GetScrollY(size.y) * GetScrollLineY());

 // NOTE: This size should be cached, but first we need to add calls to
 // InvalidateBestSize everywhere that could change the results of this
 // calculation\.
 // CacheBestSize\(size\);

 return wxSize\(sizeFit\.x \+ m\_rowLabelWidth, sizeFit\.y \+

m_colLabelHeight)
+ GetWindowBorderSize();

}

--
Robin Dunn
Software Craftsmanhttp://wxPython.org

This almost works for me. Is there a way to force the horizontal
scrollbar to not appear? (I've tried EnableScrolling(False, True) but
it doesn't stop the horizontal scrollbar.)

Apparently, it's not possible to disable scrollbars within a wx.Grid.
Here's a thread on the topic: http://old.nabble.com/Disable-Scrollbar-in-wxGrid-td20148862.html

Basically, then, although I'm hoping to be corrected here, it's not
possible using wx.Grid within a sizer to have a vertical and not a
horizontal scrollbar. Several posts back, Robin mentioned how to make
sufficient space so the vertical scrollbar doesn't appear over the
grid cells, and this helps. Still, though, the scrollbar will appear
over the extra space, and this will cause a horizontal scrollbar to be
created.

Thanks, again to Robin for help with this problem.

This almost works for me. Is there a way to force the horizontal
scrollbar to not appear? (I've tried EnableScrolling(False, True) but
it doesn't stop the horizontal scrollbar.)

The Grid re-enables it when it sees that a scrollbar is needed.

Apparently, it's not possible to disable scrollbars within a wx.Grid.
Here's a thread on the topic: http://old.nabble.com/Disable-Scrollbar-in-wxGrid-td20148862.html

Basically, then, although I'm hoping to be corrected here, it's not
possible using wx.Grid within a sizer to have a vertical and not a
horizontal scrollbar. Several posts back, Robin mentioned how to make
sufficient space so the vertical scrollbar doesn't appear over the
grid cells, and this helps. Still, though, the scrollbar will appear
over the extra space, and this will cause a horizontal scrollbar to be
created.

I mentioned before that the virtual size of a scrolled window will be effectively rounded up to the next multiple of the scroll rate, in order to ensure that there are enough 'steps' in the scrollbar's range to show everything. I'm guessing that what you are seeing is due to the the extra few pixels needed to round up the virtual size, causing the adjusted virtual size to be larger than the client size you are allowing the grid to have. IIRC the scroll rate that the Grid uses is 15 pixels, (you can verify that with GetScrollLineX()) so if you add an extra 15 it should take care of it. If you want to be more exact then you can calculate how much extra is needed and set the width such that width - row label size is an exact multiple of 15.

···

On 4/1/10 12:40 PM, Tom wrote:

--
Robin Dunn
Software Craftsman