How to create an On-Off button alternative to CheckBox (Phoenix)

Keywords : CheckBox, RadioButton, Custom.


Demonstrating :

Tested py3.8.10, wx4.x and Linux.

Tested py3.10.5, wx4.2.1 and Win11.

Are you ready to use some samples ? ;)

Test, modify, correct, complete, improve and share your discoveries ! (!)


First example

Latest version here : https://discuss.wxpython.org/t/an-on-off-button-alternative-to-checkbox/36304

img_sample_one.jpg img_sample_two.jpg img_sample_three.jpg

   1 '''
   2     onoffbutton.py
   3 
   4     A custom class On/Off clickable - meant as a replacement for CheckBox.
   5 
   6     Events: EVT_ON_OFF          Generic event on binary change
   7 
   8             EVT_ON              The control changed to On
   9 
  10             EVT_OFF             The control changed to Off
  11 
  12     Event Notes:
  13         All events return GetValue() and GetId() i.e.
  14 
  15             def OnOff(self, event):
  16                 current_value = event.GetValue()
  17                 id = event.GetId()
  18 
  19     Functions:
  20         GetValue()              Returns numeric value in the control On (True) = 1 Off (False) = 0
  21         SetValue(int)           Set numeric value in the control
  22         GetId()                 Get widget Id
  23         GetLabel()              Get current label for the control
  24         SetLabel(str)           Set label for the control
  25         SetToolTip(str)         Set tooltip for the control
  26         SetForegroundColour     Set text colour
  27         SetBackgroundColour     Set background colour
  28         SetOnColour             Set On background colour
  29         SetOnForegroundColour   Set On foreground colour
  30         SetOffColour            Set Off background colour
  31         SetOffForegroundColour  Set Off foreground colour
  32         SetFont(font)           Set label font
  33         GetFont()               Get label font
  34         Enable(Bool)            Enable/Disable the control
  35         IsEnabled()             Is the control Enabled - Returns True/False
  36         SetSpacing(int)         Set size of space between control and label
  37         GetSpacing()            Get size of space between control and label
  38 
  39     Default Values:
  40         initial - 0 (False) Off
  41         label   - blank
  42         style   - wx.BORDER_NONE
  43         mono    - False
  44         border  - True
  45         size    - 24x16 (Almost as small as a CheckBox)
  46         circle  - True
  47         internal_style - OOB_CIRCLE = 0
  48 
  49         The control is either a rounded rectangle or a rectangle, controlled by the parameter, circle
  50         The internal style indicator which shows the controls position, On or Off, can be a circle, an arrow
  51             a rectangle or a sized radio button, the default is a circle
  52             Choosing a rectangle forces the control to be a rectangular shape
  53             Choosing Radio forces the control to look like a radio button
  54 
  55         Tooltips are either [On] or [Off] unless a tooltip has been set, in which case [Currently On] or
  56          [Currently Off] is added to the existing tooltip
  57 
  58     Valid control styles:
  59         Window border styles e.g. wx.BORDER_NONE, wx.BORDER_SIMPLE etc
  60         wx.ALIGN_RIGHT  Align control to the Right of the Label
  61 
  62     Valid internal styles:
  63         OOB_CIRCLE      = 0
  64         OOB_ARROW       = 1
  65         OOB_RECTANGLE   = 2
  66         OOB_RADIO       = 3
  67 
  68 Author:     J Healey
  69 Created:    14/01/2021
  70 Copyright:  J Healey - 2021-2023
  71 License:    GPL 3 or any later version
  72 Version:    1.15.4
  73 Email:      <rolfofsaxony@gmx.com>
  74 Written & tested: Linux, Python 3.8.10
  75 
  76 Change Log:
  77 1.15.4  Minor, mainly cosmetic, adjustments
  78         Function Enable defaults to True
  79         Add function Disable defaulting to False
  80 
  81 1.15.3  Rectangular radio box internal indicator is now a square
  82         Control's text forced to system disabled grey and italic when disabled
  83 
  84 1.15.2  Radio dimensions are forced to be an odd value, as this centres better within the outer border
  85         Disabled image bitmap.ConvertToDisabled() not satisfactory, now built specifically again
  86 
  87 1.15.1  Label and Spacing are hidden if not present, to prevent the control displaying larger than necessary
  88         This becomes more obvious if someone is using gradient colours for panel backgrounds
  89 
  90 1.15.0  Adds sized Radio button style
  91         Rationalise internal style (arrow, rect) to a single variable 'internal_style'
  92         Values:
  93             OOB_CIRCLE      = 0 Default
  94             OOB_ARROW       = 1
  95             OOB_RECTANGLE   = 2
  96             OOB_RADIO       = 3
  97 
  98 1.14.0  Rectangle control indicator re-introduced via rect parameter
  99         Control's text forced to dim grey when disabled
 100 
 101 1.13.0  Choice between Circle and Arrow Head for the control's indicator
 102         The left click Toggle is bound to both the Label and the control.
 103          As this widget is envisaged to be used for very small on/off controls,
 104          it is easier to toggle if the Bind includes the Label
 105         Add a small border width, left or right as appropriate, to the Label
 106         Utilise wx.Bitmap's ConvertToDisabled() function for disabled bitmaps
 107 
 108 1.12.3  Change allows for the recognition of mnemonics, allowing an
 109         Alt key + a letter key, to toggle the button, when the letter is preceded
 110         by an ampersand (&), in the widgets label e.g- ("&Go") Alt+G
 111         Fixes for python3 10+ differences
 112         Colours revert to #xxxxxx rather than colour names, as MS Windows reportedly struggles with names
 113         Text sizing is performed using a DC, not calculated from pixel size
 114         Disabled button is specifically set as MS Windows reportedly does not change the image with the control
 115         Disabled bitmaps now created for Disabled On and Disabled Off to cater for the above
 116 
 117 1.11.0  Changes rectangular button to have a Circular on/off widget
 118         This makes whether the button is on or off clearer
 119         especially if it is Mono colour
 120 
 121 1.10.0  Adds
 122         SetOnForegroundColour function
 123         SetOffForegroundColour function
 124         Default Off background colour changed from red to grey
 125 
 126 1.9.0   Adds
 127         SetOnColour function
 128         SetOffColour function
 129 
 130 1.8.0   Adds
 131         rectangle style On/Off control
 132         SetSpacing function
 133         Align Right style
 134 
 135 1.7.0   First real release
 136 
 137 Usage example:
 138 
 139 import wx
 140 import onoffbutton as oob
 141 class Frame(wx.Frame):
 142     def __init__(self, parent):
 143         wx.Frame.__init__(self, parent, -1, "On Off button Demo")
 144         panel = wx.Panel(self)
 145         panel.SetBackgroundColour("azure")
 146         self.onoff = oob.OnOffButton(panel, -1, label="80 X 48", pos=(10, 50), size=(160, 96), initial=1)
 147         self.onoff1 = oob.OnOffButton(panel, -1, label="Smaller", pos=(10, 200),\
 148                                       internal_style=oob.OOB_RECTANGLE, initial=0)
 149         self.Show()
 150 
 151 app = wx.App()
 152 frame = Frame(None)
 153 app.MainLoop()
 154 
 155 '''
 156 
 157 import wx
 158 
 159 # Events OnOff, On and Off
 160 oobEVT_ON_OFF = wx.NewEventType()
 161 EVT_ON_OFF = wx.PyEventBinder(oobEVT_ON_OFF, 1)
 162 oobEVT_ON = wx.NewEventType()
 163 EVT_ON = wx.PyEventBinder(oobEVT_ON, 1)
 164 oobEVT_OFF = wx.NewEventType()
 165 EVT_OFF = wx.PyEventBinder(oobEVT_OFF, 1)
 166 
 167 # Internal indicator styles
 168 OOB_CIRCLE = 0
 169 OOB_ARROW = 1
 170 OOB_RECTANGLE = 2
 171 OOB_RADIO = 3
 172 
 173 class OnOffEvent(wx.PyCommandEvent):
 174     """ Events sent from the :class:`OnOffButton` when the control changes.
 175         EVT_ON_OFF  The Control value has changed
 176         EVT_ON      The Control turned On
 177         EVT_OFF     The Control turned Off
 178     """
 179 
 180     def __init__(self, eventType, eventId=1, value=0):
 181         """
 182         Default class constructor.
 183 
 184         :param `eventType`: the event type;
 185         :param `eventId`: the event identifier.
 186         """
 187 
 188         wx.PyCommandEvent.__init__(self, eventType, eventId)
 189         self._eventType = eventType
 190         self.value = value
 191 
 192     def GetValue(self):
 193         """
 194         Retrieve the value of the control at the time
 195         this event was generated.
 196         """
 197         return self.value
 198 
 199 
 200 class OnOffButton(wx.Control):
 201 
 202     def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition, size=wx.DefaultSize, initial=0,\
 203                  style=wx.BORDER_NONE, mono=False, border=True, circle=True, internal_style=OOB_CIRCLE,\
 204                  name="OnOff_Button"):
 205         """
 206         Default class constructor.
 207 
 208         @param parent:  Parent window. Must not be None.
 209         @param id:      identifier. A value of -1 indicates a default value.
 210         @param label:   control label.
 211         @param pos:     Position. If the position (-1, -1) is specified
 212                         then a default position is chosen.
 213         @param size:    If the default size (-1, -1) is specified then the minimum size is chosen.
 214         @param initial: Initial value 0 False or 1 True
 215                         - default False.
 216         @param style:   wx.Border style and/or wx.ALIGN_RIGHT = Align control to the Right of the Label.
 217         @param mono:    True or False makes the image monochrome
 218                         - default False
 219         @param border:  True or False adds a border to the controls image
 220                         - default True
 221         @param circle:  True or False Control is a Rounded Rectangle or a Standard Rectangle
 222                         - default True
 223         @param internal_style:   0 - 3 The style of On/Off indicator
 224                         - default 0 (Circle)
 225         @param name:    Widget name.
 226         """
 227 
 228         wx.Control.__init__(self, parent, id, pos=pos, size=size, style=style, name=name)
 229         self._initial = initial
 230         self._label = label
 231         self._pos = pos
 232         self._size = size
 233         self._name = name
 234         self._mono = mono
 235         self._border = border
 236         self._style = style
 237         self._circle = circle
 238         self._internal_style = internal_style
 239         if self._internal_style == OOB_RECTANGLE:
 240             self._circle = False
 241         self.OnClr = '#698B22' # olivedrab4
 242         self.OffClr = '#787878' # grey
 243         self.OnClrForeground = None
 244         self.OffClrForeground = None
 245         self.mnemonic = False
 246         self._spacing = 0
 247         self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
 248         if self._mono:
 249             self.OnClr = self.OffClr = '#ffffff' # white
 250         if self._internal_style == OOB_RADIO:
 251             self.OffClr = '#ffffff' # white
 252         if self._initial > 1:
 253             self._initial = 1
 254         if self._initial < 0:
 255             self._initial = 0
 256 
 257         self._Value = initial
 258         self._backgroundcolour = parent.GetBackgroundColour()
 259         self._foregroundcolour = parent.GetForegroundColour()
 260         self.SetBackgroundColour(self._backgroundcolour)
 261         self.SetForegroundColour(self._foregroundcolour)
 262 
 263         # Initialize images
 264         self.InitialiseBitmaps()
 265         self.onoff = wx.StaticBitmap(self, -1, bitmap=wx.Bitmap(self._img))
 266         self.spacer = wx.StaticText(self, -1, "")
 267         self.label = wx.StaticText(self, -1, self._label)
 268         self.mnemonic = self.Mnemonic(self._label)
 269 
 270         # Bind the event
 271         self.onoff.Bind(wx.EVT_LEFT_DOWN, self.OnOff)
 272         self.label.Bind(wx.EVT_LEFT_DOWN, self.OnOff)
 273         self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
 274         self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
 275 
 276         self.sizer = wx.BoxSizer(wx.HORIZONTAL)
 277         if self._style & wx.ALIGN_RIGHT:
 278             self.sizer.Add(self.label, 0, wx.ALIGN_CENTRE|wx.RIGHT, 5)
 279             self.sizer.Add(self.spacer, 0, 0, 0)
 280             self.sizer.Add(self.onoff, 0, 0, 0)
 281         else:
 282             self.sizer.Add(self.onoff, 0, 0, 0)
 283             self.sizer.Add(self.spacer, 0, 0, 0)
 284             self.sizer.Add(self.label, 0, wx.ALIGN_CENTRE|wx.LEFT, 5)
 285         if not self._spacing:
 286             self.sizer.Hide(self.spacer)
 287         if not self._label:
 288             self.sizer.Hide(self.label)
 289         self.SetImage(self._initial)
 290         self.SetSizerAndFit(self.sizer)
 291 
 292         if self._label:
 293             self.SetFocus()
 294 
 295     def InitialiseBitmaps(self):
 296         self._bitmaps = {
 297             "On": self._CreateBitmap("On"),
 298             "Off": self._CreateBitmap("Off"),
 299             "DisableOff": self._CreateBitmap("DisableOff"),
 300             "DisableOn": self._CreateBitmap("DisableOn"),
 301             }
 302 #        self._bitmaps["DisableOn"] = self.DisableImage(self._bitmaps["On"])
 303 #        self._bitmaps["DisableOff"] = self.DisableImage(self._bitmaps["Off"])
 304 
 305         if self._initial <= 0:
 306             self._img = self._bitmaps['Off']
 307         elif self._initial >= 1:
 308             self._img = self._bitmaps['On']
 309 
 310     def _CreateBitmap(self, type):
 311         if type == "On":
 312             self.SetImageSize()
 313         bmp = wx.Bitmap(self.bmpw, self.bmph)
 314         dc = wx.MemoryDC(bmp)
 315         try:
 316             gcdc = wx.GCDC(dc) # Anti-aliased for Windows ?? Who knows? Not me!
 317         except Exception as e:
 318             gcdc = dc
 319 
 320         bg = self.GetBackgroundColour()
 321         fg = self.GetForegroundColour()
 322         if self.OnClrForeground is None:
 323             self.OnClrForeground = fg
 324         if self.OffClrForeground is None:
 325             self.OffClrForeground = fg
 326         brush = wx.Brush(bg)
 327         gcdc.SetBackground(brush)
 328         gcdc.SetPen(wx.Pen(fg))
 329         gcdc.Clear()
 330         gcdc.SetBrush(brush)
 331 
 332         if self._circle:
 333             if self._border:
 334                 gcdc.DrawRoundedRectangle(
 335                     self.rrouterposx,
 336                     self.rrouterposy,
 337                     self.rrouterw,
 338                     self.rrouterh,
 339                     self.rrouterradius
 340                     )
 341             if type == "On":
 342                 gcdc.SetBrush(wx.Brush(self.OnClr))
 343             elif type == "Off":
 344                 gcdc.SetBrush(wx.Brush(self.OffClr))
 345             else:
 346                 gcdc.SetBrush(wx.Brush('#787878'))
 347             if type[:7] == "Disable" or self._mono:
 348                 gcdc.SetBrush(wx.Brush('#ffffff')) # white
 349             gcdc.SetPen(wx.Pen(bg))
 350             gcdc.DrawRoundedRectangle(
 351                 self.rrinnerposx,
 352                 self.rrinnerposy,
 353                 self.rrinnerw,
 354                 self.rrinnerh,
 355                 self.rrinnerradius
 356                 )
 357         else:
 358             if self._border:
 359                 gcdc.DrawRectangle(
 360                     self.rrouterposx,
 361                     self.rrouterposy,
 362                     self.rrouterw,
 363                     self.rrouterh,
 364                     )
 365             if type == "On":
 366                 gcdc.SetBrush(wx.Brush(self.OnClr))
 367             elif type == "Off":
 368                 gcdc.SetBrush(wx.Brush(self.OffClr))
 369             else:
 370                 gcdc.SetBrush(wx.Brush('#787878')) # grey
 371             if type[:7] == "Disable" or self._mono:
 372                 gcdc.SetBrush(wx.Brush('#ffffff')) # white/grey
 373             gcdc.SetPen(wx.Pen(bg))
 374             gcdc.DrawRectangle(
 375                 self.rrinnerposx,
 376                 self.rrinnerposy,
 377                 self.rrinnerw,
 378                 self.rrinnerh,
 379                 )
 380         if type == "On" or type == "DisableOn":
 381             gcdc.SetBrush(wx.Brush(self.OnClrForeground))
 382             if type == "DisableOn":
 383                 gcdc.SetBrush(wx.Brush('#484848')) # grey
 384             if self._internal_style == OOB_ARROW:
 385                 gcdc.DrawPolygon([(self.bmpw-4, int(self.bmph/2)),(int(self.bmpw/2),2),(int(self.bmpw/2),self.bmph-1)],0,0)
 386             elif self._internal_style == OOB_RECTANGLE:
 387                 x = self.rrinnerposx+int(self.rrouterw/2) - 2
 388                 y = self.rrinnerposy
 389                 w = int(self.rrinnerw/2)
 390                 h = self.rrinnerh
 391                 gcdc.DrawRectangle(x, y, w, h)
 392             elif self._internal_style == OOB_RADIO and self._circle:
 393                 gcdc.DrawCircle(int(self.circoffpos[0]), int(self.circoffpos[1]), int(self.circradius))
 394             elif self._internal_style == OOB_RADIO:
 395                 gcdc.DrawRectangle(
 396                     self.rrinnerposx,
 397                     self.rrinnerposy,
 398                     self.rrinnerw,
 399                     self.rrinnerh,
 400                     )
 401             else:
 402                 gcdc.DrawCircle(int(self.circonpos[0]), int(self.circonpos[1]), int(self.circradius))
 403         else:
 404             gcdc.SetBrush(wx.Brush(self.OffClrForeground))
 405             if type == "DisableOff":
 406                 gcdc.SetBrush(wx.Brush('#484848')) # grey
 407             if self._internal_style == OOB_ARROW:
 408                 gcdc.DrawPolygon([(4, int(self.bmph/2)),(int(self.bmpw/2),2),(int(self.bmpw/2),self.bmph-1)],0,0)
 409             elif self._internal_style == OOB_RECTANGLE:
 410                 x = self.rrinnerposx
 411                 y = self.rrinnerposy
 412                 w = int(self.rrinnerw/2)
 413                 h = self.rrinnerh
 414                 gcdc.DrawRectangle(x, y, w, h)
 415             elif self._internal_style == OOB_RADIO and self._circle:
 416                 gcdc.DrawCircle(int(self.circoffpos[0]), int(self.circoffpos[1]), int(0))
 417             elif self._internal_style == OOB_RADIO:
 418                 gcdc.DrawRectangle(
 419                     self.rrinnerposx,
 420                     self.rrinnerposy,
 421                     0,
 422                     0,
 423                     )
 424             else:
 425                 gcdc.DrawCircle(int(self.circoffpos[0]), int(self.circoffpos[1]), int(self.circradius))
 426 
 427         bmp = dc.GetAsBitmap((0, 0, self.bmpw, self.bmph))
 428         del dc, gcdc
 429         return bmp
 430 
 431     def DisableImage(self, bmp):
 432         bmp = bmp.ConvertToDisabled()
 433         return bmp
 434 
 435     def SetImageSize(self):
 436         w, h = self._size
 437         # Cater for only Width or only Height parameter given
 438         if h < 0 and w >= 0:
 439             h = int(w * 0.6667)
 440         if w < 0 and h >= 0:
 441             w = int(h / 0.6667)
 442         # Set Default minimum size, also caters for no size set (-1, -1)
 443         if self._internal_style == OOB_RADIO:
 444             w = max(self._size[0], 16)
 445             h = max(self._size[1], 16)
 446         else:
 447             w = max(w, 24)
 448             h = max(h, 16)
 449 
 450         if self._internal_style == OOB_RADIO: # force equal dimensions for radio
 451             m = max(w, h)
 452             if not m % 2: # Odd number centres properly
 453                 m -= 1
 454             w = h = m
 455 
 456         # Size bitmap
 457         self.bmpw = w
 458         self.bmph = h
 459 
 460         # Outer rounded rectangle
 461         self.rrouterw = w - 2
 462         self.rrouterh = h - 2
 463         self.rrouterposx = 2
 464         self.rrouterposy = 2
 465         self.rrouterradius = (self.rrouterh/2)
 466         # Inner rounded rectangle
 467         self.rrinnerw = self.rrouterw - 4
 468         self.rrinnerh = self.rrouterh - 4
 469         self.rrinnerposx = self.rrouterposx + 2
 470         self.rrinnerposy = self.rrouterposy + 2
 471         self.rrinnerradius = self.rrinnerh/2
 472         # Circle position and size
 473         self.circradius = round(self.rrinnerradius - 1)
 474         self.circoffpos = (round(self.rrinnerradius+self.rrinnerposx), round(self.rrinnerposy + self.rrinnerradius))
 475         self.circonpos = (round(self.rrinnerw - (self.rrinnerradius-3)), round(self.rrinnerposy + self.rrinnerradius))
 476 
 477     def SetValue(self, value):
 478         if value > 1:
 479             value = 1
 480         if value < 0:
 481             value = 0
 482         self._Value = value
 483         self.SetImage(value)
 484         self.Update()
 485 
 486     def GetValue(self):
 487         if self._Value:
 488             return True
 489         else:
 490             return False
 491 
 492     def SetLabel(self, label):
 493         self.label.SetLabel(label)
 494         self.spacer.SetLabel(" "*self._spacing)
 495         font = self.GetFont()
 496         if not font.IsOk(): # Invalid font - swap out for the system default
 497             font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
 498         dc = wx.ClientDC(self)
 499         dc.SetFont(font)
 500         textW, textH = dc.GetTextExtent(label)
 501         spacingW, _ = dc.GetTextExtent(" "*self._spacing)
 502         imgW, imgH = self._img.GetSize()
 503         maxW = textW + spacingW + imgW
 504         maxH = max(textH, imgH)
 505         best = wx.Size(maxW, maxH)
 506         self.CacheBestSize(best)
 507         self.label.SetMinSize(wx.Size(textW, textH))
 508         self.label.SetSize(wx.Size(textW, textH))
 509         self.spacer.SetMinSize(wx.Size(spacingW, textH))
 510         self.spacer.SetSize(wx.Size(spacingW, textH))
 511         self.SetMinSize(best)
 512         self.Fit()
 513         self.mnemonic = self.Mnemonic(label)
 514         self._label = label
 515         if label:
 516             self.SetFocus()
 517             self.sizer.Show(self.label)
 518         self.Update()
 519 
 520     def Mnemonic(self, label):
 521         mnemonic = label
 522         if "&&" in mnemonic: # remove literal & from the test
 523             mnemonic = mnemonic.replace('&&', '')
 524         if "&" in mnemonic: # find first &
 525             st, *end = mnemonic.split('&', 1)
 526             char = end[0][0]
 527             return ord(char.upper())
 528         else:
 529             return False
 530 
 531     def OnKey(self, event):
 532 #        if self.HasFocus():
 533         keycode = event.GetKeyCode()
 534         mods = event.GetModifiers()
 535         if mods == 1 and keycode == self.mnemonic: # Alt + & marked label character
 536             self.OnOff(None)
 537         event.Skip(True)
 538 
 539     def GetLabel(self):
 540         return self.label.GetLabel()
 541 
 542     def IsEnabled(self):
 543         return wx.Control.IsEnabled(self)
 544 
 545     def Disable(self, value=True):
 546         self.Enable(not value)
 547 
 548     def Enable(self, value=True):
 549         wx.Control.Enable(self, value)
 550         self.SetImage(self.GetValue())
 551         if self.IsEnabled(): # force text change
 552             self._font.SetStyle(wx.FONTSTYLE_NORMAL)
 553             self.SetFont(self._font)
 554             self.label.SetForegroundColour(self._foregroundcolour)
 555         else:
 556             self._font.SetStyle(wx.FONTSTYLE_ITALIC)
 557             self.SetFont(self._font)
 558             self.label.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
 559         self.Refresh()
 560 
 561     def SetToolTip(self, tip):
 562         wx.Control.SetToolTip(self, tip)
 563         self.Refresh()
 564 
 565     def SetHelpText(self, text):
 566         wx.Control.SetHelpText(self, text)
 567         self.onoff.SetHelpText(text)
 568         self.Refresh()
 569 
 570     def ShouldInheritColours(self):
 571         return True
 572 
 573     def SetForegroundColour(self, colour):
 574         wx.Control.SetForegroundColour(self, colour)
 575         self.InitialiseBitmaps()
 576         self.SetImage(self._Value)
 577         self.Refresh()
 578 
 579     def SetBackgroundColour(self, colour):
 580         wx.Control.SetBackgroundColour(self, colour)
 581         self.InitialiseBitmaps()
 582         self.SetImage(self._Value)
 583         self.Refresh()
 584 
 585     def SetOnColour(self, colour):
 586         self.OnClr = colour
 587         self.InitialiseBitmaps()
 588         self.SetImage(self._Value)
 589         self.Refresh()
 590 
 591     def SetOnForegroundColour(self, colour):
 592         self.OnClrForeground = colour
 593         self.InitialiseBitmaps()
 594         self.SetImage(self._Value)
 595         self.Refresh()
 596 
 597     def SetOffColour(self, colour):
 598         self.OffClr = colour
 599         self.InitialiseBitmaps()
 600         self.SetImage(self._Value)
 601         self.Refresh()
 602 
 603     def SetOffForegroundColour(self, colour):
 604         self.OffClrForeground = colour
 605         self.InitialiseBitmaps()
 606         self.SetImage(self._Value)
 607         self.Refresh()
 608 
 609     def SetFont(self, font):
 610         wx.Control.SetFont(self, font)
 611         self._font = font
 612         # Font changed reset label
 613         self.SetLabel(self._label)
 614 
 615     def GetFont(self):
 616         return self._font
 617 
 618     def SetSpacing(self, spacing):
 619         self._spacing = spacing
 620         if self._spacing:
 621             self.sizer.Show(self.spacer)
 622         self.SetLabel(self._label)
 623         self.Refresh()
 624 
 625     def GetSpacing(self):
 626         return self._spacing
 627 
 628     def OnOff(self, event):
 629         state = self._Value
 630         if state == 0:
 631             state = 1
 632         else:
 633             state = 0
 634 
 635         self.SetImage(state)
 636         self.SetValue(state)
 637         # event change
 638         event = OnOffEvent(oobEVT_ON_OFF, self.GetId(), state)
 639         event.SetEventObject(self)
 640         self.GetEventHandler().ProcessEvent(event)
 641 
 642         if state:
 643             # event On
 644             event = OnOffEvent(oobEVT_ON, self.GetId(), state)
 645             event.SetEventObject(self)
 646             self.GetEventHandler().ProcessEvent(event)
 647         else:
 648             # event Off
 649             event = OnOffEvent(oobEVT_OFF, self.GetId(), state)
 650             event.SetEventObject(self)
 651             self.GetEventHandler().ProcessEvent(event)
 652 
 653     def SetImage(self, value):
 654         # Set appropriate image and tooltip
 655         tp = self.GetToolTip()
 656         if self.IsEnabled():
 657             if value <= 0:
 658                 self._img = self._bitmaps['Off']
 659                 if tp is not None:
 660                     tt = tp.GetTip()+"\n[ Off ]"
 661                 else:
 662                     tt = "[ Off ]"
 663             else:
 664                 self._img = self._bitmaps['On']
 665                 if tp is not None:
 666                     tt = tp.GetTip()+"\n[ On ]"
 667                 else:
 668                     tt = "[ On ]"
 669         else: # Disabled
 670             if value <= 0:
 671                 self._img = self._bitmaps['DisableOff']
 672             else:
 673                 self._img = self._bitmaps['DisableOn']
 674             if tp is not None:
 675                 tt = tp.GetTip()+"\n[ Disabled ]"
 676             else:
 677                 tt = "[ Disabled ]"
 678 
 679         if hasattr(self, 'onoff'): # Not present if initially setting the parent backgroundcolour
 680             self.onoff.SetBitmap(wx.Bitmap(self._img))
 681             self.onoff.SetToolTip(tt)
 682 
 683     def OnKillFocus(self, event):
 684         event.Skip()
 685 
 686 if __name__ == '__main__':
 687 
 688     class DemoFrame(wx.Frame):
 689 
 690         def __init__(self, parent):
 691 
 692             wx.Frame.__init__(self, parent, -1, "On/Off Button Demonstration", size=(-1, 750))
 693 
 694             panel = wx.Panel(self)
 695             panel.SetBackgroundColour("#f2f0e6") # alabaster
 696             #panel.SetForegroundColour("#000000") # black
 697 
 698             self.onoff = OnOffButton(panel, -1, label="80 x 48", pos=(10, 50), size=(160, 96), initial=1, name ="1")
 699 
 700             self.onoff1 = OnOffButton(panel, -1, "60 x 36 Line 1\nLine2\n&Line3", pos=(10, 200),\
 701                                       size=(60, 36), initial=0, border=True, name="2")
 702 
 703             self.onoff2 = OnOffButton(panel, -1, " 40 x 24 Rectangle, Right, Arrow && &Spacing", pos=(10, 270),\
 704                                       size=(40, 24), initial=1, circle=False, internal_style=1,\
 705                                       style=wx.BORDER_NONE | wx.ALIGN_RIGHT, name="3")
 706 
 707             self.onoff3 = OnOffButton(panel, -1, "30 x 20 Mono Rectangle", pos=(10, 300),\
 708                                       size=(30, 20), initial=0, mono=True, circle=False, internal_style=1,  name="4")
 709 
 710             self.onoff4 = OnOffButton(panel, -1, "Minimum Mono", pos=(10, 350),\
 711                                       initial=0, mono=True, border=True, internal_style=1, name="5")
 712 
 713             self.onoff4a = OnOffButton(panel, -1, "Minimum Mono", pos=(170, 350),\
 714                                       initial=0, mono=True, circle=False, internal_style=OOB_ARROW, name="5a")
 715 
 716             self.onoff5 = OnOffButton(panel, -1, "Minimum 20 x 16 ", pos=(10, 400), initial=0, style=wx.BORDER_SIMPLE, name="6")
 717 
 718             self.onoff6 = OnOffButton(panel, -1, "Minimum Mono", pos=(170, 400), initial=0, mono=True,\
 719                                       style=wx.BORDER_NONE|wx.ALIGN_RIGHT, name="7")
 720 
 721             self.onoff7 = OnOffButton(panel, -1, "Minimum 20 x 16 &Rectangle && spacing", pos=(10, 450), initial=0,\
 722                                       circle=False, internal_style=OOB_RECTANGLE, name="8")
 723 
 724             self.onoff8 = OnOffButton(panel, -1, "&Minimum 20 x 16", pos=(10, 500),\
 725                                       initial=0, circle=True, internal_style=1, name="9")
 726 
 727             self.onoff8a = OnOffButton(panel, -1, "No border", pos=(170, 500),\
 728                                       initial=0, border=False, circle=True, internal_style=1, name="9a")
 729 
 730             self.onoff9 = OnOffButton(panel, -1, "40 x 24\n....\nRectagonal Indicator", pos=(10, 550), size=(40, 24),\
 731                                       initial=0, mono=False, circle=False, internal_style=OOB_RECTANGLE, name="10")
 732 
 733             self.onoff10 = OnOffButton(panel, -1, "Minimum Mono", pos=(170, 550),\
 734                                       initial=0, mono=True, circle=False, internal_style=2, name="10a")
 735 
 736             self.onoff11 = OnOffButton(panel, -1, "Radio 40 x 40", pos=(10, 620), size=(40, -1),\
 737                                       initial=1, internal_style=OOB_RADIO, name="11")
 738 
 739             self.onoff12 = OnOffButton(panel, -1, "Radio minimum default", pos=(170, 620),\
 740                                       initial=1, internal_style=OOB_RADIO, name="12")
 741 
 742             self.onoff13 = OnOffButton(panel, -1, "Minimum rectangular\nMono radio", pos=(170, 650),\
 743                                       initial=0, mono=True, circle=False, internal_style=3, name="13")
 744 
 745             self.txt = wx.TextCtrl(panel, -1, "On", size=(50, 30), pos=(300, 50))
 746             self.txt1 = wx.TextCtrl(panel, -1, "Off", size=(50, 30), pos=(300, 200))
 747 
 748             # Bind via Id
 749             self.Bind(EVT_ON_OFF, self.OnOff, id=self.onoff.GetId())
 750 
 751             self.Bind(EVT_ON, self.SwOn)
 752             self.Bind(EVT_OFF, self.SwOff)
 753 
 754             # Bind by name
 755             self.onoff1.Bind(EVT_ON_OFF, self.OnOff1)
 756 
 757             # Demonstrate individual control adjustments
 758             self.onoff1.SetOnColour("#00f100") # green
 759             self.onoff1.SetOnForegroundColour("#006400") # darkgreen
 760             self.onoff1.SetOffColour("#ababab") # grey
 761             self.onoff1.SetOffForegroundColour("#ff0000") # red
 762             self.onoff1.SetToolTip("Coloured button")
 763 
 764             self.onoff2.SetOnColour("#6b8e23") # olivedrab
 765             self.onoff2.SetOffColour("#bfefff") # lightblue
 766             self.onoff2.SetOnForegroundColour("#006400") # darkgreen
 767             self.onoff2.SetBackgroundColour("#bfefff") # lightblue
 768 
 769             self.onoff5.SetBackgroundColour("#bfefff") # lightblue
 770 
 771             self.onoff7.SetOnColour("#00f100") # green
 772 
 773             self.onoff8.SetOffColour("#ffc0cb") # pink
 774             self.onoff8.SetOffForegroundColour("#ff0000") # red
 775             self.onoff8.SetOnColour("#00f100") # green
 776             self.onoff8.SetOnForegroundColour("#006400") # darkgreen
 777 
 778             self.onoff9.SetOffColour("#ffc0cb") # pink
 779             self.onoff9.SetOnColour("#00f100") # green
 780 
 781             self.onoff11.SetOnForegroundColour("#006400") # darkgreen
 782             self.onoff11.SetOffColour("#ababab") # grey67
 783             #self.onoff11.SetOffColour(panel.GetBackgroundColour())
 784 
 785             self.onoff12.SetOnForegroundColour("#006400") # darkgreen
 786             self.onoff12.SetOffColour("#ababab") # grey67
 787 
 788             font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
 789             font.SetPointSize(16)
 790             # testing - look for a fixed width font
 791             #fnt = wx.FontEnumerator()
 792             #fwfnt = fnt.GetFacenames(encoding=wx.FONTENCODING_SYSTEM, fixedWidthOnly=True)
 793             #if fwfnt:
 794             #    font.SetFaceName(fwfnt[0])
 795             font.SetFamily(wx.FONTFAMILY_TELETYPE)
 796             font.SetWeight(wx.FONTWEIGHT_BOLD)
 797             self.onoff.SetFont(font)
 798             self.onoff2.SetSpacing(6)
 799             self.onoff7.SetSpacing(10)
 800             self.Show()
 801             #self.onoff3.Enable(False)
 802 
 803             # Demonstrate Enabled/Disabled
 804             self.timer = wx.Timer(self)
 805             self.Bind(wx.EVT_TIMER, self.Toggle)
 806             self.timer.Start(5000)
 807 
 808         def Toggle(self, event):
 809             self.onoff1.Enable(not self.onoff1.IsEnabled())
 810             self.onoff3.Enable(not self.onoff3.IsEnabled())
 811 
 812         def OnOff(self, event):
 813             if event.GetValue():
 814                 self.txt.SetValue("On")
 815                 self.onoff.SetLabel("Label &On")
 816             else:
 817                 self.txt.SetValue("Off")
 818                 self.onoff.SetLabel("Label &Off")
 819             event.Skip()
 820 
 821         def OnOff1(self, event):
 822             if event.GetValue():
 823                 self.txt1.SetValue("On")
 824             else:
 825                 self.txt1.SetValue("Off")
 826             event.Skip()
 827 
 828         def SwOn(self, event):
 829             obj = event.GetEventObject()
 830             print(obj.Name + " On", event.GetId())
 831 
 832         def SwOff(self, event):
 833             obj = event.GetEventObject()
 834             print(obj.Name + " Off", event.GetId())
 835 
 836     app = wx.App()
 837     frame = DemoFrame(None)
 838     app.SetTopWindow(frame)
 839     frame.Show()
 840     app.MainLoop()


Download source

source.zip


Additional Information

Link :

- - - - -

https://wiki.wxpython.org/TitleIndex

https://docs.wxpython.org/

https://discuss.wxpython.org/t/an-on-off-button-alternative-to-checkbox/36304


Thanks to

J. Healey (aka RolfofSaxony), the wxPython community...


About this page

Date(d/m/y) Person (bot) Comments :

11/01/23 - Ecco (Created page for wxPython Phoenix).


Comments

- blah, blah, blah....

How to create an On-Off button alternative to CheckBox (Phoenix) (last edited 2024-08-16 18:02:35 by Ecco)

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