How to create an On-Off button alternative to CheckBox (Phoenix)
Keywords : CheckBox, RadioButton, Custom.
Contents
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
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
Additional Information
Link :
- - - - -
https://wiki.wxpython.org/TitleIndex
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....