Attachment 'CustomCheckBox.py'
Download 1 import wx
2
3
4 #----------------------------------------------------------------------
5 def GetCheckedBitmap():
6 return wx.BitmapFromImage(GetCheckedImage())
7
8 def GetCheckedImage():
9 return wx.Image("checked.ico", wx.BITMAP_TYPE_ICO)
10
11 #----------------------------------------------------------------------
12
13 def GetNotCheckedBitmap():
14 return wx.BitmapFromImage(GetNotCheckedImage())
15
16 def GetNotCheckedImage():
17 return wx.Image("notchecked.ico", wx.BITMAP_TYPE_ICO)
18
19 #----------------------------------------------------------------------
20
21 def GrayOut(anImage):
22 """
23 Convert the given image (in place) to a grayed-out version,
24 appropriate for a 'disabled' appearance.
25 """
26
27 factor = 0.7 # 0 < f < 1. Higher Is Grayer
28
29 if anImage.HasMask():
30 maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
31 else:
32 maskColor = None
33
34 data = map(ord, list(anImage.GetData()))
35
36 for i in range(0, len(data), 3):
37
38 pixel = (data[i], data[i+1], data[i+2])
39 pixel = MakeGray(pixel, factor, maskColor)
40
41 for x in range(3):
42 data[i+x] = pixel[x]
43
44 anImage.SetData(''.join(map(chr, data)))
45
46 return anImage.ConvertToBitmap()
47
48
49 def MakeGray((r,g,b), factor, maskColor):
50 """
51 Make a pixel grayed-out. If the pixel matches the maskcolor, it won't be
52 changed.
53 """
54
55 if (r,g,b) != maskColor:
56 return map(lambda x: int((230 - x) * factor) + x, (r,g,b))
57 else:
58 return (r,g,b)
59
60
61 class CustomCheckBox(wx.PyControl):
62 """
63 A custom class that replicates some of the functionalities of wx.CheckBox,
64 while being completely owner-drawn with a nice check bitmaps.
65 """
66
67 def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition,
68 size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator,
69 name="CustomCheckBox"):
70 """
71 Default class constructor.
72
73 @param parent: Parent window. Must not be None.
74 @param id: CustomCheckBox identifier. A value of -1 indicates a default value.
75 @param label: Text to be displayed next to the checkbox.
76 @param pos: CustomCheckBox position. If the position (-1, -1) is specified
77 then a default position is chosen.
78 @param size: CustomCheckBox size. If the default size (-1, -1) is specified
79 then a default size is chosen.
80 @param style: not used in this demo, CustomCheckBox has only 2 state
81 @param validator: Window validator.
82 @param name: Window name.
83 """
84
85 # Ok, let's see why we have used wx.PyControl instead of wx.Control.
86 # Basically, wx.PyControl is just like its wxWidgets counterparts
87 # except that it allows some of the more common C++ virtual method
88 # to be overridden in Python derived class. For CustomCheckBox, we
89 # basically need to override DoGetBestSize and AcceptsFocusFromKeyboard
90
91 wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
92
93 # Initialize our cool bitmaps
94 self.InitializeBitmaps()
95
96 # Initialize the focus pen colour/dashes, for faster drawing later
97 self.InitializeColours()
98
99 # By default, we start unchecked
100 self._checked = False
101
102 # Set the spacing between the check bitmap and the label to 3 by default.
103 # This can be changed using SetSpacing later.
104 self._spacing = 3
105 self._hasFocus = False
106
107 # Ok, set the wx.PyControl label, its initial size (formerly known an
108 # SetBestFittingSize), and inherit the attributes from the standard
109 # wx.CheckBox
110 self.SetLabel(label)
111 self.SetInitialSize(size)
112 self.InheritAttributes()
113
114 # Bind the events related to our control: first of all, we use a
115 # combination of wx.BufferedPaintDC and an empty handler for
116 # wx.EVT_ERASE_BACKGROUND (see later) to reduce flicker
117 self.Bind(wx.EVT_PAINT, self.OnPaint)
118 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
119
120 # Then we want to monitor user clicks, so that we can switch our
121 # state between checked and unchecked
122 self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
123 if wx.Platform == '__WXMSW__':
124 # MSW Sometimes does strange things...
125 self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseClick)
126
127 # We want also to react to keyboard keys, namely the
128 # space bar that can toggle our checked state
129 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
130
131 # Then, we react to focus event, because we want to draw a small
132 # dotted rectangle around the text if we have focus
133 # This might be improved!!!
134 self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
135 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
136
137
138 def InitializeBitmaps(self):
139 """ Initializes the check bitmaps. """
140
141 # We keep 4 bitmaps for CustomCheckBox, depending on the
142 # checking state (Checked/UnCkecked) and the control
143 # state (Enabled/Disabled).
144 self._bitmaps = {"CheckedEnable": GetCheckedBitmap(),
145 "UnCheckedEnable": GetNotCheckedBitmap(),
146 "CheckedDisable": GrayOut(GetCheckedImage()),
147 "UnCheckedDisable": GrayOut(GetNotCheckedImage())}
148
149
150 def InitializeColours(self):
151 """ Initializes the focus indicator pen. """
152
153 textClr = self.GetForegroundColour()
154 if wx.Platform == "__WXMAC__":
155 self._focusIndPen = wx.Pen(textClr, 1, wx.SOLID)
156 else:
157 self._focusIndPen = wx.Pen(textClr, 1, wx.USER_DASH)
158 self._focusIndPen.SetDashes([1,1])
159 self._focusIndPen.SetCap(wx.CAP_BUTT)
160
161
162 def GetBitmap(self):
163 """
164 Returns the appropriated bitmap depending on the checking state
165 (Checked/UnCkecked) and the control state (Enabled/Disabled).
166 """
167
168 if self.IsEnabled():
169 # So we are Enabled
170 if self.IsChecked():
171 # We are Checked
172 return self._bitmaps["CheckedEnable"]
173 else:
174 # We are UnChecked
175 return self._bitmaps["UnCheckedEnable"]
176 else:
177 # Poor CustomCheckBox, Disabled and ignored!
178 if self.IsChecked():
179 return self._bitmaps["CheckedDisable"]
180 else:
181 return self._bitmaps["UnCheckedDisable"]
182
183
184 def SetLabel(self, label):
185 """
186 Sets the CustomCheckBox text label and updates the control's size to
187 exactly fit the label plus the bitmap.
188 """
189
190 wx.PyControl.SetLabel(self, label)
191
192 # The text label has changed, so we must recalculate our best size
193 # and refresh ourselves.
194 self.InvalidateBestSize()
195 self.Refresh()
196
197
198 def SetFont(self, font):
199 """
200 Sets the CustomCheckBox text font and updates the control's size to
201 exactly fit the label plus the bitmap.
202 """
203
204 wx.PyControl.SetFont(self, font)
205
206 # The font for text label has changed, so we must recalculate our best
207 # size and refresh ourselves.
208 self.InvalidateBestSize()
209 self.Refresh()
210
211
212 def DoGetBestSize(self):
213 """
214 Overridden base class virtual. Determines the best size of the control
215 based on the label size, the bitmap size and the current font.
216 """
217
218 # Retrieve our properties: the text label, the font and the check
219 # bitmap
220 label = self.GetLabel()
221 font = self.GetFont()
222 bitmap = self.GetBitmap()
223
224 if not font:
225 # No font defined? So use the default GUI font provided by the system
226 font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
227
228 # Set up a wx.ClientDC. When you don't have a dc available (almost
229 # always you don't have it if you are not inside a wx.EVT_PAINT event),
230 # use a wx.ClientDC (or a wx.MemoryDC) to measure text extents
231 dc = wx.ClientDC(self)
232 dc.SetFont(font)
233
234 # Measure our label
235 textWidth, textHeight = dc.GetTextExtent(label)
236
237 # Retrieve the check bitmap dimensions
238 bitmapWidth, bitmapHeight = bitmap.GetWidth(), bitmap.GetHeight()
239
240 # Get the spacing between the check bitmap and the text
241 spacing = self.GetSpacing()
242
243 # Ok, we're almost done: the total width of the control is simply
244 # the sum of the bitmap width, the spacing and the text width,
245 # while the height is the maximum value between the text width and
246 # the bitmap width
247 totalWidth = bitmapWidth + spacing + textWidth
248 totalHeight = max(textHeight, bitmapHeight)
249
250 best = wx.Size(totalWidth, totalHeight)
251
252 # Cache the best size so it doesn't need to be calculated again,
253 # at least until some properties of the window change
254 self.CacheBestSize(best)
255
256 return best
257
258
259 def AcceptsFocusFromKeyboard(self):
260 """Overridden base class virtual."""
261
262 # We can accept focus from keyboard, obviously
263 return True
264
265
266 def AcceptsFocus(self):
267 """ Overridden base class virtual. """
268
269 # It seems to me that wx.CheckBox does not accept focus with mouse
270 # but please correct me if I am wrong!
271 return False
272
273
274 def HasFocus(self):
275 """ Returns whether or not we have the focus. """
276
277 # We just returns the _hasFocus property that has been set in the
278 # wx.EVT_SET_FOCUS and wx.EVT_KILL_FOCUS event handlers.
279 return self._hasFocus
280
281
282 def SetForegroundColour(self, colour):
283 """ Overridden base class virtual. """
284
285 wx.PyControl.SetForegroundColour(self, colour)
286
287 # We have to re-initialize the focus indicator per colour as it should
288 # always be the same as the foreground colour
289 self.InitializeColours()
290 self.Refresh()
291
292
293 def SetBackgroundColour(self, colour):
294 """ Overridden base class virtual. """
295
296 wx.PyControl.SetBackgroundColour(self, colour)
297
298 # We have to refresh ourselves
299 self.Refresh()
300
301
302 def Enable(self, enable=True):
303 """ Enables/Disables CustomCheckBox. """
304
305 wx.PyControl.Enable(self, enable)
306
307 # We have to refresh ourselves, as our state changed
308 self.Refresh()
309
310
311 def GetDefaultAttributes(self):
312 """
313 Overridden base class virtual. By default we should use
314 the same font/colour attributes as the native wx.CheckBox.
315 """
316
317 return wx.CheckBox.GetClassDefaultAttributes()
318
319
320 def ShouldInheritColours(self):
321 """
322 Overridden base class virtual. If the parent has non-default
323 colours then we want this control to inherit them.
324 """
325
326 return True
327
328
329 def SetSpacing(self, spacing):
330 """ Sets a new spacing between the check bitmap and the text. """
331
332 self._spacing = spacing
333
334 # The spacing between the check bitmap and the text has changed,
335 # so we must recalculate our best size and refresh ourselves.
336 self.InvalidateBestSize()
337 self.Refresh()
338
339
340 def GetSpacing(self):
341 """ Returns the spacing between the check bitmap and the text. """
342
343 return self._spacing
344
345
346 def GetValue(self):
347 """
348 Returns the state of CustomCheckBox, True if checked, False
349 otherwise.
350 """
351
352 return self._checked
353
354
355 def IsChecked(self):
356 """
357 This is just a maybe more readable synonym for GetValue: just as the
358 latter, it returns True if the CustomCheckBox is checked and False
359 otherwise.
360 """
361
362 return self._checked
363
364
365 def SetValue(self, state):
366 """
367 Sets the CustomCheckBox to the given state. This does not cause a
368 wx.wxEVT_COMMAND_CHECKBOX_CLICKED event to get emitted.
369 """
370
371 self._checked = state
372
373 # Refresh ourselves: the bitmap has changed
374 self.Refresh()
375
376
377 def OnKeyDown(self, event):
378 """ Handles the wx.EVT_KEY_DOWN event for CustomCheckBox. """
379
380 if event.GetKeyCode() == wx.WXK_SPACE:
381 # The spacebar has been pressed: toggle our state
382 self.SendCheckBoxEvent()
383 event.Skip()
384 return
385
386 event.Skip()
387
388
389 def OnSetFocus(self, event):
390 """ Handles the wx.EVT_SET_FOCUS event for CustomCheckBox. """
391
392 self._hasFocus = True
393
394 # We got focus, and we want a dotted rectangle to be painted
395 # around the checkbox label, so we refresh ourselves
396 self.Refresh()
397
398
399 def OnKillFocus(self, event):
400 """ Handles the wx.EVT_KILL_FOCUS event for CustomCheckBox. """
401
402 self._hasFocus = False
403
404 # We lost focus, and we want a dotted rectangle to be cleared
405 # around the checkbox label, so we refresh ourselves
406 self.Refresh()
407
408
409 def OnPaint(self, event):
410 """ Handles the wx.EVT_PAINT event for CustomCheckBox. """
411
412 # If you want to reduce flicker, a good starting point is to
413 # use wx.BufferedPaintDC.
414 dc = wx.BufferedPaintDC(self)
415
416 # Is is advisable that you don't overcrowd the OnPaint event
417 # (or any other event) with a lot of code, so let's do the
418 # actual drawing in the Draw() method, passing the newly
419 # initialized wx.BufferedPaintDC
420 self.Draw(dc)
421
422
423 def Draw(self, dc):
424 """
425 Actually performs the drawing operations, for the bitmap and
426 for the text, positioning them centered vertically.
427 """
428
429 # Get the actual client size of ourselves
430 width, height = self.GetClientSize()
431
432 if not width or not height:
433 # Nothing to do, we still don't have dimensions!
434 return
435
436 # Initialize the wx.BufferedPaintDC, assigning a background
437 # colour and a foreground colour (to draw the text)
438 backColour = self.GetBackgroundColour()
439 backBrush = wx.Brush(backColour, wx.SOLID)
440 dc.SetBackground(backBrush)
441 dc.Clear()
442
443 if self.IsEnabled():
444 dc.SetTextForeground(self.GetForegroundColour())
445 else:
446 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
447
448 dc.SetFont(self.GetFont())
449
450 # Get the text label for the checkbox, the associated check bitmap
451 # and the spacing between the check bitmap and the text
452 label = self.GetLabel()
453 bitmap = self.GetBitmap()
454 spacing = self.GetSpacing()
455
456 # Measure the text extent and get the check bitmap dimensions
457 textWidth, textHeight = dc.GetTextExtent(label)
458 bitmapWidth, bitmapHeight = bitmap.GetWidth(), bitmap.GetHeight()
459
460 # Position the bitmap centered vertically
461 bitmapXpos = 0
462 bitmapYpos = (height - bitmapHeight)/2
463
464 # Position the text centered vertically
465 textXpos = bitmapWidth + spacing
466 textYpos = (height - textHeight)/2
467
468 # Draw the bitmap on the DC
469 dc.DrawBitmap(bitmap, bitmapXpos, bitmapYpos, True)
470
471 # Draw the text
472 dc.DrawText(label, textXpos, textYpos)
473
474 # Let's see if we have keyboard focus and, if this is the case,
475 # we draw a dotted rectangle around the text (Windows behavior,
476 # I don't know on other platforms...)
477 if self.HasFocus():
478 # Yes, we are focused! So, now, use a transparent brush with
479 # a dotted black pen to draw a rectangle around the text
480 dc.SetBrush(wx.TRANSPARENT_BRUSH)
481 dc.SetPen(self._focusIndPen)
482 dc.DrawRectangle(textXpos, textYpos, textWidth, textHeight)
483
484
485 def OnEraseBackground(self, event):
486 """ Handles the wx.EVT_ERASE_BACKGROUND event for CustomCheckBox. """
487
488 # This is intentionally empty, because we are using the combination
489 # of wx.BufferedPaintDC + an empty OnEraseBackground event to
490 # reduce flicker
491 pass
492
493
494 def OnMouseClick(self, event):
495 """ Handles the wx.EVT_LEFT_DOWN event for CustomCheckBox. """
496
497 if not self.IsEnabled():
498 # Nothing to do, we are disabled
499 return
500
501 self.SendCheckBoxEvent()
502 event.Skip()
503
504
505 def SendCheckBoxEvent(self):
506 """ Actually sends the wx.wxEVT_COMMAND_CHECKBOX_CLICKED event. """
507
508 # This part of the code may be reduced to a 3-liner code
509 # but it is kept for better understanding the event handling.
510 # If you can, however, avoid code duplication; in this case,
511 # I could have done:
512 #
513 # self._checked = not self.IsChecked()
514 # checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
515 # self.GetId())
516 # checkEvent.SetInt(int(self._checked))
517 if self.IsChecked():
518
519 # We were checked, so we should become unchecked
520 self._checked = False
521
522 # Fire a wx.CommandEvent: this generates a
523 # wx.wxEVT_COMMAND_CHECKBOX_CLICKED event that can be caught by the
524 # developer by doing something like:
525 # MyCheckBox.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
526 checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
527 self.GetId())
528
529 # Set the integer event value to 0 (we are switching to unchecked state)
530 checkEvent.SetInt(0)
531
532 else:
533
534 # We were unchecked, so we should become checked
535 self._checked = True
536
537 checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
538 self.GetId())
539
540 # Set the integer event value to 1 (we are switching to checked state)
541 checkEvent.SetInt(1)
542
543 # Set the originating object for the event (ourselves)
544 checkEvent.SetEventObject(self)
545
546 # Watch for a possible listener of this event that will catch it and
547 # eventually process it
548 self.GetEventHandler().ProcessEvent(checkEvent)
549
550 # Refresh ourselves: the bitmap has changed
551 self.Refresh()
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.