如何在 tkinter 中跨框架拖放项目?

问题描述 投票:0回答:1

我有一个 tkinter gui,它显示关卡中的一些节点。 级别被分成帧,并且在每个帧上,节点根据类 Viewer 中定义的级别字典放置。 我想将此节点从一个级别拖放到另一个级别,并且当我这样做时,级别应该自行调整。

例如,如果某一级别只有一个节点,并且该节点向上移动,则该级别实际上将不再存在,并且下面的级别应向上移动以填充级别层次结构逻辑。 但是,拖放操作并未按预期工作。
下面是我的代码:
关于代码的一些注释:

    Viewer 中的
  1. level_dict 定义级别。 DEPS 键表示一个节点对另一个节点的依赖关系。

  2. DragDrop 类实现单击时的拖放功能。

     import tkinter as tk
     from tkinter import ttk, filedialog
     import os
    
    
     class DragDrop(tk.Label):
         def __init__(self, parent, text, app, **kwargs):
             super().__init__(parent, text=text, **kwargs)
             self.parent = parent
             self.app = app
             self.text = text
             self.bind("<Button-1>", self.on_click)
             self.bind("<B1-Motion>", self.on_drag)
             self.bind("<ButtonRelease-1>", self.on_release)
             self._drag_data = {"x": 0, "y": 0, "item": None}
    
         def on_click(self, event):
             self._drag_data["item"] = self
             self._drag_data["x"] = event.x
             self._drag_data["y"] = event.y
    
         def on_drag(self, event):
             x = self.winfo_x() - self._drag_data["x"] + event.x
             y = self.winfo_y() - self._drag_data["y"] + event.y
             self.place(x=x, y=y)
    
         def on_release(self, event):
             self._drag_data = {"x": 0, "y": 0, "item": None}
             self.app.update_node_position(self)
    
     class Viewer:
         def __init__(self, root):
             self.root = root
             self.level_dict = {'JK': 0, 'pun': 1, 'utp': 1, 'pun utp': 0, 'utk': 1, 'gjr': 2, 'wbk': 3, 'nest': 4, 'mahm': 5, 'ksl': 6, 'krtk': 5}
             self.sections = {'JK': {'DEPS': None, 'TYPES': None, 'RATING': '0'}, 'pun': {'DEPS': 'JK', 'TYPES': None, 'RATING': '0'}, 'utp': {'DEPS': 'JK', 'TYPES': None, 'RATING': '0'}, 'utk': {'DEPS': 'pun utp', 'TYPES': None, 'RATING': '0'}, 'gjr': {'DEPS': 'utk', 'TYPES': None, 'RATING': '0'}, 'wbk': {'DEPS': 'gjr', 'TYPES': None, 'RATING': '0'}, 'nest': {'DEPS': 'wbk', 'TYPES': None, 'RATING': '0'}, 'mahm': {'DEPS': 'nest', 'TYPES': None, 'RATING': '0'}, 'ksl': {'DEPS': 'mahm', 'TYPES': None, 'RATING': '0'}, 'krtk': {'DEPS': 'nest', 'TYPES': None, 'RATING': '0'}}
    
             self.canvas = tk.Canvas(root, bg="white")
             self.h_scrollbar = tk.Scrollbar(root, orient=tk.HORIZONTAL, command=self.canvas.xview)
             self.v_scrollbar = tk.Scrollbar(root, orient=tk.VERTICAL, command=self.canvas.yview)
             self.scrollable_frame = tk.Frame(self.canvas)
    
             self.scrollable_frame.bind(
             "<Configure>",
             lambda e: self.canvas.configure(
                 scrollregion=self.canvas.bbox("all")
             )
         )
    
             self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
    
           self.canvas.configure(xscrollcommand=self.h_scrollbar.set, yscrollcommand=self.v_scrollbar.set)
    
             self.h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
             self.v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
             self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    
             self.event_var = tk.StringVar(value="All")
             self.create_widgets()
             self.draw_graph()
    
         def create_widgets(self):
             control_frame = tk.Frame(self.root)
             control_frame.pack(side=tk.TOP, fill=tk.X)
    
             event_label = tk.Label(control_frame, text="Select Types:")
             event_label.pack(side=tk.LEFT, padx=5, pady=5)
    
             event_options = ["All", "zones", "states"]
             event_menu = ttk.Combobox(control_frame, textvariable=self.event_var, values=event_options)
             event_menu.pack(side=tk.LEFT, padx=5, pady=5)
             event_menu.bind("<<ComboboxSelected>>", self.on_event_change)
    
             browse_button = tk.Button(control_frame, text="Browse data_file", command=self.browse_file)
             browse_button.pack(side=tk.LEFT, padx=5, pady=5)
    
             save_button = tk.Button(control_frame, text="Save", command=self.save_data_file_file)
             save_button.pack(side=tk.LEFT, padx=5, pady=5)
    
         def browse_file(self):
             data_file_file = filedialog.askopenfilename(filetypes=[("data_file files", "*.data_file")])
             if data_file_file:
                 self.data_file_file = data_file_file
                 self.main_func()
                 self.draw_graph()
    
         def save_data_file_file(self):
             save_file = filedialog.asksaveasfilename(defaultextension=".data_file", filetypes=[("data_file files", "*.data_file")])
             if save_file:
                 config = configparser.ConfigParser()
                 for section, attributes in self.sections.items():
                     config[section] = attributes
                     config[section]['LEVEL'] = str(self.level_dict[section])
                 with open(save_file, 'w') as configfile:
                     config.write(configfile)
    
         def on_event_change(self, event):
             self.event_filter = self.event_var.get() if self.event_var.get() != "All" else None
             self.main_func()
             self.draw_graph()
    
         def draw_graph(self):
             for widget in self.scrollable_frame.winfo_children():
                 widget.destroy()
    
             self.level_frames = {}
             levels = {}
             for section, level in self.level_dict.items():
                 if level not in levels:
                     levels[level] = []
                 levels[level].append(section)
    
             colors = ["lightblue", "lightgreen", "lightyellow", "lightpink", "lightgray"]
             for level, nodes in sorted(levels.items()):
                 level_frame = tk.Frame(self.scrollable_frame, bg=colors[level % len(colors)], bd=2, relief=tk.SOLID)
                 level_frame.pack(fill=tk.X, padx=10, pady=5)
                 self.level_frames[level] = level_frame
    
                 level_label = tk.Label(level_frame, text=f"Level {level}", bg=colors[level % len(colors)], font=("Arial", 12, "bold"), anchor="w")
                 level_label.pack(side=tk.TOP, fill=tk.X)
    
                 for node in nodes:
                     self.draw_node(level_frame, node)
    
         def draw_node(self, parent, node):
             level = self.level_dict.get(node, 0)
             label = f'{node}({level})'
             if node in self.sections:
                 if self.sections[node]['RATING'] == '1':
                     color = 'lightblue'
                 else:
                     color = 'skyblue'
                 fg_color = 'darkblue'
    
                 node_label = DragDrop(parent, text=label, app=self, bg=color, fg=fg_color, font=("Arial", 10), bd=1, relief=tk.SOLID, padx=5, pady=5)
                 node_label.pack(side=tk.LEFT, padx=5, pady=5)
    
         def update_node_position(self, node_label):
             node_text = node_label.cget("text")
             node_name = node_text.split('(')[0]
             old_level = self.level_dict[node_name]
    
             for level, frame in self.level_frames.items():
                 if node_label.winfo_y() >= frame.winfo_y() and node_label.winfo_y() < frame.winfo_y() + frame.winfo_height():
                     if old_level != level:
                         self.level_dict[node_name] = level
                         self.draw_graph()
                     break
    
             # Remove empty levels and adjust subsequent levels
             self.adjust_levels()
    
         def adjust_levels(self):
             levels = sorted(self.level_frames.keys())
             for i, level in enumerate(levels):
                 if not any(node in self.level_dict and self.level_dict[node] == level for node in self.sections):
                     del self.level_frames[level]
                     for node in self.level_dict:
                         if self.level_dict[node] > level:
                             self.level_dict[node] -= 1
             self.draw_graph()
    
     if __name__ == '__main__':
    
         root = tk.Tk()
         root.title("Order Viewer")
         app = Viewer(root)
         root.geometry("800x600")
         root.mainloop()
    
python python-3.x tkinter
1个回答
0
投票

您无法将子标签从其父框架移动到另一个框架中。 解决方法是将标签创建为

self.scrollable_frame
的子级,但将其打包在水平框架内,然后您可以使用
.place()
将标签移动到这些水平框架上。

以下是所需的更改:

...

class DragDrop(tk.Label):
    ...

    def on_click(self, event):
         self._drag_data["item"] = self
         self._drag_data["x"] = event.x
         self._drag_data["y"] = event.y
         self.tkraise()   ### make it the top-most widget in parent container

    ...

...

class Viewer:
    ...

    def draw_node(self, parent, node):
         level = self.level_dict.get(node, 0)
         label = f'{node}({level})'
         if node in self.sections:
             if self.sections[node]['RATING'] == '1':
                 color = 'lightblue'
             else:
                 color = 'skyblue'
             fg_color = 'darkblue'

             ### create as child of self.scrollable_frame
             node_label = DragDrop(self.scrollable_frame, text=label, app=self, bg=color, fg=fg_color, font=("Arial", 10), bd=1, relief=tk.SOLID, padx=5, pady=5)
             ### but pack it inside "parent"
             node_label.pack(side=tk.LEFT, padx=5, pady=5, in_=parent)

     def update_node_position(self, node_label):
         node_text = node_label.cget("text")
         node_name = node_text.split('(')[0]
         old_level = self.level_dict[node_name]

         for level, frame in self.level_frames.items():
             if node_label.winfo_y() >= frame.winfo_y() and node_label.winfo_y() < frame.winfo_y() + frame.winfo_height():
                 if old_level != level:
                     self.level_dict[node_name] = level
                     #self.draw_graph()   ### it is called inside self.adjust_levels() as well
                 break

         # Remove empty levels and adjust subsequent levels
         self.adjust_levels()

    ...

...
© www.soinside.com 2019 - 2024. All rights reserved.