Frame doesn't resize to display all grid columns completely

Hi,

In the code below, there is a frame, with a notebook and 2 notebook pages. What I’m trying to do is: when I change the notebook page, the frame’s size should change so that it completely displays the widgets on the notebook page. When I select the “Grid” page, the frame’s size should change so that the grid’s entire width will be visible (I don’t want all grid rows to be displayed, because if there are lots of rows in the grid, then the frame’s height will be too big. Entire grid width visibility is what I want.)

The problem is: when I select the “Grid” page, the frame’s size gets big but it doesn’t completely display the entire grid width, there appears a horizontal scroll bar, the user has to scroll to right to display last 2 columns. How to change the code so that the frame automatically resizes and displays all columns without adding a horizontal scroll bar? Thanks.

import wx
import wx.grid as grid_lib
import wx.lib.agw.flatnotebook as fnb
from wx.adv import CalendarCtrl, GenericCalendarCtrl, CalendarDateAttr
import random
import string



class TheFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "The Frame",
                          style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX
                          )

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.the_notebook = fnb.FlatNotebook(self, -1, agwStyle=fnb.FNB_VC8 | fnb.FNB_NO_X_BUTTON)
        self.calendar_panel =  wx.Panel(self, -1)
        self.grid_panel =  wx.Panel(self, -1)
        self.the_notebook.AddPage(self.calendar_panel, "Calendar")
        self.the_notebook.AddPage(self.grid_panel, "Grid")
        self.the_notebook.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.on_nb_page_change)
        self.sizer.Add(self.the_notebook, 0, wx.ALL | wx.EXPAND, 10)

        self.calendar1 = GenericCalendarCtrl(self.calendar_panel, -1, wx.DateTime().Today(),
                                  style = wx.adv.CAL_SHOW_HOLIDAYS
                                        | wx.adv.CAL_MONDAY_FIRST
                                        | wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION)

        self.calendar2 = GenericCalendarCtrl(self.calendar_panel, -1, wx.DateTime().Today(),
                                  style = wx.adv.CAL_SHOW_HOLIDAYS
                                        | wx.adv.CAL_MONDAY_FIRST
                                        | wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION)

        the_choices = [str(i) for i in range(1, 10)]
        self.choice_box = wx.CheckListBox(self.calendar_panel, -1, choices=the_choices)
        calendar_sizer = wx.BoxSizer(wx.HORIZONTAL)

        calendar_sizer.Add(self.calendar1, 0, wx.ALL, 5)
        calendar_sizer.Add(self.calendar2, 0, wx.ALL, 5)
        calendar_sizer.Add(self.choice_box, 0, wx.ALL, 5)
        self.calendar_panel.SetSizer(calendar_sizer)

        self.the_grid = grid_lib.Grid(self.grid_panel, -1, name="grid")
        self.the_grid.CreateGrid(50, 12)
        self.the_grid.DisableDragColSize()
        self.the_grid.DisableDragRowSize()
        self.the_grid.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)

        for colx in range(12):
            for rowx in range(50):
                self.the_grid.SetCellValue(rowx, colx, random.choice(string.ascii_letters))

        grid_sizer = wx.BoxSizer(wx.VERTICAL)
        grid_sizer.Add(self.the_grid, 0, wx.ALL|wx.EXPAND, 5)
        self.grid_panel.Layout()
        self.grid_panel.SetSizer(grid_sizer)
        self.grid_panel.Layout()

    def on_nb_page_change(self, event):
        w, h = self.GetSize()
        the_page = self.the_notebook.GetCurrentPage()
        bw, bh = the_page.GetBestSize()
        self.SetSize((bw, h))
        self.SendSizeEvent()


if __name__ == "__main__":
    app = wx.App(False)
    frame = TheFrame()
    frame.Show()
    app.MainLoop()

Hi Steve,

I think there are two factors causing the issue here.

The first is that the Notebook has all its borders set to 10. This should mean that the Frame needs to be wider than the Notebook by 20 pixels.

The second is that the_page.GetBestSize() is returning the size for the page without scroll bars. However, in the event handler you are only changing the Frame’s width and not its height, so there will be a vertical scroll bar. Therefore you need to add in the width of that scroll bar, which you can get using wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X).

import wx
import wx.grid as grid_lib
import wx.lib.agw.flatnotebook as fnb
from wx.adv import CalendarCtrl, GenericCalendarCtrl, CalendarDateAttr
import random
import string



class TheFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "The Frame",
                          style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX
                          )

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.the_notebook = fnb.FlatNotebook(self, -1, agwStyle=fnb.FNB_VC8 | fnb.FNB_NO_X_BUTTON)
        self.calendar_panel =  wx.Panel(self, -1)
        self.grid_panel =  wx.Panel(self, -1)
        self.the_notebook.AddPage(self.calendar_panel, "Calendar")
        self.the_notebook.AddPage(self.grid_panel, "Grid")
        self.the_notebook.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.on_nb_page_change)
        border = 10
        self.sizer.Add(self.the_notebook, 0, wx.ALL | wx.EXPAND, border)

        self.calendar1 = GenericCalendarCtrl(self.calendar_panel, -1, wx.DateTime().Today(),
                                  style = wx.adv.CAL_SHOW_HOLIDAYS
                                        | wx.adv.CAL_MONDAY_FIRST
                                        | wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION)

        self.calendar2 = GenericCalendarCtrl(self.calendar_panel, -1, wx.DateTime().Today(),
                                  style = wx.adv.CAL_SHOW_HOLIDAYS
                                        | wx.adv.CAL_MONDAY_FIRST
                                        | wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION)

        the_choices = [str(i) for i in range(1, 10)]
        self.choice_box = wx.CheckListBox(self.calendar_panel, -1, choices=the_choices)
        calendar_sizer = wx.BoxSizer(wx.HORIZONTAL)

        calendar_sizer.Add(self.calendar1, 0, wx.ALL, 5)
        calendar_sizer.Add(self.calendar2, 0, wx.ALL, 5)
        calendar_sizer.Add(self.choice_box, 0, wx.ALL, 5)
        self.calendar_panel.SetSizer(calendar_sizer)

        self.the_grid = grid_lib.Grid(self.grid_panel, -1, name="grid")
        self.the_grid.CreateGrid(50, 12)
        self.the_grid.DisableDragColSize()
        self.the_grid.DisableDragRowSize()
        self.the_grid.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)

        vsb_width = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
        self.pad_width = (2 * border) + vsb_width

        for colx in range(12):
            for rowx in range(50):
                self.the_grid.SetCellValue(rowx, colx, random.choice(string.ascii_letters))

        grid_sizer = wx.BoxSizer(wx.VERTICAL)
        grid_sizer.Add(self.the_grid, 0, wx.ALL|wx.EXPAND, 5)
        self.grid_panel.Layout()
        self.grid_panel.SetSizer(grid_sizer)
        self.grid_panel.Layout()

    def on_nb_page_change(self, event):
        w, h = self.GetSize()
        the_page = self.the_notebook.GetCurrentPage()
        bw, bh = the_page.GetBestSize()
        self.SetSize((bw + self.pad_width, h))
        self.SendSizeEvent()


if __name__ == "__main__":
    app = wx.App(False)
    frame = TheFrame()
    frame.Show()
    app.MainLoop()

What is a bit weird is that when I tried that that version, it makes the Frame a bit too wide. If I use the following line instead, then it seems to fit exactly, which I don’t understand…

    self.pad_width = border + vsb_width

Tested using Python 3.10.6 + wxPython 4.2.0 gtk3 (phoenix) wxWidgets 3.2.0 on Linux Mint 21.1

1 Like

I guess because you forgot this for the Frame?

        self.SetSizer(self.sizer)

In addition, a little tweaks were necessary for Windows 10:

        vsb_width = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
        self.pad_width = 16 + (2 * border) + vsb_width

where 16 may be due to the Windows-specific shadow effects (16,8).

2 Likes

Well spotted! The sad thing is that I did have that in one of my earlier tests, but missed it out in the later ones.

However, now I’ve included it, the pad_width is not quite big enough on linux either. Using my normal desktop theme I need to add an extra 10, but if I change to the Mint-Y theme I only need to add and extra 2. One noticeable difference between the themes is that my custom theme has a wider border.

@komoto48g - what do you get on Windows if you call the following code after the Frame has been displayed?

    wx.SystemSettings.GetMetric(wx.SYS_BORDER_X, self)

With my custom theme it returns 4, with the Mint-Y theme it returns 1.

Hi Richard,

On windows, I got:

>>> wx.SystemSettings.GetMetric(wx.SYS_BORDER_X, frame)
1

That’s a shame, I was hoping that we could do something portable like the following, but it wouldn’t work for Windows…

        # end of __init__()...
        self.SetSizer(self.sizer)
        self.Show()
        wx.CallAfter(self._calcPadWidth, nb_border)


    def _calcPadWidth(self, nb_border):
        f_border_width = wx.SystemSettings.GetMetric(wx.SYS_BORDER_X, self)
        vsb_width = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
        self.pad_width = 2 * (1 + f_border_width + nb_border) + vsb_width

Yeah, it seems there are platform dependencies.
I think the better way is to let the sizer do the layout

    def on_nb_page_change(self, event):
        w, h = self.GetSize()
        the_page = self.the_notebook.GetCurrentPage()
        ## bw, bh = the_page.GetBestSize()
        ## self.SetSize((bw + self.pad_width, h))
        ## self.SendSizeEvent()
        the_page.Sizer.Fit(self)
        self.Size = (self.Size[0] + self.pad_width, h) # Add padding of the page.

where

        vsb_width = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
        self.pad_width = (2 * border) + vsb_width

Note: @steve2, the width of the scrollbar is also added to the first page, so you need to modify it a bit.

1 Like

having a ‘fixed’ border in the code is not a good idea (being politely, I think) :frowning_face:
on my Windows just adding the double V-scrollbar width is enough :sweat_smile:

        self.the_grid = grid_lib.Grid(self.grid_panel, -1, name="grid")
        self.the_grid.CreateGrid(50, 12)
        self.the_grid.DisableDragColSize()
        self.the_grid.DisableDragRowSize()
        self.the_grid.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)

        for colx in range(12):
            for rowx in range(50):
                self.the_grid.SetCellValue(rowx, colx, random.choice(string.ascii_letters))

        grid_sizer = wx.BoxSizer(wx.VERTICAL)
        grid_sizer.Add(self.the_grid, 0, wx.ALL|wx.EXPAND, 5)
        self.grid_panel.SetSizer(grid_sizer)

    def on_nb_page_change(self, event):
        w, h = self.GetSize()
        the_page = self.the_notebook.GetCurrentPage()
        bw, bh = the_page.GetBestSize()
        self.SetSize((
            bw + 2 * wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X),
            h))
1 Like

Worked perfect for me, thank you Richard.