Превъртане на няколко списъчни кутии на Tkinter заедно

Имам няколко списъчни кутии на Tkinter, които превъртам заедно с помощта на една лента за превъртане, но СЪЩО бих искал те да превъртат заедно за активност на колелцето на мишката върху който и да е от списъчните кутии.

Как да стане това?

Настоящият ми код се основава на последния модел, обсъден тук: http://effbot.org/tkinterbook/listbox.htm Работи добре, когато се използва само лентата за превъртане, но списъците се превъртат независимо, когато се използва колелцето на мишката.


person BobC    schedule 01.11.2010    source източник
comment
Предполагам, че също би било приемливо да деактивирате колелцето на мишката над отделните списъци, ако това е възможно.   -  person BobC    schedule 01.11.2010


Отговори (4)


Решете проблема почти по същия начин, както го направихте, за да свържете двете джаджи към една лента за превъртане: създайте персонализирани обвързвания за колелцето на мишката и накарайте тези обвързвания да засягат и двата списъчни полета, а не само единия.

Единственият истински трик е да знаете, че получавате различни събития за колелото на мишката в зависимост от платформата: 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()
person Bryan Oakley    schedule 01.11.2010
comment
Под Ubuntu 10.10 все още виждам независимо превъртане на колелцето на мишката, когато мишката е над всеки списък. - person BobC; 01.11.2010
comment
@BobC: ако сте използвали горния код точно както е публикуван, да, ще видите това. Прочетохте ли частта, в която казвам, че Linux има различни събития за колелото на мишката? Ubuntu е Linux. - person Bryan Oakley; 01.11.2010
comment
да! В Linux колелцето на мишката използва събития ‹Button-4› и ‹Button-5›. За да бъда общ, обвързвам всичките 3. - person BobC; 01.11.2010
comment
Освен това event.delta на Linux е 120, така че следният фрагмент гарантира плавно превъртане: if (event.num == 4): delta = -1 elif (event.num == 5): delta = 1 else: delta = event .делта - person BobC; 01.11.2010
comment
Имах същия проблем. Просто забелязах нещо. Създадох списък със списъчни кутии и използвах for-цикъл, за да обвържа едно и също събитие с всеки един от тях и друг for-цикъл вътре в OnMouseWheel(), за да обходя всеки списък. Не работи с event.delta, защото списъците се превъртаха в различни посоки от тази, над която беше мишката ми. Това може да се поправи, като просто кажете -(event.delta). Работи на Windows, Python 3.5.1 - person Milan Todorovic; 09.02.2016

Знам, че това е доста старо, но мисля, че решението е малко по-просто от тези, предложени тук. Ако приемем, че винаги искате списъчните кутии да са в съгласие, тогава горните два отговора дори не са пълни решения - промяната на селекцията с помощта на клавишите със стрелки ще превърти единия списък, но не и другия.

И така, гледайки отговорите, попитах - защо не закачат обратното извикване на 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()
person Santiclause    schedule 05.07.2012

Ето текущото ми решение, кодирано като самостоятелна функция (да, трябва да е обект).

Характеристики/изисквания:

  • Той обработва произволен брой списъци (минимум 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()

Предложения за подобрения са добре дошли!

person BobC    schedule 03.11.2010
comment
Току-що забелязах, че кодирах твърдо root.title: Вероятно ще искате нещо различно. - person BobC; 03.11.2010

Направих много просто процедурно решение. След като разгледахте сайта с уроци за информация как да използвате лентата за превъртане за една джаджа (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()
person MattieG4    schedule 01.06.2020