【问题标题】:Scrolling multiple Tkinter listboxes together一起滚动多个 Tkinter 列表框
【发布时间】:2011-05-03 06:59:35
【问题描述】:

我有多个 Tkinter 列表框,我使用单个滚动条一起滚动,但我也希望它们一起滚动以在任何列表框上进行鼠标滚轮活动。

如何做到这一点?

我当前的代码基于此处讨论的最后一个模式:http://effbot.org/tkinterbook/listbox.htm 仅使用滚动条时工作正常,但使用鼠标滚轮时列表框独立滚动。

【问题讨论】:

  • 我想如果可能的话,在单个列表框上禁用鼠标滚轮也是可以接受的。

标签: python listbox scrollbar tkinter mousewheel


【解决方案1】:

我知道这已经很老了,但我认为解决方案比这里提供的解决方案要简单一些。假设您总是希望列表框一致,那么以上两个答案甚至都不是完整的解决方案 - 通过箭头键更改选择将滚动一个列表框,而不滚动另一个列表框。

所以,看着答案,我问 - 为什么他们不挂钩 yscrollcommand 回调而不是直接将其发送到滚动条?所以,我就是这样做的:

try:
    from Tkinter import *
except ImportError:
    from tkinter import *


class MultipleScrollingListbox(Tk):

    def __init__(self):
        Tk.__init__(self)
        self.title('Scrolling Multiple Listboxes')

        #the shared scrollbar
        self.scrollbar = Scrollbar(self, orient='vertical')

        #note that yscrollcommand is set to a custom method for each listbox
        self.list1 = Listbox(self, yscrollcommand=self.yscroll1)
        self.list1.pack(fill='y', side='left')

        self.list2 = Listbox(self, yscrollcommand=self.yscroll2)
        self.list2.pack(expand=1, fill='both', side='left')

        self.scrollbar.config(command=self.yview)
        self.scrollbar.pack(side='right', fill='y')

        #fill the listboxes with stuff
        for x in xrange(30):
            self.list1.insert('end', x)
            self.list2.insert('end', x)

    #I'm sure there's probably a slightly cleaner way to do it than this
    #Nevertheless - whenever one listbox updates its vertical position,
    #the method checks to make sure that the other one gets updated as well.
    #Without the check, I *think* it might recurse infinitely.
    #Never tested, though.
    def yscroll1(self, *args):
        if self.list2.yview() != self.list1.yview():
            self.list2.yview_moveto(args[0])
        self.scrollbar.set(*args)

    def yscroll2(self, *args):
        if self.list1.yview() != self.list2.yview():
            self.list1.yview_moveto(args[0])
        self.scrollbar.set(*args)

    def yview(self, *args):
        self.list1.yview(*args)
        self.list2.yview(*args)


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

【讨论】:

    【解决方案2】:

    解决问题的方法与将两个小部件连接到单个滚动条的方法几乎相同:为鼠标滚轮创建自定义绑定,并让这些绑定影响两个列表框,而不仅仅是一个。

    唯一真正的诀窍是知道您会根据平台获得不同的鼠标滚轮事件:Windows 和 Mac 获得 <MouseWheel> 事件,linux 获得 <Button-4><Button-5> 事件。

    这是一个示例,在我的 Mac 上使用 python 2.5 进行了测试:

    import Tkinter as tk
    
    class App:
        def __init__(self):
            self.root=tk.Tk()
            self.vsb = tk.Scrollbar(orient="vertical", command=self.OnVsb)
            self.lb1 = tk.Listbox(self.root, yscrollcommand=self.vsb.set)
            self.lb2 = tk.Listbox(self.root, yscrollcommand=self.vsb.set)
            self.vsb.pack(side="right",fill="y")
            self.lb1.pack(side="left",fill="x", expand=True)
            self.lb2.pack(side="left",fill="x", expand=True)
            self.lb1.bind("<MouseWheel>", self.OnMouseWheel)
            self.lb2.bind("<MouseWheel>", self.OnMouseWheel)
            for i in range(100):
                self.lb1.insert("end","item %s" % i)
                self.lb2.insert("end","item %s" % i)
            self.root.mainloop()
    
        def OnVsb(self, *args):
            self.lb1.yview(*args)
            self.lb2.yview(*args)
    
        def OnMouseWheel(self, event):
            self.lb1.yview("scroll", event.delta,"units")
            self.lb2.yview("scroll",event.delta,"units")
            # this prevents default bindings from firing, which
            # would end up scrolling the widget twice
            return "break"
    
    app=App()
    

    【讨论】:

    • 在 Ubunto 10.10 下,当鼠标悬停在每个列表框上时,我仍然看到独立的鼠标滚轮滚动。
    • @BobC:如果您完全按照发布的方式使用上述代码,是的,您会看到的。你读过我说linux有不同的鼠标滚轮事件的部分吗? Ubuntu 是 linux。
    • 噢!在 Linux 上,鼠标滚轮使用事件 。一般来说,我绑定了所有 3 个。
    • 另外,Linux event.delta 是 120,所以下面的 sn-p 确保平滑滚动: if (event.num == 4): delta = -1 elif (event.num == 5 ): delta = 1 其他: delta = event.delta
    • 我遇到了同样的问题。刚注意到一件事。我创建了一个列表框列表,并使用一个 for 循环将相同的事件绑定到每个列表框,并使用 OnMouseWheel() 中的另一个 for 循环来迭代每个列表框。它不适用于 event.delta,因为列表框的滚动方向与我的鼠标悬停的方向不同。这可以通过说 -(event.delta) 来解决。适用于 Windows,Python 3.5.1
    【解决方案3】:

    这是我当前的解决方案,编码为独立函数(是的,它应该是一个对象)。

    功能/要求:

    • 它可以处理任意数量的列表 (最少 1 个)。
    • 所有列表目前必须具有 长度相同。
    • 每个列表框宽度的宽度为 调整以匹配内容。
    • 列表框一起滚动使用 鼠标滚轮或 滚动条。
    • 应该可以在 Windows、OSX 和 Linux,但仅在 Linux。

    代码:

    def showLists(l, *lists):
        """
        Present passed equal-length lists in adjacent scrollboxes.
        """
        # This exists mainly for me to start learning about Tkinter.
        # This widget reqires at least one list be passed, and as many additional
        # lists as desired.  Each list is displayed in its own listbox, with
        # additional listboxes added to the right as needed to display all lists.
        # The width of each listbox is set to match the max width of its contents.
        # Caveat: Too wide or too many lists, and the widget can be wider than the screen!
        # The listboxes scroll together, using either the scrollbar or mousewheel.
    
        # :TODO: Refactor as an object with methods.
        # :TODO: Move to a separate file when other widgets are built.
    
        # Check arguments
        if (l is None) or (len(l) < 1):
            return
        listOfLists = [l]     # Form a list of lists for subsequent processing
        listBoxes = []  # List of listboxes
        if len(lists) > 0:
            for list in lists:
                # All lists must match length of first list
                # :TODO: Add tail filling for short lists, with error for long lists
                if len(list) != len(l):
                    return
                listOfLists.append(list)
    
        import Tkinter
    
        def onVsb(*args):
            """
            When the scrollbar moves, scroll the listboxes.
            """
            for lb in listBoxes:
                lb.yview(*args)
    
        def onMouseWheel(event):
            """
            Convert mousewheel motion to scrollbar motion.
            """
            if (event.num == 4):    # Linux encodes wheel as 'buttons' 4 and 5
                delta = -1
            elif (event.num == 5):
                delta = 1
            else:                   # Windows & OSX
                delta = event.delta
            for lb in listBoxes:
                lb.yview("scroll", delta, "units")
            # Return 'break' to prevent the default bindings from
            # firing, which would end up scrolling the widget twice.
            return "break"
    
        # Create root window and scrollbar
        root = Tkinter.Tk()
        root.title('Samples w/ time step < 0')
        vsb = Tkinter.Scrollbar(root, orient=Tkinter.VERTICAL, command=onVsb)
        vsb.pack(side=Tkinter.RIGHT, fill=Tkinter.Y)
    
        # Create listboxes
        for i in xrange(0,len(listOfLists)):
            lb = Tkinter.Listbox(root, yscrollcommand=vsb.set)
            lb.pack(side=Tkinter.LEFT, fill=Tkinter.BOTH)
            # Bind wheel events on both Windows/OSX & Linux;
            lb.bind("<MouseWheel>", onMouseWheel)
            lb.bind("<Button-4>", onMouseWheel)
            lb.bind("<Button-5>", onMouseWheel)
            # Fill the listbox
            maxWidth = 0
            for item in listOfLists[i]:
                s = str(item)
                if len(s) > maxWidth:
                    maxWidth = len(s)
                lb.insert(Tkinter.END, s)
            lb.config(width=maxWidth+1)
            listBoxes.append(lb)        # Add listbox to list of listboxes
    
        # Show the widget
        Tkinter.mainloop()
    # End of showLists()
    

    欢迎提出改进建议!

    【讨论】:

    • 刚刚注意到我对 root.title 进行了硬编码:您可能想要一些不同的东西。
    【解决方案4】:

    我制定了一个非常简单的程序解决方案。在查看教程点站点以获取有关如何将滚动条用于一个小部件 (https://www.tutorialspoint.com/python/tk_scrollbar.htm) 的信息后,我对其进行了调整以同时滚动多个文本框(您可以更改代码以使其使用列表框)。当您使用滚动条时,此解决方案将更新所有三个文本框。

    import tkinter as tk
    
    HEIGHT = 200
    WIDTH = 300
    
    def scroll(x, y):
        l_textbox.yview(x,y)
        m_textbox.yview(x,y)
        r_textbox.yview(x,y)
    
    root = tk.Tk()
    
    canvas = tk.Canvas(root,height = HEIGHT, width = WIDTH, bg = "white")
    canvas.pack()
    
    frame = tk.Frame(root, bg ='white')
    frame.place(relx=0,rely=0,relwidth=1,relheight=1)
    
    scrollbar = tk.Scrollbar(frame)
    
    l_label = tk.Label (frame, text = "Left")
    l_label.place(relx=0, rely=0)
    
    m_label = tk.Label (frame, text= "Middle")
    m_label.place(relx=0.3, rely=0)
    
    r_label = tk.Label (frame, text= "Right")
    r_label.place(relx=0.6, rely=0)
    
    l_textbox = tk.Text(frame, yscrollcommand = scrollbar.set)
    l_textbox.config(font = ('Arial',9))
    l_textbox.place(relx=0, rely=0.2,relwidth=0.3,relheight=0.8)
    
    m_textbox = tk.Text(frame, yscrollcommand = scrollbar.set)
    m_textbox.config(font = ('Arial',9))
    m_textbox.place(relx=0.3, rely=0.2,relwidth=0.3,relheight=0.8)
    
    r_textbox = tk.Text(frame, yscrollcommand = scrollbar.set)
    r_textbox.config(font = ('Arial',9))
    r_textbox.place(relx=0.6, rely=0.2,relwidth=0.3,relheight=0.8)
    
    scrollbar.config( command = scroll)
    scrollbar.place(relx = 0.9, relwidth = 0.1,relheight = 1)
    
    for i in range(0, 100):
        l_textbox.insert(tk.INSERT, str(i)+"\n")
        m_textbox.insert(tk.INSERT, str(i)+"\n")
        r_textbox.insert(tk.INSERT, str(i)+"\n")
        l_textbox.place()
        m_textbox.place()
        r_textbox.place()
    
    root.mainloop()
    

    【讨论】:

      猜你喜欢
      • 2019-11-26
      • 2018-07-31
      • 2020-04-28
      • 1970-01-01
      • 2013-02-03
      • 1970-01-01
      • 2017-09-23
      • 2017-09-23
      • 2017-07-31
      相关资源
      最近更新 更多