Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions Doc/library/tkinter.ttk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
--------

Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,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
----

Expand Down
179 changes: 178 additions & 1 deletion Lib/tkinter/test/test_ttk/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,183 @@ def test_traversal(self):
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):
Expand Down Expand Up @@ -1700,7 +1877,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__":
Expand Down
29 changes: 28 additions & 1 deletion Lib/tkinter/ttk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "Spinbox", "Style", "Treeview",
# Extensions
"LabeledScale", "OptionMenu",
# functions
Expand Down Expand Up @@ -1151,6 +1151,33 @@ 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

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.

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@ The Dragon De Monsyne
Bastien Montagne
Skip Montanaro
Peter Moody
Alan D. Moore
Paul Moore
Ross Moore
Ben Morgan
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Ttk spinbox widget to to tkinter.ttk. Patch by Alan D Moore.