wx.Notebook on Windows (notebook tabs disappear when calling Remove/DeletePage)

Hello everyone!

I am having a problem with the wxNotebook control on Windows platform.

I am writing a simple program that adds an arbitrary number of notebook pages to the notebook control. Each notebook page can then be closed on demand by using a button control event placed on each of the notebook pages.

My target platform is Windows XP/2003. I have tried Python versions 2.4 and 2.5 and wxPython versions 2.6 and 2.8. I have searched Google extensively for similar problem or proper use of the wxNotebook control. I have studied the wxwidgets and wxPython documentation and examples and tried to create workarounds without any success. I greatly appreciate all your help.
I use wxGlage to generate the skeleton code for the GUI.

Please do not hesitate to run and try the code for yourself. The problem I am experiencing is as follows: The tabs are to be displayed on the top side and only on one row of the notebook control. The top window of the application has specific dimensions and the number of pages and corresponding tabs arbitrarily added is greater than the width of the top window.
wxNotebook control automatically adds left and right scroll buttons in the top right corner that allow me to browse and select any of the tabs.
However, if I start closing the notebook pages one by one starting with the highest index of the GetPageCount and using either the RemovePage or the DeletePage method, the notebook tabs are not properly redrawn. This can been seen running the example code below once "tab number 8 is closed".

Any help would be greatly appreciated.
Andre

---cut here---
import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.CAPTION|wx.CLOSE_BOX|wx.STAY_ON_TOP|wx.SYSTEM_MENU
        wx.Frame.__init__(self, *args, **kwds)
        self.frame_panel = wx.Panel(self, -1)
        self.notebook = wx.Notebook(self.frame_panel, -1, style=wx.NB_FIXEDWIDTH)

        self.__set_properties()
        self.__do_layout()

    def __set_properties(self):
        self.SetTitle("wx.Notebook Example")
        self.SetSize((400, 300))

    def __do_layout(self):
        sizer_frame = wx.BoxSizer(wx.VERTICAL)
        sizer_notebook = wx.BoxSizer(wx.HORIZONTAL)
        sizer_notebook.Add(self.notebook, 1, wx.EXPAND, 0)
        self.frame_panel.SetAutoLayout(True)
        self.frame_panel.SetSizer(sizer_notebook)
        sizer_notebook.Fit(self.frame_panel)
        sizer_notebook.SetSizeHints(self.frame_panel)
        sizer_frame.Add(self.frame_panel, 1, wx.EXPAND, 0)
        self.SetAutoLayout(True)
        self.SetSizer(sizer_frame)
        self.Layout()
        self.Centre()

class MyPanel(wx.Panel):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)
        self.close_button = wx.Button(self, -1, "Close")

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.close_button_handler, self.close_button)

    def __set_properties(self):
        self.close_button.SetDefault()

    def __do_layout(self):
        sizer_panel = wx.BoxSizer(wx.VERTICAL)
        sizer_panel.Add(self.close_button, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 115)
        self.SetAutoLayout(True)
        self.SetSizer(sizer_panel)
        sizer_panel.Fit(self)
        sizer_panel.SetSizeHints(self)

    def close_button_handler(self, event):
        page = self.Parent.GetSelection() # currently selected page
        print "tab %2d closed" % self.Parent.GetPageCount()
        if self.Parent.GetPageCount() > 1:
            self.Parent.RemovePage(page)
        else:
            self.Parent.DeletePage(page)

if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame = MyFrame(None, -1, "")

    app.SetTopWindow(frame)
    frame.Show()

    panel = MyPanel(frame.notebook, -1)

    frame.notebook.AddPage(panel, "Tab One", True)
    frame.notebook.AddPage(panel, "Tab Two", True)
    frame.notebook.AddPage(panel, "Tab Three", True)
    frame.notebook.AddPage(panel, "Tab Four", True)
    frame.notebook.AddPage(panel, "Tab Five", True)
    frame.notebook.AddPage(panel, "Tab Six", True)
    frame.notebook.AddPage(panel, "Tab Seven", True)
    frame.notebook.AddPage(panel, "Tab Eight", True)
    frame.notebook.AddPage(panel, "Tab Nine", True)
    frame.notebook.AddPage(panel, "Tab Ten", True)

    app.MainLoop()
---cut here---

···

_________________________________________________________________
PC Magazine�s 2007 editors� choice for best Web mail�award-winning Windows Live Hotmail. http://imagine-windowslive.com/hotmail/?locale=en-us&ocid=TXT_TAGHM_migration_HM_mini_pcmag_0507

To be more in line with what you will likely be doing in a practical
sense, I have replaced your adding of the same panel 10 times by
inserting 10 different panels. This allows you to destroy each panel as
it is being removed, for at least a slightly more consistant result.

The changed code is as follows...

class MyPanel(wx.Panel):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)
        self.Parent = self.GetParent()
        self.close_button = wx.Button(self, -1, "Close")

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.close_button_handler, self.close_button)

    def __set_properties(self):
        self.close_button.SetDefault()

    def __do_layout(self):
        sizer_panel = wx.BoxSizer(wx.VERTICAL)
        sizer_panel.Add(self.close_button, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 115)
        self.SetAutoLayout(True)
        self.SetSizer(sizer_panel)
        sizer_panel.Fit(self)
        sizer_panel.SetSizeHints(self)

    def close_button_handler(self, event):
        page = self.Parent.GetSelection() # currently selected page
        print "tab %2d closed" % (page,)
        
        self.Parent.RemovePage(page)
        if self.Parent.GetPageCount():
            sel = self.Parent.GetSelection()
            self.Parent.SetSelection(0)
            self.Parent.SetSelection(sel)
        self.Destroy()

if __name__ == "__main__":
    app = wx.App(0)
    wx.InitAllImageHandlers()
    frame = MyFrame(None, -1, "")

    app.SetTopWindow(frame)
    frame.Show()
    for i in xrange(10):
        panel = MyPanel(frame.notebook, -1)
        frame.notebook.AddPage(panel, "Tab %i" % (i+1,), True)

    app.MainLoop()

The code above forces the notebook to redraw the first 'page' of
notebook tabs, then the 'page' with the notebook tab that you really
want to select. This can cause a bit of flicker, and will generally
push the 'selected' tab to be the rightmost fully exposed tab. Wrapping
the selection code in a notebook.Freeze() and notebook.Thaw() doesn't
seem to help with flicker. Using notebook.Refresh() also does not seem
to do it.

If this behavior is not sufficient, you may consider using the
wx.lib.flatnotebook.FlatNotebook in wxPython 2.8 (or via Andrea's module
listing). It seems to inexplicably die on me when I'm deleting pages by
hitting the buttons, but not when hitting the 'x' in the upper right
corner (win2k, 2.8.3 ansi, python 2.3 and 2.5).

- Josiah

···

"Andrej Solc" <andrejoid@hotmail.com> wrote:

Hello everyone!

I am having a problem with the wxNotebook control on Windows platform.

I am writing a simple program that adds an arbitrary number of notebook
pages to the notebook control. Each notebook page can then be closed on
demand by using a button control event placed on each of the notebook pages.

Josiah,

Thanks for the workaround and excellent suggestions!

It still makes me wonder though, whether the disappearing tabs behavior is a bug in wxPython or wxWidgets itself, and something that I should report to developers...

I added few more comments below:..

From: Josiah Carlson <jcarlson@uci.edu>
Reply-To: wxPython-users@lists.wxwidgets.org
To: wxpython-users@lists.wxwindows.org
Subject: Re: [wxPython-users] wx.Notebook on Windows (notebook tabs disappear when calling Remove/DeletePage)
Date: Mon, 25 Jun 2007 06:35:42 -0700

> Hello everyone!
>
> I am having a problem with the wxNotebook control on Windows platform.
>
> I am writing a simple program that adds an arbitrary number of notebook
> pages to the notebook control. Each notebook page can then be closed on
> demand by using a button control event placed on each of the notebook pages.

To be more in line with what you will likely be doing in a practical
sense, I have replaced your adding of the same panel 10 times by
inserting 10 different panels. This allows you to destroy each panel as
it is being removed, for at least a slightly more consistent result.

makes a lot of sense, I should have used a loop, but for the sake of the example I just came up with some quick code. In real world, each panel/page would be an object/instance of its own...

The changed code is as follows...

class MyPanel(wx.Panel):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)
        self.Parent = self.GetParent()
        self.close_button = wx.Button(self, -1, "Close")

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.close_button_handler, self.close_button)

    def __set_properties(self):
        self.close_button.SetDefault()

    def __do_layout(self):
        sizer_panel = wx.BoxSizer(wx.VERTICAL)
        sizer_panel.Add(self.close_button, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 115)
        self.SetAutoLayout(True)
        self.SetSizer(sizer_panel)
        sizer_panel.Fit(self)
        sizer_panel.SetSizeHints(self)

    def close_button_handler(self, event):
        page = self.Parent.GetSelection() # currently selected page
        print "tab %2d closed" % (page,)

        self.Parent.RemovePage(page)
        if self.Parent.GetPageCount():
            sel = self.Parent.GetSelection()
            self.Parent.SetSelection(0)
            self.Parent.SetSelection(sel)
        self.Destroy()

if __name__ == "__main__":
    app = wx.App(0)
    wx.InitAllImageHandlers()
    frame = MyFrame(None, -1, "")

    app.SetTopWindow(frame)
    frame.Show()
    for i in xrange(10):
        panel = MyPanel(frame.notebook, -1)
        frame.notebook.AddPage(panel, "Tab %i" % (i+1,), True)

    app.MainLoop()

The code above forces the notebook to redraw the first 'page' of
notebook tabs, then the 'page' with the notebook tab that you really
want to select. This can cause a bit of flicker, and will generally
push the 'selected' tab to be the rightmost fully exposed tab. Wrapping
the selection code in a notebook.Freeze() and notebook.Thaw() doesn't
seem to help with flicker. Using notebook.Refresh() also does not seem
to do it.

Works great as a workaround!
Since the tabs are being redrawn each time a page is removed, the tab animation is not as visually pleasing or consistent, but works much better than no tabs at all.
I was indeed able to get rid of the flicker completely by wrapping the notebook's parent inside the Freeze and Thaw methods instead of the notebook control itself, so I really appreciate all your input.

If this behavior is not sufficient, you may consider using the
wx.lib.flatnotebook.FlatNotebook in wxPython 2.8 (or via Andrea's module
listing). It seems to inexplicably die on me when I'm deleting pages by
hitting the buttons, but not when hitting the 'x' in the upper right
corner (win2k, 2.8.3 ansi, python 2.3 and 2.5).

The above solution was sufficient, will have to check out the FlatNotebook control some other time...

Andre

···

"Andrej Solc" <andrejoid@hotmail.com> wrote:

- Josiah

---------------------------------------------------------------------
To unsubscribe, e-mail: wxPython-users-unsubscribe@lists.wxwidgets.org
For additional commands, e-mail: wxPython-users-help@lists.wxwidgets.org

_________________________________________________________________
Get a preview of Live Earth, the hottest event this summer - only on MSN MSN

Hi Andrej,

Andrej Solc wrote:

...

The changed code is as follows...

class MyPanel(wx.Panel):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)
        self.Parent = self.GetParent()
        self.close_button = wx.Button(self, -1, "Close")

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.close_button_handler, self.close_button)

    def __set_properties(self):
        self.close_button.SetDefault()

    def __do_layout(self):
        sizer_panel = wx.BoxSizer(wx.VERTICAL)
        sizer_panel.Add(self.close_button, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 115)
        self.SetAutoLayout(True)
        self.SetSizer(sizer_panel)
        sizer_panel.Fit(self)
        sizer_panel.SetSizeHints(self)

    def close_button_handler(self, event):
        page = self.Parent.GetSelection() # currently selected page
        print "tab %2d closed" % (page,)

        self.Parent.RemovePage(page)

I had some weird problems doing something similar with flatnotebook and things work very nicely if I use notebook.DeletePage(page) instead of notebook.RemovePage(page).

You might want to give this a try.

Werner