【问题标题】:Python Tkinter Table order table columns with drag and dropPython Tkinter Table order 表列与拖放
【发布时间】:2018-07-17 10:05:25
【问题描述】:

我尝试创建一个示例表/网格,其中包含可以通过拖放来移动的列。 我为此使用了 tkinter 树视图。 我的示例有效,但它很简陋,与例如 C# 中的内置网格相比,我对它的操作/外观非常不满意。 有人可以改进我在这里所做的吗?

import Tkinter as tk
import ttk


def bDown(event):
    print "button down ---------------------------"
    global col_from
    tv = event.widget
    col = int(tv.identify_column(event.x)[1:]) - 1  # subtract 1 because display columns array 0 = tree column 1
    col_from = col
    print "selected index {}".format(col)


def bUp(event):
    print "button up ---------------------------"
    tv = event.widget
    col_to = int(tv.identify_column(event.x)[1:]) - 1  # subtract 1 because display columns array 0 = tree column 1

    if col_from != col_to:
        dcols = list(tv["displaycolumns"])
        if dcols[0] == "#all":
            dcols = list(tv["columns"])
        print "Display Columns"
        print dcols
        print "move from {} to {}".format(col_from, col_to)

        if col_from > col_to:
            dcols.insert(col_to, dcols[col_from])
            dcols.pop(col_from + 1)
        else:
            dcols.insert(col_to + 1, dcols[col_from])
            dcols.pop(col_from)
        tv.config(displaycolumns=dcols)
        print dcols


# Variable to hold initial choice of column to move
col_from = 0

root = tk.Tk()

# List of columns
columns = ["A", "B", "C", "D", "E", "F", "G"]

# Create treeview with columns. Display all columns
tree = ttk.Treeview(columns=columns)#, displaycolumns=columns)

# Set headers
for col in columns:
    tree.heading(col, text=col)

# Make first column (tree node) very narrow
tree.column("#0", stretch=tk.N, minwidth=10, width=10)

# insert some items into the tree
for i in range(10):
    tree.insert('', 'end',iid='line%i' % i, values=(i, i+10, i+20, i+30, i+40, i+50, i+60))

tree.grid()
tree.bind("<ButtonPress-1>", bDown)
tree.bind("<ButtonRelease-1>",bUp, add='+')

root.mainloop()

【问题讨论】:

  • 我担心“有人可以改进我在这里所做的事情吗?”这个问题。对于 Stack Overflow 来说太宽泛了。 “改进”也是相当客观的。您认为哪些更改是改进,您自己尝试过哪些改进以及您在哪里遇到问题?
  • 我明白你的意思。我不喜欢的主要事情是,您无法像使用其他语言的内置网格一样看到被拖动的列。也许这只是 tkinter 的一个无法克服的限制,但是那里知道的比我知道的多得多的人可能知道。

标签: python tkinter drag-and-drop


【解决方案1】:

查看您移动的列的一种方法是在第二个Treeview 中创建表格的副本,但仅显示拖动的列。然后,此副本可以使用 place 和事件绑定跟随光标。这不如您可以在其他语言中看到的可以看到列交换的动画那么好,但它改进了一点拖动。

import Tkinter as tk
import ttk

def bDown(event):
    global col_from, dx, col_from_id
    tv = event.widget
    if tv.identify_region(event.x, event.y) != 'separator':
        col = tv.identify_column(event.x)
        col_from_id = tv.column(col, 'id')
        col_from = int(col[1:]) - 1  # subtract 1 because display columns array 0 = tree column 1
        # get column x coordinate and width
        bbox = tv.bbox(tv.get_children("")[0], col_from_id)
        dx = bbox[0] - event.x  # distance between cursor and column left border
        tv.heading(col_from_id, text='')
        visual_drag.configure(displaycolumns=[col_from_id])
        visual_drag.place(in_=tv, x=bbox[0], y=0, anchor='nw', width=bbox[2], relheight=1)
    else:
        col_from = None

def bUp(event):
    tv = event.widget
    col_to = int(tv.identify_column(event.x)[1:]) - 1  # subtract 1 because display columns array 0 = tree column 1
    visual_drag.place_forget()
    if col_from is not None:
        tv.heading(col_from_id, text=visual_drag.heading('#1', 'text'))
        if col_from != col_to:
            dcols = list(tv["displaycolumns"])
            if dcols[0] == "#all":
                dcols = list(tv["columns"])

            if col_from > col_to:
                dcols.insert(col_to, dcols[col_from])
                dcols.pop(col_from + 1)
            else:
                dcols.insert(col_to + 1, dcols[col_from])
                dcols.pop(col_from)
            tv.config(displaycolumns=dcols)

def bMotion(event):
    # drag around label if visible
    if visual_drag.winfo_ismapped():
        visual_drag.place_configure(x=dx + event.x)


# Variable to hold initial choice of column to move
col_from = 0

root = tk.Tk()

# List of columns
columns = ["A", "B", "C", "D", "E", "F", "G"]

# Create treeview with columns. Display all columns
tree = ttk.Treeview(root, columns=columns, show='headings')  # , displaycolumns=columns)
# treeview to show column motion
visual_drag = ttk.Treeview(root, columns=columns, show='headings')
# Set headers
for col in columns:
    tree.heading(col, text=col)
    visual_drag.heading(col, text=col)

# insert some items into the tree
for i in range(10):
    tree.insert('', 'end', iid='line%i' % i,
                values=(i, i+10, i+20, i+30, i+40, i+50, i+60))
    visual_drag.insert('', 'end', iid='line%i' % i,
                       values=(i, i+10, i+20, i+30, i+40, i+50, i+60))

tree.grid()
tree.bind("<ButtonPress-1>", bDown)
tree.bind("<ButtonRelease-1>",bUp)
tree.bind("<Motion>",bMotion)

root.mainloop()

如果被拖动列的中心在新列内,您还可以通过签入bMotion 在拖动时交换列:

import Tkinter as tk
import ttk

def swap(tv, col1, col2):
    dcols = list(tv["displaycolumns"])
    if dcols[0] == "#all":
        dcols = list(tv["columns"])
    id1 = tree.column(col1, 'id')
    id2 = tree.column(col2, 'id')
    i1 = dcols.index(id1)
    i2 = dcols.index(id2)
    dcols[i1] = id2
    dcols[i2] = id1
    tv["displaycolumns"] = dcols

def bDown(event):
    global dx, col_from_id
    tv = event.widget
    if tv.identify_region(event.x, event.y) != 'separator':
        col = tv.identify_column(event.x)
        col_from_id = tv.column(col, 'id')
        # get column x coordinate and width
        bbox = tv.bbox(tv.get_children("")[0], col_from_id)
        dx = bbox[0] - event.x  # distance between cursor and column left border
#        tv.heading(col_from_id, text='')
        visual_drag.configure(displaycolumns=[col_from_id])
        visual_drag.place(in_=tv, x=bbox[0], y=0, anchor='nw', width=bbox[2], relheight=1)
    else:
        col_from_id = None

def bUp(event):
    visual_drag.place_forget()


def bMotion(event):
    tv = event.widget
    # drag around label if visible
    if visual_drag.winfo_ismapped():
        x = dx + event.x
        # middle of the dragged column
        xm = int(x + visual_drag.column('#1', 'width')/2)
        visual_drag.place_configure(x=x)
        col = tv.identify_column(xm)
        # if the middle of the dragged column is in another column, swap them
        if tv.column(col, 'id') != col_from_id:
            swap(tv, col_from_id, col)


# Variable to hold initial choice of column to move
col_from = 0

root = tk.Tk()

# List of columns
columns = ["A", "B", "C", "D", "E", "F", "G"]

# Create treeview with columns. Display all columns
tree = ttk.Treeview(root, columns=columns, show='headings')  # , displaycolumns=columns)
# treeview to show column motion
visual_drag = ttk.Treeview(root, columns=columns, show='headings')
# Set headers
for col in columns:
    tree.heading(col, text=col)
    visual_drag.heading(col, text=col)

# insert some items into the tree
for i in range(10):
    tree.insert('', 'end', iid='line%i' % i,
                values=(i, i+10, i+20, i+30, i+40, i+50, i+60))
    visual_drag.insert('', 'end', iid='line%i' % i,
                       values=(i, i+10, i+20, i+30, i+40, i+50, i+60))

tree.grid()
tree.bind("<ButtonPress-1>", bDown)
tree.bind("<ButtonRelease-1>",bUp)
tree.bind("<Motion>",bMotion)

root.mainloop()

【讨论】:

  • @RickBowden 你的问题给了我一个 Table ttk 小部件的想法,如果你有兴趣,可以选择可拖动的列和行,现在可以在 ttkwidgets 项目中的 Github 上使用。
  • 今晚我正在考虑截屏树视图,使用带有画布的新窗口打开屏幕截图,然后除以列矩形并在画布内移动它。仍然是初步的,但听起来它应该可以工作。
  • 我上周根据我最初的想法发布了一个新答案。
【解决方案2】:

一个简单的类调用就可以完成:

MoveTreeviewColumn(mytoplevel, mytree)

  • 使用对齐网格
  • 在列标题上按住鼠标按钮可发起
  • 将列从左到右再向后移动
  • 树形视图下方的按钮被涂黑
  • 列带有矩形边框
  • 松开按钮时,Treeview 列会重新排序

代码是这个周末才写的,所以可以使用抛光:


try:  # Python 3
    import tkinter as tk
except ImportError:  # Python 2
    import Tkinter as tk
from PIL import Image, ImageTk
from collections import namedtuple
from os import popen
BUTTON_HEIGHT = 63                  # Button region to black out during move


class MoveTreeviewColumn:
    """ Shift treeview column to preferred order """

    def __init__(self, toplevel, treeview, row_release=None):

        self.toplevel = toplevel
        self.treeview = treeview
        self.row_release = row_release      # Button-Release not on heading
        self.region = None                  # Region of treeview clicked

        self.col_cover_top = None           # toplevel move columns
        self.col_top_is_active = False      # column move in progress?
        self.canvas = None                  # tk Canvas with column photos
        self.col_being_moved = None         # Column being moved in '#?' form
        self.col_swapped = False            # Did we swap a column?

        self.images = []                    # GIC protected image list
        self.canvas_names = []              # treeview column names
        self.canvas_widths = []             # matching widths
        self.canvas_objects = []            # List of canvas objects
        self.canvas_x_offsets = []          # matching x-offsets within canvas

        self.canvas_index = None            # Canvas index being moved
        self.canvas_name = None             # Treeview column name
        self.canvas_object = None           # Canvas item object being moved
        self.canvas_original_x = None       # Canvas item starting offset
        self.start_mouse_pos = None         # Starting position to calc delta

        self.treeview.bind("<ButtonPress-1>", self.start)
        self.treeview.bind("<ButtonRelease-1>", self.stop)
        self.treeview.bind("<B1-Motion>", self.motion)

    def close(self):
        self.treeview.unbind("<ButtonPress-1>")
        self.treeview.unbind("<ButtonRelease-1>")
        self.treeview.unbind("<B1-Motion>")

    def start(self, event):
        """
            Button 1 was just pressed for library treeview or backups treeview

        :param event: tkinter event
        :return:
        """

        #print('<ButtonPress-1>', event.x, event.y)
        self.region = self.treeview.identify("region", event.x, event.y)

        if self.region != 'heading':
            return

        Mouse = namedtuple('Mouse', 'x y')
        # noinspection PyArgumentList
        self.start_mouse_pos = Mouse(event.x, event.y)

        if self.col_cover_top is not None:
            print('toolkit.py MoveTreeviewColumn attempting to create self.col_cover_top a second time.')
            return

        self.create_move_column()
        if self.col_top_is_active is False:
            return  # Released button quickly or error creating top level

        # The column being moved - Recalculated after snap to grid
        self.col_being_moved = self.treeview.identify_column(event.x)
        #print('self.col_being_moved:', self.col_being_moved)
        self.get_source(self.col_being_moved)
        self.treeview.config(cursor='boat red red')  # boat cursor supports red
        self.col_swapped = False
        #print('\n columns BEFORE:', self.canvas_names)

    def stop(self, event):
        """ Determine if we were in motion before we lifted mouse button
        """
        if self.region != 'heading':
            # If button release not on heading call optional row_release
            if self.row_release is not None:
                self.row_release(event)
            return

        ''' Destroy toplevel used for moving columns on canvas '''
        if self.col_top_is_active:
            # Destroy top level window covering up old music player position
            if self.col_cover_top is not None:
                if self.col_swapped:
                    #print('columns AFTER :', self.canvas_names)
                    self.treeview["displaycolumns"] = self.canvas_names
                    self.toplevel.update_idletasks()  # just in case
                self.col_cover_top.destroy()
                self.col_cover_top = None
            self.col_top_is_active = False
            self.treeview.config(cursor='')

    def motion(self, event):
        """
        What if only 1 column?

        What if horizontal scroll and non-displayed columns to left or right
        of displayed treeview columns? Need to compare 'displaycolumns' to
        current treeview.

        :param event: Tkinter event with x, y, widget
        :return:
        """
        if self.region != 'heading':
            return

        # Calculate delta - distance travelled since startup or snap to grid
        change = event.x - self.start_mouse_pos.x

        # Calculate new start, middle and ending x offsets for source object
        new_x = int(self.canvas_original_x + change)  # Sometimes we get float?
        new_middle_x = new_x + self.canvas_widths[self.canvas_index] // 2
        new_x2 = new_x + self.canvas_widths[self.canvas_index]
        self.canvas.coords(self.canvas_object, (new_x, 0))  # Move on screen

        ''' Make column snap to next (jump) when over half way -
            Either half of target is covered or half of source
            has moved into target 
        '''
        if change < 0:  # Mouse is moving column to the left
            if self.canvas_index == 0:
                return  # We are already first column on left
            target_index = self.canvas_index - 1
            target_start_x, target_middle_x, target_end_x = self.get_target(
                target_index)
            if new_x > target_middle_x and new_middle_x > target_end_x:
                return  # Not eligible for snap to grid

        elif change > 0:  # Mouse is moving column to the right
            if self.canvas_index == len(self.canvas_x_offsets) - 1:
                return  # We are already last column on right
            target_index = self.canvas_index + 1
            target_start_x, target_middle_x, target_end_x = self.get_target(
                target_index)
            if new_x2 < target_middle_x and new_middle_x < target_start_x:
                return  # Not eligible for snap to grid
        else:
            #print('toolkit.py MoveTreeviewColumn motion() called with no motion.')
            # Common occurrence when mouse moves fraction back and forth
            return  # Mouse didn't change position

        ''' Swap our column and the target column beside us (snap to grid).
            Calculate jump factor and then make mouse jump by same amount
        '''

        ''' Diagnostic section
        print('\n<B1-Motion>', event.x, event.y)
        print('\tcanvas_index   :', self.canvas_index,
              '\ttarget_index:  :', target_index,
              '\toriginal_x     :', self.canvas_original_x)
        print('\tnew_x          :', new_x,
              '\tnew_middle_x   :', new_middle_x,
              '\tnew_x2         :', new_x2)
        print('\ttarget_start_x :', target_start_x,
              '\ttarget_middle_x:', target_middle_x,
              '\ttarget_end_x   :', target_end_x)
        '''

        if target_index < self.canvas_index:
            # snapping to grid on left
            if self.canvas_index == 0:
                return  # Can't go before first column
            new_target_x = self.canvas_x_offsets[target_index] + \
                self.canvas_widths[self.canvas_index]
            new_source_x = self.canvas_x_offsets[target_index]
        else:
            # snapping to grid on right
            if self.canvas_index == len(self.canvas_widths) - 1:
                return  # Can't go past last column
            new_source_x = self.canvas_x_offsets[self.canvas_index] + \
                self.canvas_widths[target_index]
            new_target_x = self.canvas_x_offsets[self.canvas_index]

        # Swap lists at target index and self.canvas_index
        source_old_x = self.canvas.coords(self.canvas_object)[0]
        self.source_to_target(target_index, new_target_x, new_source_x)
        source_new_x = self.canvas.coords(self.canvas_object)[0]
        source_x_jump = source_new_x - source_old_x
        #print('source_x_jump:', source_x_jump)

        # Move mouse on screen to reflect snapping to grid
        self.treeview.unbind("<B1-Motion>")            # Don't call ourself
        ''' If you don't have xdotool installed, activate following code
        mouse_x = self.toplevel.winfo_x() + event.x + source_x_jump
        mouse_y = self.toplevel.winfo_y() + event.y
        # mouse_move_to takes .1 to .14 seconds and flickers new window
        move_mouse_to(mouse_x, mouse_y)
        # xdotool takes .006 to .012 seconds and no flickering window
        '''
        popen("xdotool mousemove_relative -- " + str(int(source_x_jump)) + " 0")
        self.treeview.bind("<B1-Motion>", self.motion)

        # Recalibrate mouse starting position within toplevel
        Mouse = namedtuple('Mouse', 'x y')
        # noinspection PyArgumentList
        self.start_mouse_pos = Mouse(event.x + source_x_jump, event.y)

        self.col_swapped = True  # We swapped a column so update treeview

    def get_source(self, col_being_moved):
        """ Set self.canvas_xxx instances """
        # Strip treeview '#' from '#?' column number
        self.canvas_index = int(col_being_moved.replace('#', '')) - 1
        self.canvas_name = self.canvas_names[self.canvas_index]
        self.canvas_object = self.canvas_objects[self.canvas_index]
        self.canvas_original_x = self.canvas_x_offsets[self.canvas_index]
        self.canvas.tag_raise(self.canvas_object)  # Top stacking order

    def get_target(self, target_index):
        target_start_x = self.canvas_x_offsets[target_index]
        target_middle_x = target_start_x + \
            self.canvas_widths[target_index] // 2
        if target_index == len(self.canvas_x_offsets) - 1:
            # This is the last column on right so use canvas width
            target_end_x = self.canvas.winfo_width()
        else:
            # This is the last column on right so use canvas width
            target_end_x = self.canvas_x_offsets[target_index + 1]

        return target_start_x, target_middle_x, target_end_x

    @staticmethod
    def swap(lst, x1, x2):
        # Shorthand
        lst[x1], lst[x2] = lst[x2], lst[x1]

    def source_to_target(self, target_index, new_target_x, new_source_x):
        """ Swap source and target columns """
        self.swap(self.canvas_names, self.canvas_index, target_index)
        self.swap(self.canvas_objects, self.canvas_index, target_index)
        self.swap(self.canvas_widths, self.canvas_index, target_index)
        self.canvas_x_offsets[self.canvas_index] = new_target_x
        self.canvas_x_offsets[target_index] = new_source_x

        # Swap the two images on canvas
        self.canvas.coords(self.canvas_objects[self.canvas_index],
                           (self.canvas_x_offsets[self.canvas_index], 0))
        self.canvas.coords(self.canvas_objects[target_index],
                           (self.canvas_x_offsets[target_index], 0))

        # Now that columns swapped on canvas, get new variables
        self.col_being_moved = "#" + str(target_index + 1)
        self.get_source(self.col_being_moved)

    def create_move_column(self):
        """
            Create canvas toplevel covering up treeview.
            Canvas divided into rectangles for each column.
            Track <B1-Motion> horizontally to swap with next column.
        """

        if self.col_cover_top is not None:
            print('trying to create self.col_cover_top again!!!')
            return

        self.toplevel.update()              # Refresh current coordinates
        self.col_top_is_active = True

        # create named tuple class with names x, y, w, h
        Geom = namedtuple('Geom', ['x', 'y', 'w', 'h'])

        # noinspection PyArgumentList
        top_geom = Geom(self.toplevel.winfo_x(),
                        self.toplevel.winfo_y(),
                        self.toplevel.winfo_width(),
                        self.toplevel.winfo_height())

        #print('\n tkinter top_geom:', top_geom)

        ''' Take screenshot of treeview region (x, y, w, h)
        '''
        # X11 takes 4.5 seconds first time and .67 seconds subsequent times
        #top_image = x11.screenshot(top_geom.x, top_geom.y,
        #                           top_geom.w, top_geom.h)

        # gnome screenshot entire desktop takes .25 seconds
        top_image = gnome_screenshot(top_geom)

        # Did button get released while we were capturing screen?
        if self.col_top_is_active is False:
            return

        # Mount our column moving window over original treeview
        self.col_cover_top = tk.Toplevel()
        self.col_cover_top.overrideredirect(True)   # No window decorations
        self.col_cover_top.withdraw()
        # No title when undecorated (override direct = true)
        #self.col_cover_top.title("Shift column - bserve")
        self.col_cover_top.grid_columnconfigure(0, weight=1)
        self.col_cover_top.grid_rowconfigure(0, weight=1)

        can_frame = tk.Frame(self.col_cover_top, bg="grey",
                             width=top_geom.w, height=top_geom.h)
        can_frame.grid(column=0, row=0, sticky=tk.NSEW)
        can_frame.grid_columnconfigure(0, weight=1)
        can_frame.grid_rowconfigure(0, weight=1)

        self.canvas = tk.Canvas(can_frame, width=top_geom.w,
                                height=top_geom.h, bg="grey")
        self.canvas.grid(row=0, column=0, sticky='nsew')
        '''
        Publish to: https://stackoverflow.com/a/51425272/6929343

        TODO -  We are looping through all columns. We only want the ones
                in currently visible scrolled region.
        '''

        total_width = 0
        self.images = []                    # Reset GIC protected image list
        self.canvas_names = []              # treeview column ids (names)
        self.canvas_widths = []             # matching widths
        self.canvas_objects = []            # List of canvas objects
        self.canvas_x_offsets = []          # matching x-offsets within canvas

        for i, column in enumerate(self.treeview['displaycolumns']):

            col_width = self.treeview.column(column)['width']
            # Create cropped image for column out of screenshot using 1 px
            # border width.  Extra crop from bottom to exclude buttons.
            image = top_image.crop([total_width + 1, 1,
                                    total_width + col_width - 2,
                                    top_geom.h - 63])

            # Make a black background image at original column size
            new_im = Image.new("RGB", (col_width, top_geom.h))

            # Paste cropped column image inside black image making a border
            new_im.paste(image, (2, 2))
            photo = ImageTk.PhotoImage(new_im)
            self.images.append(photo)       # Prevent GIC (garbage collection)
            item = self.canvas.create_image(total_width, 0,
                                            image=photo, anchor=tk.NW)
            self.canvas_names.append(column)
            self.canvas_objects.append(item)
            self.canvas_widths.append(col_width)
            self.canvas_x_offsets.append(total_width)
            total_width += col_width

            # Did button get released while we were formatting canvas?
            if self.col_top_is_active is False:
                return

        # Move the column cover window with canvas over original treeview
        self.col_cover_top.geometry('{}x{}+{}+{}'.format(
            top_geom.w, top_geom.h, top_geom.x, top_geom.y))
        self.col_cover_top.deiconify()  # Forces window to appear
        self.col_cover_top.update()  # This is required for visibility


def move_mouse_to(x, y):
    """ Moves the mouse to an absolute location on the screen.
        Rather slow at .1 second and causes brief screen flicker.
        From: https://stackoverflow.com/a/66808226/6929343
        Visit link for other options under Windows and Mac.
        For Linux use xdotool for .007 response time and no flicker.
    """
    # Create a new temporary root
    temp_root = tk.Tk()
    # Move it to +0+0 and remove the title bar
    temp_root.overrideredirect(True)
    # Make sure the window appears on the screen and handles the `overrideredirect`
    temp_root.update()
    # Generate the event as @a bar nert did
    temp_root.event_generate("<Motion>", warp=True, x=x, y=y)
    # Make sure that tcl handles the event
    temp_root.update()
    # Destroy the root
    temp_root.destroy()


def gnome_screenshot(geom):
    """ Screenshot using old gnome 3.18 standards """

    import gi
    gi.require_version('Gdk', '3.0')
    gi.require_version('Gtk', '3.0')
    gi.require_version('Wnck', '3.0')
    # gi.require_versions({"Gtk": "3.0", "Gdk": "3.0", "Wnck": "3.0"})  # Python 3

    from gi.repository import Gdk, GdkPixbuf, Gtk, Wnck

    Gdk.threads_init()  # From: https://stackoverflow.com/questions/15728170/
    while Gtk.events_pending():
        Gtk.main_iteration()

    screen = Wnck.Screen.get_default()
    screen.force_update()
    w = Gdk.get_default_root_window()
    pb = Gdk.pixbuf_get_from_window(w, *geom)
    desk_pixels = pb.read_pixel_bytes().get_data()
    raw_img = Image.frombytes('RGB', (geom.w, geom.h), desk_pixels,
                              'raw', 'RGB', pb.get_rowstride(), 1)
    return raw_img

# End of: toolkit.py

【讨论】:

  • 感谢您分享您的替代解决方案。我已经使用 OP 代码中的树视图进行了尝试,并调用了 MoveTreeviewColumn(root, tree),但我得到了 tkinter.TclError: Invalid column index #all。似乎self.treeview['displaycolumns'] 返回("#all", ) 而不是列列表(在create_move_column() 中)。我在 Linux 中使用 python 3.9 和 tk8.6.11。
  • 否则,如果我在显示所有列时使用self.treeview['columns'],它就可以正常工作。
猜你喜欢
  • 2013-01-05
  • 2013-05-14
  • 2018-11-21
  • 2013-03-06
  • 2019-11-19
  • 2013-04-15
  • 2022-10-05
  • 2010-11-26
  • 2021-01-10
相关资源
最近更新 更多