From 2589b3d0fa90344e33a863dc65adf7ef1d7cc93c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 25 Sep 2022 20:46:21 +0300 Subject: [PATCH 1/2] gh-73588: Fix generation of the default name of tkinter.Checkbutton. Previously, checkbuttons in different parent widgets could have the same short name and share the same state if arguments "name" and "variable" are not specified. Now they are globally unique. --- Lib/test/test_tkinter/test_widgets.py | 26 +++++++++++++++++++ Lib/test/test_ttk/test_widgets.py | 15 +++++++++++ Lib/tkinter/__init__.py | 12 ++++++++- Lib/tkinter/dialog.py | 2 +- Lib/tkinter/tix.py | 2 +- ...2-09-25-20-42-33.gh-issue-73588.uVtjEA.rst | 4 +++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-09-25-20-42-33.gh-issue-73588.uVtjEA.rst diff --git a/Lib/test/test_tkinter/test_widgets.py b/Lib/test/test_tkinter/test_widgets.py index 1cddbe17ba5207..cc4064ad0d4410 100644 --- a/Lib/test/test_tkinter/test_widgets.py +++ b/Lib/test/test_tkinter/test_widgets.py @@ -212,6 +212,32 @@ def test_configure_onvalue(self): widget = self.create() self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') + def test_unique_variables(self): + frames = [] + buttons = [] + for i in range(2): + f = tkinter.Frame(self.root) + f.pack() + frames.append(f) + for j in 'AB': + b = tkinter.Checkbutton(f, text=j) + b.pack() + buttons.append(b) + variables = [str(b['variable']) for b in buttons] + self.assertEqual(len(set(variables)), 4, variables) + + def test_same_name(self): + f1 = tkinter.Frame(self.root) + f2 = tkinter.Frame(self.root) + b1 = tkinter.Checkbutton(f1, name='test', text='Test1') + b2 = tkinter.Checkbutton(f1, name='test', text='Test2') + + v = tkinter.IntVar(self.root, name='test') + b1.select() + self.assertEqual(v.get(), 1) + b2.deselect() + self.assertEqual(v.get(), 0) + @add_standard_options(StandardOptionsTests) class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index eb0cc93ce270b5..6f47ccb8e8b3de 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -275,6 +275,21 @@ def cb_test(): self.assertEqual(cbtn['offvalue'], cbtn.tk.globalgetvar(cbtn['variable'])) + def test_unique_variables(self): + frames = [] + buttons = [] + for i in range(2): + f = ttk.Frame(self.root) + f.pack() + frames.append(f) + for j in 'AB': + b = ttk.Checkbutton(f, text=j) + b.pack() + buttons.append(b) + variables = [str(b['variable']) for b in buttons] + print(variables) + self.assertEqual(len(set(variables)), 4, variables) + @add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) class EntryTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 296320235afddb..356446911e0abc 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -2619,7 +2619,7 @@ def __init__(self, master, widgetName, cnf={}, kw={}, extra=()): if kw: cnf = _cnfmerge((cnf, kw)) self.widgetName = widgetName - BaseWidget._setup(self, master, cnf) + self._setup(master, cnf) if self._tclCommands is None: self._tclCommands = [] classes = [(k, v) for k, v in cnf.items() if isinstance(k, type)] @@ -3038,6 +3038,8 @@ def type(self, tagOrId): return self.tk.call(self._w, 'type', tagOrId) or None +_checkbutton_count = 0 + class Checkbutton(Widget): """Checkbutton widget which is either in on- or off-state.""" @@ -3053,6 +3055,14 @@ def __init__(self, master=None, cnf={}, **kw): underline, variable, width, wraplength.""" Widget.__init__(self, master, 'checkbutton', cnf, kw) + def _setup(self, master, cnf): + if not cnf.get('name'): + global _checkbutton_count + name = self.__class__.__name__.lower() + _checkbutton_count += 1 + cnf['name'] = f'!{name}{_checkbutton_count}' + super()._setup(master, cnf) + def deselect(self): """Put the button in off-state.""" self.tk.call(self._w, 'deselect') diff --git a/Lib/tkinter/dialog.py b/Lib/tkinter/dialog.py index 8ae214011727cc..36ae6c277cb66a 100644 --- a/Lib/tkinter/dialog.py +++ b/Lib/tkinter/dialog.py @@ -11,7 +11,7 @@ class Dialog(Widget): def __init__(self, master=None, cnf={}, **kw): cnf = _cnfmerge((cnf, kw)) self.widgetName = '__dialog__' - Widget._setup(self, master, cnf) + self._setup(master, cnf) self.num = self.tk.getint( self.tk.call( 'tk_dialog', self._w, diff --git a/Lib/tkinter/tix.py b/Lib/tkinter/tix.py index 44ecae1a326831..ce218265d4a824 100644 --- a/Lib/tkinter/tix.py +++ b/Lib/tkinter/tix.py @@ -310,7 +310,7 @@ def __init__ (self, master=None, widgetName=None, del cnf[k] self.widgetName = widgetName - Widget._setup(self, master, cnf) + self._setup(master, cnf) # If widgetName is None, this is a dummy creation call where the # corresponding Tk widget has already been created by Tix diff --git a/Misc/NEWS.d/next/Library/2022-09-25-20-42-33.gh-issue-73588.uVtjEA.rst b/Misc/NEWS.d/next/Library/2022-09-25-20-42-33.gh-issue-73588.uVtjEA.rst new file mode 100644 index 00000000000000..d8a0e690e29105 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-09-25-20-42-33.gh-issue-73588.uVtjEA.rst @@ -0,0 +1,4 @@ +Fix generation of the default name of :class:`tkinter.Checkbutton`. +Previously, checkbuttons in different parent widgets could have the same +short name and share the same state if arguments "name" and "variable" are +not specified. Now they are globally unique. From c915a56350a79bd03908d836ec8ee35b9150536f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 25 Sep 2022 22:55:26 +0300 Subject: [PATCH 2/2] Fix typo in test_same_name. --- Lib/test/test_tkinter/test_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tkinter/test_widgets.py b/Lib/test/test_tkinter/test_widgets.py index cc4064ad0d4410..6cde1ccdbfcda8 100644 --- a/Lib/test/test_tkinter/test_widgets.py +++ b/Lib/test/test_tkinter/test_widgets.py @@ -230,7 +230,7 @@ def test_same_name(self): f1 = tkinter.Frame(self.root) f2 = tkinter.Frame(self.root) b1 = tkinter.Checkbutton(f1, name='test', text='Test1') - b2 = tkinter.Checkbutton(f1, name='test', text='Test2') + b2 = tkinter.Checkbutton(f2, name='test', text='Test2') v = tkinter.IntVar(self.root, name='test') b1.select()