Entry, Combox, Spinbox

Several widgets allow to enter text or numerical information in a single field.

  • the Entry widgets allows to type text
  • the Combobox allows to either type text or select text from a drop-down list
  • the Spinbox allows to select from a numerical range or from a list
  • the Scale allows to use a slider to choose a value
../_images/entry0.png

entry0.py

Since these widgets are very similar, we treat them in the same section.

Entry widget

An entry widget presents the user an empty field where he can enter a text value.

Each ttk.Entry object has these options:

  • parent - the parent object
  • textvariable - the text variable to hold the entered string
  • width - the numbers of characters
  • show - to indicate * for passwords

The Entry widget does not have a text or image option. You have to use an additional label widget instead.

../_images/entry1.png
import tkinter as tk
root = tk.Tk()

name = tk.StringVar()
tk.Label(text='Name').pack()
tk.Entry(root, textvariable=name, width=10).pack()

password = tk.StringVar()
tk.Label(text='Password').pack()
tk.Entry(root, textvariable=password, show='*').pack()

root.mainloop()

entry1.py

A better Entry class

It’s time now to define a new and better Entry class which can do everything in one line:

Entry('First name:', 'print(self.var.get())')

This new class has the attributes:

  • label - to automatically add label in front of the entry field
  • cmd - to execute a command string when hitting the Return key
  • val - a default value
../_images/entry2.png

The command function evaluates the expression entered and displays the result in the following label widget:

Entry('Enter expression', 'App.res["text"] = eval(self.var.get())')
App.res = Label('Result')
"""Create entry fields."""
from tklib import *
app = App('Entry')

Label('Entry fields', font='Arial 24')
Entry('First name:', 'print(self.var.get())', 'James')
Entry('Last name:', 'print(self.var.get())')
Entry('Password:', 'print(self.var.get())', show='*')

Entry('Enter expression', 'App.res["text"] = eval(self.var.get())')
App.res = Label('Result')

app.run()

entry2.py

Now let’s see how this class is defined

class Entry(ttk.Entry, EntryMixin):
    """Create an Entry object with label and callback."""

    def __init__(self, label='', cmd='', val='',  **kwargs):
        self.var = tk.StringVar()
        self.var.set(val)

        self.add_widget(label, Entry, kwargs)
        self['textvariable'] = self.var
        self.add_cmd(cmd)

Combobox

A combobox combines an entry widget with a list of choices. The user can either select text from a drop-down menu or write his own text.

The first combobox allows to also enter your own text, while the second one is restricted to chose an item from the drop-down menu by setting state='readonly'.

../_images/combo1.png

The Combobox class has the options

  • parent - for the parent object
  • textvariable - for the variable which stores the value
  • values - for the items list
  • state - to indicate readonly

Standard Combobox

from tklib import *
app = App('Combobox')

Label('Combobox with text entry')
days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday')
day = tk.StringVar()
day.set(days[0])
ttk.Combobox(App.stack[-1], textvariable=day, values=days).grid()

Label('state=readonly')
day2 = tk.StringVar()
day2.set(days[1])
ttk.Combobox(App.stack[-1], textvariable=day2, values=days, state='readonly').grid()

app.run()

combo1.py

A better Combobox class

A combobox combines a list of choices with an entry. The user can select from the list, but he can also enter directly a value.

../_images/combo2.png

The items list can be a:

  • semicolon-separated string
  • integer list: [2005, 2006, 2007]
  • list expression (list(range10))
from tklib import *
app = App('Combobox')

Combobox('Weekday', 'Mon;Tue;Wed;Thu;Fri', 'print(self.item)')
Combobox('Country', 'Switzerland;France;Italy;Germany', 'print(self.item)')
Combobox('Year', [2005, 2006, 2007], 'print(self.item)')
Combobox('Integer', list(range(10)), 'print(self.item)')

app.run()

combo2.py

How is this new class defined ?

class Combobox(ttk.Combobox, EntryMixin):
    """Create a Combobox with label and callback."""

    def __init__(self, label='', values='', cmd='', val=0, **kwargs):
        if isinstance(values, str):
            values = values.split(';')

        self.var = tk.StringVar()
        self.var.set(values[val])

        self.add_widget(label, Combobox, kwargs)
        self['textvariable'] = self.var
        self['values'] = values

        self.add_cmd(cmd)
        self.bind('<<ComboboxSelected>>', self.cb)

Exemple

../_images/combo3.png
from tklib import *
app = App('Combobox')

Combobox('dir(tk)', dir(tk), 'print(self.item); App.entry.var.set(eval("tk."+self.item))')
App.entry = Entry('value')
Combobox('dir(ttk)', dir(ttk), 'print(eval("ttk."+self.item))')

app.run()

combo3.py

Another exemple

../_images/combo4.png

The items list can be a:

  • semicolon-separated string
  • tuple
  • list
  • list expression (list(range10))

The command function can be either a string to execute or a function.

from tklib import *
app = App('Combobox')

def cb(e=None):
    print(c1.var.get())

Combobox('string - "1;2;3"', '1;2;3')
Combobox('tuple - (1, 2, 3)', (1, 2, 3))
Combobox('list - [1, 2, 3]', [1, 2, 3])
Combobox('range(100)', list(range(100)))
Combobox('width=10', '1;2;3', width=10)

c1 = Combobox('cmd function', (1, 2, 3), cb)
Combobox('cmd string', (1, 2, 3), 'print(self.item)')
Combobox(values='a;b;c', cmd='print(self.item)')
Combobox(values='a;b;c', cmd='print(self.item)', width=10)

app.run()

combo4.py

Spinbox

A spinbox widget is an entry widget with built-in up and down buttons that are used to either modify a numeric value or to select among a set of values. The widget implements all the features of the entry widget.

../_images/spinbox1.png
from tklib import *

app = App('Spinbox')

var1 = tk.StringVar(value='10')
ttk.Spinbox(App.stack[-1], from_=5.0, to=100.0, increment=25, textvariable=var1).grid()

# default increment=1, from_=0, to=0
var2 = tk.StringVar(value='2')
ttk.Spinbox(App.stack[-1], to=10, textvariable=var2).grid()

# how to use a value list
values = 'Frank;Conny;Jim'.split(';')
var3 = tk.StringVar(value=values[0])
ttk.Spinbox(App.stack[-1], values=values, textvariable=var3).grid()

app.run()

spinbox1.py

../_images/spinbox2.png
from entry import *
app = App('Spinbox')

Spinbox('values=(1, 2, 5, 50)', values=(1, 2, 5, 10), val=5)
Spinbox('from_=-5, to=5', from_=-5, to=5, wrap=True)
Spinbox('to=5, wrap=True', to=5, wrap=True)
Spinbox('to=50, increment=5', to=50, increment=5)
Spinbox('to=1, increment=0.1', to=1, increment=0.1)
Spinbox('<Return> self.configure()', 'print(self.configure())')
Spinbox('<Return> self.var.get()', 'print(self.var.get())', to=5)
Spinbox('state=disabled', to=5, state='disabled', val=2)
Spinbox('state=readonly', to=5, state='readonly', val=2)
app.run()

spinbox2.py

Scale

A scale widget provides a way for users to choose a numeric value through direct manipulation.

../_images/scale2.png
from entry import *

app = App('Scale')      
Scale('Scale', 'print(self.var.get())', to=10, length=200)
Scale('from=-50, to=50', 'print(self.var.get())', from_=-50, to=50)

app.run()

scale2.py

Final implementation

The four classes Entry, Combobox, Spinbox and Scale have two common parts:

  • adding an optional label in front of the widget
  • adding a callback function

This two functions can be placed in a specal class called EntryMixin, which serves as second parent class for the 4 entry classes.

class EntryMixin:
    """Add label, widget and callback function."""

    def add_widget(self, label, widget, kwargs):
        """Add widget with optional label."""
        if label == '':
            super(widget, self).__init__(App.stack[-1], **kwargs)
            self.grid()
        else:
            d = 2 if App.debug else 0
            frame = ttk.Frame(App.stack[-1], relief='solid', borderwidth=d)
            frame.grid(sticky='e')
            ttk.Label(frame, text=label).grid()
            super(widget, self).__init__(frame, **kwargs)
            self.grid(row=0, column=1)

    def add_cmd(self, cmd):
        # if cmd is a string store it, and replace it 'cb' callback function
        if isinstance(cmd, str):
            self.cmd = cmd
            cmd = self.cb
        self.bind('<Return>', lambda event: cmd(self, event))

    def cb(self, item=None, event=None):
        """Execute the cmd string in the widget context."""
        exec(self.cmd)

This allows to make the Entry class much shorter.

class Entry(ttk.Entry, EntryMixin):
    """Create an Entry object with label and callback."""

    def __init__(self, label='', cmd='', val='',  **kwargs):
        self.var = tk.StringVar()
        self.var.set(val)

        self.add_widget(label, Entry, kwargs)
        self['textvariable'] = self.var
        self.add_cmd(cmd)

The other class definitions are as follows:

class Combobox(ttk.Combobox, EntryMixin):
    """Create a Combobox with label and callback."""

    def __init__(self, label='', values='', cmd='', val=0, **kwargs):
        if isinstance(values, str):
            values = values.split(';')

        self.var = tk.StringVar()
        self.var.set(values[val])

        self.add_widget(label, Combobox, kwargs)
        self['textvariable'] = self.var
        self['values'] = values

        self.add_cmd(cmd)
        self.bind('<<ComboboxSelected>>', self.cb)
class Spinbox(ttk.Spinbox, EntryMixin):
    """Create a Spinbox with label and callback."""

    def __init__(self, label='', cmd='', values='', val=0, **kwargs):
        if isinstance(values, str):
            values = values.split(';')
            if len(values) > 1:
                val = values[val]

        self.var = tk.StringVar(value=val)

        self.add_widget(label, Spinbox, kwargs)
        self['textvariable'] = self.var

        if len(values) > 1:
            self['values'] = values
        self.add_cmd(cmd)
class Scale(ttk.Scale, EntryMixin):
    """Create a Spinbox with label and callback."""

    def __init__(self, label='', cmd='', val=0, **kwargs):
        self.var = tk.IntVar(value=val)

        if not 'length' in kwargs:
            kwargs.update({'length': 200})

        self.add_widget(label, Scale, kwargs)
        self['variable'] = self.var

        self.add_cmd(cmd)
        if isinstance(cmd, str):
            self.cmd = cmd
            cmd = self.cb
        self['command'] = lambda event: cmd(self, event)