Crad's .NET Blog

L'UGIblog di Marco De Sanctis
posts - 190, comments - 457, trackbacks - 70

A scuola di Drag'n Drop

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:

  1. 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.)
  2. 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
  3. 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 )

powered by IMHO 1.3

Print | posted on sabato 5 agosto 2006 17:09 | Filed Under [ .Net 2.0 Windows Forms ]

Powered by:
Powered By Subtext Powered By ASP.NET