Sto, al momento, studiando come realizzare un "drag and drop" in tkinter, che non fornisce (a quanto ne so) una interfaccia nativa in merito, l'idea di fondo è realizzare dei widget con tale capacità, per scambio di dati tra diverse finestre di una stessa applicazione tramite trascinamento.
Con l'aiuto della documentazione, principalmente di winfo mi è riuscito di realizzare una bozza di base funzionante, sub-classando una tkinter.Entry, scambiando testo tra due oggetti di detto sub-classamento posti in finestre diverse, per trascinamento con il tasto destro del mouse premuto.
Nell'immagine sottostante un esempio del risultato del trascinamento dalla finestra principale ad una finestra secondaria, previa abilitazione a farlo, e viceversa (con abilitazione definita alla costruzione)

... però, anche se funziona, non sono pienamente convinto della metodologia adottata, al momento, al rilascio del pulsante destro del mouse l'oggetto su cui si è avviato il "drag" esamina tutta la gerarchia dei parent sino a trovare la main-window (root per gli amici) e utilizza questa per estrarre tra i widget figli quello su cui è stato rilasciato il pulsante, se quest'ultimo ha uno specifico attributo ("dragable") vi viene aggiunto il testo del widget "draggato".
Come detto, è solo un abbozzo preliminare di studio della funzionalità, prima di integrare il concetto in vari elementi (penso almeno a treeview ed entry che si scambino oggetti) non mi dispiacerebbero consigli circa approcci migliori e/o accorgimenti da tenere.
Se qualcuno vuol provare il codice sin ora implementato (106 righe):
Codice: Seleziona tutto
import tkinter as tk
class DragEntry(tk.Entry):
def __init__(self, parent: callable, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
self.dragable = True
self.active = False
self.bind("<Button-3>", self._on_press)
self.bind("<B3-Motion>", self._on_motion)
self.bind("<ButtonRelease-3>", self._on_release)
def _on_press(self, evt):
if not self.active: return
x,y = self.parent.winfo_pointerxy()
y = self.parent.winfo_pointery()
# TODO: costruire finestra x mostrare trascinamento
def _on_motion(self, evt):
if not self.active: return
x = self.parent.winfo_pointerx()
y = self.parent.winfo_pointery()
# TODO: mostrare trascinamento
def _on_release(self, evt):
if not self.active: return
x = self.parent.winfo_pointerx()
y = self.parent.winfo_pointery()
# TODO: distruggere finestra x mostrare trascinamento
obj = self.__verify_to_coords((x,y))
if obj and hasattr(obj, 'dragable'):
txt = self.get()
obj.insert(tk.END, txt)
def __verify_to_coords(self, c2):
top = self.__get_top(self)
if not top: return
w = top.winfo_containing(*c2)
return top._nametowidget(w)
def __get_top(self, wdg):
p = wdg.winfo_parent()
w = wdg._nametowidget(p)
if w:
if isinstance(w, tk.Tk):
top = w
else:
top = self.__get_top(w)
else:
top = None
return top
def activate(self):
self.active = not self.active
class WinDest(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
lbl = tk.Label(self, text='Casella destinazione :')
lbl.grid(row=0, column=0, padx=5, pady=5, sticky='w')
self.e_test = DragEntry(self)
self.e_test.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
self.e_test.activate()
def message(self, msg):
self.dida.configure(text=msg)
self.update()
class WinTest(tk.Tk):
def __init__(self):
super().__init__()
lbl = tk.Label(self, text='Casella sorgente :')
lbl.grid(row=0, column=0, padx=5, pady=5, sticky='w')
self.e_test = DragEntry(self)
self.e_test.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
pn_bt = tk.Frame(self)
pn_bt.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky='ew')
self.bt_active = tk.Button(pn_bt, text='Attiva', command=self._on_active)
self.bt_active.grid(row=0, column=0, padx=(5,2), pady=5, sticky='ew')
bt_close = tk.Button(pn_bt, text='Chiudi', command=self.destroy)
bt_close.grid(row=0, column=1, padx=(2,5), pady=5, sticky='ew')
pn_bt.grid_columnconfigure(0, weight=1, uniform='bt')
pn_bt.grid_columnconfigure(1, weight=1, uniform='bt')
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def _on_active(self):
self.e_test.activate()
if self.e_test.active:
self.bt_active.configure(text='Disattiva')
else:
self.bt_active.configure(text='Attiva')
def message(self, msg):
self.dida.configure(text=msg)
self.update()
if __name__ == '__main__':
app = WinTest()
w = WinDest(app)
app.mainloop()
