summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/tkinter.ttk.rst91
-rw-r--r--Doc/whatsnew/3.7.rst6
-rw-r--r--Lib/tkinter/test/test_ttk/test_widgets.py179
-rw-r--r--Lib/tkinter/ttk.py29
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2018-01-18-13-09-00.bpo-32585.qpeijr.rst1
6 files changed, 300 insertions, 7 deletions
diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst
index 9c0c4cd..5ba31fe 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 **<<Increment>>** virtual event when the
+user presses <Up>, and a **<<Decrement>>** virtual event when the user
+presses <Down>.
+
+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
--------
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 8d4772f..3b4ba6e 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -663,6 +663,12 @@ 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
+-------
+
+Added :class:`tkinter.ttk.Spinbox`.
+
time
----
diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py
index 08f5fc3..bbc508d 100644
--- a/Lib/tkinter/test/test_ttk/test_widgets.py
+++ b/Lib/tkinter/test/test_ttk/test_widgets.py
@@ -1105,6 +1105,183 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase):
self.nb.event_generate('<Alt-a>')
self.assertEqual(self.nb.select(), str(self.child1))
+@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
+class SpinboxTest(EntryTest, unittest.TestCase):
+ OPTIONS = (
+ '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):
+ 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()
+ height = self.spin.winfo_height()
+ x = width - 5
+ y = height//2 - 5
+ self.spin.event_generate('<ButtonPress-1>', x=x, y=y)
+ self.spin.event_generate('<ButtonRelease-1>', x=x, y=y)
+ self.spin.update_idletasks()
+
+ def _click_decrement_arrow(self):
+ width = self.spin.winfo_width()
+ height = self.spin.winfo_height()
+ x = width - 5
+ y = height//2 + 4
+ self.spin.event_generate('<ButtonPress-1>', x=x, y=y)
+ self.spin.event_generate('<ButtonRelease-1>', x=x, y=y)
+ self.spin.update_idletasks()
+
+ def test_command(self):
+ success = []
+
+ self.spin['command'] = lambda: success.append(True)
+ self.spin.update()
+ self._click_increment_arrow()
+ self.spin.update()
+ self.assertTrue(success)
+
+ self._click_decrement_arrow()
+ self.assertEqual(len(success), 2)
+
+ # testing postcommand removal
+ self.spin['command'] = ''
+ self.spin.update_idletasks()
+ self._click_increment_arrow()
+ self._click_decrement_arrow()
+ self.spin.update()
+ 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):
+ 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.spin['values'] = ['a', 1, 'c']
+
+ # test incrementing / decrementing values
+ self.spin.set('a')
+ self.spin.update()
+ self._click_increment_arrow()
+ self.assertEqual(self.spin.get(), '1')
+
+ 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'],
+ ('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 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):
@@ -1679,7 +1856,7 @@ tests_gui = (
FrameTest, LabelFrameTest, LabelTest, MenubuttonTest,
NotebookTest, PanedWindowTest, ProgressbarTest,
RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest,
- SizegripTest, TreeviewTest, WidgetTest,
+ SizegripTest, SpinboxTest, TreeviewTest, WidgetTest,
)
if __name__ == "__main__":
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
index 2ab5b59..490b502 100644
--- a/Lib/tkinter/ttk.py
+++ b/Lib/tkinter/ttk.py
@@ -19,7 +19,7 @@ __author__ = "Guilherme Polo <ggpolo@gmail.com>"
__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
"Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow",
"PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar",
- "Separator", "Sizegrip", "Style", "Treeview",
+ "Separator", "Sizegrip", "Spinbox", "Style", "Treeview",
# Extensions
"LabeledScale", "OptionMenu",
# functions
@@ -1149,6 +1149,33 @@ class Sizegrip(Widget):
Widget.__init__(self, master, "ttk::sizegrip", kw)
+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
+ string values.
+ """
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Spinbox widget with the parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus, validate,
+ validatecommand, xscrollcommand, invalidcommand
+
+ WIDGET-SPECIFIC OPTIONS
+
+ to, from_, increment, values, wrap, format, command
+ """
+ Entry.__init__(self, master, "ttk::spinbox", **kw)
+
+
+ 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.
diff --git a/Misc/ACKS b/Misc/ACKS
index c5eadc5..4c8b86a 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1073,6 +1073,7 @@ The Dragon De Monsyne
Bastien Montagne
Skip Montanaro
Peter Moody
+Alan D. Moore
Paul Moore
Ross Moore
Ben Morgan
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 0000000..c504e8b
--- /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.