6 #----------------------------------------------------------------------
13 # This class is used to provide an interface between a ComboCtrl and a
14 # ListCtrl that is used as the popoup for the combo widget. In this
15 # case we use multiple inheritance to derive from both wx.ListCtrl and
16 # wx.ComboPopup, but it also works well when deriving from just
17 # ComboPopup and using a has-a relationship with the popup control,
18 # you just need to be sure to return the control itself from the
21 class ListCtrlComboPopup(wx
.ListCtrl
, wx
.combo
.ComboPopup
):
23 def __init__(self
, log
=None):
30 # Since we are using multiple inheritance, and don't know yet
31 # which window is to be the parent, we'll do 2-phase create of
32 # the ListCtrl instead, and call its Create method later in
33 # our Create method. (See Create below.)
34 self
.PostCreate(wx
.PreListCtrl())
36 # Also init the ComboPopup base class.
37 wx
.combo
.ComboPopup
.__init
__(self
)
40 def AddItem(self
, txt
):
41 self
.InsertStringItem(self
.GetItemCount(), txt
)
43 def OnMotion(self
, evt
):
44 item
, flags
= self
.HitTest(evt
.GetPosition())
49 def OnLeftDown(self
, evt
):
50 self
.value
= self
.curitem
54 # The following methods are those that are overridable from the
55 # ComboPopup base class. Most of them are not required, but all
56 # are shown here for demonstration purposes.
59 # This is called immediately after construction finishes. You can
60 # use self.GetCombo if needed to get to the ComboCtrl instance.
62 self
.log
.write("ListCtrlComboPopup.Init")
67 # Create the popup child control. Return true for success.
68 def Create(self
, parent
):
69 self
.log
.write("ListCtrlComboPopup.Create")
70 wx
.ListCtrl
.Create(self
, parent
,
71 style
=wx
.LC_LIST|wx
.LC_SINGLE_SEL|wx
.SIMPLE_BORDER
)
72 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
73 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
77 # Return the widget that is to be used for the popup
79 #self.log.write("ListCtrlComboPopup.GetControl")
82 # Called just prior to displaying the popup, you can use it to
83 # 'select' the current item.
84 def SetStringValue(self
, val
):
85 self
.log
.write("ListCtrlComboPopup.SetStringValue")
86 idx
= self
.FindItem(-1, val
)
87 if idx
!= wx
.NOT_FOUND
:
90 # Return a string representation of the current item.
91 def GetStringValue(self
):
92 self
.log
.write("ListCtrlComboPopup.GetStringValue")
94 return self
.GetItemText(self
.value
)
97 # Called immediately after the popup is shown
99 self
.log
.write("ListCtrlComboPopup.OnPopup")
100 wx
.combo
.ComboPopup
.OnPopup(self
)
102 # Called when popup is dismissed
104 self
.log
.write("ListCtrlComboPopup.OnDismiss")
105 wx
.combo
.ComboPopup
.OnDismiss(self
)
107 # This is called to custom paint in the combo control itself
108 # (ie. not the popup). Default implementation draws value as
110 def PaintComboControl(self
, dc
, rect
):
111 self
.log
.write("ListCtrlComboPopup.PaintComboControl")
112 wx
.combo
.ComboPopup
.PaintComboControl(self
, dc
, rect
)
114 # Receives key events from the parent ComboCtrl. Events not
115 # handled should be skipped, as usual.
116 def OnComboKeyEvent(self
, event
):
117 self
.log
.write("ListCtrlComboPopup.OnComboKeyEvent")
118 wx
.combo
.ComboPopup
.OnComboKeyEvent(self
, event
)
120 # Implement if you need to support special action when user
121 # double-clicks on the parent wxComboCtrl.
122 def OnComboDoubleClick(self
):
123 self
.log
.write("ListCtrlComboPopup.OnComboDoubleClick")
124 wx
.combo
.ComboPopup
.OnComboDoubleClick(self
)
126 # Return final size of popup. Called on every popup, just prior to OnPopup.
127 # minWidth = preferred minimum width for window
128 # prefHeight = preferred height. Only applies if > 0,
129 # maxHeight = max height for window, as limited by screen size
130 # and should only be rounded down, if necessary.
131 def GetAdjustedSize(self
, minWidth
, prefHeight
, maxHeight
):
132 self
.log
.write("ListCtrlComboPopup.GetAdjustedSize: %d, %d, %d" % (minWidth
, prefHeight
, maxHeight
))
133 return wx
.combo
.ComboPopup
.GetAdjustedSize(self
, minWidth
, prefHeight
, maxHeight
)
135 # Return true if you want delay the call to Create until the popup
136 # is shown for the first time. It is more efficient, but note that
137 # it is often more convenient to have the control created
139 # Default returns false.
140 def LazyCreate(self
):
141 self
.log
.write("ListCtrlComboPopup.LazyCreate")
142 return wx
.combo
.ComboPopup
.LazyCreate(self
)
146 #----------------------------------------------------------------------
147 # This class is a popup containing a TreeCtrl. This time we'll use a
148 # has-a style (instead of is-a like above.)
150 class TreeCtrlComboPopup(wx
.combo
.ComboPopup
):
152 # overridden ComboPopup methods
159 def Create(self
, parent
):
160 self
.tree
= wx
.TreeCtrl(parent
, style
=wx
.TR_HIDE_ROOT
165 self
.tree
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
166 self
.tree
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
169 def GetControl(self
):
173 def GetStringValue(self
):
175 return self
.tree
.GetItemText(self
.value
)
181 self
.tree
.EnsureVisible(self
.value
)
182 self
.tree
.SelectItem(self
.value
)
185 def SetStringValue(self
, value
):
186 # this assumes that item strings are unique...
187 root
= self
.tree
.GetRootItem()
190 found
= self
.FindItem(root
, value
)
193 self
.tree
.SelectItem(found
)
196 def GetAdjustedSize(self
, minWidth
, prefHeight
, maxHeight
):
197 return wx
.Size(minWidth
, min(200, maxHeight
))
202 def FindItem(self
, parentItem
, text
):
203 item
, cookie
= self
.tree
.GetFirstChild(parentItem
)
205 if self
.tree
.GetItemText(item
) == text
:
207 if self
.tree
.ItemHasChildren(item
):
208 item
= self
.FindItem(item
, text
)
209 item
, cookie
= self
.tree
.GetNextChild(parentItem
, cookie
)
210 return wx
.TreeItemId();
213 def AddItem(self
, value
, parent
=None):
215 root
= self
.tree
.GetRootItem()
217 root
= self
.tree
.AddRoot("<hidden root>")
220 item
= self
.tree
.AppendItem(parent
, value
)
224 def OnMotion(self
, evt
):
225 # have the selection follow the mouse, like in a real combobox
226 item
, flags
= self
.tree
.HitTest(evt
.GetPosition())
227 if item
and flags
& wx
.TREE_HITTEST_ONITEMLABEL
:
228 self
.tree
.SelectItem(item
)
233 def OnLeftDown(self
, evt
):
234 # do the combobox selection
235 item
, flags
= self
.tree
.HitTest(evt
.GetPosition())
236 if item
and flags
& wx
.TREE_HITTEST_ONITEMLABEL
:
243 #----------------------------------------------------------------------
244 # Here we subclass wx.combo.ComboCtrl to do some custom popup animation
246 CUSTOM_COMBOBOX_ANIMATION_DURATION
= 200
248 class ComboCtrlWithCustomPopupAnim(wx
.combo
.ComboCtrl
):
249 def __init__(self
, *args
, **kw
):
250 wx
.combo
.ComboCtrl
.__init
__(self
, *args
, **kw
)
251 self
.Bind(wx
.EVT_TIMER
, self
.OnTimer
)
252 self
.aniTimer
= wx
.Timer(self
)
255 def AnimateShow(self
, rect
, flags
):
256 self
.aniStart
= wx
.GetLocalTimeMillis()
257 self
.aniRect
= wx
.Rect(*rect
)
258 self
.aniFlags
= flags
261 bmp
= wx
.EmptyBitmap(rect
.width
, rect
.height
)
262 mdc
= wx
.MemoryDC(bmp
)
263 if "wxMac" in wx
.PlatformInfo
:
266 mdc
.Blit(0, 0, rect
.width
, rect
.height
, dc
, rect
.x
, rect
.y
)
268 self
.aniBackBitmap
= bmp
270 self
.aniTimer
.Start(10, wx
.TIMER_CONTINUOUS
)
275 def OnTimer(self
, evt
):
277 popup
= self
.GetPopupControl().GetControl()
281 if self
.IsPopupWindowState(self
.Hidden
):
284 pos
= wx
.GetLocalTimeMillis() - self
.aniStart
285 if pos
< CUSTOM_COMBOBOX_ANIMATION_DURATION
:
286 # Actual animation happens here
290 center_x
= rect
.x
+ (width
/2)
291 center_y
= rect
.y
+ (height
/2)
293 dc
.SetPen( wx
.BLACK_PEN
)
294 dc
.SetBrush( wx
.TRANSPARENT_BRUSH
)
296 w
= (((pos
*256)/CUSTOM_COMBOBOX_ANIMATION_DURATION
)*width
)/256
297 ratio
= float(w
) / float(width
)
298 h
= int(height
* ratio
)
300 dc
.DrawBitmap( self
.aniBackBitmap
, rect
.x
, rect
.y
)
301 dc
.DrawRectangle( center_x
- w
/2, center_y
- h
/2, w
, h
)
306 dc
.DrawBitmap( self
.aniBackBitmap
, rect
.x
, rect
.y
)
309 self
.DoShowPopup( rect
, self
.aniFlags
)
312 #----------------------------------------------------------------------
313 # FileSelectorCombo displays a dialog instead of a popup control, it
314 # also uses a custom bitmap on the combo button.
316 class FileSelectorCombo(wx
.combo
.ComboCtrl
):
317 def __init__(self
, *args
, **kw
):
318 wx
.combo
.ComboCtrl
.__init
__(self
, *args
, **kw
)
320 # make a custom bitmap showing "..."
322 bmp
= wx
.EmptyBitmap(bw
,bh
)
323 dc
= wx
.MemoryDC(bmp
)
325 # clear to a specific background colour
326 bgcolor
= wx
.Colour(255,254,255)
327 dc
.SetBackground(wx
.Brush(bgcolor
))
330 # draw the label onto the bitmap
332 font
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
)
333 font
.SetWeight(wx
.FONTWEIGHT_BOLD
)
335 tw
,th
= dc
.GetTextExtent(label
)
336 dc
.DrawText(label
, (bw
-tw
)/2, (bw
-tw
)/2)
339 # now apply a mask using the bgcolor
340 bmp
.SetMaskColour(bgcolor
)
342 # and tell the ComboCtrl to use it
343 self
.SetButtonBitmaps(bmp
, True)
346 # Overridden from ComboCtrl, called when the combo button is clicked
347 def OnButtonClick(self
):
351 path
, name
= os
.path
.split(self
.GetValue())
353 dlg
= wx
.FileDialog(self
, "Choose File", path
, name
,
354 "All files (*.*)|*.*", wx
.FD_OPEN
)
355 if dlg
.ShowModal() == wx
.ID_OK
:
356 self
.SetValue(dlg
.GetPath())
360 # Overridden from ComboCtrl to avoid assert since there is no ComboPopup
361 def DoSetPopupControl(self
, popup
):
365 #----------------------------------------------------------------------
368 class TestPanel(wx
.Panel
):
369 def __init__(self
, parent
, log
):
371 wx
.Panel
.__init
__(self
, parent
, -1)
373 fgs
= wx
.FlexGridSizer(cols
=3, hgap
=10, vgap
=10)
375 cc
= self
.MakeLCCombo(log
=self
.log
)
378 fgs
.Add(wx
.StaticText(self
, -1, "wx.ComboCtrl with a ListCtrl popup"))
380 cc
= self
.MakeLCCombo(style
=wx
.CB_READONLY
)
383 fgs
.Add(wx
.StaticText(self
, -1, " Read-only"))
385 cc
= self
.MakeLCCombo()
386 cc
.SetButtonPosition(side
=wx
.LEFT
)
389 fgs
.Add(wx
.StaticText(self
, -1, " Button on the left"))
391 cc
= self
.MakeLCCombo()
392 cc
.SetPopupMaxHeight(250)
395 fgs
.Add(wx
.StaticText(self
, -1, " Max height of popup set"))
397 cc
= wx
.combo
.ComboCtrl(self
, size
=(250,-1))
398 tcp
= TreeCtrlComboPopup()
399 cc
.SetPopupControl(tcp
)
402 fgs
.Add(wx
.StaticText(self
, -1, "TreeCtrl popup"))
403 # add some items to the tree
405 item
= tcp
.AddItem('Item %d' % (i
+1))
407 tcp
.AddItem('Subitem %d-%d' % (i
+1, j
+1), parent
=item
)
409 cc
= ComboCtrlWithCustomPopupAnim(self
, size
=(250, -1))
410 popup
= ListCtrlComboPopup()
411 cc
.SetPopupMaxHeight(150)
412 cc
.SetPopupControl(popup
)
415 fgs
.Add(wx
.StaticText(self
, -1, "Custom popup animation"))
416 for word
in "How cool was that!? Way COOL!".split():
418 if "wxMac" in wx
.PlatformInfo
:
419 cc
.SetValue("Sorry, animation not working yet on Mac")
422 cc
= FileSelectorCombo(self
, size
=(250, -1))
425 fgs
.Add(wx
.StaticText(self
, -1, "Custom popup action, and custom button bitmap"))
428 box
.Add(fgs
, 1, wx
.EXPAND|wx
.ALL
, 20)
432 def MakeLCCombo(self
, log
=None, style
=0):
434 cc
= wx
.combo
.ComboCtrl(self
, style
=style
, size
=(250,-1))
437 popup
= ListCtrlComboPopup(log
)
439 # Associate them with each other. This also triggers the
440 # creation of the ListCtrl.
441 cc
.SetPopupControl(popup
)
443 # Add some items to the listctrl.
445 popup
.AddItem("Item-%02d" % x
)
450 #----------------------------------------------------------------------
452 def runTest(frame
, nb
, log
):
453 win
= TestPanel(nb
, log
)
456 #----------------------------------------------------------------------
460 overview
= """<html><body>
461 <h2><center>wx.combo.ComboCtrl</center></h2>
463 A combo control is a generic combobox that allows a totally custom
464 popup. In addition it has other customization features. For instance,
465 position and size of the dropdown button can be changed.
472 if __name__
== '__main__':
475 run
.main(['', os
.path
.basename(sys
.argv
[0])] + sys
.argv
[1:])