agw.aui.AuiNotebook

http://www.blog.pythonlibrary.org/wp-content/uploads/2009/12/agwAuiNotebookDemo.png

Introduction

Andrea Gavana went to the trouble of creating a pure python version of the Advanced User Interface (AUI) that provides perspective saving, floating sub-windows that can be docked, customizable look and feel and the splittable AUI Notebook. His notebook will be the focus of this section. The AGW AUI Notebook has lots of features, but I’m just going to go over some of the basics. If you want to see all the features, be sure to read the code and check out the demo in the official wxPython Demo. As I mentioned at the beginning of this tutorial, be sure to download the latest version of AUI (or AGW as a whole) from SVN to get all the bug fixes.

Let’s take a look at the simple example I used for the screenshot above:

Listing 1

   1 import wx
   2 import wx.lib.agw.aui as aui
   3 
   4 ########################################################################
   5 class TabPanelOne(wx.Panel):
   6     """
   7     A simple wx.Panel class
   8     """
   9     #----------------------------------------------------------------------
  10     def __init__(self, parent):
  11         """"""
  12         wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  13 
  14         sizer = wx.BoxSizer(wx.VERTICAL)
  15         txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
  16         txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
  17 
  18         sizer = wx.BoxSizer(wx.VERTICAL)
  19         sizer.Add(txtOne, 0, wx.ALL, 5)
  20         sizer.Add(txtTwo, 0, wx.ALL, 5)
  21 
  22         self.SetSizer(sizer)
  23 
  24 ########################################################################
  25 class DemoFrame(wx.Frame):
  26     """
  27     wx.Frame class
  28     """
  29 
  30     #----------------------------------------------------------------------
  31     def __init__(self):
  32         wx.Frame.__init__(self, None, wx.ID_ANY,
  33                           "AGW AUI Notebook Tutorial",
  34                           size=(600,400))
  35 
  36         self._mgr = aui.AuiManager()
  37 
  38         # tell AuiManager to manage this frame
  39         self._mgr.SetManagedWindow(self)
  40 
  41         notebook = aui.AuiNotebook(self)
  42         panelOne = TabPanelOne(notebook)
  43         panelTwo = TabPanelOne(notebook)
  44 
  45         notebook.AddPage(panelOne, "PanelOne", False)
  46         notebook.AddPage(panelTwo, "PanelTwo", False)
  47 
  48         self._mgr.AddPane(notebook,
  49                           aui.AuiPaneInfo().Name("notebook_content").
  50                           CenterPane().PaneBorder(False))
  51         self._mgr.Update()
  52         #notebook.EnableTab(1, False)
  53 
  54 #----------------------------------------------------------------------
  55 # Run the program
  56 if __name__ == "__main__":
  57     app = wx.PySimpleApp()
  58     frame = DemoFrame()
  59     frame.Show()
  60     app.MainLoop()

The first difference between this notebook and the original AuiNotebook is that this one requires an AuiManager object. It may be that something similar is behind the original as well, but that’s hidden from us. Anyway, the first step is instantiating the AuiManager and then giving it the frame to manage via its SetManagedWindow() method. Now we can add the AUI Notebook. Note that we pass the frame as the parent of the notebook instead of the AuiManager. I think the reason is that when the AuiManager is given the frame, it becomes the top level window.

The next part of the equation should look familiar: AddPage(). Let’s see what it accepts:

   1 AddPage(self, page, caption, select=False,
   2         bitmap=wx.NullBitmap,
   3         disabled_bitmap=wx.NullBitmap,
   4         control=None)

In my code, I only pass in the first three parameters, but you can also add a couple bitmaps and a wx.Window for the control. The next bit is a little tricky. We need to call the AuiManager’s AddPane() method to tell the AuiManager that we want it to “manage” something (in this case, the notebook). We also pass in a second argument which looks kind of confusing:

   1 self.aui_mgr.AddPane(self.notebook,
   2                      aui.AuiPaneInfo().Name("notebook_content").
   3                      CenterPane().PaneBorder(False))

This line of code tells the AuiManager what to do with the notebook. In this case, we are telling it that the pane’s (i.e the notebook’s) name is “notebook_content”, which is what we use to look up the pane. We’re also telling the AuiManager that we want the pane to be in the centered dock position and the PaneBorder(False) command tells the AuiManager that we want a hidden border drawn around the notebook pane.

Changing a Few AuiNotebook Settings

Our next example will be more complex and will show you how to change a few notebook settings (and yes, it's a doozy!).

Listing 2 AuiNotebook_Demo_2.py

   1 import wx
   2 import wx.lib.agw.aui as aui
   3 
   4 ID_NotebookArtGloss = 0
   5 ID_NotebookArtSimple = 1
   6 ID_NotebookArtVC71 = 2
   7 ID_NotebookArtFF2 = 3
   8 ID_NotebookArtVC8 = 4
   9 ID_NotebookArtChrome = 5
  10 
  11 ########################################################################
  12 class TabPanelOne(wx.Panel):
  13     """
  14     A simple wx.Panel class
  15     """
  16     #----------------------------------------------------------------------
  17     def __init__(self, parent):
  18         """"""
  19         wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  20 
  21         izer = wx.BoxSizer(wx.VERTICAL)
  22         txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
  23         txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
  24 
  25         sizer = wx.BoxSizer(wx.VERTICAL)
  26         sizer.Add(txtOne, 0, wx.ALL, 5)
  27         sizer.Add(txtTwo, 0, wx.ALL, 5)
  28 
  29         self.SetSizer(sizer)
  30 
  31 ########################################################################
  32 class AUIManager(aui.AuiManager):
  33     """
  34     AUI Manager class
  35     """
  36 
  37     #----------------------------------------------------------------------
  38     def __init__(self, managed_window):
  39         """Constructor"""
  40         aui.AuiManager.__init__(self)
  41         self.SetManagedWindow(managed_window)
  42 
  43 ########################################################################
  44 class AUINotebook(aui.AuiNotebook):
  45     """
  46     AUI Notebook class
  47     """
  48 
  49     #----------------------------------------------------------------------
  50     def __init__(self, parent):
  51         """Constructor"""
  52         aui.AuiNotebook.__init__(self, parent=parent)
  53         self.default_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER
  54         self.SetWindowStyleFlag(self.default_style)
  55 
  56         # add some pages to the notebook
  57         pages = [TabPanel, TabPanel, TabPanel]
  58 
  59         x = 1
  60         for page in pages:
  61             label = "Tab #%i" % x
  62             tab = page(self)
  63             self.AddPage(tab, label, False)
  64             x += 1
  65 
  66 ########################################################################
  67 class DemoFrame(wx.Frame):
  68     """
  69     wx.Frame class
  70     """
  71     #----------------------------------------------------------------------
  72     def __init__(self):
  73         """Constructor"""
  74         title = "AGW AUI Notebook Feature Tutorial"
  75         wx.Frame.__init__(self, None, wx.ID_ANY,
  76                           title=title, size=(600,400))
  77         self.themeDict = {"Glossy Theme (Default)":0,
  78                           "Simple Theme":1,
  79                           "VC71 Theme":2,
  80                           "Firefox 2 Theme":3,
  81                           "VC8 Theme":4,
  82                           "Chrome Theme":5,
  83                           }
  84 
  85         # create the AUI manager
  86         self.aui_mgr = AUIManager(self)
  87 
  88         # create the AUI Notebook
  89         self.notebook = AUINotebook(self)
  90 
  91         self._notebook_style = self.notebook.default_style
  92 
  93         # add notebook to AUI manager
  94         self.aui_mgr.AddPane(self.notebook,
  95                              aui.AuiPaneInfo().Name("notebook_content").
  96                              CenterPane().PaneBorder(False))
  97         self.aui_mgr.Update()
  98 
  99         # create menu and tool bars
 100         self.createMenu()
 101         self.createTB()
 102 
 103     #----------------------------------------------------------------------
 104     def createMenu(self):
 105         """
 106         Create the menu
 107         """
 108         def doBind(item, handler):
 109             """ Create menu events. """
 110             self.Bind(wx.EVT_MENU, handler, item)
 111 
 112         menubar = wx.MenuBar()
 113 
 114         fileMenu = wx.Menu()
 115 
 116         doBind( fileMenu.Append(wx.ID_ANY, "&Exit\tAlt+F4",
 117                                 "Exit Program"),self.onExit)
 118 
 119         optionsMenu = wx.Menu()
 120 
 121         doBind( optionsMenu.Append(wx.ID_ANY,
 122                                    "Disable Current Tab"),
 123                 self.onDisableTab)
 124 
 125         # add the menus to the menubar
 126         menubar.Append(fileMenu, "File")
 127         menubar.Append(optionsMenu, "Options")
 128 
 129         self.SetMenuBar(menubar)
 130 
 131     #----------------------------------------------------------------------
 132     def createTB(self):
 133         """
 134         Create the toolbar
 135         """
 136         TBFLAGS = ( wx.TB_HORIZONTAL
 137                     | wx.NO_BORDER
 138                     | wx.TB_FLAT )
 139         tb = self.CreateToolBar(TBFLAGS)
 140         keys = self.themeDict.keys()
 141         keys.sort()
 142         choices = keys
 143         cb = wx.ComboBox(tb, wx.ID_ANY, "Glossy Theme (Default)",
 144                          choices=choices,
 145                          size=wx.DefaultSize,
 146                          style=wx.CB_DROPDOWN)
 147         cb.Bind(wx.EVT_COMBOBOX, self.onChangeTheme)
 148         tb.AddControl(cb)
 149         tb.AddSeparator()
 150 
 151         self.closeChoices = ["No Close Button", "Close Button At Right",
 152                              "Close Button On All Tabs",
 153                              "Close Button On Active Tab"]
 154         cb = wx.ComboBox(tb, wx.ID_ANY,
 155                          self.closeChoices[3],
 156                          choices=self.closeChoices,
 157                          size=wx.DefaultSize,
 158                          style=wx.CB_DROPDOWN)
 159         cb.Bind(wx.EVT_COMBOBOX, self.onChangeTabClose)
 160         tb.AddControl(cb)
 161 
 162         tb.Realize()
 163 
 164     #----------------------------------------------------------------------
 165     def onChangeTabClose(self, event):
 166         """
 167         Change how the close button behaves on a tab
 168 
 169         Note: Based partially on the agw AUI demo
 170         """
 171         choice = event.GetString()
 172         self._notebook_style &= ~(aui.AUI_NB_CLOSE_BUTTON |
 173                                  aui.AUI_NB_CLOSE_ON_ACTIVE_TAB |
 174                                  aui.AUI_NB_CLOSE_ON_ALL_TABS)
 175 
 176         # note that this close button doesn't work for some reason
 177         if choice == "Close Button At Right":
 178             self._notebook_style ^= aui.AUI_NB_CLOSE_BUTTON
 179         elif choice == "Close Button On All Tabs":
 180             self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ALL_TABS
 181         elif choice == "Close Button On Active Tab":
 182             self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
 183 
 184         self.notebook.SetWindowStyleFlag(self._notebook_style)
 185         self.notebook.Refresh()
 186         self.notebook.Update()
 187 
 188     #----------------------------------------------------------------------
 189     def onChangeTheme(self, event):
 190         """
 191         Changes the notebook's theme
 192 
 193         Note: Based partially on the agw AUI demo
 194         """
 195 
 196         print event.GetString()
 197         evId = self.themeDict[event.GetString()]
 198         print evId
 199 
 200         all_panes = self.aui_mgr.GetAllPanes()
 201 
 202         for pane in all_panes:
 203 
 204             if isinstance(pane.window, aui.AuiNotebook):
 205                 nb = pane.window
 206 
 207                 if evId == ID_NotebookArtGloss:
 208 
 209                     nb.SetArtProvider(aui.AuiDefaultTabArt())
 210                     self._notebook_theme = 0
 211 
 212                 elif evId == ID_NotebookArtSimple:
 213                     nb.SetArtProvider(aui.AuiSimpleTabArt())
 214                     self._notebook_theme = 1
 215 
 216                 elif evId == ID_NotebookArtVC71:
 217                     nb.SetArtProvider(aui.VC71TabArt())
 218                     self._notebook_theme = 2
 219 
 220                 elif evId == ID_NotebookArtFF2:
 221                     nb.SetArtProvider(aui.FF2TabArt())
 222                     self._notebook_theme = 3
 223 
 224                 elif evId == ID_NotebookArtVC8:
 225                     nb.SetArtProvider(aui.VC8TabArt())
 226                     self._notebook_theme = 4
 227 
 228                 elif evId == ID_NotebookArtChrome:
 229                     nb.SetArtProvider(aui.ChromeTabArt())
 230                     self._notebook_theme = 5
 231 
 232                 #nb.SetWindowStyleFlag(self._notebook_style)
 233                 nb.Refresh()
 234                 nb.Update()
 235 
 236     #----------------------------------------------------------------------
 237     def onDisableTab(self, event):
 238         """
 239         Disables the current tab
 240         """
 241         page = self.notebook.GetCurrentPage()
 242         page_idx = self.notebook.GetPageIndex(page)
 243 
 244         self.notebook.EnableTab(page_idx, False)
 245         self.notebook.AdvanceSelection()
 246 
 247     #----------------------------------------------------------------------
 248     def onExit(self, event):
 249         """
 250         Close the demo
 251         """
 252         self.Close()
 253 
 254 
 255 #----------------------------------------------------------------------
 256 if __name__ == "__main__":
 257     app = wx.PySimpleApp()
 258     frame = DemoFrame()
 259     frame.Show()
 260     app.MainLoop()

For this demo, I decided to try subclassing AuiManager and the aui.AuiNotebook. While I think this could be helpful if you ever needed to instantiate multiple AuiManager instances, for the purposes of this demo, it really didn’t help much other than showing you how to do it. Let’s unpack this example bit by bit and see how it works!

In the AuiManager class, I force the programmer to pass in the window to be managed and it calls the SetManagedWindow() automatically. You could do this with some of AuiManager’s other functions as well. In the AuiNotebook’s case, I set a default style using its SetWindowStyleFlag() method and then I add some pages to the notebook. This gives me a quick and easy way to create multiple notebooks quickly.

The DemoFrame does the bulk of the work. It creates a theme dictionary for later use, instantiates the AuiManager and AuiNotebook, and creates a toolbar and menubar. Our focus will be the event handlers related to the menubar and the toolbar as they affect the way the AuiNotebook functions. Our first method of interest is onChangeTabClose().

Listing 3 - Messing with the Close Button Position

   1 def onChangeTabClose(self, event):
   2     """
   3     Change how the close button behaves on a tab
   4 
   5     Note: Based partially on the agw AUI demo
   6     """
   7     choice = event.GetString()
   8     self._notebook_style &= ~(aui.AUI_NB_CLOSE_BUTTON |
   9                              aui.AUI_NB_CLOSE_ON_ACTIVE_TAB |
  10                              aui.AUI_NB_CLOSE_ON_ALL_TABS)
  11 
  12     # note that this close button doesn't work for some reason
  13     if choice == "Close Button At Right":
  14         self._notebook_style ^= aui.AUI_NB_CLOSE_BUTTON
  15     elif choice == "Close Button On All Tabs":
  16         self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ALL_TABS
  17     elif choice == "Close Button On Active Tab":
  18         self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
  19 
  20     self.notebook.SetWindowStyleFlag(self._notebook_style)
  21     self.notebook.Refresh()
  22     self.notebook.Update()

This event handler is invoked from combobox events generated by the second combobox in the toolbar. Its purpose is to decide the placement of the close button on the tabs. First, it grabs the user’s choice by calling “event.GetString()”. Next it uses some bitwise operators to clear the close button related styles. If I’m reading it correctly, it “ands” the current notebook style with a “notted” multi-”or”. Yes, it’s confusing. To put it simply, it says that the three styles (aui.AUI_NB_CLOSE_BUTTON, aui.AUI_NB_CLOSE_ON_ACTIVE_TAB, aui.AUI_NB_CLOSE_ON_ALL_TABS) will be subtracted from the current notebook style.

Then I use a conditional to decide which style to actually apply to the notebook. Once that’s added to the variable, I use the notebook’s SetWindowStyleFlag() to apply it and then Refresh and Update the display so the user can see the changes.

Now we turn to changing the notebook’s theme:

Listing 4 - Changing the AuiNotebook's Theme

   1 def onChangeTheme(self, event):
   2     """
   3     Changes the notebook's theme
   4 
   5     Note: Based partially on the agw AUI demo
   6     """
   7     evId = self.themeDict[event.GetString()]
   8     all_panes = self.aui_mgr.GetAllPanes()
   9 
  10     for pane in all_panes:
  11 
  12         if isinstance(pane.window, aui.AuiNotebook):
  13             nb = pane.window
  14 
  15             if evId == ID_NotebookArtGloss:
  16 
  17                 nb.SetArtProvider(aui.AuiDefaultTabArt())
  18 
  19             elif evId == ID_NotebookArtSimple:
  20                 nb.SetArtProvider(aui.AuiSimpleTabArt())
  21 
  22             elif evId == ID_NotebookArtVC71:
  23                 nb.SetArtProvider(aui.VC71TabArt())
  24 
  25             elif evId == ID_NotebookArtFF2:
  26                 nb.SetArtProvider(aui.FF2TabArt())
  27 
  28             elif evId == ID_NotebookArtVC8:
  29                 nb.SetArtProvider(aui.VC8TabArt())
  30 
  31             elif evId == ID_NotebookArtChrome:
  32                 nb.SetArtProvider(aui.ChromeTabArt())
  33 
  34             nb.Refresh()
  35             nb.Update()

The event handler is called from the first toolbar’s combobox events. It too grabs the user’s choice via event.GetString() and then uses the string as a key for my theme dictionary. The dictionary returns an integer which is assigned to “evId”. Next, the AuiManager instance calls GetAllPanes() to get a list of all the pages in the notebook. Finally, the handler then loops over the pages and uses a nested conditional to change the notebook’s them the call to SetArtProvider(). To show the changes, we finish by calling the notebook’s Refresh and Update methods.

The last method that I’m going to go over from this demo is “onDisableTab”:

Listing 5 - Disabling/Enabling AuiNotebook Pages

   1 def onDisableTab(self, event):
   2     """
   3     Disables the current tab
   4     """
   5     page = self.notebook.GetCurrentPage()
   6     page_idx = self.notebook.GetPageIndex(page)
   7 
   8     self.notebook.EnableTab(page_idx, False)
   9     self.notebook.AdvanceSelection()

This event handler gets fired by a menu event and is a pretty simple piece of code. First, we call the notebook’s GetCurrentPage() method and then pass the result to the notebook’s GetPageIndex() method. Now that we have the page index, we can use that to disable it via the notebook’s EnableTab method. As you can see, by passing False, we disable the page. You can also use the EnableTab method to re-enable the tab by passing True.

And there you have it. Now you know how to use the basics of this fun widget too. Be sure to check out the real demo too to see the other features of the AGW AuiNotebook!

All code was tested on the following:

For Further Information

Note: The tutorial on this page is a modified version of the tutorial on my blog

AuiNotebook (AGW) (last edited 2010-09-11 19:42:56 by pool-71-175-95-133)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.