bpo-32585: Add tkinter.ttk.Spinbox. (#5221)

This commit is contained in:
Alan D Moore 2018-02-08 18:03:55 -06:00 committed by Serhiy Storchaka
parent 32921f9082
commit a48e78a0b7
6 changed files with 300 additions and 7 deletions

View File

@ -66,13 +66,13 @@ for improved styling effects.
Ttk Widgets 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:`Button`, :class:`Checkbutton`, :class:`Entry`, :class:`Frame`,
:class:`Label`, :class:`LabelFrame`, :class:`Menubutton`, :class:`PanedWindow`, :class:`Label`, :class:`LabelFrame`, :class:`Menubutton`, :class:`PanedWindow`,
:class:`Radiobutton`, :class:`Scale` and :class:`Scrollbar`. The other six are :class:`Radiobutton`, :class:`Scale`, :class:`Scrollbar`, and :class:`Spinbox`.
new: :class:`Combobox`, :class:`Notebook`, :class:`Progressbar`, The other six are new: :class:`Combobox`, :class:`Notebook`,
:class:`Separator`, :class:`Sizegrip` and :class:`Treeview`. And all them are :class:`Progressbar`, :class:`Separator`, :class:`Sizegrip` and
subclasses of :class:`Widget`. :class:`Treeview`. And all them are subclasses of :class:`Widget`.
Using the Ttk widgets gives the application an improved look and feel. Using the Ttk widgets gives the application an improved look and feel.
As discussed above, there are differences in how the styling is coded. 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*. 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 Notebook
-------- --------

View File

@ -663,6 +663,12 @@ Added :attr:`sys.flags.dev_mode` flag for the new development mode.
Deprecated :func:`sys.set_coroutine_wrapper` and Deprecated :func:`sys.set_coroutine_wrapper` and
:func:`sys.get_coroutine_wrapper`. :func:`sys.get_coroutine_wrapper`.
tkinter
-------
Added :class:`tkinter.ttk.Spinbox`.
time time
---- ----

View File

@ -1105,6 +1105,183 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase):
self.nb.event_generate('<Alt-a>') self.nb.event_generate('<Alt-a>')
self.assertEqual(self.nb.select(), str(self.child1)) 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) @add_standard_options(StandardTtkOptionsTests)
class TreeviewTest(AbstractWidgetTest, unittest.TestCase): class TreeviewTest(AbstractWidgetTest, unittest.TestCase):
@ -1679,7 +1856,7 @@ tests_gui = (
FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, FrameTest, LabelFrameTest, LabelTest, MenubuttonTest,
NotebookTest, PanedWindowTest, ProgressbarTest, NotebookTest, PanedWindowTest, ProgressbarTest,
RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest,
SizegripTest, TreeviewTest, WidgetTest, SizegripTest, SpinboxTest, TreeviewTest, WidgetTest,
) )
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -19,7 +19,7 @@ __author__ = "Guilherme Polo <ggpolo@gmail.com>"
__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label", __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
"Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow", "Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow",
"PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar", "PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar",
"Separator", "Sizegrip", "Style", "Treeview", "Separator", "Sizegrip", "Spinbox", "Style", "Treeview",
# Extensions # Extensions
"LabeledScale", "OptionMenu", "LabeledScale", "OptionMenu",
# functions # functions
@ -1149,6 +1149,33 @@ class Sizegrip(Widget):
Widget.__init__(self, master, "ttk::sizegrip", kw) 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): class Treeview(Widget, tkinter.XView, tkinter.YView):
"""Ttk Treeview widget displays a hierarchical collection of items. """Ttk Treeview widget displays a hierarchical collection of items.

View File

@ -1073,6 +1073,7 @@ The Dragon De Monsyne
Bastien Montagne Bastien Montagne
Skip Montanaro Skip Montanaro
Peter Moody Peter Moody
Alan D. Moore
Paul Moore Paul Moore
Ross Moore Ross Moore
Ben Morgan Ben Morgan

View File

@ -0,0 +1 @@
Add Ttk spinbox widget to to tkinter.ttk. Patch by Alan D Moore.