[Python] How to drag and drop items across frames in tkinter?

Discussão em 'Python' iniciado por Stack, Junho 7, 2025.

  1. Stack

    Stack Membro Participativo

    I have a tkinter gui which displays some nodes in levels. The levels are split into frames and on each frame the nodes are placed as per a level dictionary defined in the class Viewer. I want to drag and drop this nodes from one level to another and as I do that the levels should adjust themselves.

    For example, if a level has only one node and that node is moved up then that level will cease to exist practically and the below levels should move up to fill the level hierarchy logic. However the drag and drop is not working as expected.
    Below is my code :
    Some notes about the code :


    1. level_dict in Viewer defines the levels. The DEPS key denotes the a nodes dependency on another.


    2. DragDrop class implements the drag and drop feature on clicking.

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

    Continue reading...

Compartilhe esta Página