Полоса прокрутки Tkinter на прокручиваемом кадре прокручивается с удвоенной скоростью, когда этого не должно быть - PullRequest
1 голос
/ 07 августа 2020

Я пытаюсь создать прокручиваемый фрейм в Tkinter, используя windows, используя код, найденный в этом вопросе . Я также хотел, чтобы фрейм можно было прокручивать с помощью колеса мыши, поэтому я скопировал функцию _on_mousewheel () из этого вопроса . Теперь проблема в том, что, поскольку колесо мыши привязано к холсту, функция on_mousewheel () заставляет полосу прокрутки прокручивать полосу прокрутки в 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 () я попытался определить, есть ли это был холст или полоса прокрутки, на которых использовалось колесо мыши, а затем использовался оператор if, чтобы холст прокручивался только при том, что колесо мыши не находилось над полосой прокрутки. Но это не так, полоса прокрутки по-прежнему будет прокручиваться с 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")

, но это тоже не работает. Есть идеи?

Ответы [ 2 ]

1 голос
/ 07 августа 2020

Это основная c математическая задача. Когда вы вызываете self.canvas.yview_scroll, вы сообщаете ему, сколько единиц прокрутить. единицы зависит от виджета и является значением параметра yscrollincrement. Если значение этого параметра равно 0, значение рассматривается как 1/10 высоты виджета.

Чем больше пикселей вы прокручиваете за один раз, тем быстрее он будет прокручиваться. Вам просто нужно выполнить базовые вычисления c над значением, передаваемым методу yview_scroll, и / или вы можете изменить значение yscrollincrement на небольшое целое число> 0, чтобы точно сказать, сколько пикселей в одной «единице».

Обратите внимание, что значение event.delta не одинаково на всех платформах. В OSX значение - это количество единиц , которые вы должны прокрутить (ie: математические вычисления не требуются). В системах Windows значение всегда составляет не менее 120, поэтому вам нужно разделить это значение на 120, чтобы получить количество фактических «единиц» для прокрутки.

Суть в том, что вы управляете скоростью прокрутку путем изменения числа, переданного в yview_scroll и / или значения параметра yscrollincrement виджета, который вы прокручиваете.

Если вы говорите, что прокрутка работает правильно, когда холст, но с двойной скоростью прокрутки полосы прокрутки, что может быть связано со встроенным поведением полосы прокрутки. Может случиться так, что ваша функция запускается, вызывая ее прокрутку, а затем запускается поведение по умолчанию, которое заставляет ее снова прокручивать. Вы можете либо проверить, что ваша функция запускается только над холстом, либо вы можете сделать так, чтобы ваша функция возвращала строку «break», которая предотвращает выполнение привязки по умолчанию.

1 голос
/ 07 августа 2020

Вместо yview_scroll используйте yview_moveto для более точного управления. Значение .02, вероятно, должно быть связано со свойством (например, scrollspeed), которое можно настраивать в каждом конкретном случае. При использовании этого метода нет необходимости проверять, находитесь ли вы на полосе прокрутки или холсте.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...