How to drag and drop items across frames in tkinter?

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

1

You cannot move a child label from its parent frame into another frame. A work-around is to create the label as child of self.scrollable_frame but pack it inside a level frame, then you can move the label over those level frames using .place().

Below is the required changes:

...

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

    ...

...

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật