简体   繁体   English

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

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

I'm trying to create a scrollable frame in Tkinter using windows, using the code found in this question .我正在尝试使用 windows 使用此问题中找到的代码在 Tkinter 中创建一个可滚动的框架。 I also wanted to have the frame be scrollable using the mouse-wheel, and so I copied over the _on_mousewheel() function from this question .我还想让框架可以使用鼠标滚轮滚动,所以我从这个问题复制了 _on_mousewheel() function 。 The issue now is that because the mouse-wheel is binded to the canvas, the on_mousewheel() function makes the scrollbar scroll at 2x it's normal speed when using the mouse-wheel over it.现在的问题是,由于鼠标滚轮绑定到 canvas,on_mousewheel() function 使滚动条以 2 倍的速度滚动,当在其上使用鼠标滚轮时,它是正常速度。 The answer in the second question mentioned this issue, but didn't explain how to solve it.第二个问题的答案提到了这个问题,但没有说明如何解决。

Code:代码:

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

In the on_mousewheel() method, I've tried identifying whether it was the canvas or the scrollbar that the mousewheel was used on, and then using an if statement so that the canvas will only scroll with the mousewheel is not over the scrollbar.在 on_mousewheel() 方法中,我尝试确定是 canvas 还是使用鼠标滚轮的滚动条,然后使用 if 语句,以便 canvas 只会滚动鼠标滚轮不在滚动条上方。 But this doesn't, the scrollbar will still scroll at 2x speed when wheeling over it.但事实并非如此,滚动条在滚动时仍会以 2 倍速度滚动。

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

I've also tried dividing the movement of the scrollbar by a larger value我还尝试将滚动条的移动除以更大的值

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

but this doesn't work either.但这也不起作用。 Any ideas?有任何想法吗?

Instead of yview_scroll , use yview_moveto for finer control.代替yview_scroll ,使用yview_moveto进行更精细的控制。 The .02 value should probably be tied to a property (ex: scrollspeed) that can be adjusted on a case by case basis. .02值可能应该与可以根据具体情况进行调整的属性(例如:滚动速度)相关联。 There is no need to check if you are on the scrollbar or canvas with this method.使用此方法无需检查您是否在滚动条或 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

This is a basic math problem.这是一道基本的数学题。 When you call self.canvas.yview_scroll , you are telling it how many units to scroll.当您调用self.canvas.yview_scroll时,您是在告诉它要滚动多少个单位 units is dependent on the widget, and is the value of the yscrollincrement option. units取决于小部件,并且是yscrollincrement选项的值。 If the value of that option is 0, the value is treated as 1/10 the height of the widget.如果该选项的值为 0,则该值被视为小部件高度的 1/10。

The more pixels you scroll at any one time, the faster it will scroll.您在任何时候滚动的像素越多,滚动的速度就越快。 You just need to do basic math on the value being passed to the yview_scroll method and/or you can change the yscrollincrement value to a small integer > 0 to say exactly how many pixels one "unit" is.您只需要对传递给yview_scroll方法的值进行基本数学运算和/或您可以将yscrollincrement值更改为一个小的 integer > 0 以准确说明一个“单位”有多少像素。

Note that the event.delta value is not the same on all platforms.请注意,所有平台上的event.delta值都不相同。 On OSX the value is the number of units you should scroll (ie: no math is required).在 OSX 上,该值是您应该滚动的单位数(即:不需要数学)。 On Windows systems the value is always at least 120, so you need to divide that value by 120 to get the number of actual "units" to scroll.在 Windows 系统上,该值始终至少为 120,因此您需要将该值除以 120 以获得要滚动的实际“单位”数。

The bottom line is that you control the speed of the scrolling by varying the number passed to yview_scroll and/or the value of the yscrollincrement option of the widget you are scrolling.最重要的是,您可以通过改变传递给yview_scroll的数字和/或您正在滚动的小部件的yscrollincrement选项的值来控制滚动速度。

If what you're saying is that the scrolling works correctly when over the canvas but scrolling at double speed over the scrollbar, that could be due to the built-in behavior of the scrollbar.如果您要说的是在 canvas 上滚动工作正常,但在滚动条上以双倍速度滚动,那可能是由于滚动条的内置行为。 What might be happening is that your function runs causing it to scroll, and then the default behavior runs which causes it to scroll again.可能发生的情况是您的 function 运行导致它滚动,然后默认行为运行导致它再次滚动。 You can either check that your function only runs when over the canvas, or you can have your function return the string "break" which prevents the default binding from happening.您可以检查您的 function 是否仅在 canvas 上运行,或者您可以让您的 function 返回字符串“break”,以防止发生默认绑定。

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

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