Esortato da Janky, dato che
poteva essere utile per NHibernate Domain Mapper
, stamattina mi son messo un po' a ripassare il Drag'n Drop nelle
Windows Forms, una funzionalità che personalmente non implemento quasi mai, e
che invece è particolarmente intuitiva per l'utente... d'ora in poi dovrò
sforzarmi un po' di più!
Per questa ragione, ho realizzato una piccola applicazione d'esempio (che
potete scaricare qui) che permette di trascinare nodi tra due TreeView o
all'interno dello stesso TreeView.
L'interfaccia è assolutamente spartana, ma tanto che ce frega?
Prima di guardare un po' di codice, diciamo subito che il drag'n drop si
divide in tre fasi, che dobbiamo quindi gestire:
- L'utente inizia l'operazione di trascinamento: in
questa fase dobbiamo fornire a Windows alcune informazioni circa il dato che
stiamo trasportando e le operazioni consentite (es. Move, Copy, Link,
ecc.ecc.)
- L'utente sposta il mouse sopra un altro Control
abilitato per il Drop (nel caso delle WinForms, lo è se abbiamo settato
AllowDrop = True): in questa fase dobbiamo possiamo decidere se
accettare o meno il Drop e, nel secondo caso, Windows mostrerà all'utente il
classico puntatore a forma di divieto
- L'utente alza il dito dal mouse ed effettua il drop: in questa ultima fase
gestiremo l'operazione di copia, spostamento, ecc.ecc.
Bene, la fase 1 nelle WinForms è gestita richiamando il
metodo DoDragDrop della classe base Control solitamente in corrispondenza di un evento MouseDown. Alcuni controlli, come ListView e TreeView, mettono a disposizione un evento specifico,
chiamato ItemDrag. Guardando il codice dell'esempio troviamo:
private void treeViewLeft_ItemDrag(object sender, ItemDragEventArgs e)
{
startDragDrop(sender as TreeView, e);
}
private void startDragDrop(TreeView treeView, ItemDragEventArgs e)
{
if (e.Item == null || e.Button != MouseButtons.Left)
return;
DragDropEffects allowedEffects =
DragDropEffects.Copy | DragDropEffects.Move;
treeView.DoDragDrop(e.Item, allowedEffects);
}
e.Item contiene l'elemento correntemente selezionato, quindi tutto ciò
che dobbiamo fare è specificare che questa operazione di Drag'n Drop potrà
terminare con una Move o con una Copy.
Quando l'utente trascina qualcosa, ogni Control che ha
AllowDrop = true solleva l'evento DragOver
, che può essere gestito per determinare se accettare o
meno l'oggetto.
private void treeViewLeft_DragOver(object sender, DragEventArgs e)
{
executingDragDrop(sender as TreeView, e);
}
private void executingDragDrop(TreeView treeView, DragEventArgs e)
{
if (!e.Data.GetDataPresent(typeof(TreeNode)))
{
e.Effect = DragDropEffects.None;
return;
}
treeView.Focus();
// we retrieve the source node
TreeNode sourceNode = (TreeNode) e.Data.GetData(typeof(TreeNode));
// we retrieve the current treenode
TreeNode targetNode = treeView.GetNodeAt(
treeView.PointToClient(new Point(e.X, e.Y)));
if (targetNode != null)
targetNode.TreeView.SelectedNode = targetNode;
// user cannot dragdrop a node into itself
if (targetNode == sourceNode)
{
e.Effect = DragDropEffects.None;
return;
}
// Ctrl pressed? copy, otherwise move!
int CtrlKey = 8;
if ((e.KeyState & CtrlKey) == CtrlKey)
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.Move;
}
Qui impersoniamo l'oggetto destinazione, e lo scopo di questa fase è
quello di valorizzare correttamente e.Effects specificando ciò che vogliamo
fare; in questo caso ad esempio, se l'oggetto trascinato non è un TreeNode
rifiutiamo il Drop, altrimenti selezioniamo l'operazione di Copy o Move a
seconda della pressione del tasto Ctrl.
A questo punto l'utente solleva il ditino dal pulsante del mouse e rilascia
l'oggetto. Se l'operazione è consentita (quindi e.Effect !=
DragDropEffects.None), viene sollevato l'evento DragDrop, che possiamo gestire
nel nostro codice:
private void treeViewRight_DragDrop(object sender, DragEventArgs e)
{
completeDragDrop(sender as TreeView, e);
}
private void completeDragDrop(TreeView treeView, DragEventArgs e)
{
TreeNode targetNode = treeView.GetNodeAt(
treeView.PointToClient(new Point(e.X, e.Y)));
TreeNodeCollection targetCollection =
(targetNode == null ?
treeView.Nodes :
targetNode.Nodes);
TreeNode sourceNode = (TreeNode)
e.Data.GetData(typeof(TreeNode));
TreeNodeCollection sourceCollection =
(sourceNode.Parent == null ?
sourceNode.TreeView.Nodes :
sourceNode.Parent.Nodes);
if (e.Effect == DragDropEffects.Move)
sourceCollection.Remove(sourceNode);
else
sourceNode = (TreeNode) sourceNode.Clone();
targetCollection.Add(sourceNode);
}
In questo snippet di codice, non facciamo altro che recuperare il
TreeNode destinazione tramite le coordinate del mouse e appendere ad esso il
nodo sorgente, clonandolo nel caso l'operazione sia una Copy o rimuovendolo
dalla collection di nodi a cui apparteneva nel caso stiamo eseguendo una
Move.
Spero sia utile a qualcuno, per qualsiasi info sono qui! ('mmazza quanto
scrivo in questi giorni )