一起滚动多个 Tkinter 列表框

发布于 2024-09-29 21:25:12 字数 260 浏览 6 评论 0原文

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

如何做到这一点?

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

I have multiple Tkinter listboxes that I have scrolling together using a single scrollbar, but I'd ALSO like them to scroll together for mousewheel activity over any of the listboxes.

How to do this?

My current code is based on the last pattern discussed here: http://effbot.org/tkinterbook/listbox.htm It works fine when using only the scrollbar, but the listboxes scroll independently when the mousewheel is used.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

橘味果▽酱 2024-10-06 21:25:13

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

因此,在查看答案时,我问 - 为什么他们不挂钩 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()

I know this is pretty old, but I think the solution is a bit more simple than those proferred here. Assuming that you always want the listboxes to be in agreement, then the above two answers aren't even complete solutions - changing selection by means of the arrow keys will scroll one listbox but not the other.

So, looking at the answers, I asked - why aren't they hooking the yscrollcommand callback instead of just sending it straight to the scrollbar? So, I did just that:

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()
情场扛把子 2024-10-06 21:25:13

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

唯一真正的技巧是知道您会根据平台获得不同的鼠标滚轮事件:Windows 和 Mac 获得 事件,linux 获得 code> 和 事件。

这是一个在我的 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()

Solve the problem pretty much the same way as you did to connect the two widgets to a single scrollbar: create custom bindings for the mousewheel and have those bindings affect both listboxes rather than just one.

The only real trick is knowing that you get different events for the mousewheel depending on the platform: windows and the Mac gets <MouseWheel> events, linux gets <Button-4> and <Button-5> events.

Here's an example, tested on my Mac with 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()
心是晴朗的。 2024-10-06 21:25:13

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

特征/要求:

  • 它处理任意数量的列表
    (至少 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()

欢迎提出改进建议!

Here's my current solution, coded as a stand-alone function (yes, it should be an object).

Features/requirements:

  • It handles any number of lists
    (minimum 1).
  • All lists must presently have the
    same length.
  • The width of each listbox width is
    adjusted to match the content.
  • The listboxes scroll together using
    either the mouse wheel or the
    scrollbar.
  • Should work on Windows, OSX and
    Linux, but has been tested only on
    Linux.

Code:

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

Suggestions for improvements are welcome!

一身软味 2024-10-06 21:25:13

我做了一个非常简单的程序解决方案。在查看教程点站点以获取有关如何使用一个小部件的滚动条的信息后(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()

I've made a very simple procedural solution. After looking on the tutorials point site for information on how to use the scrollbar for one widget (https://www.tutorialspoint.com/python/tk_scrollbar.htm), I adapted it to scroll through multiple text boxes at the same time (you can change the code so it uses list boxes). This solution will update all three textboxes when you use the scroll bar.

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()
殊姿 2024-10-06 21:25:13

下面的清单是 John K. Ousterhout 的书 Tcl and Tk Toolkit - 2009,第 18.9.2 Synchronized Scrolling of Multiple Widgets 章节中 Tcl 代码示例的 Python 实现:

不需要直接连接滚动条和小部件。一个程序
可以在两者之间使用来执行诸如滚动多个小部件之类的事情
单个滚动条。获得准确同步滚动的技巧是
识别将控制滚动条和从属部件的主部件
小部件。

import tkinter as tk
from tkinter import ttk

# Scrolling multiple listboxes together


class App:
    def __init__(self):
        self.root = tk.Tk()
        self.root.config(padx=10, pady=10)
        self.vsb = ttk.Scrollbar(command=self.scrollbar_command)
        self.lb1 = tk.Listbox(
            self.root, yscrollcommand=self.lb1_yscrollcommand)
        self.lb2 = tk.Listbox(
            self.root, yscrollcommand=self.lb2_yscrollcommand)
        self.vsb.pack(side="right", fill="y")
        self.lb1.pack(side="left", fill="both", expand=True)
        self.lb2.pack(side="left", fill="both", expand=True)
        # Generate some dummy data
        for i in range(100):
            self.lb1.insert("end", f"item lb1 {i}")
            self.lb2.insert("end", f"item lb2 {i}")
        self.root.mainloop()

    def lb1_yscrollcommand(self, *args):
        """Only one listbox should update the scrollbar.
        Listbox 'lb1' plays the role of the master listbox.
        It controls the scrollbar and the 'slave' listbox 'lb2' """

        print("lb1_yscrollcommand:", args)
        self.vsb.set(*args)  # update the scrollbar
        self.lb2.yview("moveto", args[0])  # update the 'slave' listbox

    def lb2_yscrollcommand(self, *args):
        """Slave listbox 'lb2' controls the master listbox 'lb1' """

        print("lb2_yscrollcommand:", args)
        self.lb1.yview("moveto", args[0])  # update the 'master' listbox

    def scrollbar_command(self, *args):
        """Scrollbar controls listboxes"""
        self.lb1.yview(*args)
        self.lb2.yview(*args)


app = App()

在 Linux 和 Windows 上进行了测试。

The listing below is a Python implementation of the Tcl code example from the John K. Ousterhout's book Tcl and Tk Toolkit - 2009, chapter 18.9.2 Synchronized Scrolling of Multiple Widgets:

It is not necessary to connect the scrollbar and widget directly. A procedure
can be used in between to do things like scrolling multiple widgets with a
single scrollbar. The trick to getting accurate synchronized scrolling is to
identify a master widget that will control the scrollbar and the slave
widgets.

import tkinter as tk
from tkinter import ttk

# Scrolling multiple listboxes together


class App:
    def __init__(self):
        self.root = tk.Tk()
        self.root.config(padx=10, pady=10)
        self.vsb = ttk.Scrollbar(command=self.scrollbar_command)
        self.lb1 = tk.Listbox(
            self.root, yscrollcommand=self.lb1_yscrollcommand)
        self.lb2 = tk.Listbox(
            self.root, yscrollcommand=self.lb2_yscrollcommand)
        self.vsb.pack(side="right", fill="y")
        self.lb1.pack(side="left", fill="both", expand=True)
        self.lb2.pack(side="left", fill="both", expand=True)
        # Generate some dummy data
        for i in range(100):
            self.lb1.insert("end", f"item lb1 {i}")
            self.lb2.insert("end", f"item lb2 {i}")
        self.root.mainloop()

    def lb1_yscrollcommand(self, *args):
        """Only one listbox should update the scrollbar.
        Listbox 'lb1' plays the role of the master listbox.
        It controls the scrollbar and the 'slave' listbox 'lb2' """

        print("lb1_yscrollcommand:", args)
        self.vsb.set(*args)  # update the scrollbar
        self.lb2.yview("moveto", args[0])  # update the 'slave' listbox

    def lb2_yscrollcommand(self, *args):
        """Slave listbox 'lb2' controls the master listbox 'lb1' """

        print("lb2_yscrollcommand:", args)
        self.lb1.yview("moveto", args[0])  # update the 'master' listbox

    def scrollbar_command(self, *args):
        """Scrollbar controls listboxes"""
        self.lb1.yview(*args)
        self.lb2.yview(*args)


app = App()

Tested on Linux and on Windows.

美煞众生 2024-10-06 21:25:13

不久前查看了这个问题并且不理解答案后,我现在已经使用鼠标滚轮制作并理解了我自己对这个问题的实现,如下所示:

滚动的绑定参数是操作系统之间有所不同。在 Windows 上,它是 '',在 Mac 上,它是 <'Button-4'>,在 Linux 上,它是 <'按钮-5'>。因此,首先您可以像这样找到用户的操作系统:

from sys import platform
global OS
if platform == 'linux' or platform == 'linux2':
    OS = 'Linux'
elif platform == 'darwin':
    OS = 'Mac'
elif platform == 'win32' or platform == 'win64':
    OS = 'Windows'
else:
    raise Exception("An error occurred in determining user's operating system.")

声明和绑定列表框小部件:
如果没有将 exportselection = False 参数放入小部件声明中,则 Tkinter 在任何时候都只允许一个列表框进行选择。即使列表框可以一起滚动,用户仍然可以通过滚动鼠标滚轮以外的方式来扰乱滚动同步。

  • <'B1-Leave'> 允许用户通过单击鼠标并将其拖出小部件来滚动列表框。
  • <'ButtonRelease-1'> 允许用户通过单击并拖动来更改列表框的辅助选择
  • <'Key'> 允许用户滚动带有箭头键的列表框

这些事件必须停止以保持滚动同步,这必须通过事件击中 break 语句来完成。即使事件被传递给另一个函数,如果它没有遇到 break,它仍然会继续。要中断绑定语句中的事件,'break' 必须是字符串。例如:

LB1 = Listbox(parent, bg = White, height = 20, width = 1, relief = 'sunken', exportselection = False, selectmode = 'single') #Width = 1 note on line 75
LB1.grid(row = 0, column = 0)
LB1.bind('<<ListboxSelect>>', lambda event: SearchListboxClick(event, listOfListboxes, LB1))
LB1.bind('<MouseWheel>', lambda event: CombinedListboxScroll(event, listOfListboxes))
LB1.bind('<Button-4>', lambda event: CombinedListboxScroll(event, listOfListboxes))
LB1.bind('<Button-5>', lambda event: CombinedListboxScroll(event, listOfListboxes))
LB1.bind('<B1-Leave>', lambda event: 'break') #Stops listbox scrolling by clicking mouse and dragging
LB1.bind('<ButtonRelease-1>', lambda event: 'break') #Stops listbox secondary selection being changed by mouse click and drag
LB1.bind('<Key>', lambda event: 'break') #Stops arrow keys from 

对所有列表框重复此操作,并创建要同步滚动的列表框的列表:

listOfListboxes = [LB1, LB2, LB3, LB4, LB5]

然后,要滚动列表框,您可以针对您知道的任何操作系统执行此操作在任何操作系统上运行或适应任何操作系统,如下所示(操作系统之间的滚动速度不同,需要不同的数学):

def CombinedListboxScroll(event, listOfListboxes):
    '''Takes list of listboxes and scrolls all of them simultaneously.'''
    if OS == 'Windows':
        for lb in listOfListboxes:
            lb.yview_scroll(int(-1 * (event.delta/120)), 'units')
    elif OS == 'Mac':
        for lb in listOfListboxes:
            lb.yview_scroll(int(-1 * event.delta), 'units')
    else: #OS == Linux
        for lb in listOfListboxes:
            lb.yview_scroll(int(-1 * (event.delta/120)), 'units')

此外,要将所有列表框的选择合并到用户的一行中,您可以使用一个函数像这样:

def CombinedListboxSelect(event, listOfListboxes, boxClicked):
    '''Takes a list of listboxes, and sets their selection to an index clicked by the user in boxClicked.'''
    if boxClicked.curselection() != (): #To stop blank tuples erroring the function. I don't know why they are passed sometimes
        selectedIndex = (boxClicked.curselection())[0] #Parenthesis have to be like this
        boxClicked.selection_set(selectedIndex)
        for listbox in listOfListboxes:
            listbox.selection_clear(0, END) #If this is not done, the last selected item will stay selected on top of the new one also being selected.
            listbox.selection_set(selectedIndex)

After looking at this question a while ago and not understanding the answers, I have now made and understood my own implementation of this problem with the mouse wheel, as below:

The bind argument for scrolling is different between operating systems. On Windows it is '<MouseWheel>', on Mac it is <'Button-4'>, and on Linux it is <'Button-5'>. So, first you can find the operating system of the user like this:

from sys import platform
global OS
if platform == 'linux' or platform == 'linux2':
    OS = 'Linux'
elif platform == 'darwin':
    OS = 'Mac'
elif platform == 'win32' or platform == 'win64':
    OS = 'Windows'
else:
    raise Exception("An error occurred in determining user's operating system.")

Declaring and binding listbox widgets:
Tkinter will only allow one listbox to have a selection at any time if the exportselection = False argument is not put into the widget declaration. Even if the listboxes can scroll together, there are still ways the user can mess up the scroll synchronization by scrolling through means other than the mouse wheel.

  • <'B1-Leave'> allows the user to scroll listboxes by clicking the mouse and dragging out of the widget.
  • <'ButtonRelease-1'> allows the user to change the secondary selection of the listbox by clicking and dragging
  • <'Key'> allows the user to scroll listboxes with the arrow keys

These events have to be stopped to maintain scroll synchronization, which has to be done by the event hitting a break statement. Even if the event is passed to another function, if it doesn't hit a break, it will still proceed. To break off an event in a bind statement, 'break' must be a string. For example:

LB1 = Listbox(parent, bg = White, height = 20, width = 1, relief = 'sunken', exportselection = False, selectmode = 'single') #Width = 1 note on line 75
LB1.grid(row = 0, column = 0)
LB1.bind('<<ListboxSelect>>', lambda event: SearchListboxClick(event, listOfListboxes, LB1))
LB1.bind('<MouseWheel>', lambda event: CombinedListboxScroll(event, listOfListboxes))
LB1.bind('<Button-4>', lambda event: CombinedListboxScroll(event, listOfListboxes))
LB1.bind('<Button-5>', lambda event: CombinedListboxScroll(event, listOfListboxes))
LB1.bind('<B1-Leave>', lambda event: 'break') #Stops listbox scrolling by clicking mouse and dragging
LB1.bind('<ButtonRelease-1>', lambda event: 'break') #Stops listbox secondary selection being changed by mouse click and drag
LB1.bind('<Key>', lambda event: 'break') #Stops arrow keys from 

Repeat this for all listboxes, and create a list of the listboxes you want to scroll synchronously:

listOfListboxes = [LB1, LB2, LB3, LB4, LB5]

Then, to scroll your listboxes, you can either do so for whatever operating system you know you will be running on, or accomodating for any operating system, like this (the scroll speeds are different between operating systems, necessitating the different math):

def CombinedListboxScroll(event, listOfListboxes):
    '''Takes list of listboxes and scrolls all of them simultaneously.'''
    if OS == 'Windows':
        for lb in listOfListboxes:
            lb.yview_scroll(int(-1 * (event.delta/120)), 'units')
    elif OS == 'Mac':
        for lb in listOfListboxes:
            lb.yview_scroll(int(-1 * event.delta), 'units')
    else: #OS == Linux
        for lb in listOfListboxes:
            lb.yview_scroll(int(-1 * (event.delta/120)), 'units')

Also, to combine the selection of all the listboxes into one row for the user, you could use a function like this:

def CombinedListboxSelect(event, listOfListboxes, boxClicked):
    '''Takes a list of listboxes, and sets their selection to an index clicked by the user in boxClicked.'''
    if boxClicked.curselection() != (): #To stop blank tuples erroring the function. I don't know why they are passed sometimes
        selectedIndex = (boxClicked.curselection())[0] #Parenthesis have to be like this
        boxClicked.selection_set(selectedIndex)
        for listbox in listOfListboxes:
            listbox.selection_clear(0, END) #If this is not done, the last selected item will stay selected on top of the new one also being selected.
            listbox.selection_set(selectedIndex)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文