tkinter列表框拖放与python

时间:2013-01-22 13:43:59

标签: python listbox widget tkinter

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

4 个答案:

答案 0 :(得分:5)

以下是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

答案 1 :(得分:2)

this link处的食谱11.4显示了一个例子。

答案 2 :(得分:1)

如果您将MULTIPLE作为selectmode(而不是SINGLE)处理,则此处是修改后的食谱。

所做的更改:

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

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

答案 3 :(得分:0)

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

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

上面的意思是只拖动一个项目,需要先选择它,然后再次单击并拖动。

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'