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.
  • [get | view] (2009-11-30 08:50:11, 18.7 KB) [[attachment:CustomCheckBox.py]]
  • [get | view] (2009-11-30 08:52:03, 9.6 KB) [[attachment:CustomCheckBox.zip]]
  • [get | view] (2009-11-30 08:50:15, 8.8 KB) [[attachment:CustomCheckBoxDemo.py]]
  • [get | view] (2011-09-02 03:55:29, 14.1 KB) [[attachment:CustomCheckBoxMod_Demo.zip]]
  • [get | view] (2009-11-30 08:50:06, 1.4 KB) [[attachment:checked.ico]]
  • [get | view] (2009-11-30 08:50:17, 1.4 KB) [[attachment:notchecked.ico]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.

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