如何在滚动tkinter treeview时移动弹出窗口?

5

我有一个带有垂直滚动条的tkinter treeview。为了使它看起来像可编辑,当用户双击树视图中的单元格时,我会创建一个弹出式输入框。然而,我无法使弹出窗口在滚动treeview时移动。

import tkinter  as tk
from tkinter import ttk

class EntryPopup(ttk.Entry):
    def __init__(self, parent, itemId, col, **kw):
        super().__init__(parent, **kw)
        self.tv = parent
        self.iId = itemId
        self.column = col
        self['exportselection'] = False

        self.focus_force()
        self.bind("<Return>", self.onReturn)

    def saveEdit(self):
        self.tv.set(self.iId, column=self.column, value=self.get())
        print("EntryPopup::saveEdit---{}".format(self.iId))

    def onReturn(self, event):
        self.tv.focus_set()        
        self.saveEdit()
        self.destroy()


class EditableDataTable(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        self.parent = parent
        self.tree = None
        self.entryPopup = None
        columns = ("Col1", "Col2")
        # Create a treeview with vertical scrollbar.
        self.tree = ttk.Treeview(self, columns=columns, show="headings")
        self.tree.grid(column=0, row=0, sticky='news')
        self.tree.heading("#1", text="col1")
        self.tree.heading("#2", text="col2")

        self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=self.vsb.set)
        self.vsb.grid(column=1, row=0, sticky='ns')

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.entryPopup = None
        self.curSelectedRowId = ""

        col1 = []
        col2 = []
        for r in range(50):
            col1.append("data 1-{}".format(r))
            col2.append("data 2-{}".format(r))

        for i in range(min(len(col1),len(col2))):
            self.tree.insert('', i, values=(col1[i], col2[i]))

        self.tree.bind('<Double-1>', self.onDoubleClick)

    def createPopup(self, row, column):
        x,y,width,height = self.tree.bbox(row, column)

        # y-axis offset
        pady = height // 2
        self.entryPopup = EntryPopup(self.tree, row, column)
        self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)


    def onDoubleClick(self, event):
        rowid = self.tree.identify_row(event.y)
        column = self.tree.identify_column(event.x)
        self.createPopup(rowid, column)

root = tk.Tk()

for row in range(2):
    root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)

label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)

dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)

root.geometry("450x300")
root.mainloop()

为了重现问题,请双击treeview。在编辑框打开时,将鼠标指针移动到悬停在treeview上。现在使用鼠标滚轮滚动。treeview 移动了,但弹出式编辑框没有跟随移动。

你确定这不是Treeview小部件的正常行为吗?如果是,那么你有两个选择:通过查看Treeview文档找到一个修复你问题的参数或方法。第二个选择是制作自己的Treeview小部件,这是两个选项中更难的一个。如果你必须选择第二个选项,那么我建议你使用Listbox,在我看来,这将是迈向正确答案的一步。希望这可以帮助你! - 10 Rep
3个回答

1

我有比跟踪鼠标事件更简单的解决方案:

self.tree.configure(yscrollcommand = self.ScrollTree)

def ScrollTree(self, a, b):
    if self.entryPopup is not None:
        pos = self.tree.bbox(self.entryPopup.iid , 'value')
        # if cell visible
        if pos != '':
            self.entryPopup.place(x=pos[0], y=pos[1], width = pos[2], height = pos[3])
        else:
            self.entryPopup.place_forget()
    # update attached scrollbar
    self.vsb.set(a, b)

1
我以前做过类似的事情,通过将函数绑定到mousewheel并重新计算悬停小部件的所有新的x和y位置来完成。
class EditableDataTable(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        self.parent = parent
        self.tree = None
        self.entryPopup = None
        self.list_of_entries = []

        ...

        self.tree.bind("<MouseWheel>", self._on_mousewheel)

    def _on_mousewheel(self, event):
        if self.list_of_entries:
            def _move():
                for i in self.list_of_entries:
                    try:
                        iid = i.iId
                        x, y, width, height = self.tree.bbox(iid, column="Col2") #hardcoded to col2
                        i.place(x=x, y=y+height//2, anchor='w', width=width)
                    except ValueError:
                        i.place_forget()
                    except tk.TclError:
                        pass
            self.master.after(5, _move)

    def createPopup(self, row, column):
        x,y,width,height = self.tree.bbox(row, column)
        # y-axis offset
        pady = height // 2
        self.entryPopup = EntryPopup(self.tree, row, column)
        self.list_of_entries.append(self.entryPopup)
        self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)

请注意,这仅适用于第二列,但应该很容易实现到其他列。

谢谢,这个很好用!有时我会使用ttk.Combobox作为弹出窗口,而不是ttk.Entry。在这些情况下,只有“下拉”小部件(带箭头)随着我的鼠标滚轮移动;下拉列表框(作为组合框的一部分)不会移动。我该如何访问列表框,并使其移动? - Jakaria

0

当树被滚动时,您需要进行计算并移动输入小部件。我已经编辑了您的代码,并仅编写了滚动条按钮的程序。如果您单击按钮,则输入小部件将随树一起滚动。我没有编写鼠标滚轮滚动或拖动滚动条的程序。您应该能够解决其余的问题。

import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk

class EntryPopup(ttk.Entry):
    def __init__(self, parent, itemId, col, **kw):
        super().__init__(parent, **kw)
        self.tv = parent
        self.iId = itemId
        self.column = col
        self['exportselection'] = False

        self.focus_force()
        self.bind("<Return>", self.onReturn)

    def saveEdit(self):
        self.tv.set(self.iId, column=self.column, value=self.get())
        print("EntryPopup::saveEdit---{}".format(self.iId))

    def onReturn(self, event):
        self.tv.focus_set()
        self.saveEdit()
        self.destroy()


class EditableDataTable(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        self.parent = parent
        self.tree = None
        self.entryPopup = None
        columns = ("Col1", "Col2")
        # Create a treeview with vertical scrollbar.
        self.tree = ttk.Treeview(self, columns=columns, show="headings")
        self.tree.grid(column=0, row=0, sticky='news')
        self.tree.heading("#1", text="col1")
        self.tree.heading("#2", text="col2")
        self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=self.vsb.set)
        self.vsb.grid(column=1, row=0, sticky='ns')

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.entryPopup = None
        self.curSelectedRowId = ""

        col1 = []
        col2 = []
        for r in range(50):
            col1.append("data 1-{}".format(r))
            col2.append("data 2-{}".format(r))

        for i in range(min(len(col1),len(col2))):
            self.tree.insert('', i, values=(col1[i], col2[i]))

        self.tree.bind('<Double-1>', self.onDoubleClick)
        self.vsb.bind('<ButtonPress-1>', self.func)

    def func(self, event):
        print(self.vsb.identify(event.x, event.y))
        if hasattr(self.entryPopup, 'y'):
            item = self.vsb.identify(event.x, event.y)

            if item == 'uparrow':
                self.entryPopup.y += 20
            elif item == 'downarrow':
                self.entryPopup.y -= 20

            self.entryPopup.place(x=self.entryPopup.x, y=self.entryPopup.y, )

    def createPopup(self, row, column):
        x, y, width, height = self.tree.bbox(row, column)

        # y-axis offset
        pady = height // 2
        self.entryPopup = EntryPopup(self.tree, row, column)
        self.entryPopup.x = x
        self.entryPopup.y = y+pady
        self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)


    def onDoubleClick(self, event):
        rowid = self.tree.identify_row(event.y)
        column = self.tree.identify_column(event.x)
        self.createPopup(rowid, column)


root = tk.Tk()

for row in range(2):
    root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)

label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)

dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)

root.geometry("450x300")
root.mainloop()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接