如何让 ttk.Scale 表现得更像 tk.Scale? [英] How to make ttk.Scale behave more like tk.Scale?

查看:67
本文介绍了如何让 ttk.Scale 表现得更像 tk.Scale?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Ttk 版本中也存在几个 Tk 小部件.通常它们具有相同的一般行为,但使用样式"和主题"而不是每个实例的外观属性(例如 bg 等...).这很好,因为 Ttk 小部件默认采用操作系统窗口管理器的标准外观",无需配置任何外观.

然而,由于某种原因,ttk.Scale 小部件没有 tk.Scale 小部件的两个非常有用的选项:showvaluetickinterval(见

解决方案

您可以使用 place 及其位置 x(以像素为单位)以自动方式放置刻度和显示值的标签由公式给出:

x = ((value - start)/extent) * (width - sliderlength) + sliderlength/2

与:

  • value:勾号的值
  • start:刻度的起点(即from选项)
  • extent:结束 - 开始
  • width:刻度的宽度

((value - start)/extent) 以百分比给出位置,然后,我只需将其乘以比例尺的长度,但要考虑滑块的长度.

然后用以下方式打勾:
place(in_=self.scale,bordermode='outside',x=x,rely=1,anchor='n')
(使用 rely=0, anchor='s' 作为显示值的标签)

下面是完整的代码.我还添加了对 digits 选项的支持.

导入 tkinter 作为 tk将 tkinter.ttk 导入为 ttk类 TtkScale(ttk.Frame):def __init__(self, master=None, **kwargs):ttk.Frame.__init__(self, master)self.columnconfigure(0, weight=1)self.showvalue = kwargs.pop('showvalue', True)self.tickinterval = kwargs.pop('tickinterval', 0)self.digits = kwargs.pop('digits', '0')如果 kwargs 中的命令":# 将 self.display_value 添加到命令中fct = kwargs['命令']定义 cmd(值):fct(值)self.display_value(value)kwargs['命令'] = cmd别的:kwargs['command'] = self.display_valueself.scale = ttk.Scale(self, **kwargs)# 获取滑块长度风格 = ttk.Style(self)style_name = kwargs.get('style', '%s.TScale' % (str(self.scale.cget('orient')).capitalize()))self.sliderlength = style.lookup(style_name, 'sliderlength', default=30)self.extent = kwargs['to'] - kwargs['from_']self.start = kwargs['from_']# 显示值如果 self.showvalue:ttk.Label(self, text='').grid(row=0)self.label = ttk.Label(self, text='0')self.label.place(in_=self.scale, bordermode='outside', x=0, y=0, anchor='s')self.display_value(self.scale.get())self.scale.grid(row=1,sticky='ew')# 滴答声如果 self.tickinterval:ttk.Label(self, text='').grid(row=2)self.ticks = []self.ticklabels = []nb_interv = round(self.extent/self.tickinterval)格式化程序 = '{:.'+ str(self.digits) + 'f}'对于 i 在范围内(nb_interv + 1):tick = kwargs['from_'] + i * self.tickintervalself.ticks.append(tick)self.ticklabels.append(ttk.Label(self, text=formatter.format(tick)))self.ticklabels[i].place(in_=self.scale,bordermode='outside',x=0,relay=1,anchor='n')self.place_ticks()self.scale.bind('', self.on_configure)def convert_to_pixels(self, value):return ((value - self.start)/self.extent) * (self.scale.winfo_width()- self.sliderlength) + self.sliderlength/2def display_value(self, value):# 滑块中心的位置(以像素为单位)x = self.convert_to_pixels(float(value))# 注意边界half_width = self.label.winfo_width()/2如果 x + half_width >self.scale.wininfo_width():x = self.scale.winfo_width() - half_widthelif x - half_width <0:x = half_widthself.label.place_configure(x=x)格式化程序 = '{:.'+ str(self.digits) + 'f}'self.label.configure(text=formatter.format(float(value)))def place_ticks(self):# 第一个勾滴答 = self.ticks[0]标签 = self.ticklabels[0]x = self.convert_to_pixels(tick)half_width = label.winfo_width()/2如果 x - half_width <0:x = half_widthlabel.place_configure(x=x)# 在中间打勾对于刻度线,zip 中的标签(self.ticks[1:-1], self.ticklabels[1:-1]):x = self.convert_to_pixels(tick)label.place_configure(x=x)# 最后一个刻度滴答 = self.ticks[-1]标签 = self.ticklabels[-1]x = self.convert_to_pixels(tick)half_width = label.winfo_width()/2如果 x + half_width >self.scale.wininfo_width():x = self.scale.winfo_width() - half_widthlabel.place_configure(x=x)def on_configure(self, event):"重新显示刻度和标签,使它们适应新的比例尺大小."self.display_value(self.scale.get())self.place_ticks()如果 __name__ == '__main__':根 = tk.Tk()root.geometry('400x300')风格 = ttk.Style(root)style.configure('my.Horizo​​ntal.TScale',sliderlength=10)s1 = tk.Scale(root, orient='horizo​​ntal', tickinterval=0.2, from_=-1,to=1,showvalue=True,分辨率=0.1,sliderlength=10)s2 = TtkScale(root, style='my.Horizo​​ntal.TScale', orient='horizo​​ntal',tickinterval=0.2, from_=-1, to=1, showvalue=True,位数=1)ttk.Label(root, text='tk.Scale').pack()s1.pack(fill='x')ttk.Label(root, text='ttk.Scale').pack()s2.pack(fill='x')root.mainloop()

在名为 TickScale 的 ttkwidgets 模块中提供了此小部件的更完整版本.

Several Tk widgets also exist in Ttk versions. Usually they have the same general behaviour, but use "styles" and "themes" rather than per-instance appearance attributes (such as bg, etc...). This is good, as the Ttk widgets take the "standard appearance" of the OS's window manager by default, without needing to configure anything about appearance.

However, for some reason the ttk.Scale widget does not have two very useful options of the tk.Scale widget: showvalue and tickinterval (see reference). This is strange as those are more about behaviour than about look.

It would be great to "immitate" these two options while keeping a ttk look. The following code is my clumsy attempt at this. The question is: is there a better way? (besides encapsulating the whole thing in a class, obviously) and how would one reasonably get a semi-automated tickinterval substitute (rather than doing it "by hand" as in the code below).

import tkinter as tk
import tkinter.ttk as ttk

# initial setup
root = tk.Tk()
frame = tk.Frame(root)

#################################################################
# create a tk slider showing current value and ticks
# (showvalue=True is the default)
tkslider = tk.Scale(frame, from_=-4, to=4,
                    orient=tk.HORIZONTAL, tickinterval=2)
#################################################################

#################################################################
# create a ttk slider showing current value and ticks
# use a ttk frame to get ttk style background
ttkslider = ttk.Frame(frame)
# define a callback function to update the value label
def ttk_slider_callback(value):
    # 'value' seems to be a string - bug or feature?
    value_label.config(text=round(float(value)))
    # 'text' can apparently be an int and gets converted into str
    # (...) possibly do other stuff
# decompose frame into two ttk labels and a ttk scale
value_label = ttk.Label(ttkslider, text=0)
actual_slider = ttk.Scale(ttkslider, from_=-4, to=4,
                          command=ttk_slider_callback)
# (orient=tk.HORIZONTAL is the default)
ticks_label = ttk.Label(ttkslider, text='  -4 -2  0  2  4   ')
# put it all together
value_label.grid()
actual_slider.grid()
ticks_label.grid()
#################################################################

# final setup
tkslider.grid(row=0, column=0)
ttkslider.grid(row=0, column=1)
frame.grid()
root.mainloop()

The result of the previous code, before actualy "sliding" the Scales, may look like this, with the Tk Scale on the left and the Ttk Scale on the right (will vary obviously per OS / window manager):

解决方案

You can place in an automated way both the ticks and the label showing the value using place and their position x (in pixels) given by the formula:

x = ((value - start) / extent) * (width - sliderlength) + sliderlength / 2

with:

  • value: the value of the tick
  • start: the starting point of the scale (i.e. the from option)
  • extent: end - start
  • width: the width of the scale

((value - start) / extent) gives the position in percent and then, I just have to multiply it by the length of the scale, but taking into account the length of the slider.

Then place the tick with:
place(in_=self.scale, bordermode='outside', x=x, rely=1, anchor='n')
(use rely=0, anchor='s' for the label showing the value)

And below is the full code. I have also added support for the digits option.

import tkinter as tk
import tkinter.ttk as ttk

class TtkScale(ttk.Frame):
    def __init__(self, master=None, **kwargs):
        ttk.Frame.__init__(self, master)
        self.columnconfigure(0, weight=1)
        self.showvalue = kwargs.pop('showvalue', True)
        self.tickinterval = kwargs.pop('tickinterval', 0)
        self.digits = kwargs.pop('digits', '0')
        
        if 'command' in kwargs:
            # add self.display_value to the command
            fct = kwargs['command']
            
            def cmd(value):
                fct(value)
                self.display_value(value)
                
            kwargs['command'] = cmd
        else:
            kwargs['command'] = self.display_value
            
        self.scale = ttk.Scale(self, **kwargs)
        
        # get slider length
        style = ttk.Style(self)
        style_name = kwargs.get('style', '%s.TScale' % (str(self.scale.cget('orient')).capitalize()))
        self.sliderlength = style.lookup(style_name, 'sliderlength', default=30)
        
        self.extent = kwargs['to'] - kwargs['from_']
        self.start = kwargs['from_']
        # showvalue
        if self.showvalue:
            ttk.Label(self, text=' ').grid(row=0)
            self.label = ttk.Label(self, text='0')
            self.label.place(in_=self.scale, bordermode='outside', x=0, y=0, anchor='s')
            self.display_value(self.scale.get())
            
        self.scale.grid(row=1, sticky='ew')
        
        # ticks
        if self.tickinterval:
            ttk.Label(self, text=' ').grid(row=2)
            self.ticks = []
            self.ticklabels = []
            nb_interv = round(self.extent/self.tickinterval)
            formatter = '{:.' + str(self.digits) + 'f}'
            for i in range(nb_interv + 1):
                tick = kwargs['from_'] + i * self.tickinterval
                self.ticks.append(tick)
                self.ticklabels.append(ttk.Label(self, text=formatter.format(tick)))
                self.ticklabels[i].place(in_=self.scale, bordermode='outside', x=0, rely=1, anchor='n')
            self.place_ticks()

        self.scale.bind('<Configure>', self.on_configure)
        
    def convert_to_pixels(self, value):
        return ((value - self.start)/ self.extent) * (self.scale.winfo_width()- self.sliderlength) + self.sliderlength / 2
        
    def display_value(self, value):
        # position (in pixel) of the center of the slider
        x = self.convert_to_pixels(float(value))
        # pay attention to the borders
        half_width = self.label.winfo_width() / 2
        if x + half_width > self.scale.winfo_width():
            x = self.scale.winfo_width() - half_width
        elif x - half_width < 0:
            x = half_width
        self.label.place_configure(x=x)
        formatter = '{:.' + str(self.digits) + 'f}'
        self.label.configure(text=formatter.format(float(value)))
    
    def place_ticks(self):
        # first tick 
        tick = self.ticks[0]
        label = self.ticklabels[0]
        x = self.convert_to_pixels(tick)
        half_width = label.winfo_width() / 2
        if x - half_width < 0:
            x = half_width
        label.place_configure(x=x)
        # ticks in the middle
        for tick, label in zip(self.ticks[1:-1], self.ticklabels[1:-1]):
            x = self.convert_to_pixels(tick)
            label.place_configure(x=x)
        # last tick
        tick = self.ticks[-1]
        label = self.ticklabels[-1]
        x = self.convert_to_pixels(tick)
        half_width = label.winfo_width() / 2
        if x + half_width > self.scale.winfo_width():
            x = self.scale.winfo_width() - half_width
        label.place_configure(x=x)
        
    def on_configure(self, event):
        """Redisplay the ticks and the label so that they adapt to the new size of the scale."""
        self.display_value(self.scale.get())
        self.place_ticks()

if __name__ == '__main__':
    root = tk.Tk()
    root.geometry('400x300')
    style = ttk.Style(root)
    style.configure('my.Horizontal.TScale', sliderlength=10)

    s1 = tk.Scale(root, orient='horizontal', tickinterval=0.2, from_=-1, 
                  to=1, showvalue=True, resolution=0.1,  sliderlength=10)
    s2 = TtkScale(root, style='my.Horizontal.TScale', orient='horizontal', 
                  tickinterval=0.2, from_=-1, to=1, showvalue=True, 
                  digits=1)

    ttk.Label(root, text='tk.Scale').pack()
    s1.pack(fill='x')
    ttk.Label(root, text='ttk.Scale').pack()
    s2.pack(fill='x')

    root.mainloop()

A more complete version of this widget is available in the ttkwidgets module under the name TickScale.

这篇关于如何让 ttk.Scale 表现得更像 tk.Scale?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆