代码之家  ›  专栏  ›  技术社区  ›  Dalker

如何制作ttk。Scale的行为更像tk。规模

  •  7
  • Dalker  · 技术社区  · 7 年前

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

    然而,由于某种原因 ttk.Scale 小部件没有两个非常有用的 tk.Scale 小装置: showvalue tickinterval (参见 reference ). 这很奇怪,因为这些更多的是关于行为而不是关于外表。

    在保持一个稳定的基础上,将这两个选项“融合”起来会很好 ttk 看 下面的代码是我笨拙的尝试。问题是:有更好的方法吗?(显然,除了将整个内容封装在一个类中之外)以及如何合理地获得半自动化的 滴答声间隔

    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()
    

    在实际“滑动”刻度之前,前面代码的结果可能如下所示,Tk刻度在左侧,Ttk刻度在右侧(每个操作系统/窗口管理器的结果明显不同):

    enter image description here

    1 回复  |  直到 7 年前
        1
  •  5
  •   Community CDub    5 年前

    您可以使用自动方式放置刻度和显示值的标签 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 选项

    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()
    

    screenshot

    此小部件的更完整版本可在 ttkwidgets 名为TickScale的模块。