Welcome, guest | Sign In | My Account | Store | Cart

I wrote this foldable panel to keep the look of my applications consistant with Windows. The code is extremely simple and is nowhere as flexible as the foldable panel already available in wxPython. Demo available in the code.

Python, 253 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#Simple foldable panels. Layout lifted off Windows. Use at your own risk!
#Code put in the public domain, for someone else could find a use for it.

import wx
#from images import Images

#---------------------------------------------------------------------------
#following code is normaly hidden in images.py
#---------------------------------------------------------------------------
import wx
from cStringIO import StringIO
from binascii import a2b_base64

class Images:
    def __init__(self):
        for name in _names:
            img = wx.ImageFromStream(StringIO(a2b_base64(eval("_"+name))))
            img.ConvertAlphaToMask(100)
            exec("self.bmp_"+name+"=img.ConvertToBitmap()")
        self._names=_names

_names=['bd', 'bdb', 'bu', 'bub', 'wd', 'wdb', 'wu', 'wub']

_bd="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAAA\
CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCEC0fpVpQHAAAAB10RVh0Q29tbWVudABDcmVhd\
GVkIHdpdGggVGhlIEdJTVDvZCVuAAAAJ0lEQVR42mNgGAXEgv9QTEiMJIPwGsBIhEHEqiXaoNEwoT\
BMRjIAACcEE/EBpo2WAAAAAElFTkSuQmCC"

_bdb="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAA\
ACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCEC0JUY7lTQAAAB10RVh0Q29tbWVudABDcmVh\
dGVkIHdpdGggVGhlIEdJTVDvZCVuAAAATklEQVR42u2SQQrAMAgEs325vnx6ySGXVoMJuWRABMEBF\
wXQqlQlZsbTFrBVQq9o9ivRsDh2zZ6jjCCTiSJBNhPdTD5FZz52CgG4e0nyAuqUHhOGDvckAAAAAE\
lFTkSuQmCC"

_bu="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAAA\
CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCECoWk8d+fwAAAB10RVh0Q29tbWVudABDcmVhd\
GVkIHdpdGggVGhlIEdJTVDvZCVuAAAAK0lEQVR42mNgGHHgPyEFjCQYgFMtExEGMBLrImwG/CdCbD\
RMKAiTUUAGAAAJFA/7QbaUdQAAAABJRU5ErkJggg=="

_bub="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAA\
ACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCECosVcunzQAAAB10RVh0Q29tbWVudABDcmVh\
dGVkIHdpdGggVGhlIEdJTVDvZCVuAAAATUlEQVR42uXSMQrAMAxD0cj04PbJf/dAE4FDl2jU8AYhA\
YxuukhmEuNAHIQuggOFAWgHhQksoecD0QRq6m7fZNXZZzvy2H8QAVRVC3kBY3EeFIhSPfsAAAAASU\
VORK5CYII="

_wd="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAAA\
CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCECwtdJYw3QAAAB10RVh0Q29tbWVudABDcmVhd\
GVkIHdpdGggVGhlIEdJTVDvZCVuAAAAKUlEQVR42mNgGAVEgf9QQEiMJIMIGcBIyCC4QkZGRoq8Nh\
omVAqTEQ4A+cRPu0JNXG8AAAAASUVORK5CYII="

_wdb="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAA\
ACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCECwQLP58zAAAAB10RVh0Q29tbWVudABDcmVh\
dGVkIHdpdGggVGhlIEdJTVDvZCVuAAAAVUlEQVR42uWSSwrAMAgF83pyPfl0UyGUNh9syKJvIwgOO\
iiAkk0WYmYc5YOsg3Cl12tCJKn2FTX6w+fcQW+ArpMYbAGGnNQb/dnJE2jPx85GAO6egpwFJkgTeB\
7jjAAAAABJRU5ErkJggg=="

_wu="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAAA\
CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCECsIcNNyXQAAAB10RVh0Q29tbWVudABDcmVhd\
GVkIHdpdGggVGhlIEdJTVDvZCVuAAAAM0lEQVR42mNgGFng/////wmpYSTWAEZGRpxqmQgZANNMjI\
swDEDXhE1sNEwoCJNRQB4AAEnQP9cbKVhHAAAAAElFTkSuQmCC"

_wub="iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAABmJLR0QA/wD/AP+gvaeTAAA\
ACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QgCECsgRWbapwAAAB10RVh0Q29tbWVudABDcmVh\
dGVkIHdpdGggVGhlIEdJTVDvZCVuAAAAT0lEQVR42u3SwQrAMAgD0Cbsw/XLs8t6K0RQdmqOAR8iQ\
pJWN10kIsQ1EE5sygrgIDoAABzECuCg51TuoT2IL/cmvis/28jH/oZAkjKzhbzRt0gLkdhH3gAAAA\
BJRU5ErkJggg=="
#---------------------------------------------------------------------------
#end of images.py
#---------------------------------------------------------------------------

class FoldPanel(wx.Panel,Images):
    def __init__(self,parent,psizer,title,tooltip=None,rimcolour="light grey",
                                bgcolour="white",txtcolour="black",is_open=1):
        wx.Panel.__init__(self, parent, -1)
        Images.__init__(self)

        self.is_open=is_open
        #XXX It won't work properly without refreshing the parent sizer/panel.
        self.parentsizer=psizer
        self.parent=parent

        #now can add more than 1 page...
        self.pages=[]

        #the enter/leave counter
        self.counter_enterleave = 0

        self.rimcolour,self.bgcolour,self.txtcolour=rimcolour,bgcolour,txtcolour
        self.SetBackgroundColour(rimcolour)

        self.vbox = wx.BoxSizer(wx.VERTICAL)
        box = wx.BoxSizer(wx.HORIZONTAL)

        #title panel
        self.tpanel = wx.Panel(self, -1)
        self.title=wx.StaticText( self.tpanel, -1, title)
        self.title.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD))
        self.title.SetForegroundColour(txtcolour)
        
        if is_open==1:
            if txtcolour=="black": bmp = self.bmp_bu
            else: bmp = self.bmp_wu
        else:
            if txtcolour=="black": bmp = self.bmp_bd
            else: bmp = self.bmp_wd

        self.sb=wx.StaticBitmap(self.tpanel, -1, bmp,
                size=(bmp.GetWidth(), bmp.GetHeight()))

        box.Add(self.title,1, wx.EXPAND|wx.LEFT|wx.TOP,5)
        box.Add(self.sb,0,wx.ALL,4)

        if tooltip is not None:
            self.sb.SetToolTipString(tooltip)
            self.title.SetToolTipString(tooltip)
            self.tpanel.SetToolTipString(tooltip)

        cursor = wx.StockCursor(wx.CURSOR_HAND)
        self.tpanel.SetCursor(cursor)
        self.tpanel.SetSizer(box)

        self.vbox.Add(self.tpanel,0,wx.EXPAND)

        #page panel
        self.ppanel = wx.Panel(self, -1)
        self.ppanel.SetBackgroundColour(self.bgcolour)

        self.pbox = wx.BoxSizer(wx.VERTICAL)
        self.pbox.Layout()
        self.ppanel.SetSizer(self.pbox)

        self.vbox.Add(self.ppanel,1,wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT,1)

        if self.is_open==0:
            self.vbox.Hide(self.ppanel)

        self.vbox.Layout()
        self.SetSizer(self.vbox)

        self.sb.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
        self.sb.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
        self.sb.Bind(wx.EVT_LEFT_UP, self.OnButton)

        self.title.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
        self.title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
        self.title.Bind(wx.EVT_LEFT_UP, self.OnButton)

        self.tpanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
        self.tpanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
        self.tpanel.Bind(wx.EVT_LEFT_UP, self.OnButton)

    def OnEnter(self,event):
        self.counter_enterleave+=1
        if self.counter_enterleave > 0:
            if self.txtcolour=="black":
                if self.is_open == 1: bmp = self.bmp_bub
                else: bmp = self.bmp_bdb
            else:
                if self.is_open == 1: bmp = self.bmp_wub
                else: bmp = self.bmp_wdb

            self.sb.SetBitmap(bmp)

    def OnLeave(self,event):
        self.counter_enterleave-=1
        if self.counter_enterleave == 0:
            if self.txtcolour=="black":
                if self.is_open == 1: bmp = self.bmp_bu
                else: bmp = self.bmp_bd
            else:
                if self.is_open == 1: bmp = self.bmp_wu
                else: bmp = self.bmp_wd

            self.sb.SetBitmap(bmp)

    def OnButton(self,event):
        if self.is_open == 1:
            self.vbox.Hide(self.ppanel)
            if self.txtcolour=="black": bmp = self.bmp_bdb
            else: bmp = self.bmp_wdb
        else:
            self.vbox.Show(self.ppanel)
            if self.txtcolour=="black": bmp = self.bmp_bub
            else: bmp = self.bmp_wub

        #Ugly, all three statements are needed (only tested in windows, though)
        self.parentsizer.RecalcSizes()
        self.parentsizer.Layout()
        self.parent.Refresh()

        self.sb.SetBitmap(bmp)
        self.is_open=1-self.is_open
        event.Skip()

    def AddPage(self,page,setbg=0):
        page.Reparent(self.ppanel)
        self.pages.append(page)

        if setbg:
            page.SetBackgroundColour(self.bgcolour)

        if len(self.pages)==1:
            self.pbox.Add(page,1,wx.EXPAND|wx.ALL,5)
        else:
            self.pbox.Add(page,1,wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT,5)

        self.pbox.Layout()

    def GetPage(self,i_page=0):
        return self.pages[i_page]

if __name__ == "__main__":

    class ColorPanel(wx.Panel):
        def __init__(self, parent, color):
            wx.Panel.__init__(self, parent, -1,size=(-1,75))
            self.SetBackgroundColour(color)

    class MainPanel(wx.Panel):
        def __init__(self, parent):
            wx.Panel.__init__(self, parent, -1)
            self.SetBackgroundColour("white")

            self.vbox = wx.BoxSizer(wx.VERTICAL)

            tooltip="Click to unfold the green panel"
            p1=FoldPanel(self,self.vbox,"Green Panel",tooltip,
                                wx.Colour(10,10,96),"white","white",is_open=0)
            
            p1.AddPage(ColorPanel(p1,wx.Colour(128,255,128)),0)
            p1.AddPage(ColorPanel(p1,wx.Colour(128,128,255)),0)

            self.vbox.Add(p1,0,wx.EXPAND|wx.ALL,5)

            tooltip="The titlebar arrow icon is only available in two colours\n"\
                    "... but is transparent."
            p2=FoldPanel(self,self.vbox,"Red Panel",tooltip,"light grey","white")
            p2.AddPage(ColorPanel(p2,wx.Colour(255,128,128)),0)
            self.vbox.Add(p2,0,wx.EXPAND|wx.ALL,5)

            self.vbox.Layout()
            self.SetSizer(self.vbox)

    class MyFrame(wx.Frame):
        def __init__(self, pos=wx.DefaultPosition, size=(180,290)):
            wx.Frame.__init__(self, None, -1, "Foldable Panels", pos, size)
            self.CreateStatusBar()
            mp= MainPanel(self)

    app = wx.PySimpleApp()
    f = MyFrame(None)
    f.Show()
    app.MainLoop()

I recently upgraded my wxPython to 2.6.3.3 and noticed that wx.StaticText and wx.StaticBitmap also generate wx.EVT_ENTER_WINDOW and wx.EVT_LEAVE_WINDOW. This affects my title bar (in the FoldPanel class, the static bitmap self.sb , the static text self.title and the title panel self.tpanel ). I don't know how to "disable" events, so I am using a counter which increases every time a window is entered (OnEnter) and decreased when left (OnLeave). If the counter is > 0 (1 or 2) then the mouse pointer is in the title bar, if it's 0 then we've left the title bar. Any idea how to clean this up?