kakyo
kakyo

Reputation: 11640

Tkinter: Control ttk.Scale's increment as with tk.Scale and a tk.DoubleVar

I used to use digits property of tk.Scale to make sure the numbers in a Label or Spinbox show fixed number of decimal digits as the slider moves. Like 3.456, 4444.567, 5555555.678, ...

However, with ttk.Scale digits and resolution are gone. I'm left with a long decimal number if a tk.DoubleVar is used as the scale variable. So according to this post:

How to make ttk.Scale behave more like tk.Scale?

I'd have to code up my own number display scheme for the label, which seems crazy when I already work with a widget class. I have yet to see how to keep the DoubleVar for this because I'll need to change the string representation of it.

Is there an easier way to achieve what I want?

UPDATE:

Here is the code:

import tkinter as tk
import tkinter.ttk as ttk

root = tk.Tk()
mainframe = tk.Frame(root)

# Model

input = tk.DoubleVar(value=0.)

spin = tk.Spinbox(mainframe, textvariable=input, wrap=True, width=10)
slide = ttk.Scale(mainframe, variable=input, orient='horizontal', length=200)
spin['to'] = 1.0
spin['from'] = 0.0
spin['increment'] = 0.01
slide['to'] = 1.0
slide['from'] = 0.0
# slide['digits'] = 4
# slide['resolution'] = 0.01

# Layout

weights = {'spin': 1, 'slide': 100}

mainframe.grid_rowconfigure(0, weight=1)
mainframe.grid_columnconfigure(0, weight=weights['spin'])
mainframe.grid_columnconfigure(1, weight=weights['slide'])
spin.grid(row=0, column=0, sticky='news')
slide.grid(row=0, column=1, sticky='news')

root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
mainframe.grid(row=0, column=0)

root.mainloop()

As I drag the Scale, the decimal number suddenly becomes out of control.

enter image description here

Upvotes: 4

Views: 4354

Answers (1)

martineau
martineau

Reputation: 123491

Here's a better solution than what was in my first (now deleted) answer which I feel is a much cleaner and better way to implement the functionality in an more object-oriented way — so doesn't require a hack like my original one did.

Instead it accomplishes what is needed by defining a ttk.Scale subclass I named "Limiter" which supports an additional keyword argument named precision.

import tkinter as tk
import tkinter.ttk as ttk


root = tk.Tk()
mainframe = tk.Frame(root)


# Model

class Limiter(ttk.Scale):
    """ ttk.Scale sublass that limits the precision of values. """

    def __init__(self, *args, **kwargs):
        self.precision = kwargs.pop('precision')  # Remove non-std kwarg.
        self.chain = kwargs.pop('command', lambda *a: None)  # Save if present.
        super(Limiter, self).__init__(*args, command=self._value_changed, **kwargs)

    def _value_changed(self, newvalue):
        newvalue = round(float(newvalue), self.precision)
        self.winfo_toplevel().globalsetvar(self.cget('variable'), (newvalue))
        self.chain(newvalue)  # Call user specified function.


# Sample client callback.
def callback(newvalue):
    print('callback({!r})'.format(newvalue))

input_var = tk.DoubleVar(value=0.)
spin = tk.Spinbox(mainframe, textvariable=input_var, wrap=True, width=10)
slide = Limiter(mainframe, variable=input_var, orient='horizontal', length=200,
                command=callback, precision=4)

spin['to'] = 1.0
spin['from'] = 0.0
spin['increment'] = 0.01
slide['to'] = 1.0
slide['from'] = 0.0
# slide['digits'] = 4
# slide['resolution'] = 0.01

# Layout

weights = {'spin': 1, 'slide': 100}

mainframe.grid_rowconfigure(0, weight=1)
mainframe.grid_columnconfigure(0, weight=weights['spin'])
mainframe.grid_columnconfigure(1, weight=weights['slide'])
spin.grid(row=0, column=0, sticky='news')
slide.grid(row=0, column=1, sticky='news')

root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
mainframe.grid(row=0, column=0)

root.mainloop()

screenshot of widget

Upvotes: 6

Related Questions