agw.aui.AuiNotebook
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:
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:
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
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:
- Windows XP, wxPython 2.8.10.1 (unicode), Python 2.5
- Windows Vista, wxPython 2.8.10.1 (unicode), Python 2.5
For Further Information
Note: The tutorial on this page is a modified version of the tutorial on my blog