Search code examples
wxpythonradio-button

wxPython, radio button selection/deselection and wx.RB_GROUP


wxPython and (grouped) radio-button issue:

I have three radio buttons that are bound in a group, I see this gives me a choice of radio buttons A, B or C, where one is always selected -- I appreciate that this is the nature of the wx.RB_GROUP style;

Is it possible to deselect all buttons as in A=B=C=False by clicking on the radio buttons only? I have streamlined code (below) where the Reset button does this function, but ideally I'd just like to deselect in the GUI.

import wx
class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Tutorial", size=(300,250))
        panel = wx.Panel(self, wx.ID_ANY)
        self.radio1 = wx.RadioButton(panel, label="A", pos=(20,40), style=wx.RB_GROUP)
        self.radio2 = wx.RadioButton(panel, label="B", pos=(20,70))
        self.radio3 = wx.RadioButton(panel, label="C", pos=(20,100))

        btn = wx.Button(panel, label="Check", pos=(20,140))
        rst = wx.Button(panel, label="Reset", pos=(20,170))
        btn.Bind(wx.EVT_BUTTON, self.onBtn)
        rst.Bind(wx.EVT_BUTTON, self.onRst)

    def onBtn(self, event):
        print "A = ", self.radio1.GetValue()
        print "B = ", self.radio2.GetValue()
        print "C = ", self.radio3.GetValue()
        print "\n\n"

    def onRst(self, event):
        self.radio1.SetValue( 0 )
        self.radio2.SetValue( 0 )
        self.radio3.SetValue( 0 )

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

Solution

  • So far as I can tell this is not natively supported in wxPython. Furthermore, this is not the expected behaviour of radio buttons in any UI so my first thought is that your interface design is incorrect. Have you considered adding a further "None" radio button which is logically equivalent to none of your radio buttons being selected? Why not?

    However, I did try to hack you up a solution but to no success. In fact, given the difficulty I faced I highly suspect that even if it is possible to program this behaviour you're still better off finding an alternative.

    My first idea was to overwrite how wx.RadioButton handles being selected by creating a custom wx.RadioButton. Here are some of the problems I found with that approach:

    1. wx.EVT_RADIOBUTTON is only caught after the system has already changed the value of the radio button so I cannot actually overwrite the behaviour
    2. Buttons catch wx.EVT_RADIOBUTTON (when no id is specified) at different times so it's impossible to predict whether the system has further changes queued which might overwrite any changes you make to the button's value.
    3. wx.EVT_RADIOBUTTON is not generated if the radio button is already selected
    4. wxPython only have wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP events, neither of which fully describes how the radio button knows it has been clicked

    I then moved on to a custom wx.RadioBox but I quickly realized that there is no method to completely deselect the buttons inside the radio box, meaning it is not an acceptable choice either.

    I suppose you could write some kind of custom wx.Panel containing multiple wx.CheckBox objects and try to enforce that only a maximum of one box at a time is selected but you run into the problem again that this is not how users expect check boxes to work. I have seen this behaviour before but every time it hit me as strange and I cannot recommend it. Also it will require considerable effort to develop.

    The closest solution I can find to what you're asking is a custom wx.RadioButton that deselects when double clicked. The problem here is that user's don't expect double clicking to deselect a radio button, so you now need to think about how you will teach them this.

    class DeselectableRB(wx.RadioButton):
        def __init__(self, parent, label, pos=None, style=0):
            #do the default init
            wx.RadioButton.__init__(self, parent, label=label, pos=pos, style=style)
    
            #catch double clicks
            self.Bind(wx.EVT_LEFT_DCLICK, self.onDClick)
    
        def onDClick(self, event):
            self.SetValue(0)
    

    However, I still strongly think that an additional wx.RadioButton with the label "None" is your best solution as it requires minimal effort and it conforms to user's expectations about how radio buttons should work (making it more intuitive).