Why do Tkinter's Radio Buttons all Start Selected When Using StringVar but not IntVar?
Asked Answered
R

2

16

Here is some example code that creates 4 Radiobuttons, 2 using int and 2 using str:

from tkinter import *

class test:
    def __init__(self):
        wind = Tk()
        frame1 = Frame(wind)
        frame1.pack()
        self.v1 = IntVar()
        self.v2 = StringVar()

        int1 = Radiobutton(frame1, text = 'int1', variable = self.v1, value = 1,
                           command = self.ipress)
        int2 = Radiobutton(frame1, text = 'int2', variable = self.v1, value = 2,
                           command = self.ipress)

        str1 = Radiobutton(frame1, text = 'str1', variable = self.v2, value = '1',
                           command = self.spress)
        str2 = Radiobutton(frame1, text = 'str2', variable = self.v2, value = '2',
                           command = self.spress)

        int1.grid(row = 1, column = 1)
        int2.grid(row = 1, column = 2)

        str1.grid(row = 2, column = 1)
        str2.grid(row = 2, column = 2)

        str1.deselect() #this didn't fix it
        str2.deselect()

    def ipress(self):
        print('int'+str(self.v1.get()))
    def spress(self):
        print('str'+self.v2.get())

test()

After running this I have a box that look like this:

For some reason the str ones start selected while the int ones do not. Is there a reason for this? Is there any fix for it? I know I could work around it by just using number values and then converting them to strings but I'd like to understand why this is happening first.

I am using Windows 10 if that matters.

Edit: for clarification, the buttons still work properly after they have been clicked on.

Ridden answered 18/11, 2016 at 19:33 Comment(4)
FWIW, I see similar behavior on Ubuntu 14.04. The str buttons are both selected, but both greyed out.Flock
@Robᵩ Thanks for the info, now I know its not some wired windows thing !Ridden
Related: mail.python.org/pipermail/tutor/2009-October/072105.htmlFlock
From that thread, a work-around is self.v2.set(None).Flock
B
21

In the case of the second set of radiobuttons, they are being rendered in "tri-state mode".

According to the official documentation1:

If the variable's value matches the tristateValue, then the radiobutton is drawn using the tri-state mode. This mode is used to indicate mixed or multiple values.

Explanation

The default for tristatevalue is the empty string, and the default value for a StringVar is the empty string. For your second set of radiobuttons both the variable value and the tristatevalue are the same so you are seeing the "tri-state mode".

In the case of the IntVar, the default value of the variable is zero. The tristatevalue is still the empty string. Those two are different so the widget does not appear in "tri-state mode".

To prove this, set tristatevalue to zero for the first set of radiobuttons so that it matches the default value of the associated variable and you will see that their appearance changes to match the second set.

int1 = Radiobutton(..., tristatevalue=0)
int2 = Radiobutton(..., tristatevalue=0)

Likewise, you could set the tristatevalue of the second set to something other than the default value so that they will look like the first set:

str1 = Radiobutton(..., tristatevalue="x")
str2 = Radiobutton(..., tristatevalue="x")

Solution

The best practice with radiobuttons is to always make sure the default value corresponds to one of the radiobutton values, unless you truly do want "tri-state mode".

In your case, you should initialize the variables to the value of one of the radiobuttons:

self.v1 = IntVar(value=1)
self.v2 = StringVar(value="1")

... or after you create them, via set:

self.v1.set(1)
self.v2.set("1")

1 the link goes to the tcl/tk man pages. Tkinter is just a thin wrapper around tcl/tk, and this documentation is the definitive answer to how the widgets behave.

Blue answered 18/11, 2016 at 19:57 Comment(11)
It would help me if you provided a link to the source of your quote.Flock
But how to set the value to '', no ways?Coppinger
@CoolCloud I don't understand the question. You can set it to the empty string in the normal way.Blue
But then the setting value='' is causing this effect in radiobuttons right?Coppinger
@CoolCloud: yes. If you don't want this effect but you want a legitimate value to be the empty string, you need to set the tristatevalue to something else.Blue
Sorry Bryan, this is as clear as mud to me — I've read and reread everything and done some experiments been unable to do what I want — which is to create a group of Radiobuttons all associated with the same StringVar and have all of them be displayed initially without any of them selected?Benares
@martineau: I would argue that in a proper UI there should always be something selected. Regardless, just set the variable to a value that isn't represented by any of the radiobuttons and isn't the same as the tristatevalue.Blue
Thanks for the clarification, I was hoping it could be summed-up into a sentence or two like that. Part of my own confusion was due to the fact that ttk.Radiobuttons work differently in this respect. Also appreciate the UX tip about always having a default selection (I can always make it be one that says "None").Benares
@martineau: "Part of my own confusion was due to the fact that ttk.Radiobuttons work differently in this respect." - that's not true. They work the same.Blue
Bryan: If you run the code in the question tkinter GUI only partially updating until mouse is moved as-is (on my non-OSX system) neither ttk.Radiobutton is selected initially. However if you change them both to "plain" tk.RadioButtons, they both are. That's what I meant about the two behaving differently.Benares
@martineau: I apologize, I misunderstood. Yes, they behave differently because ttk widgets don't have a tristatevalue. When you take tristatevalue out of the equation, the radiobuttons behave the same with respect to all coming out unselected.Blue
H
0

Thanks for @Bryan Oakley detailed explanation.

For simplification,

The "Sam" radio button will be selected once started

self.name = tk.StringVar(value='Sam')  ## is like self.name.set('Sam'), will preset the value as "Sam" 
name_rad1 = tk.Radiobutton(frame, text='Sam', variable=self.name, value='Sam', command=None)
name_rad1.pack(side=tk.LEFT)

No radio button will be selected once started

self.name = tk.StringVar(value=' ')  ## the value will be " ", when you print(self.name.get())
name_rad1 = tk.Radiobutton(frame, text='Sam', variable=self.name, value='Sam', command=None)
name_rad1.pack(side=tk.LEFT)
Heirship answered 13/7, 2021 at 8:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.