2010-11-01 9 views
7

J'ai plusieurs listes de sélection Tkinter que je fais défiler ensemble en utilisant une seule barre de défilement, mais je voudrais aussi les faire défiler ensemble pour l'activité mousewheel sur l'une des listes.Faire défiler plusieurs listes de boîtes Tkinter ensemble

Comment faire?

Mon code actuel est basé sur le dernier modèle discuté ici: http://effbot.org/tkinterbook/listbox.htm Cela fonctionne très bien lorsque vous utilisez uniquement la barre de défilement, mais les zones de liste défilent indépendamment lorsque la roulette de la souris est utilisée.

+0

Je suppose qu'il serait également acceptable de désactiver la molette de la souris sur les listes individuelles, si cela est possible. – BobC

Répondre

9

Résolvez le problème de la même manière que vous avez connecté les deux widgets à une seule barre de défilement: créez des liaisons personnalisées pour la roulette de la souris et faites en sorte que ces liaisons affectent les deux listes plutôt qu'une seule.

Le seul vrai truc est de savoir que vous obtenez différents événements pour la roulette de la souris en fonction de la plate-forme: Windows et Mac obtient <MouseWheel> événements, linux et obtient <Button-4><Button-5> événements.

Voici un exemple, testé sur mon Mac avec 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() 
+0

Sous Ubunto 10.10, je vois toujours le défilement indépendant de la molette lorsque la souris est sur chaque liste. – BobC

+0

@BobC: si vous avez utilisé le code ci-dessus exactement comme affiché, oui, vous le verrez.Avez-vous lu la partie où je dis que Linux a des événements différents pour la molette de la souris? Ubuntu est linux. –

+0

Doh! Sous Linux, la roulette de la souris utilise les événements et . Pour être général, je lie tous les 3. – BobC

1

Voici ma solution actuelle, codée en fonction autonome (oui, il devrait être un objet).

Caractéristiques/Exigences:

  • Il gère un certain nombre de listes (minimum 1). Toutes les listes doivent actuellement avoir le même longueur. La largeur de chaque largeur de liste est ajustée pour correspondre au contenu.
  • Les zones de liste défilent ensemble en utilisant soit la molette de la souris, soit la barre de défilement .
  • Devrait fonctionner sur Windows, OSX et Linux, mais a été testé uniquement sur 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 d'amélioration sont les bienvenus!

+0

Juste remarqué que j'ai codé en dur root.title: Vous voudrez probablement quelque chose de différent. – BobC

7

Je sais que c'est assez vieux, mais je pense que la solution est un peu plus simple que celles qui sont décrites ici. En supposant que vous toujours voulez que les boîtes à liste soient en accord, alors les deux réponses ci-dessus ne sont même pas des solutions complètes - changer la sélection au moyen des touches fléchées fera défiler une liste mais pas l'autre. Donc, en regardant les réponses, j'ai demandé - pourquoi ne pas accrocher le callback yscrollcommand au lieu de simplement l'envoyer directement à la barre de défilement? Donc, je l'ai fait:

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