我有一个 tkinter gui,它显示关卡中的一些节点。
级别被分成帧,并且在每个帧上,节点根据类 Viewer 中定义的级别字典放置。
我想将此节点从一个级别拖放到另一个级别,并且当我这样做时,级别应该自行调整。
例如,如果某一级别只有一个节点,并且该节点向上移动,则该级别实际上将不再存在,并且下面的级别应向上移动以填充级别层次结构逻辑。
但是,拖放操作并未按预期工作。
下面是我的代码:
关于代码的一些注释:
level_dict 定义级别。 DEPS 键表示一个节点对另一个节点的依赖关系。
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()
您无法将子标签从其父框架移动到另一个框架中。 解决方法是将标签创建为
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()
...
...