【问题标题】:tkinter listbox drag and drop with pythontkinter 列表框用 python 拖放
【发布时间】:2013-01-05 18:26:12
【问题描述】:

谁能指出我在哪里可以找到有关制作能够拖放项目以进行重新排列的列表框的信息?我发现了一些与 Perl 相关的东西,但我对那种语言一无所知,而且我对 tkinter 还很陌生,所以这很令人困惑。我知道如何生成列表框,但我不确定如何通过拖放重新排序。

【问题讨论】:

    标签: python listbox widget tkinter


    【解决方案1】:

    以下类是具有EXTENDED 选择模式的列表框,可以在多个选定项目周围拖动。

    • 保留默认选择机制(通过拖动和单击,包括按住 Ctrl 或 Shift),但不按住 Ctrl 拖动已选择的项目除外。
    • 要拖动所选内容,请将其中一个所选项目拖动到最后一个所选项目的下方或第一个所选项目的上方。
    • 要在拖动选择时滚动列表框,请使用鼠标滚轮或将光标移动到列表框顶部或底部附近或之外。 => 这可以改进:因为它绑定到B1‑Motion 事件,所以需要额外移动鼠标才能继续滚动。在较长的列表框中感觉有问题。
    • 如果选择不连续,拖动将通过分别向上或向下移动未选择的项目使其连续。

    上面的意思是只拖一个item,需要先选中,然后再点击拖拽。

    import tkinter as tk;
    
    class ReorderableListbox(tk.Listbox):
        """ A Tkinter listbox with drag & drop reordering of lines """
        def __init__(self, master, **kw):
            kw['selectmode'] = tk.EXTENDED
            tk.Listbox.__init__(self, master, kw)
            self.bind('<Button-1>', self.setCurrent)
            self.bind('<Control-1>', self.toggleSelection)
            self.bind('<B1-Motion>', self.shiftSelection)
            self.bind('<Leave>',  self.onLeave)
            self.bind('<Enter>',  self.onEnter)
            self.selectionClicked = False
            self.left = False
            self.unlockShifting()
            self.ctrlClicked = False
        def orderChangedEventHandler(self):
            pass
    
        def onLeave(self, event):
            # prevents changing selection when dragging
            # already selected items beyond the edge of the listbox
            if self.selectionClicked:
                self.left = True
                return 'break'
        def onEnter(self, event):
            #TODO
            self.left = False
    
        def setCurrent(self, event):
            self.ctrlClicked = False
            i = self.nearest(event.y)
            self.selectionClicked = self.selection_includes(i)
            if (self.selectionClicked):
                return 'break'
    
        def toggleSelection(self, event):
            self.ctrlClicked = True
    
        def moveElement(self, source, target):
            if not self.ctrlClicked:
                element = self.get(source)
                self.delete(source)
                self.insert(target, element)
    
        def unlockShifting(self):
            self.shifting = False
        def lockShifting(self):
            # prevent moving processes from disturbing each other
            # and prevent scrolling too fast
            # when dragged to the top/bottom of visible area
            self.shifting = True
    
        def shiftSelection(self, event):
            if self.ctrlClicked:
                return
            selection = self.curselection()
            if not self.selectionClicked or len(selection) == 0:
                return
    
            selectionRange = range(min(selection), max(selection))
            currentIndex = self.nearest(event.y)
    
            if self.shifting:
                return 'break'
    
            lineHeight = 15
            bottomY = self.winfo_height()
            if event.y >= bottomY - lineHeight:
                self.lockShifting()
                self.see(self.nearest(bottomY - lineHeight) + 1)
                self.master.after(500, self.unlockShifting)
            if event.y <= lineHeight:
                self.lockShifting()
                self.see(self.nearest(lineHeight) - 1)
                self.master.after(500, self.unlockShifting)
    
            if currentIndex < min(selection):
                self.lockShifting()
                notInSelectionIndex = 0
                for i in selectionRange[::-1]:
                    if not self.selection_includes(i):
                        self.moveElement(i, max(selection)-notInSelectionIndex)
                        notInSelectionIndex += 1
                currentIndex = min(selection)-1
                self.moveElement(currentIndex, currentIndex + len(selection))
                self.orderChangedEventHandler()
            elif currentIndex > max(selection):
                self.lockShifting()
                notInSelectionIndex = 0
                for i in selectionRange:
                    if not self.selection_includes(i):
                        self.moveElement(i, min(selection)+notInSelectionIndex)
                        notInSelectionIndex += 1
                currentIndex = max(selection)+1
                self.moveElement(currentIndex, currentIndex - len(selection))
                self.orderChangedEventHandler()
            self.unlockShifting()
            return 'break'
    

    【讨论】:

      【解决方案2】:

      如果您将MULTIPLE 处理为selectmode(而不是SINGLE),这是一个修改后的配方。

      所做的更改:

      1. 当拖过一个已经选择的项目时,它会取消选择它,这是一种糟糕的用户体验。
      2. 当单击一个被选中的项目时,它会从单击中变为未选中状态。所以我添加了一个self.curState 位来跟踪单击项的状态是否最初被选中。当您拖动它时,它不会丢失其状态。
      3. 我还使用add='+' 将两个事件绑定到Button-1 事件,但只需将其全部保存在setCurrent 下即可避免这种情况。
      4. 我更喜欢activestyle等于'none'
      5. Listbox tk.MULTIPLE 代替 tk.SINGLE

      代码如下:

      class Drag_and_Drop_Listbox(tk.Listbox):
        """ A tk listbox with drag'n'drop reordering of entries. """
        def __init__(self, master, **kw):
          kw['selectmode'] = tk.MULTIPLE
          kw['activestyle'] = 'none'
          tk.Listbox.__init__(self, master, kw)
          self.bind('<Button-1>', self.getState, add='+')
          self.bind('<Button-1>', self.setCurrent, add='+')
          self.bind('<B1-Motion>', self.shiftSelection)
          self.curIndex = None
          self.curState = None
        def setCurrent(self, event):
          ''' gets the current index of the clicked item in the listbox '''
          self.curIndex = self.nearest(event.y)
        def getState(self, event):
          ''' checks if the clicked item in listbox is selected '''
          i = self.nearest(event.y)
          self.curState = self.selection_includes(i)
        def shiftSelection(self, event):
          ''' shifts item up or down in listbox '''
          i = self.nearest(event.y)
          if self.curState == 1:
            self.selection_set(self.curIndex)
          else:
            self.selection_clear(self.curIndex)
          if i < self.curIndex:
            # Moves up
            x = self.get(i)
            selected = self.selection_includes(i)
            self.delete(i)
            self.insert(i+1, x)
            if selected:
              self.selection_set(i+1)
            self.curIndex = i
          elif i > self.curIndex:
            # Moves down
            x = self.get(i)
            selected = self.selection_includes(i)
            self.delete(i)
            self.insert(i-1, x)
            if selected:
              self.selection_set(i-1)
            self.curIndex = i
      

      示例演示:

      root = tk.Tk()
      listbox = Drag_and_Drop_Listbox(root)
      for i,name in enumerate(['name'+str(i) for i in range(10)]):
        listbox.insert(tk.END, name)
        if i % 2 == 0:
          listbox.selection_set(i)
      listbox.pack(fill=tk.BOTH, expand=True)
      root.mainloop()
      

      【讨论】:

      • 我玩了一下,发现无论选择多少,它一次只允许移动一个项目。用户希望所有选定的项目一起移动,所以我建议只在SINGLE 模式下使用它,直到解决。我还将getState() 方法中的最后一行更改为self.curState = 1,这样当移动未选择的行时,它只会保持选中状态,而不是奇怪地闪烁选中。这在SINGLE 模式下再次看起来非常好,因为它突出显示了用户正在移动/上次移动的内容。
      【解决方案3】:

      这是来自Recipe 11.4的代码:

      import Tkinter 
      
      class DragDropListbox(Tkinter.Listbox):
          """ A Tkinter listbox with drag'n'drop reordering of entries. """
          def __init__(self, master, **kw):
              kw['selectmode'] = Tkinter.SINGLE
              Tkinter.Listbox.__init__(self, master, kw)
              self.bind('<Button-1>', self.setCurrent)
              self.bind('<B1-Motion>', self.shiftSelection)
              self.curIndex = None
      
          def setCurrent(self, event):
              self.curIndex = self.nearest(event.y)
      
          def shiftSelection(self, event):
              i = self.nearest(event.y)
              if i < self.curIndex:
                  x = self.get(i)
                  self.delete(i)
                  self.insert(i+1, x)
                  self.curIndex = i
              elif i > self.curIndex:
                  x = self.get(i)
                  self.delete(i)
                  self.insert(i-1, x)
                  self.curIndex = i
      

      【讨论】:

        猜你喜欢
        • 2013-05-14
        • 1970-01-01
        • 2018-11-21
        • 2012-07-18
        • 2016-11-03
        • 2012-07-27
        • 2017-12-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多