2010-11-01 119 views
7

我有多個Tkinter列表框,我使用單個滾動條一起滾動,但我也想讓它們一起滾動以查找任何列表框中的滾輪活動。將多個Tkinter列表框一起滾動

如何做到這一點?

我現在的代碼是基於這裏討論的最後一個模式:http://effbot.org/tkinterbook/listbox.htm只使用滾動條時工作正常,但當使用鼠標滾輪時,列表框獨立滾動。

+0

我想如果可能的話,禁用鼠標滾輪在單個列表框上也是可以接受的。 – BobC 2010-11-01 06:45:46

回答

9

解決問題的方式與將兩個小部件連接到單個滾動條的方式幾乎相同:爲鼠標滾輪創建自定義綁定,並讓這些綁定影響兩個列表框,而不僅僅是一個。

唯一真正的訣竅是知道您得到取決於平臺上的鼠標滾輪不同的事件: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() 
+0

在Ubunto 10.10下,當鼠標懸停在每個列表框上時,我仍然看到獨立的鼠標滾輪。 – BobC 2010-11-01 18:58:26

+0

@BobC:如果您完全按照上面的代碼使用上面的代碼,是的,您會看到。您是否閱讀過我說linux有鼠標滾輪不同事件的部分? Ubuntu是linux。 – 2010-11-01 19:07:03

+0

Doh!在Linux上,鼠標滾輪使用事件。通常,我約束所有3. – BobC 2010-11-01 19:24:20

1

這是我目前的解決方案,編碼爲一個獨立的功能(是的,它應該是一個對象)。

特點/要求:

  • 它處理任何數量的列表 (最小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() 

改進建議,歡迎!

+0

剛注意到我硬編碼的root.title:你可能會想要一些不同的東西。 – BobC 2010-11-03 04:59:23

7

我知道這已經很老了,但我認爲解決方案比在這裏訓練的人更簡單。假設你總是希望列表框一致,那麼上述兩個答案甚至不是完整的解決方案 - 通過箭頭鍵改變選擇將滾動一個列表框,但不滾動另一個列表框。

所以,看着答案,我問 - 爲什麼他們不掛鉤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()