繁体   English   中英

Tkinter 可滚动框架上的滚动条在不应该以 2 倍速度滚动时

[英]Tkinter Scrollbar on scrollable frame scrolls at 2x speed when it shouldn't

我正在尝试使用 windows 使用此问题中找到的代码在 Tkinter 中创建一个可滚动的框架。 我还想让框架可以使用鼠标滚轮滚动,所以我从这个问题复制了 _on_mousewheel() function 。 现在的问题是,由于鼠标滚轮绑定到 canvas,on_mousewheel() function 使滚动条以 2 倍的速度滚动,当在其上使用鼠标滚轮时,它是正常速度。 第二个问题的答案提到了这个问题,但没有说明如何解决。

代码:

import tkinter as tk
from tkinter import *
from PIL import Image, ImageTk

SCROLL_CANVAS_H = 500
SCROLL_CANVAS_W = 1000

class ScrollbarFrame(tk.Frame):
    """
    Extends class tk.Frame to support a scrollable Frame 
    This class is independent from the widgets to be scrolled and 
    can be used to replace a standard tk.Frame
    """
    def __init__(self, parent, height, width):
        """ Initiates a scrollable frame with labels using specified
        height and width. Canvas is scrollable both over canvas and scrollbar.
        """

        super().__init__(parent)

        self.height = height
        self.width = width

        # Place the scrollbar on self, layout to the right
        self.v_scrollbar = tk.Scrollbar(self, orient="vertical")
        self.v_scrollbar.pack(side="right", fill="y")

        # The Canvas which supports the Scrollbar Interface, 
        # placed on self and layed out to the left.
        self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff", 
                                height = height, 
                                width = width)
        self.canvas.pack(side="left", fill="both", expand=True)

        # Attach scrollbar action to scroll of canvas
        self.canvas.configure(yscrollcommand=self.v_scrollbar.set)
        self.v_scrollbar.configure(command=self.canvas.yview)

        ## Allow canvas to be scrolled using mousewheel while hovering 
        ## over the canvas region.
        #self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)

        # Place a frame on the canvas, this frame will hold the child widgets
        # All widgets to be scrolled have to use this Frame as parent
        self.scrolled_frame = tk.Frame(self.canvas, background=self.canvas.cget('bg'))
        self.canvas.create_window((0, 0), window=self.scrolled_frame, anchor="nw")
        
        self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)

        # Configures the scrollregion of the Canvas dynamically
        self.scrolled_frame.bind("<Configure>", self.on_configure)

        # Reset the scroll region to encompass the inner frame
        self.scrolled_frame.bind("<Configure>", self.on_frame_configure)

    def on_configure(self, event):
        """Set the scroll region to encompass the scrolled frame"""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
    
    def on_mousewheel(self, event):
        """Allows canvas to be scrolled using mousewheel while hovering
        over canvas.
        """
        self.canvas.yview_scroll(-1 * (event.delta // 120), "units")

    def on_frame_configure(self, event):
        """Reset the scroll region to encompass the inner frame"""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))


class App(tk.Tk):
    def __init__(self):
        #initializes self as root
        tk.Tk.__init__(self)

        # add a new scrollable frame
        sbf = ScrollbarFrame(self, height = 500, width = 1000)
        sbf.grid(row=0, column=0, sticky='nsew')
        #sbf.pack(side="top", fill="both", expand=True)

        # Some data, layout into the sbf.scrolled_frame
        frame = sbf.scrolled_frame
        for row in range(50):
            text = "%s" % row
            tk.Label(frame, text=text,
                     width=3, borderwidth="1", relief="solid") \
                .grid(row=row, column=0)

            text = "this is the second column for row %s" % row
            tk.Label(frame, text=text,
                     background=sbf.scrolled_frame.cget('bg')) \
                .grid(row=row, column=1)

            text = "this is the third column for row %s" % row
            tk.Label(frame, text=text,
                     background=sbf.scrolled_frame.cget('bg')) \
                .grid(row=row, column=2)

if __name__ == "__main__":
    root = App()
    root.mainloop()

在 on_mousewheel() 方法中,我尝试确定是 canvas 还是使用鼠标滚轮的滚动条,然后使用 if 语句,以便 canvas 只会滚动鼠标滚轮不在滚动条上方。 但事实并非如此,滚动条在滚动时仍会以 2 倍速度滚动。

if event.widget != self.v_scrollbar:
            self.canvas.yview_scroll(-1 * (event.delta // 120), "units")

我还尝试将滚动条的移动除以更大的值

if event.widget == self.v_scrollbar: 
            self.canvas.yview_scroll(-1 * (event.delta // 240), "units")
        else:
            self.canvas.yview_scroll(-1 * (event.delta // 120), "units")

但这也不起作用。 有任何想法吗?

代替yview_scroll ,使用yview_moveto进行更精细的控制。 .02值可能应该与可以根据具体情况进行调整的属性(例如:滚动速度)相关联。 使用此方法无需检查您是否在滚动条或 canvas 上。

def on_mousewheel(self, event):
    """Allows canvas to be scrolled using mousewheel while hovering over canvas.
    """
    n = -event.delta / abs(event.delta)     #1 inversion
    p = self.v_scrollbar.get()[0] + (n*.02) #return scrollbar position and adjust it by a fraction
    self.canvas.yview_moveto(p)             #apply new position

这是一道基本的数学题。 当您调用self.canvas.yview_scroll时,您是在告诉它要滚动多少个单位 units取决于小部件,并且是yscrollincrement选项的值。 如果该选项的值为 0,则该值被视为小部件高度的 1/10。

您在任何时候滚动的像素越多,滚动的速度就越快。 您只需要对传递给yview_scroll方法的值进行基本数学运算和/或您可以将yscrollincrement值更改为一个小的 integer > 0 以准确说明一个“单位”有多少像素。

请注意,所有平台上的event.delta值都不相同。 在 OSX 上,该值是您应该滚动的单位数(即:不需要数学)。 在 Windows 系统上,该值始终至少为 120,因此您需要将该值除以 120 以获得要滚动的实际“单位”数。

最重要的是,您可以通过改变传递给yview_scroll的数字和/或您正在滚动的小部件的yscrollincrement选项的值来控制滚动速度。

如果您要说的是在 canvas 上滚动工作正常,但在滚动条上以双倍速度滚动,那可能是由于滚动条的内置行为。 可能发生的情况是您的 function 运行导致它滚动,然后默认行为运行导致它再次滚动。 您可以检查您的 function 是否仅在 canvas 上运行,或者您可以让您的 function 返回字符串“break”,以防止发生默认绑定。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM