agw.FlatNotebook

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

The Flatbook control is written in Python rather than a wrapped widget from wxWidgets. It was added to wxPython with the release of wxPython 2.8.9.2 on February 16, 2009 as a part of the 3rd party package, agw. Since then Andrea Gavana has been updating the agw library with lots of fixes. My examples will work with the 2.8.9.2+ versions of wxPython, but I recommend getting the SVN version of agw and replacing your default one with it as it is the most up-to-date version of the code.

Here are a few of the FlatNotebook’s Features:

=== Creating a Simple Example ===

Now that we’ve done an unpaid commercial, let’s create a simple example:

Listing 1

   1 import wx
   2 import wx.lib.agw.flatnotebook as fnb
   3 
   4 ########################################################################
   5 class TabPanel(wx.Panel):
   6     """
   7     This will be the first notebook tab
   8     """
   9     #----------------------------------------------------------------------
  10     def __init__(self, parent):
  11         """"""
  12 
  13         wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  14 
  15         sizer = wx.BoxSizer(wx.VERTICAL)
  16         txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
  17         txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
  18 
  19         sizer = wx.BoxSizer(wx.VERTICAL)
  20         sizer.Add(txtOne, 0, wx.ALL, 5)
  21         sizer.Add(txtTwo, 0, wx.ALL, 5)
  22 
  23         self.SetSizer(sizer)
  24  
  25 ########################################################################
  26 class FlatNotebookDemo(fnb.FlatNotebook):
  27     """
  28     Flatnotebook class
  29     """
  30  
  31     #----------------------------------------------------------------------
  32     def __init__(self, parent):
  33         """Constructor"""
  34         fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY)
  35  
  36         pageOne = TabPanel(self)
  37         pageTwo = TabPanel(self)
  38         pageThree = TabPanel(self)
  39  
  40         self.AddPage(pageOne, "PageOne")
  41         self.AddPage(pageTwo, "PageTwo")
  42         self.AddPage(pageThree, "PageThree")
  43  
  44 ########################################################################
  45 class DemoFrame(wx.Frame):
  46     """
  47     Frame that holds all other widgets
  48     """
  49  
  50     #----------------------------------------------------------------------
  51     def __init__(self):
  52         """Constructor"""
  53         wx.Frame.__init__(self, None, wx.ID_ANY,
  54                           "FlatNotebook Tutorial",
  55                           size=(600,400)
  56                           )
  57         panel = wx.Panel(self)
  58  
  59         notebook = FlatNotebookDemo(panel)
  60         sizer = wx.BoxSizer(wx.VERTICAL)
  61         sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
  62         panel.SetSizer(sizer)
  63         self.Layout()
  64  
  65         self.Show()
  66  
  67 #----------------------------------------------------------------------
  68 if __name__ == "__main__":
  69     app = wx.PySimpleApp()
  70     frame = DemoFrame()
  71     app.MainLoop()

In Listing 1, I subclass FlatNotebook and use a generic panel for the pages. You’ll notice that FlatNotebook has its own AddPage method that mimics the wx.Notebook. This should come as no surprise as the FlatNotebook’s API is such that you should be able to use it as a drop-in replacement for wx.Notebook. Of course, right out of the box, FlatNotebook has the advantage. If you run the demo above, you’ll see that FlatNotebook allows the user to rearrange the tabs, close the tabs and it includes some previous/next buttons in case you have more tabs than can fit on-screen at once.

Applying Styles to the FlatNotebook

Now let’s take a look at the various styles that we can apply to FlatNotebook:

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

Listing 2

   1 import wx
   2 import wx.lib.agw.flatnotebook as fnb
   3 
   4 ########################################################################
   5 class TabPanel(wx.Panel):
   6     """
   7     This will be the first notebook tab
   8     """
   9     #----------------------------------------------------------------------
  10     def __init__(self, parent):
  11         """"""
  12 
  13         wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  14 
  15         sizer = wx.BoxSizer(wx.VERTICAL)
  16         txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
  17         txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
  18 
  19         sizer = wx.BoxSizer(wx.VERTICAL)
  20         sizer.Add(txtOne, 0, wx.ALL, 5)
  21         sizer.Add(txtTwo, 0, wx.ALL, 5)
  22 
  23         self.SetSizer(sizer)
  24 
  25 ########################################################################
  26 class FlatNotebookDemo(fnb.FlatNotebook):
  27     """
  28     Flatnotebook class
  29     """
  30  
  31     #----------------------------------------------------------------------
  32     def __init__(self, parent):
  33         """Constructor"""
  34         fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY)
  35  
  36         pageOne = TabPanel(self)
  37         pageTwo = TabPanel(self)
  38         pageThree = TabPanel(self)
  39  
  40         self.AddPage(pageOne, "PageOne")
  41         self.AddPage(pageTwo, "PageTwo")
  42         self.AddPage(pageThree, "PageThree")
  43  
  44  
  45 ########################################################################
  46 class DemoFrame(wx.Frame):
  47     """
  48     Frame that holds all other widgets
  49     """
  50  
  51     #----------------------------------------------------------------------
  52     def __init__(self):
  53         """Constructor"""
  54         wx.Frame.__init__(self, None, wx.ID_ANY,
  55                           "FlatNotebook Tutorial with Style",
  56                           size=(600,400)
  57                           )
  58         self.styleDict = {"Default":self.OnDefaultStyle,
  59                           "VC71":self.OnVC71Style,
  60                           "VC8":self.OnVC8Style,
  61                           "Fancy":self.OnFancyStyle,
  62                           "Firefox 2":self.OnFF2Style}
  63         choices = self.styleDict.keys()
  64  
  65         panel = wx.Panel(self)
  66         self.notebook = FlatNotebookDemo(panel)
  67         self.styleCbo = wx.ComboBox(panel, wx.ID_ANY, "Default",
  68                                     wx.DefaultPosition, wx.DefaultSize,
  69                                     choices=choices, style=wx.CB_DROPDOWN)
  70         styleBtn = wx.Button(panel, wx.ID_ANY, "Change Style")
  71         styleBtn.Bind(wx.EVT_BUTTON, self.onStyle)
  72  
  73         # create some sizers
  74         sizer = wx.BoxSizer(wx.VERTICAL)
  75         hSizer = wx.BoxSizer(wx.HORIZONTAL)
  76  
  77         # add the widgets to the sizers
  78         sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 5)
  79         hSizer.Add(self.styleCbo, 0, wx.ALL|wx.CENTER, 5)
  80         hSizer.Add(styleBtn, 0, wx.ALL, 5)
  81         sizer.Add(wx.StaticLine(panel), 0, wx.ALL|wx.EXPAND, 5)
  82         sizer.Add(hSizer, 0, wx.ALL, 5)
  83  
  84         panel.SetSizer(sizer)
  85         self.Layout()
  86  
  87         self.Show()
  88  
  89     #----------------------------------------------------------------------
  90     def onStyle(self, event):
  91         """
  92         Changes the style of the tabs
  93         """
  94         print "in onStyle"
  95         style = self.styleCbo.GetValue()
  96         print style
  97         self.styleDict[style]()
  98  
  99     # The following methods were taken from the wxPython 
 100     # demo for the FlatNotebook
 101     def OnFF2Style(self):
 102  
 103         style = self.notebook.GetWindowStyleFlag()
 104  
 105         # remove old tabs style
 106         mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
 107         style &= mirror
 108  
 109         style |= fnb.FNB_FF2
 110  
 111         self.notebook.SetWindowStyleFlag(style)
 112  
 113  
 114     def OnVC71Style(self):
 115  
 116         style = self.notebook.GetWindowStyleFlag()
 117  
 118         # remove old tabs style
 119         mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
 120         style &= mirror
 121  
 122         style |= fnb.FNB_VC71
 123  
 124         self.notebook.SetWindowStyleFlag(style)
 125  
 126  
 127     def OnVC8Style(self):
 128  
 129         style = self.notebook.GetWindowStyleFlag()
 130  
 131         # remove old tabs style
 132         mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
 133         style &= mirror
 134  
 135         # set new style
 136         style |= fnb.FNB_VC8
 137  
 138         self.notebook.SetWindowStyleFlag(style)
 139  
 140  
 141     def OnDefaultStyle(self):
 142  
 143         style = self.notebook.GetWindowStyleFlag()
 144  
 145         # remove old tabs style
 146         mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
 147         style &= mirror
 148  
 149         self.notebook.SetWindowStyleFlag(style)
 150  
 151  
 152     def OnFancyStyle(self):
 153  
 154         style = self.notebook.GetWindowStyleFlag()
 155  
 156         # remove old tabs style
 157         mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
 158         style &= mirror
 159  
 160         style |= fnb.FNB_FANCY_TABS
 161         self.notebook.SetWindowStyleFlag(style)
 162  
 163 #----------------------------------------------------------------------
 164 if __name__ == "__main__":
 165     app = wx.PySimpleApp()
 166     frame = DemoFrame()
 167     app.MainLoop()

That’s a lot of code for a “simple” example, but I think it will help us understand how to apply tab styles to our widget. I borrowed most of the methods from the wxPython demo, in case you didn’t notice. The primary talking point in this code is the contents of those methods, which are mostly the same. Here’s the main snippet to take away from this section:

   1 style = self.notebook.GetWindowStyleFlag()
   2  
   3 # remove old tabs style
   4 mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
   5 style &= mirror
   6 style |= fnb.FNB_FF2
   7 self.notebook.SetWindowStyleFlag(style)

First, we need to get the current style of the FlatNotebook. Then we use some fancy magic in the “mirror” line that creates a set of styles that we want to remove. The line, “style &= mirror” actually does the removing and then we add the style we wanted with “style |= fnb.FNB_FF2?. Finally, we use SetWindowStyleFlag() to actually apply the style to the widget. You may be wondering what’s up with all those goofy symbols (i.e. |, ~, &). Well, those are known as bitwise operators. I don’t use them much myself, so I recommend reading the Python documentation for full details as I don’t fully understand them myself.

Adding and Removing Pages from FlatNotebook

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

For my next demo, I created a way to add and delete pages from the FlatNotebook. Let’s see how:

Listing 3

   1 import random
   2 import sys
   3 import wx
   4 import wx.lib.agw.flatnotebook as fnb
   5 import wx.lib.mixins.listctrl  as  listmix
   6 
   7 musicdata = {
   8 1 : ("Bad English", "The Price Of Love", "Rock"),
   9 2 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
  10 3 : ("George Michael", "Praying For Time", "Rock"),
  11 4 : ("Gloria Estefan", "Here We Are", "Rock"),
  12 5 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
  13 6 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"),
  14 7 : ("Paul Young", "Oh Girl", "Rock"),
  15 8 : ("Paula Abdul", "Opposites Attract", "Rock"),
  16 9 : ("Richard Marx", "Should've Known Better", "Rock"),
  17 10: ("Rod Stewart", "Forever Young", "Rock"),
  18 11: ("Roxette", "Dangerous", "Rock"),
  19 12: ("Sheena Easton", "The Lover In Me", "Rock"),
  20 13: ("Sinead O'Connor", "Nothing Compares 2 U", "Rock"),
  21 14: ("Stevie B.", "Because I Love You", "Rock"),
  22 15: ("Taylor Dayne", "Love Will Lead You Back", "Rock"),
  23 16: ("The Bangles", "Eternal Flame", "Rock"),
  24 17: ("Wilson Phillips", "Release Me", "Rock"),
  25 18: ("Billy Joel", "Blonde Over Blue", "Rock"),
  26 19: ("Billy Joel", "Famous Last Words", "Rock"),
  27 20: ("Billy Joel", "Lullabye (Goodnight, My Angel)", "Rock"),
  28 21: ("Billy Joel", "The River Of Dreams", "Rock"),
  29 22: ("Billy Joel", "Two Thousand Years", "Rock")
  30 }
  31 
  32 ########################################################################
  33 class TabPanelOne(wx.Panel):
  34     """
  35     This will be the first notebook tab
  36     """
  37     #----------------------------------------------------------------------
  38     def __init__(self, parent):
  39         """"""
  40 
  41         wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  42 
  43         sizer = wx.BoxSizer(wx.VERTICAL)
  44         txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
  45         txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
  46 
  47         sizer = wx.BoxSizer(wx.VERTICAL)
  48         sizer.Add(txtOne, 0, wx.ALL, 5)
  49         sizer.Add(txtTwo, 0, wx.ALL, 5)
  50 
  51         self.SetSizer(sizer)
  52 
  53 ########################################################################
  54 class TestListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
  55     def __init__(self, parent, ID, pos=wx.DefaultPosition,
  56                  size=wx.DefaultSize, style=0):
  57         wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
  58         listmix.ListCtrlAutoWidthMixin.__init__(self)
  59 
  60 ########################################################################
  61 class TabPanelTwo(wx.Panel, listmix.ColumnSorterMixin):
  62     """
  63     This will be the second notebook tab
  64     """
  65     #----------------------------------------------------------------------
  66     def __init__(self, parent):
  67         """"""
  68         wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  69         self.createAndLayout()
  70         
  71     def createAndLayout(self):
  72         sizer = wx.BoxSizer(wx.VERTICAL)
  73         self.list = TestListCtrl(self, wx.ID_ANY, style=wx.LC_REPORT
  74                                  | wx.BORDER_NONE
  75                                  | wx.LC_EDIT_LABELS
  76                                  | wx.LC_SORT_ASCENDING)
  77         sizer.Add(self.list, 1, wx.EXPAND)
  78         self.populateList()
  79         # Now that the list exists we can init the other base class,
  80         # see wx/lib/mixins/listctrl.py
  81         self.itemDataMap = musicdata
  82         listmix.ColumnSorterMixin.__init__(self, 3)
  83         self.SetSizer(sizer)
  84         self.SetAutoLayout(True)
  85         
  86     def populateList(self):
  87         self.list.InsertColumn(0, "Artist")
  88         self.list.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
  89         self.list.InsertColumn(2, "Genre")
  90         items = musicdata.items()
  91         
  92         for key, data in items:
  93             index = self.list.InsertStringItem(sys.maxint, data[0])
  94             self.list.SetStringItem(index, 1, data[1])
  95             self.list.SetStringItem(index, 2, data[2])
  96             self.list.SetItemData(index, key)
  97 
  98         self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
  99         self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE)
 100         self.list.SetColumnWidth(2, 100)
 101 
 102         # show how to select an item
 103         self.list.SetItemState(5, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
 104 
 105         # show how to change the colour of a couple items
 106         item = self.list.GetItem(1)
 107         item.SetTextColour(wx.BLUE)
 108         self.list.SetItem(item)
 109         item = self.list.GetItem(4)
 110         item.SetTextColour(wx.RED)
 111         self.list.SetItem(item)
 112 
 113         self.currentItem = 0
 114         
 115     # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
 116     def GetListCtrl(self):
 117         return self.list
 118 
 119 ########################################################################
 120 class FlatNotebookDemo(fnb.FlatNotebook):
 121     """
 122     Flatnotebook class
 123     """
 124     #----------------------------------------------------------------------
 125     def __init__(self, parent):
 126         """Constructor"""
 127         fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY)
 128  
 129 ########################################################################
 130 class DemoFrame(wx.Frame):
 131     """
 132     Frame that holds all other widgets
 133     """
 134  
 135     #----------------------------------------------------------------------
 136     def __init__(self, title="FlatNotebook Add/Remove Page Tutorial"):
 137         """Constructor"""
 138         wx.Frame.__init__(self, None, wx.ID_ANY,
 139                           title=title,
 140                           size=(600,400)
 141                           )
 142         self._newPageCounter = 0
 143         panel = wx.Panel(self)
 144         self.createRightClickMenu()
 145  
 146         # create some widgets
 147         self.notebook = FlatNotebookDemo(panel)
 148         addPageBtn = wx.Button(panel, label="Add Page")
 149         addPageBtn.Bind(wx.EVT_BUTTON, self.onAddPage)
 150         removePageBtn = wx.Button(panel, label="Remove Page")
 151         removePageBtn.Bind(wx.EVT_BUTTON, self.onDeletePage)
 152         self.notebook.SetRightClickMenu(self._rmenu)
 153  
 154         # create some sizers
 155         sizer = wx.BoxSizer(wx.VERTICAL)
 156         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
 157  
 158         # layout the widgets
 159         sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 5)
 160         btnSizer.Add(addPageBtn, 0, wx.ALL, 5)
 161         btnSizer.Add(removePageBtn, 0, wx.ALL, 5)
 162         sizer.Add(btnSizer)
 163         panel.SetSizer(sizer)
 164         self.Layout()
 165  
 166         self.Show()
 167  
 168     #----------------------------------------------------------------------
 169     def createRightClickMenu(self):
 170         """
 171         Based on method from flatnotebook demo
 172         """
 173         self._rmenu = wx.Menu()
 174         item = wx.MenuItem(self._rmenu, wx.ID_ANY,
 175                            "Close Tab\tCtrl+F4",
 176                            "Close Tab")
 177         self.Bind(wx.EVT_MENU, self.onDeletePage, item)
 178         self._rmenu.AppendItem(item)
 179  
 180     #----------------------------------------------------------------------
 181     def onAddPage(self, event):
 182         """
 183         This method is based on the flatnotebook demo
 184  
 185         It adds a new page to the notebook
 186         """
 187         caption = "New Page Added #" + str(self._newPageCounter)
 188         self.Freeze()
 189  
 190         self.notebook.AddPage(self.createPage(caption), caption, True)
 191         self.Thaw()
 192         self._newPageCounter = self._newPageCounter + 1
 193  
 194     #----------------------------------------------------------------------
 195     def createPage(self, caption):
 196         """
 197         Creates a notebook page from one of three
 198         panels at random and returns the new page
 199         """
 200         panel_list = [TabPanelOne, TabPanelTwo]
 201         tab = random.choice(panel_list)
 202         page = tab(self.notebook)
 203         return page
 204  
 205     #----------------------------------------------------------------------
 206     def onDeletePage(self, event):
 207         """
 208         This method is based on the flatnotebook demo
 209  
 210         It removes a page from the notebook
 211         """
 212         self.notebook.DeletePage(self.notebook.GetSelection())
 213  
 214 #----------------------------------------------------------------------
 215 if __name__ == "__main__":
 216     app = wx.PySimpleApp()
 217     frame = DemoFrame()
 218     app.MainLoop()

The code above allows the user to add as many pages as they want by clicking the Add Page button. The Remove Page button will remove whatever page is currently selected. When adding a page, but button handler freezes the frame and calls the notebook’s AddPage method. This calls the “createPage” method which randomly grabs one of my pre-defined panels, instantiates it and returns it to the AddPage method. On returning to the “onAddPage” method, the frame is thawed and the page counter is incremented.

The Remove Page button calls the notebook’s GetSelection() method to get the currently selected tab and then calls the notebook’s DeletePage() method to remove it from the notebook.

Another fun functionality that I enabled was the tab right-click menu, which gives us another way to close a tab, although you could use to do other actions as well. All you need to do to enable it is to call the notebook’s SetRightClickMenu() method and pass in a wx.Menu object.

There are tons of other features for you to explore as well. Be sure to check out the FlatNotebook demo in the official wxPython demo where you can learn to close tabs with the middle mouse button or via double-clicks, turn on gradient colors for the tab background, disable tabs, enable smart tabbing (which is kind of like the alt+tab menu in Windows), create drag-and-drop tabs between notebooks and much, much more!

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

Flatnotebook (AGW) (last edited 2009-12-14 01:08:41 by 208)

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