bpo-42630: Improve error reporting in Tkinter for absent default root (GH-23781)

* Tkinter functions and constructors which need a default root window
  raise now RuntimeError with descriptive message instead of obscure
  AttributeError or NameError if it is not created yet or cannot
  be created automatically.

* Add tests for all functions which use default root window.

* Fix import in the pynche script.
This commit is contained in:
Serhiy Storchaka 2020-12-19 12:17:08 +02:00 committed by GitHub
parent 1e27b57dbc
commit 3d569fd6dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 315 additions and 87 deletions

View File

@ -1061,8 +1061,10 @@ class PyShell(OutputWindow):
(sys.version, sys.platform, self.COPYRIGHT, nosub)) (sys.version, sys.platform, self.COPYRIGHT, nosub))
self.text.focus_force() self.text.focus_force()
self.showprompt() self.showprompt()
# User code should use separate default Tk root window
import tkinter import tkinter
tkinter._default_root = None # 03Jan04 KBK What's this? tkinter._support_default_root = True
tkinter._default_root = None
return True return True
def stop_readline(self): def stop_readline(self):

View File

@ -20,5 +20,5 @@ from idlelib.idle_test import load_tests
if __name__ == '__main__': if __name__ == '__main__':
tk.NoDefaultRoot() tk.NoDefaultRoot()
unittest.main(exit=False) unittest.main(exit=False)
tk._support_default_root = 1 tk._support_default_root = True
tk._default_root = None tk._default_root = None

View File

@ -270,7 +270,7 @@ class Event:
) )
_support_default_root = 1 _support_default_root = True
_default_root = None _default_root = None
@ -280,13 +280,26 @@ def NoDefaultRoot():
Call this function to inhibit that the first instance of Call this function to inhibit that the first instance of
Tk is used for windows without an explicit parent window. Tk is used for windows without an explicit parent window.
""" """
global _support_default_root global _support_default_root, _default_root
_support_default_root = 0 _support_default_root = False
global _default_root # Delete, so any use of _default_root will immediately raise an exception.
# Rebind before deletion, so repeated calls will not fail.
_default_root = None _default_root = None
del _default_root del _default_root
def _get_default_root(what=None):
if not _support_default_root:
raise RuntimeError("No master specified and tkinter is "
"configured to not support default root")
if not _default_root:
if what:
raise RuntimeError(f"Too early to {what}: no default root window")
root = Tk()
assert _default_root is root
return _default_root
def _tkerror(err): def _tkerror(err):
"""Internal function.""" """Internal function."""
pass pass
@ -330,7 +343,7 @@ class Variable:
raise TypeError("name must be a string") raise TypeError("name must be a string")
global _varnum global _varnum
if not master: if not master:
master = _default_root master = _get_default_root('create variable')
self._root = master._root() self._root = master._root()
self._tk = master.tk self._tk = master.tk
if name: if name:
@ -591,7 +604,7 @@ class BooleanVar(Variable):
def mainloop(n=0): def mainloop(n=0):
"""Run the main loop of Tcl.""" """Run the main loop of Tcl."""
_default_root.tk.mainloop(n) _get_default_root('run the main loop').tk.mainloop(n)
getint = int getint = int
@ -600,9 +613,9 @@ getdouble = float
def getboolean(s): def getboolean(s):
"""Convert true and false to integer values 1 and 0.""" """Convert Tcl object to True or False."""
try: try:
return _default_root.tk.getboolean(s) return _get_default_root('use getboolean()').tk.getboolean(s)
except TclError: except TclError:
raise ValueError("invalid literal for getboolean()") raise ValueError("invalid literal for getboolean()")
@ -2248,7 +2261,7 @@ class Tk(Misc, Wm):
is the name of the widget class.""" is the name of the widget class."""
self.master = None self.master = None
self.children = {} self.children = {}
self._tkloaded = 0 self._tkloaded = False
# to avoid recursions in the getattr code in case of failure, we # to avoid recursions in the getattr code in case of failure, we
# ensure that self.tk is always _something_. # ensure that self.tk is always _something_.
self.tk = None self.tk = None
@ -2272,7 +2285,7 @@ class Tk(Misc, Wm):
self._loadtk() self._loadtk()
def _loadtk(self): def _loadtk(self):
self._tkloaded = 1 self._tkloaded = True
global _default_root global _default_root
# Version sanity checks # Version sanity checks
tk_version = self.tk.getvar('tk_version') tk_version = self.tk.getvar('tk_version')
@ -2521,12 +2534,8 @@ class BaseWidget(Misc):
def _setup(self, master, cnf): def _setup(self, master, cnf):
"""Internal function. Sets up information about children.""" """Internal function. Sets up information about children."""
if _support_default_root: if not master:
global _default_root master = _get_default_root()
if not master:
if not _default_root:
_default_root = Tk()
master = _default_root
self.master = master self.master = master
self.tk = master.tk self.tk = master.tk
name = None name = None
@ -3990,9 +3999,7 @@ class Image:
def __init__(self, imgtype, name=None, cnf={}, master=None, **kw): def __init__(self, imgtype, name=None, cnf={}, master=None, **kw):
self.name = None self.name = None
if not master: if not master:
master = _default_root master = _get_default_root('create image')
if not master:
raise RuntimeError('Too early to create image')
self.tk = getattr(master, 'tk', master) self.tk = getattr(master, 'tk', master)
if not name: if not name:
Image._last_id += 1 Image._last_id += 1
@ -4146,11 +4153,13 @@ class BitmapImage(Image):
def image_names(): def image_names():
return _default_root.tk.splitlist(_default_root.tk.call('image', 'names')) tk = _get_default_root('use image_names()').tk
return tk.splitlist(tk.call('image', 'names'))
def image_types(): def image_types():
return _default_root.tk.splitlist(_default_root.tk.call('image', 'types')) tk = _get_default_root('use image_types()').tk
return tk.splitlist(tk.call('image', 'types'))
class Spinbox(Widget, XView): class Spinbox(Widget, XView):

View File

@ -18,10 +18,10 @@ class Dialog:
command = None command = None
def __init__(self, master=None, **options): def __init__(self, master=None, **options):
if not master:
master = options.get('parent')
self.master = master self.master = master
self.options = options self.options = options
if not master and options.get('parent'):
self.master = options['parent']
def _fixoptions(self): def _fixoptions(self):
pass # hook pass # hook

View File

@ -69,7 +69,7 @@ class Font:
def __init__(self, root=None, font=None, name=None, exists=False, def __init__(self, root=None, font=None, name=None, exists=False,
**options): **options):
if not root: if not root:
root = tkinter._default_root root = tkinter._get_default_root('use font')
tk = getattr(root, 'tk', root) tk = getattr(root, 'tk', root)
if font: if font:
# get actual settings corresponding to the given font # get actual settings corresponding to the given font
@ -184,7 +184,7 @@ class Font:
def families(root=None, displayof=None): def families(root=None, displayof=None):
"Get font families (as a tuple)" "Get font families (as a tuple)"
if not root: if not root:
root = tkinter._default_root root = tkinter._get_default_root('use font.families()')
args = () args = ()
if displayof: if displayof:
args = ('-displayof', displayof) args = ('-displayof', displayof)
@ -194,7 +194,7 @@ def families(root=None, displayof=None):
def names(root=None): def names(root=None):
"Get names of defined fonts (as a tuple)" "Get names of defined fonts (as a tuple)"
if not root: if not root:
root = tkinter._default_root root = tkinter._get_default_root('use font.names()')
return root.tk.splitlist(root.tk.call("font", "names")) return root.tk.splitlist(root.tk.call("font", "names"))

View File

@ -24,9 +24,7 @@ askstring -- get a string from the user
""" """
from tkinter import * from tkinter import *
from tkinter import messagebox from tkinter import messagebox, _get_default_root
import tkinter # used at _QueryDialog for tkinter._default_root
class SimpleDialog: class SimpleDialog:
@ -128,13 +126,17 @@ class Dialog(Toplevel):
title -- the dialog title title -- the dialog title
''' '''
Toplevel.__init__(self, parent) master = parent
if not master:
master = _get_default_root('create dialog window')
Toplevel.__init__(self, master)
self.withdraw() # remain invisible for now self.withdraw() # remain invisible for now
# If the master is not viewable, don't # If the parent is not viewable, don't
# make the child transient, or else it # make the child transient, or else it
# would be opened withdrawn # would be opened withdrawn
if parent.winfo_viewable(): if parent is not None and parent.winfo_viewable():
self.transient(parent) self.transient(parent)
if title: if title:
@ -155,7 +157,7 @@ class Dialog(Toplevel):
self.protocol("WM_DELETE_WINDOW", self.cancel) self.protocol("WM_DELETE_WINDOW", self.cancel)
if self.parent is not None: if parent is not None:
self.geometry("+%d+%d" % (parent.winfo_rootx()+50, self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
parent.winfo_rooty()+50)) parent.winfo_rooty()+50))
@ -259,9 +261,6 @@ class _QueryDialog(Dialog):
minvalue = None, maxvalue = None, minvalue = None, maxvalue = None,
parent = None): parent = None):
if not parent:
parent = tkinter._default_root
self.prompt = prompt self.prompt = prompt
self.minvalue = minvalue self.minvalue = minvalue
self.maxvalue = maxvalue self.maxvalue = maxvalue

View File

@ -36,6 +36,33 @@ class AbstractTkTest:
w.destroy() w.destroy()
self.root.withdraw() self.root.withdraw()
class AbstractDefaultRootTest:
def setUp(self):
self._old_support_default_root = tkinter._support_default_root
destroy_default_root()
tkinter._support_default_root = True
self.wantobjects = tkinter.wantobjects
def tearDown(self):
destroy_default_root()
tkinter._default_root = None
tkinter._support_default_root = self._old_support_default_root
def _test_widget(self, constructor):
# no master passing
x = constructor()
self.assertIsNotNone(tkinter._default_root)
self.assertIs(x.master, tkinter._default_root)
self.assertIs(x.tk, tkinter._default_root.tk)
x.destroy()
destroy_default_root()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, constructor)
self.assertFalse(hasattr(tkinter, '_default_root'))
def destroy_default_root(): def destroy_default_root():
if getattr(tkinter, '_default_root', None): if getattr(tkinter, '_default_root', None):
tkinter._default_root.update_idletasks() tkinter._default_root.update_idletasks()

View File

@ -2,7 +2,7 @@ import unittest
import tkinter import tkinter
from tkinter import font from tkinter import font
from test.support import requires, run_unittest, gc_collect, ALWAYS_EQ from test.support import requires, run_unittest, gc_collect, ALWAYS_EQ
from tkinter.test.support import AbstractTkTest from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest
requires('gui') requires('gui')
@ -107,7 +107,37 @@ class FontTest(AbstractTkTest, unittest.TestCase):
) )
tests_gui = (FontTest, ) class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_families(self):
self.assertRaises(RuntimeError, font.families)
root = tkinter.Tk()
families = font.families()
self.assertIsInstance(families, tuple)
self.assertTrue(families)
for family in families:
self.assertIsInstance(family, str)
self.assertTrue(family)
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, font.families)
def test_names(self):
self.assertRaises(RuntimeError, font.names)
root = tkinter.Tk()
names = font.names()
self.assertIsInstance(names, tuple)
self.assertTrue(names)
for name in names:
self.assertIsInstance(name, str)
self.assertTrue(name)
self.assertIn(fontname, names)
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, font.names)
tests_gui = (FontTest, DefaultRootTest)
if __name__ == "__main__": if __name__ == "__main__":
run_unittest(*tests_gui) run_unittest(*tests_gui)

View File

@ -2,7 +2,7 @@ import unittest
import tkinter import tkinter
from test import support from test import support
from test.support import os_helper from test.support import os_helper
from tkinter.test.support import AbstractTkTest, requires_tcl from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest, requires_tcl
support.requires('gui') support.requires('gui')
@ -20,6 +20,47 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(image_names, tuple) self.assertIsInstance(image_names, tuple)
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_image_types(self):
self.assertRaises(RuntimeError, tkinter.image_types)
root = tkinter.Tk()
image_types = tkinter.image_types()
self.assertIsInstance(image_types, tuple)
self.assertIn('photo', image_types)
self.assertIn('bitmap', image_types)
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, tkinter.image_types)
def test_image_names(self):
self.assertRaises(RuntimeError, tkinter.image_names)
root = tkinter.Tk()
image_names = tkinter.image_names()
self.assertIsInstance(image_names, tuple)
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, tkinter.image_names)
def test_image_create_bitmap(self):
self.assertRaises(RuntimeError, tkinter.BitmapImage)
root = tkinter.Tk()
image = tkinter.BitmapImage()
self.assertIn(image.name, tkinter.image_names())
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, tkinter.BitmapImage)
def test_image_create_photo(self):
self.assertRaises(RuntimeError, tkinter.PhotoImage)
root = tkinter.Tk()
image = tkinter.PhotoImage()
self.assertIn(image.name, tkinter.image_names())
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, tkinter.PhotoImage)
class BitmapImageTest(AbstractTkTest, unittest.TestCase): class BitmapImageTest(AbstractTkTest, unittest.TestCase):
@classmethod @classmethod
@ -331,7 +372,7 @@ class PhotoImageTest(AbstractTkTest, unittest.TestCase):
self.assertEqual(image.transparency_get(4, 6), False) self.assertEqual(image.transparency_get(4, 6), False)
tests_gui = (MiscTest, BitmapImageTest, PhotoImageTest,) tests_gui = (MiscTest, DefaultRootTest, BitmapImageTest, PhotoImageTest,)
if __name__ == "__main__": if __name__ == "__main__":
support.run_unittest(*tests_gui) support.run_unittest(*tests_gui)

View File

@ -1,7 +1,7 @@
import unittest import unittest
import tkinter import tkinter
from test import support from test import support
from tkinter.test.support import AbstractTkTest from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest
support.requires('gui') support.requires('gui')
@ -241,7 +241,85 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
" num=3 delta=-1 focus=True" " num=3 delta=-1 focus=True"
" x=10 y=20 width=300 height=200>") " x=10 y=20 width=300 height=200>")
tests_gui = (MiscTest, ) def test_getboolean(self):
for v in 'true', 'yes', 'on', '1', 't', 'y', 1, True:
self.assertIs(self.root.getboolean(v), True)
for v in 'false', 'no', 'off', '0', 'f', 'n', 0, False:
self.assertIs(self.root.getboolean(v), False)
self.assertRaises(ValueError, self.root.getboolean, 'yea')
self.assertRaises(ValueError, self.root.getboolean, '')
self.assertRaises(TypeError, self.root.getboolean, None)
self.assertRaises(TypeError, self.root.getboolean, ())
def test_mainloop(self):
log = []
def callback():
log.append(1)
self.root.after(100, self.root.quit)
self.root.after(100, callback)
self.root.mainloop(1)
self.assertEqual(log, [])
self.root.mainloop(0)
self.assertEqual(log, [1])
self.assertTrue(self.root.winfo_exists())
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_default_root(self):
self.assertIs(tkinter._support_default_root, True)
self.assertIsNone(tkinter._default_root)
root = tkinter.Tk()
root2 = tkinter.Tk()
root3 = tkinter.Tk()
self.assertIs(tkinter._default_root, root)
root2.destroy()
self.assertIs(tkinter._default_root, root)
root.destroy()
self.assertIsNone(tkinter._default_root)
root3.destroy()
self.assertIsNone(tkinter._default_root)
def test_no_default_root(self):
self.assertIs(tkinter._support_default_root, True)
self.assertIsNone(tkinter._default_root)
root = tkinter.Tk()
self.assertIs(tkinter._default_root, root)
tkinter.NoDefaultRoot()
self.assertIs(tkinter._support_default_root, False)
self.assertFalse(hasattr(tkinter, '_default_root'))
# repeated call is no-op
tkinter.NoDefaultRoot()
self.assertIs(tkinter._support_default_root, False)
self.assertFalse(hasattr(tkinter, '_default_root'))
root.destroy()
self.assertIs(tkinter._support_default_root, False)
self.assertFalse(hasattr(tkinter, '_default_root'))
root = tkinter.Tk()
self.assertIs(tkinter._support_default_root, False)
self.assertFalse(hasattr(tkinter, '_default_root'))
root.destroy()
def test_getboolean(self):
self.assertRaises(RuntimeError, tkinter.getboolean, '1')
root = tkinter.Tk()
self.assertIs(tkinter.getboolean('1'), True)
self.assertRaises(ValueError, tkinter.getboolean, 'yea')
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, tkinter.getboolean, '1')
def test_mainloop(self):
self.assertRaises(RuntimeError, tkinter.mainloop)
root = tkinter.Tk()
root.after_idle(root.quit)
tkinter.mainloop()
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, tkinter.mainloop)
tests_gui = (MiscTest, DefaultRootTest)
if __name__ == "__main__": if __name__ == "__main__":
support.run_unittest(*tests_gui) support.run_unittest(*tests_gui)

View File

@ -0,0 +1,25 @@
import unittest
import tkinter
from test.support import requires, run_unittest, swap_attr
from tkinter.test.support import AbstractDefaultRootTest
from tkinter.simpledialog import Dialog, askinteger
requires('gui')
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_askinteger(self):
self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number")
root = tkinter.Tk()
with swap_attr(Dialog, 'wait_window', lambda self, w: w.destroy()):
askinteger("Go To Line", "Line number")
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number")
tests_gui = (DefaultRootTest,)
if __name__ == "__main__":
run_unittest(*tests_gui)

View File

@ -1,8 +1,10 @@
import unittest import unittest
import gc import gc
import tkinter
from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl, from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl,
TclError) TclError)
from test.support import ALWAYS_EQ from test.support import ALWAYS_EQ
from tkinter.test.support import AbstractDefaultRootTest
class Var(Variable): class Var(Variable):
@ -308,8 +310,21 @@ class TestBooleanVar(TestBase):
v.get() v.get()
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_variable(self):
self.assertRaises(RuntimeError, Variable)
root = tkinter.Tk()
v = Variable()
v.set("value")
self.assertEqual(v.get(), "value")
root.destroy()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, Variable)
tests_gui = (TestVariable, TestStringVar, TestIntVar, tests_gui = (TestVariable, TestStringVar, TestIntVar,
TestDoubleVar, TestBooleanVar) TestDoubleVar, TestBooleanVar, DefaultRootTest)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -5,7 +5,8 @@ import os
from test.support import requires from test.support import requires
from tkinter.test.support import (tcl_version, requires_tcl, from tkinter.test.support import (tcl_version, requires_tcl,
get_tk_patchlevel, widget_eq) get_tk_patchlevel, widget_eq,
AbstractDefaultRootTest)
from tkinter.test.widget_tests import ( from tkinter.test.widget_tests import (
add_standard_options, noconv, pixels_round, add_standard_options, noconv, pixels_round,
AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests,
@ -1295,12 +1296,21 @@ class MessageTest(AbstractWidgetTest, unittest.TestCase):
self.checkIntegerParam(widget, 'aspect', 250, 0, -300) self.checkIntegerParam(widget, 'aspect', 250, 0, -300)
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_frame(self):
self._test_widget(tkinter.Frame)
def test_label(self):
self._test_widget(tkinter.Label)
tests_gui = ( tests_gui = (
ButtonTest, CanvasTest, CheckbuttonTest, EntryTest, ButtonTest, CanvasTest, CheckbuttonTest, EntryTest,
FrameTest, LabelFrameTest,LabelTest, ListboxTest, FrameTest, LabelFrameTest,LabelTest, ListboxTest,
MenubuttonTest, MenuTest, MessageTest, OptionMenuTest, MenubuttonTest, MenuTest, MessageTest, OptionMenuTest,
PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest, PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest,
SpinboxTest, TextTest, ToplevelTest, SpinboxTest, TextTest, ToplevelTest, DefaultRootTest,
) )
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,8 +2,8 @@ import sys
import unittest import unittest
import tkinter import tkinter
from tkinter import ttk from tkinter import ttk
from test.support import requires, run_unittest, swap_attr from test.support import requires, run_unittest
from tkinter.test.support import AbstractTkTest, destroy_default_root from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest
requires('gui') requires('gui')
@ -46,20 +46,6 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase):
if hasattr(sys, 'last_type'): if hasattr(sys, 'last_type'):
self.assertNotEqual(sys.last_type, tkinter.TclError) self.assertNotEqual(sys.last_type, tkinter.TclError)
def test_initialization_no_master(self):
# no master passing
with swap_attr(tkinter, '_default_root', None), \
swap_attr(tkinter, '_support_default_root', True):
try:
x = ttk.LabeledScale()
self.assertIsNotNone(tkinter._default_root)
self.assertEqual(x.master, tkinter._default_root)
self.assertEqual(x.tk, tkinter._default_root.tk)
x.destroy()
finally:
destroy_default_root()
def test_initialization(self): def test_initialization(self):
# master passing # master passing
master = tkinter.Frame(self.root) master = tkinter.Frame(self.root)
@ -311,7 +297,13 @@ class OptionMenuTest(AbstractTkTest, unittest.TestCase):
optmenu2.destroy() optmenu2.destroy()
tests_gui = (LabeledScaleTest, OptionMenuTest) class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_labeledscale(self):
self._test_widget(ttk.LabeledScale)
tests_gui = (LabeledScaleTest, OptionMenuTest, DefaultRootTest)
if __name__ == "__main__": if __name__ == "__main__":
run_unittest(*tests_gui) run_unittest(*tests_gui)

View File

@ -6,7 +6,7 @@ import sys
from tkinter.test.test_ttk.test_functions import MockTclObj from tkinter.test.test_ttk.test_functions import MockTclObj
from tkinter.test.support import (AbstractTkTest, tcl_version, get_tk_patchlevel, from tkinter.test.support import (AbstractTkTest, tcl_version, get_tk_patchlevel,
simulate_mouse_click) simulate_mouse_click, AbstractDefaultRootTest)
from tkinter.test.widget_tests import (add_standard_options, noconv, from tkinter.test.widget_tests import (add_standard_options, noconv,
AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests,
setUpModule) setUpModule)
@ -1860,12 +1860,22 @@ class SizegripTest(AbstractWidgetTest, unittest.TestCase):
def create(self, **kwargs): def create(self, **kwargs):
return ttk.Sizegrip(self.root, **kwargs) return ttk.Sizegrip(self.root, **kwargs)
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
def test_frame(self):
self._test_widget(ttk.Frame)
def test_label(self):
self._test_widget(ttk.Label)
tests_gui = ( tests_gui = (
ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest, ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest,
FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, FrameTest, LabelFrameTest, LabelTest, MenubuttonTest,
NotebookTest, PanedWindowTest, ProgressbarTest, NotebookTest, PanedWindowTest, ProgressbarTest,
RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest,
SizegripTest, SpinboxTest, TreeviewTest, WidgetTest, SizegripTest, SpinboxTest, TreeviewTest, WidgetTest, DefaultRootTest,
) )
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -387,9 +387,7 @@ class TixWidget(tkinter.Widget):
# These are missing from Tkinter # These are missing from Tkinter
def image_create(self, imgtype, cnf={}, master=None, **kw): def image_create(self, imgtype, cnf={}, master=None, **kw):
if not master: if not master:
master = tkinter._default_root master = self
if not master:
raise RuntimeError('Too early to create image')
if kw and cnf: cnf = _cnfmerge((cnf, kw)) if kw and cnf: cnf = _cnfmerge((cnf, kw))
elif kw: cnf = kw elif kw: cnf = kw
options = () options = ()
@ -475,10 +473,7 @@ class DisplayStyle:
elif 'refwindow' in cnf: elif 'refwindow' in cnf:
master = cnf['refwindow'] master = cnf['refwindow']
else: else:
master = tkinter._default_root master = tkinter._get_default_root('create display style')
if not master:
raise RuntimeError("Too early to create display style: "
"no root window")
self.tk = master.tk self.tk = master.tk
self.stylename = self.tk.call('tixDisplayStyle', itemtype, self.stylename = self.tk.call('tixDisplayStyle', itemtype,
*self._options(cnf,kw) ) *self._options(cnf,kw) )

View File

@ -349,12 +349,7 @@ def setup_master(master=None):
If it is not allowed to use the default root and master is None, If it is not allowed to use the default root and master is None,
RuntimeError is raised.""" RuntimeError is raised."""
if master is None: if master is None:
if tkinter._support_default_root: master = tkinter._get_default_root()
master = tkinter._default_root or tkinter.Tk()
else:
raise RuntimeError(
"No master specified and tkinter is "
"configured to not support default root")
return master return master

View File

@ -0,0 +1,4 @@
:mod:`tkinter` functions and constructors which need a default root window
raise now :exc:`RuntimeError` with descriptive message instead of obscure
:exc:`AttributeError` or :exc:`NameError` if it is not created yet or cannot
be created automatically.

View File

@ -36,15 +36,11 @@ class PyncheWidget:
else: else:
# Is there already a default root for Tk, say because we're # Is there already a default root for Tk, say because we're
# running under Guido's IDE? :-) Two conditions say no, either the # running under Guido's IDE? :-) Two conditions say no, either the
# import fails or _default_root is None. # _default_root is None or it is unset.
tkroot = None tkroot = getattr(tkinter, '_default_root', None)
try:
from Tkinter import _default_root
tkroot = self.__tkroot = _default_root
except ImportError:
pass
if not tkroot: if not tkroot:
tkroot = self.__tkroot = Tk(className='Pynche') tkroot = Tk(className='Pynche')
self.__tkroot = tkroot
# but this isn't our top level widget, so make it invisible # but this isn't our top level widget, so make it invisible
tkroot.withdraw() tkroot.withdraw()
# create the menubar # create the menubar