Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tkinter Scale slider with float values doesn't work with locale of language that uses comma for floats

I have following code:

import Tkinter as tk
import locale
from Tkinter import *
#locale.setlocale(locale.LC_NUMERIC, 'pl_PL')
master = tk.Tk()
w = tk.Scale(master, from_=0.05, to=0.1, resolution=0.01)
w.pack()
tk.mainloop()

And the slider works like it should. It doesn't slide when I uncomment that line with locale setting. That is probably due to the fact, that pl_PL locale uses comma for float separation. This may be a bug. How can I work around it so I can have the locale set properly?

like image 898
mys149 Avatar asked Jul 24 '17 20:07

mys149


1 Answers

Here's a slightly clunky workaround for this bug, using some ideas & code from Bryan Oakley's answer to Dynamically reformat tkinter scale value python 2.7, where Bryan shows how to replace the usual value display with a custom formatted display. Unfortunately, we need to do a little more work here because even when we create Scale with showvalue=False it still gets blocked by float numbers that contain commas, even though it's not even showing them!

The solution is to force the numbers to be integers. That's easy enough to do if the to and from_ values are whole number multiples of resolution, as shown below.

import Tkinter as tk
import locale
locale.setlocale(locale.LC_NUMERIC, 'pl_PL.UTF8')

class NewScale(tk.Frame):
    def __init__(self, master=None, **options):
        tk.Frame.__init__(self, master)

        # Disable normal value display...
        options['showvalue'] = False
        # ... and use custom display instead
        options['command'] = self._on_scale

        # Set resolution to 1 and adjust to & from value 
        self.res = options.get('resolution', 1)
        from_ = int(options.get('from_', 0) / self.res)
        to = int(options.get('to', 100) / self.res)
        options.update({'resolution': 1, 'to': to, 'from_': from_})

        # This could be improved...
        if 'digits' in options:
            self.digits = ['digits']
            del options['digits']
        else:
            self.digits = 2

        self.scale = tk.Scale(self, **options)
        self.scale_label = tk.Label(self)
        orient = options.get('orient', tk.VERTICAL)
        if orient == tk.VERTICAL:
            side, fill = 'right', 'y'
        else:
            side, fill = 'top', 'x'
        self.scale.pack(side=side, fill=fill)
        self.scale_label.pack(side=side)

    def _on_scale(self, value):
        value = locale.atof(value) * self.res
        value = locale.format_string('%.*f', (self.digits, value))
        self.scale_label.configure(text=value)

if __name__ == '__main__':
    master = tk.Tk()
    w = NewScale(master, from_=0.05, to=0.1, resolution=0.01)
    w.pack(fill='both', expand=True)
    master.mainloop()

This code has been tested on Python 2.6.6 and 3.6.0. To run it on Python 3, change import Tkinter as tk to import tkinter as tk.

The NewScale widget supports both tk.VERTICAL and tk.HORIZONTAL orientations, with tk.VERTICAL being the default (the same as the normalScale widget). Its support for the digits option is currently rather primitive.


Here are a couple of methods that make NewScale a little more useful:

def get(self):
    return self.scale.get() * self.res

def set(self, value):
    self.scale.set(int(0.5 + value / self.res))

To test those methods, change the calling code to:

if __name__ == '__main__':
    master = tk.Tk()
    w = NewScale(master, from_=0.05, to=0.1, resolution=0.01)
    w.pack(fill='both', expand=True)
    w.set(0.07)
    tk.Button(master, text='Print', command=lambda: print(w.get())).pack()
    master.mainloop()

And it's probably a Good Idea to round the adjusted to & from_ values to the nearest integer, rather than truncating them. That can be done by changing their initializers to:

from_ = int(0.5 + options.get('from_', 0) / self.res)
to = int(0.5 + options.get('to', 100) / self.res)
like image 74
PM 2Ring Avatar answered Sep 28 '22 03:09

PM 2Ring