From a2a46f3953cc322833909f37956f11e4fad2fea1 Mon Sep 17 00:00:00 2001 From: Alan D Moore Date: Wed, 17 Jan 2018 10:43:10 -0600 Subject: [PATCH 01/25] Add ttk::spinbox Added support for ttk::spinbox in Python ttk. --- Lib/tkinter/ttk.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index e6c90cef7bb125..b5607e3b95a84e 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -1151,6 +1151,24 @@ def __init__(self, master=None, **kw): Widget.__init__(self, master, "ttk::sizegrip", kw) +class Spinbox(Entry): + """Ttk Spinbox is an Entry with increment and decrement arrows, used + for number entry""" + + def __init__(self, master=None, **kw): + """Construct a Ttk Spinbox widget with the parent master. + + STANDARD OPTIONS + + class, cursor, style, takefocus + + WIDGET-SPECIFIC OPTIONS + + to, from_, increment + """ + Entry.__init__(self, master, "ttk::spinbox", **kw) + + class Treeview(Widget, tkinter.XView, tkinter.YView): """Ttk Treeview widget displays a hierarchical collection of items. From 505c77d5b5344f20fffb256c7b05e9b972aedb68 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Thu, 18 Jan 2018 13:13:02 -0600 Subject: [PATCH 02/25] Added NEWS.d/next entry --- .../NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst diff --git a/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst b/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst new file mode 100644 index 00000000000000..f5130fd5fe0bee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst @@ -0,0 +1 @@ +Add ttk::spinbox widget to to tkinter.ttk. Patch by Alan D Moore. From c5cbac72e57d39333623f6bec5eb96bbfc890587 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Thu, 18 Jan 2018 14:33:12 -0600 Subject: [PATCH 03/25] Removed double colon that was causing a warning/error during build. --- .../next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst b/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst index f5130fd5fe0bee..c504e8b1e53806 100644 --- a/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst +++ b/Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst @@ -1 +1 @@ -Add ttk::spinbox widget to to tkinter.ttk. Patch by Alan D Moore. +Add Ttk spinbox widget to to tkinter.ttk. Patch by Alan D Moore. From 7b8219e0c80e7154a0cd85b24267575e260b4829 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Fri, 19 Jan 2018 16:26:11 -0600 Subject: [PATCH 04/25] Add more arguments to ttk.Spinbox docstring --- Lib/tkinter/ttk.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index b5607e3b95a84e..9c6756acd42ddb 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -1160,11 +1160,12 @@ def __init__(self, master=None, **kw): STANDARD OPTIONS - class, cursor, style, takefocus + class, cursor, style, takefocus, validate, + validatecommand, xscrollcommand, invalidcommand WIDGET-SPECIFIC OPTIONS - to, from_, increment + to, from_, increment, values, wrap, format, command """ Entry.__init__(self, master, "ttk::spinbox", **kw) From 826f4e3fa8bbf7de557030a9ca93dee3a22d0597 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Fri, 19 Jan 2018 16:30:49 -0600 Subject: [PATCH 05/25] Make spinbox inherit Combobox rather than Entry This enables the functionality necessary when spinbox is passed a list of values rather than a range. --- Lib/tkinter/ttk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index 9c6756acd42ddb..27c2f2d45a25ed 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -1151,7 +1151,7 @@ def __init__(self, master=None, **kw): Widget.__init__(self, master, "ttk::sizegrip", kw) -class Spinbox(Entry): +class Spinbox(Combobox): """Ttk Spinbox is an Entry with increment and decrement arrows, used for number entry""" From 3228370e6a737ddf485db99ca75514880fd4d529 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Fri, 19 Jan 2018 16:35:20 -0600 Subject: [PATCH 06/25] Expand on usage information in Spinbox docstring --- Lib/tkinter/ttk.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index 27c2f2d45a25ed..3ed36c52b16f16 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -1152,8 +1152,11 @@ def __init__(self, master=None, **kw): class Spinbox(Combobox): - """Ttk Spinbox is an Entry with increment and decrement arrows, used - for number entry""" + """Ttk Spinbox is an Entry with increment and decrement arrows + + It is commonly used for number entry or to select from a list of + string values. + """ def __init__(self, master=None, **kw): """Construct a Ttk Spinbox widget with the parent master. From af4e1a195a6dca9dfb20099759f9a2027d0d0eff Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sat, 20 Jan 2018 08:37:14 -0600 Subject: [PATCH 07/25] Revert Spinbox to Entry baseclass, add set/current methods --- Lib/tkinter/ttk.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index 3ed36c52b16f16..82bf6604ab3031 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -19,7 +19,7 @@ __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label", "Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow", "PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar", - "Separator", "Sizegrip", "Style", "Treeview", + "Separator", "Sizegrip", "Style", "Treeview", "Spinbox", # Extensions "LabeledScale", "OptionMenu", # functions @@ -1151,7 +1151,7 @@ def __init__(self, master=None, **kw): Widget.__init__(self, master, "ttk::sizegrip", kw) -class Spinbox(Combobox): +class Spinbox(Entry): """Ttk Spinbox is an Entry with increment and decrement arrows It is commonly used for number entry or to select from a list of @@ -1172,6 +1172,20 @@ def __init__(self, master=None, **kw): """ Entry.__init__(self, master, "ttk::spinbox", **kw) + def current(self, newindex=None): + """If newindex is supplied, sets the Spinbox value to the + element at position newindex in the list of values. Otherwise, + returns the index of the current value in the list of values + or -1 if the current value does not appear in the list.""" + if newindex is None: + return self.tk.getint(self.tk.call(self._w, "current")) + return self.tk.call(self._w, "current", newindex) + + + def set(self, value): + """Sets the value of the Spinbox to value.""" + self.tk.call(self._w, "set", value) + class Treeview(Widget, tkinter.XView, tkinter.YView): """Ttk Treeview widget displays a hierarchical collection of items. From 16a5b98559d394e76f9544e3d2d2fcfac19f4ece Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sat, 20 Jan 2018 19:11:37 -0600 Subject: [PATCH 08/25] Keep __all__ alphabetzied --- Lib/tkinter/ttk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index 82bf6604ab3031..87e87426ecc463 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -19,7 +19,7 @@ __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label", "Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow", "PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar", - "Separator", "Sizegrip", "Style", "Treeview", "Spinbox", + "Separator", "Sizegrip", "Spinbox", "Style", "Treeview", # Extensions "LabeledScale", "OptionMenu", # functions From a56a970a089953ec3c2ad4b24c6033212199056a Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sat, 20 Jan 2018 19:58:22 -0600 Subject: [PATCH 09/25] Test class for ttk.Spinbox Currently crashing due to "current" not being a valid ttk::spinbox command, contrary to Ttk documentation. --- Lib/tkinter/test/test_ttk/test_widgets.py | 192 ++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index ab0db2878e13df..c795e6b464cb1f 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1105,6 +1105,198 @@ def test_traversal(self): self.nb.event_generate('') self.assertEqual(self.nb.select(), str(self.child1)) +@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class SpinboxTest(EntryTest, unittest.TestCase): + OPTIONS = ( + 'background', 'class', 'command', 'cursor', 'format', + 'from', 'increment', 'invalidcommand', 'justify', + 'show', 'state', 'style', 'takefocus', 'textvariable', + 'to', 'validate', 'validatecommand', 'values', + 'width', 'wrap', 'xscrollcommand', + ) + + def setUp(self): + super().setUp() + self.spin = self.create() + self.spin.pack() + + def create(self, **kwargs): + return ttk.Spinbox(self.root, **kwargs) + + def _click_increment_arrow(self): + width = self.spin.winfo_width() + self.spin.event_generate('', x=width - 5, y=5) + self.spin.event_generate('', x=width - 5, y=5) + self.spin.update_idletasks() + + def _click_decrement_arrow(self): + width = self.spin.winfo_width() + self.spin.event_generate('', x=width - 5, y=15) + self.spin.event_generate('', x=width - 5, y=15) + self.spin.update_idletasks() + + def test_command(self): + success = [] + + self.spin['command'] = lambda: success.append(True) + self.spin.pack() + self.spin.wait_visibility() + self._click_increment_arrow() + self.assertTrue(success) + + self._click_decrement_arrow() + self.assertEqual(len(success), 2) + + # testing postcommand removal + self.spin['command'] = '' + self._click_increment_arrow() + self._click_decrement_arrow() + self.assertEqual(len(success), 2) + + def test_to(self): + self.spin['from'] = 0 + self.spin['to'] = 5 + self.spin.set(4) + self.spin.update() + self._click_increment_arrow() # 5 + + self.assertEqual(self.spin.get(), '5') + + self._click_increment_arrow() # 5 + self.assertEqual(self.spin.get(), '5') + + def test_from(self): + self.spin['from'] = 1 + self.spin['to'] = 10 + self.spin.set(2) + self.spin.update() + self._click_decrement_arrow() # 1 + self.assertEqual(self.spin.get(), '1') + self._click_decrement_arrow() # 1 + self.assertEqual(self.spin.get(), '1') + + def test_increment(self): + self.spin['from'] = 0 + self.spin['to'] = 10 + self.spin['increment'] = 4 + self.spin.set(1) + self.spin.update() + + self._click_increment_arrow() # 5 + self.assertEqual(self.spin.get(), '5') + self.spin['increment'] = 2 + self.spin.update() + self._click_decrement_arrow() # 3 + self.assertEqual(self.spin.get(), '3') + + def test_format(self): + self.spin.set(1) + self.spin['format'] = '%10.3f' + self.spin.update() + self._click_increment_arrow() + value = self.spin.get() + + self.assertEqual(len(value), 10) + self.assertEqual(value.index('.'), 6) + + self.spin['format'] = '' + self.spin.update() + self._click_increment_arrow() + value = self.spin.get() + self.assertTrue('.' not in value) + self.assertEqual(len(value), 1) + + def test_wrap(self): + self.spin['to'] = 10 + self.spin['from'] = 1 + self.spin.set(1) + self.spin['wrap'] = True + self.spin.update() + + self._click_decrement_arrow() + self.assertEqual(self.spin.get(), '10') + + self._click_increment_arrow() + self.assertEqual(self.spin.get(), '1') + + self.spin['wrap'] = False + self.spin.update() + + self._click_decrement_arrow() + self.assertEqual(self.spin.get(), '1') + + def test_values(self): + + + def check_get_current(getval, currval): + self.assertEqual(self.spin.get(), getval) + self.assertEqual(self.spin.current(), currval) + + self.assertEqual(self.spin['values'], + () if tcl_version < (8, 5) else '') + check_get_current('', -1) + + self.checkParam(self.spin, 'values', 'mon tue wed thur', + expected=('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.spin, 'values', ('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.spin, 'values', (42, 3.14, '', 'any string')) + self.checkParam(self.spin, 'values', '', + expected='' if get_tk_patchlevel() < (8, 5, 10) else ()) + + self.spin['values'] = ['a', 1, 'c'] + + self.spin.set('c') + check_get_current('c', 2) + + self.spin.current(0) + check_get_current('a', 0) + + self.spin.set('d') + check_get_current('d', -1) + + # testing values with empty string + self.spin.set('') + self.spin['values'] = (1, 2, '', 3) + check_get_current('', 2) + + # testing values with empty string set through configure + self.spin.configure(values=[1, '', 2]) + self.assertEqual(self.spin['values'], + ('1', '', '2') if self.wantobjects else + '1 {} 2') + + # testing values with spaces + self.spin['values'] = ['a b', 'a\tb', 'a\nb'] + self.assertEqual(self.spin['values'], + ('a b', 'a\tb', 'a\nb') if self.wantobjects else + '{a b} {a\tb} {a\nb}') + + # testing values with special characters + self.spin['values'] = [r'a\tb', '"a"', '} {'] + self.assertEqual(self.spin['values'], + (r'a\tb', '"a"', '} {') if self.wantobjects else + r'a\\tb {"a"} \}\ \{') + + # testing increment and decrement + self.spin.set('') + self.spin['values'] = ['a', 'b', 'c'] + self._click_increment_arrow() + self._click_increment_arrow() + check_get_current('b', 1) + self._click_decrement_arrow() + check_get_current('a', 0) + + # out of range + self.assertRaises(tkinter.TclError, self.spin.current, + len(self.spin['values'])) + # it expects an integer (or something that can be converted to int) + self.assertRaises(tkinter.TclError, self.spin.current, '') + + # testing creating spinbox with empty string in values + spin2 = ttk.Spinbox(self.root, values=[1, 2, '']) + self.assertEqual(spin2['values'], + ('1', '2', '') if self.wantobjects else '1 2 {}') + spin2.destroy() @add_standard_options(StandardTtkOptionsTests) class TreeviewTest(AbstractWidgetTest, unittest.TestCase): From 1a3b7c1ac73a7bdecd50d74578386acfeb395167 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sat, 20 Jan 2018 22:43:25 -0600 Subject: [PATCH 10/25] Documentation for ttk.Spinbox --- Doc/library/tkinter.ttk.rst | 258 ++++++++++++++++++++++++------------ 1 file changed, 172 insertions(+), 86 deletions(-) diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 927f7f3c6c1b71..55514926a480cb 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -562,7 +562,7 @@ ttk.Notebook * :kbd:`Control-Tab`: selects the tab following the currently selected one. * :kbd:`Shift-Control-Tab`: selects the tab preceding the currently selected one. * :kbd:`Alt-K`: where *K* is the mnemonic (underlined) character of any tab, will - select that tab. + select that tab. Multiple notebooks in a single toplevel may be enabled for traversal, including nested notebooks. However, notebook traversal only works @@ -692,6 +692,92 @@ Bugs * This widget supports only "southeast" resizing. +Spinbox +------- +The :class:`ttk.Spinbox` widget is a :class:`ttk.Entry` enhanced with increment +and decrement arrows. It can be used for numbers or lists of string values. +This widget is a subclass of :class:`Entry`. + +Besides the methods inherited from :class:`Widget`: :meth:`Widget.cget`, +:meth:`Widget.configure`, :meth:`Widget.identify`, :meth:`Widget.instate` +and :meth:`Widget.state`, and the following inherited from :class:`Entry`: +:meth:`Entry.bbox`, :meth:`Entry.delete`, :meth:`Entry.icursor`, +:meth:`Entry.index`, :meth:`Entry.insert`, :meth:`Entry.xview`, +it has some other methods, described at :class:`ttk.Spinbox`. + +Options +^^^^^^^ + +This widget accepts the following specific options: + + .. tabularcolumns:: |l|L| + ++----------------------+------------------------------------------------------+ +| Option | Description | ++======================+======================================================+ +| from | Float value. If set, this is the minimum value to | +| | which the decrement button will decrement. Must be | +| | spelled as `from_` when used as an argument, since | +| | `from` is a Python keyword. | ++----------------------+------------------------------------------------------+ +| to | Float value. If set, this is the maximum value to | +| | which the increment button will increment. | ++----------------------+------------------------------------------------------+ +| increment | Float value. Specifies the amount which the | +| | increment/decrement buttons change the | +| | value. Defaults to 1.0. | ++----------------------+------------------------------------------------------+ +| values | Sequence of string or float values. If specified, | +| | the increment/decrement buttons will cycle through | +| | the items in this sequence rather than incrementing | +| | or decrementing numbers. | +| | | ++----------------------+------------------------------------------------------+ +| wrap | Boolean value. If `True`, increment and decrement | +| | buttons will cycle from the `to` value to the `from` | +| | value or the `from` value to the `to` value, | +| | respectively. | ++----------------------+------------------------------------------------------+ +| format | String value. This specifies the format of numbers | +| | set by the increment/decrement buttons. It must be | +| | in the form "%W.Pf", where W is the padded width of | +| | the value, P is the precision, and '%' and 'f' are | +| | literal. | ++----------------------+------------------------------------------------------+ +| command | Python callable. Will be called with no arguments | +| | whenever either of the increment or decrement buttons| +| |are pressed. | +| | | ++----------------------+------------------------------------------------------+ + + +Virtual events +^^^^^^^^^^^^^^ + +The spinbox widget generates a **<>** virtual event when the user presses , and a **<>** virtual event when the user presses . + +ttk.Spinbox +^^^^^^^^^^^^ + +.. class:: Spinbox + + .. method:: current(newindex=None) + + If *newindex* is specified, sets the spinbox value to the element + position *newindex*. Otherwise, returns the index of the current value or + -1 if the current value is not in the values list. + + + .. method:: get() + + Returns the current value of the spinbox. + + + .. method:: set(value) + + Sets the value of the spinbox to *value*. + + Treeview -------- @@ -908,19 +994,19 @@ ttk.Treeview The valid options/values are: * id - Returns the column name. This is a read-only option. + Returns the column name. This is a read-only option. * anchor: One of the standard Tk anchor values. - Specifies how the text in this column should be aligned with respect - to the cell. + Specifies how the text in this column should be aligned with respect + to the cell. * minwidth: width - The minimum width of the column in pixels. The treeview widget will - not make the column any smaller than specified by this option when - the widget is resized or the user drags a column. + The minimum width of the column in pixels. The treeview widget will + not make the column any smaller than specified by this option when + the widget is resized or the user drags a column. * stretch: True/False - Specifies whether the column's width should be adjusted when - the widget is resized. + Specifies whether the column's width should be adjusted when + the widget is resized. * width: width - The width of the column in pixels. + The width of the column in pixels. To configure the tree column, call this with column = "#0" @@ -963,14 +1049,14 @@ ttk.Treeview The valid options/values are: * text: text - The text to display in the column heading. + The text to display in the column heading. * image: imageName - Specifies an image to display to the right of the column heading. + Specifies an image to display to the right of the column heading. * anchor: anchor - Specifies how the heading text should be aligned. One of the standard - Tk anchor values. + Specifies how the heading text should be aligned. One of the standard + Tk anchor values. * command: callback - A callback to be invoked when the heading label is pressed. + A callback to be invoked when the heading label is pressed. To configure the tree column heading, call this with column = "#0". @@ -1100,8 +1186,8 @@ ttk.Treeview act according to the following selection methods. .. deprecated-removed:: 3.6 3.8 - Using ``selection()`` for changing the selection state is deprecated. - Use the following selection methods instead. + Using ``selection()`` for changing the selection state is deprecated. + Use the following selection methods instead. .. method:: selection_set(*items) @@ -1109,7 +1195,7 @@ ttk.Treeview *items* becomes the new selection. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: selection_add(*items) @@ -1117,7 +1203,7 @@ ttk.Treeview Add *items* to the selection. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: selection_remove(*items) @@ -1125,7 +1211,7 @@ ttk.Treeview Remove *items* from the selection. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: selection_toggle(*items) @@ -1133,7 +1219,7 @@ ttk.Treeview Toggle the selection state of each item in *items*. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: set(item, column=None, value=None) @@ -1213,18 +1299,18 @@ option. If you don't know the class name of a widget, use the method For example, to change every default button to be a flat button with some padding and a different background color:: - from tkinter import ttk - import tkinter + from tkinter import ttk + import tkinter - root = tkinter.Tk() + root = tkinter.Tk() - ttk.Style().configure("TButton", padding=6, relief="flat", - background="#ccc") + ttk.Style().configure("TButton", padding=6, relief="flat", + background="#ccc") - btn = ttk.Button(text="Sample") - btn.pack() + btn = ttk.Button(text="Sample") + btn.pack() - root.mainloop() + root.mainloop() .. method:: map(style, query_opt=None, **kw) @@ -1238,20 +1324,20 @@ option. If you don't know the class name of a widget, use the method An example may make it more understandable:: - import tkinter - from tkinter import ttk + import tkinter + from tkinter import ttk - root = tkinter.Tk() + root = tkinter.Tk() - style = ttk.Style() - style.map("C.TButton", - foreground=[('pressed', 'red'), ('active', 'blue')], - background=[('pressed', '!disabled', 'black'), ('active', 'white')] - ) + style = ttk.Style() + style.map("C.TButton", + foreground=[('pressed', 'red'), ('active', 'blue')], + background=[('pressed', '!disabled', 'black'), ('active', 'white')] + ) - colored_btn = ttk.Button(text="Test", style="C.TButton").pack() + colored_btn = ttk.Button(text="Test", style="C.TButton").pack() - root.mainloop() + root.mainloop() Note that the order of the (states, value) sequences for an option does @@ -1270,9 +1356,9 @@ option. If you don't know the class name of a widget, use the method To check what font a Button uses by default:: - from tkinter import ttk + from tkinter import ttk - print(ttk.Style().lookup("TButton", "font")) + print(ttk.Style().lookup("TButton", "font")) .. method:: layout(style, layoutspec=None) @@ -1288,26 +1374,26 @@ option. If you don't know the class name of a widget, use the method To understand the format, see the following example (it is not intended to do anything useful):: - from tkinter import ttk - import tkinter + from tkinter import ttk + import tkinter - root = tkinter.Tk() + root = tkinter.Tk() - style = ttk.Style() - style.layout("TMenubutton", [ - ("Menubutton.background", None), - ("Menubutton.button", {"children": - [("Menubutton.focus", {"children": - [("Menubutton.padding", {"children": - [("Menubutton.label", {"side": "left", "expand": 1})] - })] - })] - }), - ]) + style = ttk.Style() + style.layout("TMenubutton", [ + ("Menubutton.background", None), + ("Menubutton.button", {"children": + [("Menubutton.focus", {"children": + [("Menubutton.padding", {"children": + [("Menubutton.label", {"side": "left", "expand": 1})] + })] + })] + }), + ]) - mbtn = ttk.Menubutton(text='Text') - mbtn.pack() - root.mainloop() + mbtn = ttk.Menubutton(text='Text') + mbtn.pack() + root.mainloop() .. method:: element_create(elementname, etype, *args, **kw) @@ -1321,24 +1407,24 @@ option. If you don't know the class name of a widget, use the method following options: * border=padding - padding is a list of up to four integers, specifying the left, top, - right, and bottom borders, respectively. + padding is a list of up to four integers, specifying the left, top, + right, and bottom borders, respectively. * height=height - Specifies a minimum height for the element. If less than zero, the - base image's height is used as a default. + Specifies a minimum height for the element. If less than zero, the + base image's height is used as a default. * padding=padding - Specifies the element's interior padding. Defaults to border's value - if not specified. + Specifies the element's interior padding. Defaults to border's value + if not specified. * sticky=spec - Specifies how the image is placed within the final parcel. spec - contains zero or more characters "n", "s", "w", or "e". + Specifies how the image is placed within the final parcel. spec + contains zero or more characters "n", "s", "w", or "e". * width=width - Specifies a minimum width for the element. If less than zero, the - base image's width is used as a default. + Specifies a minimum width for the element. If less than zero, the + base image's width is used as a default. If "from" is used as the value of *etype*, :meth:`element_create` will clone an existing @@ -1381,28 +1467,28 @@ option. If you don't know the class name of a widget, use the method As an example, let's change the Combobox for the default theme a bit:: - from tkinter import ttk - import tkinter + from tkinter import ttk + import tkinter - root = tkinter.Tk() + root = tkinter.Tk() - style = ttk.Style() - style.theme_settings("default", { - "TCombobox": { - "configure": {"padding": 5}, - "map": { - "background": [("active", "green2"), - ("!disabled", "green4")], - "fieldbackground": [("!disabled", "green3")], - "foreground": [("focus", "OliveDrab1"), - ("!disabled", "OliveDrab2")] - } - } - }) + style = ttk.Style() + style.theme_settings("default", { + "TCombobox": { + "configure": {"padding": 5}, + "map": { + "background": [("active", "green2"), + ("!disabled", "green4")], + "fieldbackground": [("!disabled", "green3")], + "foreground": [("focus", "OliveDrab1"), + ("!disabled", "OliveDrab2")] + } + } + }) - combo = ttk.Combobox().pack() + combo = ttk.Combobox().pack() - root.mainloop() + root.mainloop() .. method:: theme_names() From a933457e2cf7d6bbd6ea0a0f86dfc6429b7ef03c Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 13:52:59 -0600 Subject: [PATCH 11/25] Remove nonexistent "current" method Removed from code, docs, and tests. This method is documented in Ttk docs, but does not actually exist. --- Doc/library/tkinter.ttk.rst | 7 ---- Lib/tkinter/test/test_ttk/test_widgets.py | 40 +++++------------------ Lib/tkinter/ttk.py | 9 ----- 3 files changed, 8 insertions(+), 48 deletions(-) diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 55514926a480cb..54bccccc8c7ca7 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -761,13 +761,6 @@ ttk.Spinbox .. class:: Spinbox - .. method:: current(newindex=None) - - If *newindex* is specified, sets the spinbox value to the element - position *newindex*. Otherwise, returns the index of the current value or - -1 if the current value is not in the values list. - - .. method:: get() Returns the current value of the spinbox. diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index c795e6b464cb1f..6ae54ea3f4fe9d 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1227,15 +1227,8 @@ def test_wrap(self): def test_values(self): - - def check_get_current(getval, currval): - self.assertEqual(self.spin.get(), getval) - self.assertEqual(self.spin.current(), currval) - self.assertEqual(self.spin['values'], () if tcl_version < (8, 5) else '') - check_get_current('', -1) - self.checkParam(self.spin, 'values', 'mon tue wed thur', expected=('mon', 'tue', 'wed', 'thur')) self.checkParam(self.spin, 'values', ('mon', 'tue', 'wed', 'thur')) @@ -1245,19 +1238,15 @@ def check_get_current(getval, currval): self.spin['values'] = ['a', 1, 'c'] - self.spin.set('c') - check_get_current('c', 2) - - self.spin.current(0) - check_get_current('a', 0) + # test incrementing / decrementing values + self.spin.set('a') + self.spin.update() + self._click_increment_arrow() + self.assertEqual(self.spin.get(), '1') - self.spin.set('d') - check_get_current('d', -1) + self._click_decrement_arrow() + self.assertEqual(self.spin.get(), 'a') - # testing values with empty string - self.spin.set('') - self.spin['values'] = (1, 2, '', 3) - check_get_current('', 2) # testing values with empty string set through configure self.spin.configure(values=[1, '', 2]) @@ -1277,20 +1266,6 @@ def check_get_current(getval, currval): (r'a\tb', '"a"', '} {') if self.wantobjects else r'a\\tb {"a"} \}\ \{') - # testing increment and decrement - self.spin.set('') - self.spin['values'] = ['a', 'b', 'c'] - self._click_increment_arrow() - self._click_increment_arrow() - check_get_current('b', 1) - self._click_decrement_arrow() - check_get_current('a', 0) - - # out of range - self.assertRaises(tkinter.TclError, self.spin.current, - len(self.spin['values'])) - # it expects an integer (or something that can be converted to int) - self.assertRaises(tkinter.TclError, self.spin.current, '') # testing creating spinbox with empty string in values spin2 = ttk.Spinbox(self.root, values=[1, 2, '']) @@ -1298,6 +1273,7 @@ def check_get_current(getval, currval): ('1', '2', '') if self.wantobjects else '1 2 {}') spin2.destroy() + @add_standard_options(StandardTtkOptionsTests) class TreeviewTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index 87e87426ecc463..c1651159b7030d 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -1172,15 +1172,6 @@ def __init__(self, master=None, **kw): """ Entry.__init__(self, master, "ttk::spinbox", **kw) - def current(self, newindex=None): - """If newindex is supplied, sets the Spinbox value to the - element at position newindex in the list of values. Otherwise, - returns the index of the current value in the list of values - or -1 if the current value does not appear in the list.""" - if newindex is None: - return self.tk.getint(self.tk.call(self._w, "current")) - return self.tk.call(self._w, "current", newindex) - def set(self, value): """Sets the value of the Spinbox to value.""" From 6d4f9b58bc31a06ed2968dcde4f76a01c91cae66 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 14:13:53 -0600 Subject: [PATCH 12/25] Update spinbox-related documentation - Include spinbox in list of widgets - Move spinbox to just after Combobox - Fix some long lines --- Doc/library/tkinter.ttk.rst | 170 ++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 84 deletions(-) diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 54bccccc8c7ca7..819be20a4b9e0c 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -66,13 +66,13 @@ for improved styling effects. Ttk Widgets ----------- -Ttk comes with 17 widgets, eleven of which already existed in tkinter: +Ttk comes with 18 widgets, twelve of which already existed in tkinter: :class:`Button`, :class:`Checkbutton`, :class:`Entry`, :class:`Frame`, :class:`Label`, :class:`LabelFrame`, :class:`Menubutton`, :class:`PanedWindow`, -:class:`Radiobutton`, :class:`Scale` and :class:`Scrollbar`. The other six are -new: :class:`Combobox`, :class:`Notebook`, :class:`Progressbar`, -:class:`Separator`, :class:`Sizegrip` and :class:`Treeview`. And all them are -subclasses of :class:`Widget`. +:class:`Radiobutton`, :class:`Scale`, :class:`Scrollbar`, and :class:`Spinbox`. +The other six are new: :class:`Combobox`, :class:`Notebook`, +:class:`Progressbar`, :class:`Separator`, :class:`Sizegrip` and +:class:`Treeview`. And all them are subclasses of :class:`Widget`. Using the Ttk widgets gives the application an improved look and feel. As discussed above, there are differences in how the styling is coded. @@ -381,6 +381,87 @@ ttk.Combobox Sets the value of the combobox to *value*. +Spinbox +------- +The :class:`ttk.Spinbox` widget is a :class:`ttk.Entry` enhanced with increment +and decrement arrows. It can be used for numbers or lists of string values. +This widget is a subclass of :class:`Entry`. + +Besides the methods inherited from :class:`Widget`: :meth:`Widget.cget`, +:meth:`Widget.configure`, :meth:`Widget.identify`, :meth:`Widget.instate` +and :meth:`Widget.state`, and the following inherited from :class:`Entry`: +:meth:`Entry.bbox`, :meth:`Entry.delete`, :meth:`Entry.icursor`, +:meth:`Entry.index`, :meth:`Entry.insert`, :meth:`Entry.xview`, +it has some other methods, described at :class:`ttk.Spinbox`. + +Options +^^^^^^^ + +This widget accepts the following specific options: + + .. tabularcolumns:: |l|L| + ++----------------------+------------------------------------------------------+ +| Option | Description | ++======================+======================================================+ +| from | Float value. If set, this is the minimum value to | +| | which the decrement button will decrement. Must be | +| | spelled as `from_` when used as an argument, since | +| | `from` is a Python keyword. | ++----------------------+------------------------------------------------------+ +| to | Float value. If set, this is the maximum value to | +| | which the increment button will increment. | ++----------------------+------------------------------------------------------+ +| increment | Float value. Specifies the amount which the | +| | increment/decrement buttons change the | +| | value. Defaults to 1.0. | ++----------------------+------------------------------------------------------+ +| values | Sequence of string or float values. If specified, | +| | the increment/decrement buttons will cycle through | +| | the items in this sequence rather than incrementing | +| | or decrementing numbers. | +| | | ++----------------------+------------------------------------------------------+ +| wrap | Boolean value. If `True`, increment and decrement | +| | buttons will cycle from the `to` value to the `from` | +| | value or the `from` value to the `to` value, | +| | respectively. | ++----------------------+------------------------------------------------------+ +| format | String value. This specifies the format of numbers | +| | set by the increment/decrement buttons. It must be | +| | in the form "%W.Pf", where W is the padded width of | +| | the value, P is the precision, and '%' and 'f' are | +| | literal. | ++----------------------+------------------------------------------------------+ +| command | Python callable. Will be called with no arguments | +| | whenever either of the increment or decrement buttons| +| |are pressed. | +| | | ++----------------------+------------------------------------------------------+ + + +Virtual events +^^^^^^^^^^^^^^ + +The spinbox widget generates an **<>** virtual event when the +user presses , and a **<>** virtual event when the user +presses . + +ttk.Spinbox +^^^^^^^^^^^^ + +.. class:: Spinbox + + .. method:: get() + + Returns the current value of the spinbox. + + + .. method:: set(value) + + Sets the value of the spinbox to *value*. + + Notebook -------- @@ -692,85 +773,6 @@ Bugs * This widget supports only "southeast" resizing. -Spinbox -------- -The :class:`ttk.Spinbox` widget is a :class:`ttk.Entry` enhanced with increment -and decrement arrows. It can be used for numbers or lists of string values. -This widget is a subclass of :class:`Entry`. - -Besides the methods inherited from :class:`Widget`: :meth:`Widget.cget`, -:meth:`Widget.configure`, :meth:`Widget.identify`, :meth:`Widget.instate` -and :meth:`Widget.state`, and the following inherited from :class:`Entry`: -:meth:`Entry.bbox`, :meth:`Entry.delete`, :meth:`Entry.icursor`, -:meth:`Entry.index`, :meth:`Entry.insert`, :meth:`Entry.xview`, -it has some other methods, described at :class:`ttk.Spinbox`. - -Options -^^^^^^^ - -This widget accepts the following specific options: - - .. tabularcolumns:: |l|L| - -+----------------------+------------------------------------------------------+ -| Option | Description | -+======================+======================================================+ -| from | Float value. If set, this is the minimum value to | -| | which the decrement button will decrement. Must be | -| | spelled as `from_` when used as an argument, since | -| | `from` is a Python keyword. | -+----------------------+------------------------------------------------------+ -| to | Float value. If set, this is the maximum value to | -| | which the increment button will increment. | -+----------------------+------------------------------------------------------+ -| increment | Float value. Specifies the amount which the | -| | increment/decrement buttons change the | -| | value. Defaults to 1.0. | -+----------------------+------------------------------------------------------+ -| values | Sequence of string or float values. If specified, | -| | the increment/decrement buttons will cycle through | -| | the items in this sequence rather than incrementing | -| | or decrementing numbers. | -| | | -+----------------------+------------------------------------------------------+ -| wrap | Boolean value. If `True`, increment and decrement | -| | buttons will cycle from the `to` value to the `from` | -| | value or the `from` value to the `to` value, | -| | respectively. | -+----------------------+------------------------------------------------------+ -| format | String value. This specifies the format of numbers | -| | set by the increment/decrement buttons. It must be | -| | in the form "%W.Pf", where W is the padded width of | -| | the value, P is the precision, and '%' and 'f' are | -| | literal. | -+----------------------+------------------------------------------------------+ -| command | Python callable. Will be called with no arguments | -| | whenever either of the increment or decrement buttons| -| |are pressed. | -| | | -+----------------------+------------------------------------------------------+ - - -Virtual events -^^^^^^^^^^^^^^ - -The spinbox widget generates a **<>** virtual event when the user presses , and a **<>** virtual event when the user presses . - -ttk.Spinbox -^^^^^^^^^^^^ - -.. class:: Spinbox - - .. method:: get() - - Returns the current value of the spinbox. - - - .. method:: set(value) - - Sets the value of the spinbox to *value*. - - Treeview -------- From aa37596778ac6e419a87b6b9572dff482e7b1eaa Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 14:15:14 -0600 Subject: [PATCH 13/25] Remove some blank lines and reformat some long ones --- Lib/tkinter/test/test_ttk/test_widgets.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 6ae54ea3f4fe9d..7b5132b1dbf16a 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1226,15 +1226,18 @@ def test_wrap(self): self.assertEqual(self.spin.get(), '1') def test_values(self): - self.assertEqual(self.spin['values'], () if tcl_version < (8, 5) else '') self.checkParam(self.spin, 'values', 'mon tue wed thur', expected=('mon', 'tue', 'wed', 'thur')) self.checkParam(self.spin, 'values', ('mon', 'tue', 'wed', 'thur')) self.checkParam(self.spin, 'values', (42, 3.14, '', 'any string')) - self.checkParam(self.spin, 'values', '', - expected='' if get_tk_patchlevel() < (8, 5, 10) else ()) + self.checkParam( + self.spin, + 'values', + '', + expected='' if get_tk_patchlevel() < (8, 5, 10) else () + ) self.spin['values'] = ['a', 1, 'c'] @@ -1247,7 +1250,6 @@ def test_values(self): self._click_decrement_arrow() self.assertEqual(self.spin.get(), 'a') - # testing values with empty string set through configure self.spin.configure(values=[1, '', 2]) self.assertEqual(self.spin['values'], @@ -1266,7 +1268,6 @@ def test_values(self): (r'a\tb', '"a"', '} {') if self.wantobjects else r'a\\tb {"a"} \}\ \{') - # testing creating spinbox with empty string in values spin2 = ttk.Spinbox(self.root, values=[1, 2, '']) self.assertEqual(spin2['values'], From 02cb17b417ab1bb1917b7c8faac6e449b3d8e55b Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 14:29:52 -0600 Subject: [PATCH 14/25] Add patch author's name to ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 009b072d680aac..caa8e110240b8f 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1070,6 +1070,7 @@ The Dragon De Monsyne Bastien Montagne Skip Montanaro Peter Moody +Alan D. Moore Paul Moore Ross Moore Ben Morgan From 032b44fb92267ebe4bab7f8131b0dced7271acb4 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 14:33:26 -0600 Subject: [PATCH 15/25] Added tkinter.ttk.Spinbox addition. --- Doc/whatsnew/3.7.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 009df38d8ece19..36bd3a4f514353 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -510,6 +510,11 @@ sys Added :attr:`sys.flags.dev_mode` flag for the new development mode. +tkinter +------- + +Added :class:`tkinter.ttk.Spinbox`. + time ---- @@ -854,7 +859,7 @@ Changes in Python behavior f(1 for x in [1],) class C(1 for x in [1]): - pass + pass Python 3.7 now correctly raises a :exc:`SyntaxError`, as a generator expression always needs to be directly inside a set of parentheses From eecd3d49c1625dc96a5cedae686cc8469f9cb059 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 14:43:10 -0600 Subject: [PATCH 16/25] Remove tabs inserted by accident --- Doc/library/tkinter.ttk.rst | 172 ++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 819be20a4b9e0c..f9da61e082deb8 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -643,7 +643,7 @@ ttk.Notebook * :kbd:`Control-Tab`: selects the tab following the currently selected one. * :kbd:`Shift-Control-Tab`: selects the tab preceding the currently selected one. * :kbd:`Alt-K`: where *K* is the mnemonic (underlined) character of any tab, will - select that tab. + select that tab. Multiple notebooks in a single toplevel may be enabled for traversal, including nested notebooks. However, notebook traversal only works @@ -989,19 +989,19 @@ ttk.Treeview The valid options/values are: * id - Returns the column name. This is a read-only option. + Returns the column name. This is a read-only option. * anchor: One of the standard Tk anchor values. - Specifies how the text in this column should be aligned with respect - to the cell. + Specifies how the text in this column should be aligned with respect + to the cell. * minwidth: width - The minimum width of the column in pixels. The treeview widget will - not make the column any smaller than specified by this option when - the widget is resized or the user drags a column. + The minimum width of the column in pixels. The treeview widget will + not make the column any smaller than specified by this option when + the widget is resized or the user drags a column. * stretch: True/False - Specifies whether the column's width should be adjusted when - the widget is resized. + Specifies whether the column's width should be adjusted when + the widget is resized. * width: width - The width of the column in pixels. + The width of the column in pixels. To configure the tree column, call this with column = "#0" @@ -1044,14 +1044,14 @@ ttk.Treeview The valid options/values are: * text: text - The text to display in the column heading. + The text to display in the column heading. * image: imageName - Specifies an image to display to the right of the column heading. + Specifies an image to display to the right of the column heading. * anchor: anchor - Specifies how the heading text should be aligned. One of the standard - Tk anchor values. + Specifies how the heading text should be aligned. One of the standard + Tk anchor values. * command: callback - A callback to be invoked when the heading label is pressed. + A callback to be invoked when the heading label is pressed. To configure the tree column heading, call this with column = "#0". @@ -1181,8 +1181,8 @@ ttk.Treeview act according to the following selection methods. .. deprecated-removed:: 3.6 3.8 - Using ``selection()`` for changing the selection state is deprecated. - Use the following selection methods instead. + Using ``selection()`` for changing the selection state is deprecated. + Use the following selection methods instead. .. method:: selection_set(*items) @@ -1190,7 +1190,7 @@ ttk.Treeview *items* becomes the new selection. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: selection_add(*items) @@ -1198,7 +1198,7 @@ ttk.Treeview Add *items* to the selection. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: selection_remove(*items) @@ -1206,7 +1206,7 @@ ttk.Treeview Remove *items* from the selection. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: selection_toggle(*items) @@ -1214,7 +1214,7 @@ ttk.Treeview Toggle the selection state of each item in *items*. .. versionchanged:: 3.6 - *items* can be passed as separate arguments, not just as a single tuple. + *items* can be passed as separate arguments, not just as a single tuple. .. method:: set(item, column=None, value=None) @@ -1294,18 +1294,18 @@ option. If you don't know the class name of a widget, use the method For example, to change every default button to be a flat button with some padding and a different background color:: - from tkinter import ttk - import tkinter + from tkinter import ttk + import tkinter - root = tkinter.Tk() + root = tkinter.Tk() - ttk.Style().configure("TButton", padding=6, relief="flat", - background="#ccc") + ttk.Style().configure("TButton", padding=6, relief="flat", + background="#ccc") - btn = ttk.Button(text="Sample") - btn.pack() + btn = ttk.Button(text="Sample") + btn.pack() - root.mainloop() + root.mainloop() .. method:: map(style, query_opt=None, **kw) @@ -1319,20 +1319,20 @@ option. If you don't know the class name of a widget, use the method An example may make it more understandable:: - import tkinter - from tkinter import ttk + import tkinter + from tkinter import ttk - root = tkinter.Tk() + root = tkinter.Tk() - style = ttk.Style() - style.map("C.TButton", - foreground=[('pressed', 'red'), ('active', 'blue')], - background=[('pressed', '!disabled', 'black'), ('active', 'white')] - ) + style = ttk.Style() + style.map("C.TButton", + foreground=[('pressed', 'red'), ('active', 'blue')], + background=[('pressed', '!disabled', 'black'), ('active', 'white')] + ) - colored_btn = ttk.Button(text="Test", style="C.TButton").pack() + colored_btn = ttk.Button(text="Test", style="C.TButton").pack() - root.mainloop() + root.mainloop() Note that the order of the (states, value) sequences for an option does @@ -1351,9 +1351,9 @@ option. If you don't know the class name of a widget, use the method To check what font a Button uses by default:: - from tkinter import ttk + from tkinter import ttk - print(ttk.Style().lookup("TButton", "font")) + print(ttk.Style().lookup("TButton", "font")) .. method:: layout(style, layoutspec=None) @@ -1369,26 +1369,26 @@ option. If you don't know the class name of a widget, use the method To understand the format, see the following example (it is not intended to do anything useful):: - from tkinter import ttk - import tkinter + from tkinter import ttk + import tkinter - root = tkinter.Tk() + root = tkinter.Tk() - style = ttk.Style() - style.layout("TMenubutton", [ - ("Menubutton.background", None), - ("Menubutton.button", {"children": - [("Menubutton.focus", {"children": - [("Menubutton.padding", {"children": - [("Menubutton.label", {"side": "left", "expand": 1})] - })] - })] - }), - ]) + style = ttk.Style() + style.layout("TMenubutton", [ + ("Menubutton.background", None), + ("Menubutton.button", {"children": + [("Menubutton.focus", {"children": + [("Menubutton.padding", {"children": + [("Menubutton.label", {"side": "left", "expand": 1})] + })] + })] + }), + ]) - mbtn = ttk.Menubutton(text='Text') - mbtn.pack() - root.mainloop() + mbtn = ttk.Menubutton(text='Text') + mbtn.pack() + root.mainloop() .. method:: element_create(elementname, etype, *args, **kw) @@ -1402,24 +1402,24 @@ option. If you don't know the class name of a widget, use the method following options: * border=padding - padding is a list of up to four integers, specifying the left, top, - right, and bottom borders, respectively. + padding is a list of up to four integers, specifying the left, top, + right, and bottom borders, respectively. * height=height - Specifies a minimum height for the element. If less than zero, the - base image's height is used as a default. + Specifies a minimum height for the element. If less than zero, the + base image's height is used as a default. * padding=padding - Specifies the element's interior padding. Defaults to border's value - if not specified. + Specifies the element's interior padding. Defaults to border's value + if not specified. * sticky=spec - Specifies how the image is placed within the final parcel. spec - contains zero or more characters "n", "s", "w", or "e". + Specifies how the image is placed within the final parcel. spec + contains zero or more characters "n", "s", "w", or "e". * width=width - Specifies a minimum width for the element. If less than zero, the - base image's width is used as a default. + Specifies a minimum width for the element. If less than zero, the + base image's width is used as a default. If "from" is used as the value of *etype*, :meth:`element_create` will clone an existing @@ -1462,28 +1462,28 @@ option. If you don't know the class name of a widget, use the method As an example, let's change the Combobox for the default theme a bit:: - from tkinter import ttk - import tkinter + from tkinter import ttk + import tkinter - root = tkinter.Tk() + root = tkinter.Tk() - style = ttk.Style() - style.theme_settings("default", { - "TCombobox": { - "configure": {"padding": 5}, - "map": { - "background": [("active", "green2"), - ("!disabled", "green4")], - "fieldbackground": [("!disabled", "green3")], - "foreground": [("focus", "OliveDrab1"), - ("!disabled", "OliveDrab2")] - } - } - }) + style = ttk.Style() + style.theme_settings("default", { + "TCombobox": { + "configure": {"padding": 5}, + "map": { + "background": [("active", "green2"), + ("!disabled", "green4")], + "fieldbackground": [("!disabled", "green3")], + "foreground": [("focus", "OliveDrab1"), + ("!disabled", "OliveDrab2")] + } + } + }) - combo = ttk.Combobox().pack() + combo = ttk.Combobox().pack() - root.mainloop() + root.mainloop() .. method:: theme_names() From 61d2aa0c921f9159d59149dd90fb546517cb86bb Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Sun, 21 Jan 2018 14:47:16 -0600 Subject: [PATCH 17/25] Remove tab introduced by bad editor setting --- Doc/whatsnew/3.7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 36bd3a4f514353..326ad68339849d 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -859,7 +859,7 @@ Changes in Python behavior f(1 for x in [1],) class C(1 for x in [1]): - pass + pass Python 3.7 now correctly raises a :exc:`SyntaxError`, as a generator expression always needs to be directly inside a set of parentheses From 95bae22232117fbeeae34ea5a8bafe51540f383a Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Mon, 22 Jan 2018 15:01:08 -0600 Subject: [PATCH 18/25] Resolved merge conflict on whatsnew file --- Doc/whatsnew/3.7.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 326ad68339849d..655ea51d275587 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -510,6 +510,9 @@ sys Added :attr:`sys.flags.dev_mode` flag for the new development mode. +Deprecated :func:`sys.set_coroutine_wrapper` and +:func:`sys.get_coroutine_wrapper`. + tkinter ------- @@ -808,6 +811,11 @@ Windows Only Removed ======= +Platform Support Removals +------------------------- + +* FreeBSD 9 and older are no longer supported. + API and Feature Removals ------------------------ From 3a5eadb62cc1bf225633217d510cbb7c2f461ecb Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Mon, 22 Jan 2018 16:23:26 -0600 Subject: [PATCH 19/25] Fix table cell indent for Spinbox docs --- Doc/library/tkinter.ttk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index f9da61e082deb8..c3f3962fdc010a 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -435,7 +435,7 @@ This widget accepts the following specific options: +----------------------+------------------------------------------------------+ | command | Python callable. Will be called with no arguments | | | whenever either of the increment or decrement buttons| -| |are pressed. | +| | are pressed. | | | | +----------------------+------------------------------------------------------+ From 2aa62c352e001f7952561eb1f30008eff399e713 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Wed, 24 Jan 2018 10:37:50 -0600 Subject: [PATCH 20/25] Fix role-less inline code --- Doc/library/tkinter.ttk.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index c3f3962fdc010a..1e1daaaea4476e 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -406,8 +406,8 @@ This widget accepts the following specific options: +======================+======================================================+ | from | Float value. If set, this is the minimum value to | | | which the decrement button will decrement. Must be | -| | spelled as `from_` when used as an argument, since | -| | `from` is a Python keyword. | +| | spelled as ``from_`` when used as an argument, since | +| | ``from`` is a Python keyword. | +----------------------+------------------------------------------------------+ | to | Float value. If set, this is the maximum value to | | | which the increment button will increment. | @@ -422,10 +422,10 @@ This widget accepts the following specific options: | | or decrementing numbers. | | | | +----------------------+------------------------------------------------------+ -| wrap | Boolean value. If `True`, increment and decrement | -| | buttons will cycle from the `to` value to the `from` | -| | value or the `from` value to the `to` value, | -| | respectively. | +| wrap | Boolean value. If ``True``, increment and decrement | +| | buttons will cycle from the ``to`` value to the | +| | ``from`` value or the ``from`` value to the ``to`` | +| | value, respectively. | +----------------------+------------------------------------------------------+ | format | String value. This specifies the format of numbers | | | set by the increment/decrement buttons. It must be | From 2df569d4c30e753307582f882d456598ae62a042 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Wed, 7 Feb 2018 11:58:29 -0600 Subject: [PATCH 21/25] add SpinboxTest to test_gui tuple --- Lib/tkinter/test/test_ttk/test_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 7b5132b1dbf16a..10a9d422bfed84 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1869,7 +1869,7 @@ def create(self, **kwargs): FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, NotebookTest, PanedWindowTest, ProgressbarTest, RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, - SizegripTest, TreeviewTest, WidgetTest, + SizegripTest, SpinboxTest, TreeviewTest, WidgetTest, ) if __name__ == "__main__": From 8387517327043e8893c94c90d567a71fc7eef581 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 7 Feb 2018 23:04:00 +0200 Subject: [PATCH 22/25] Fix tests on HiDPI displays. --- Lib/tkinter/test/test_ttk/test_widgets.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 10a9d422bfed84..46c0c7c001e3a8 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1125,14 +1125,20 @@ def create(self, **kwargs): def _click_increment_arrow(self): width = self.spin.winfo_width() - self.spin.event_generate('', x=width - 5, y=5) - self.spin.event_generate('', x=width - 5, y=5) + height = self.spin.winfo_height() + x = width - 5 + y = height//2 - 5 + self.spin.event_generate('', x=x, y=y) + self.spin.event_generate('', x=x, y=y) self.spin.update_idletasks() def _click_decrement_arrow(self): width = self.spin.winfo_width() - self.spin.event_generate('', x=width - 5, y=15) - self.spin.event_generate('', x=width - 5, y=15) + height = self.spin.winfo_height() + x = width - 5 + y = height//2 + 4 + self.spin.event_generate('', x=x, y=y) + self.spin.event_generate('', x=x, y=y) self.spin.update_idletasks() def test_command(self): From 1ef7ca0876cb6566f0ed30580f24b6745fd33f4b Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Wed, 7 Feb 2018 15:38:05 -0600 Subject: [PATCH 23/25] Add missing options, update_idletasks to Spinbox tests Also remove redundant pack() and wait_visibility() calls --- Lib/tkinter/test/test_ttk/test_widgets.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 10a9d422bfed84..a96c6ece99baab 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1108,11 +1108,11 @@ def test_traversal(self): @add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) class SpinboxTest(EntryTest, unittest.TestCase): OPTIONS = ( - 'background', 'class', 'command', 'cursor', 'format', - 'from', 'increment', 'invalidcommand', 'justify', - 'show', 'state', 'style', 'takefocus', 'textvariable', - 'to', 'validate', 'validatecommand', 'values', - 'width', 'wrap', 'xscrollcommand', + 'background', 'class', 'command', 'cursor', 'exportselection', + 'font', 'foreground', 'format', 'from', 'increment', + 'invalidcommand', 'justify', 'show', 'state', 'style', + 'takefocus', 'textvariable', 'to', 'validate', 'validatecommand', + 'values', 'width', 'wrap', 'xscrollcommand', ) def setUp(self): @@ -1139,8 +1139,7 @@ def test_command(self): success = [] self.spin['command'] = lambda: success.append(True) - self.spin.pack() - self.spin.wait_visibility() + self.spin.update_idletasks() self._click_increment_arrow() self.assertTrue(success) @@ -1149,6 +1148,7 @@ def test_command(self): # testing postcommand removal self.spin['command'] = '' + self.spin.update_idletasks() self._click_increment_arrow() self._click_decrement_arrow() self.assertEqual(len(success), 2) From 92ecb6c009315bf70e07551d32fa6f94cdcccdd9 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Wed, 7 Feb 2018 16:50:32 -0600 Subject: [PATCH 24/25] add update() to spinbox tests to ensure callbacks are called --- Lib/tkinter/test/test_ttk/test_widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index b3b07866946eab..3ee3bf0535c0d8 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1147,6 +1147,7 @@ def test_command(self): self.spin['command'] = lambda: success.append(True) self.spin.update_idletasks() self._click_increment_arrow() + self.spin.update() self.assertTrue(success) self._click_decrement_arrow() @@ -1157,6 +1158,7 @@ def test_command(self): self.spin.update_idletasks() self._click_increment_arrow() self._click_decrement_arrow() + self.spin.update() self.assertEqual(len(success), 2) def test_to(self): From 53046dcf91481f3e69ddbc97e5d8d0d921c1d09f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 8 Feb 2018 13:51:03 +0200 Subject: [PATCH 25/25] Use update() after setting a command in the test. --- Lib/tkinter/test/test_ttk/test_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 3ee3bf0535c0d8..5325e36b5212b5 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1145,7 +1145,7 @@ def test_command(self): success = [] self.spin['command'] = lambda: success.append(True) - self.spin.update_idletasks() + self.spin.update() self._click_increment_arrow() self.spin.update() self.assertTrue(success)