Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Drag&Drop con Silverlight

Un esempio per imparare a gestire gli eventi del mouse
Un esempio per imparare a gestire gli eventi del mouse
Link copiato negli appunti

Per sviluppare con Silverlight è necessario avere buone fondamenta di JavaScript e, per applicazioni complesse, di Web Service. Chiunque, con una buona preparazione in merito, noterà quanto sia semplice ed intuitivo creare applicazioni con questa nuova tecnologia.

Microsoft ha fatto le cose in grande: essendo multipiattaforma, possiamo sviluppare un'applicazione Silverlight ed utilizzarla su più sistemi, visto il supporto per browser come FireFox e Safari. L'integrazione con linguaggi lato server come .Net, con linguaggi lato client quali JavaScript (o altri come Python, Ruby o Visual Basic) e pieno supporto di Ajax fanno di questo prodotto un ottimo sostituto di Flash (forse data anche la miglior indicizzazione sui motori dettata dai file .XAML).

In questo articolo cercheremo di interagire meglio con gli oggetti presenti in un file XAML amministrandoli completamente via JavaScript.

Preparazione del progetto

Pensiamo ad esempio ad un classico web mail, strumento con il quale è possibile leggere i messaggi presenti nella nostra casella di posta elettronica utilizzando direttamente un qualsiasi browser Web: i più recenti Web mail utilizzano tecnologia Ajax che ci permette di effettuare dei drag'n'drop dei messaggi per spostarli o cancellarli.

Realizzare lo spostamento di oggetti in una pagina Web non è poi così complicato, utilizzando un qualsiasi framework Ajax a disposizione, esistono classi ad-hoc che ci facilitano il lavoro.

E con Silverlight? Beh, forse non ci crederete ma è di una naturalezza spaventosa..

Vediamo come fare. Ipotizziamo di voler creare un elenco di voci trascinabili, di avere un cestino a disposizione in cui poter rilasciare un elemento e cancellarlo dall'elenco. Dedichiamo il resto dell'articolo alla creazione di un applicativo di questo tipo.

Il progetto in Visual Studio

Prima cosa da fare è preparare il file XAML che conterrà i vari oggetti.

Utilizzando Visual Studio 2005 potremo modificare direttamente il file Scene.xaml che l'ambiente di sviluppo aggiunge di default al momento di creare l'applicazione Silverlight JavaScript (è necessario aver installato Microsoft Silverlight 1.0 SDK).

Figura 1. Creare una applicazione Silverlight
Creare una applicazione Silverlight

Creata l'applicazione (che nominiamo "SilverlightDragDrop"), osserviamo i file presenti nel progetto (ho aggiunto personalmente il file Stylesheet.css per eliminare i margini della pagina).

Figura 2. File presenti in un progetto Silverlight
File presenti in un progetto Silverlight

Il file Default.html contiene il riferimento ai vari file JS e XAML da incorporare nella pagina.

Listato 1. Defalut.html

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>SilverlightDragDrop</title>
  <link rel="stylesheet" href="StyleSheet.css" type="text/css" />

  <script type="text/javascript" src="Silverlight.js"></script>
  <script type="text/javascript" src="Default.html.js"></script>
  <script type="text/javascript" src="Scene.xaml.js"></script>
</head>

<body>
  <div id="SilverlightPlugInHost">
    <script type="text/javascript">
      createSilverlight();
    </script>
  </div>
</body>
</html>

Il file Silverlight.js è il cuore del progetto, senza l'inclusione di questo file, non è possibile creare oggetti SL, mentre gli altri 2 file JavaScript ci permettono di gestire eventi ed oggetti dei file Default.html e Scene.xaml.

La funzione richiamata createSilverlight() crea un oggetto Silverlight nella pagina richiamando il file Scene.xaml.

Listato 2. Funzione createSilverlight()

function createSilverlight()
{
  var scene = new SilverlightDragDrop.Scene();
  Silverlight.createObjectEx({
    source: 'Scene.xaml',
    parentElement: document.getElementById('SilverlightPlugInHost'),
    id: 'SilverlightPlugIn',
    properties: {
      width: '100%',
      height: '100%',
      background:'#ffffff',
      isWindowless: 'false',
      version: '1.0'
    },
    events: {
      onError:null,
      onLoad:onLoad
    },    
    context: null
  });
}

Indichiamo dimensione massima per l'oggetto: 100% sia in larghezza che in altezza. Così avremo a disposizione tutta la dimensione del browser.

Nota: VS2005 di default utilizza il metodo createObjectEx() che utilizza specifiche JSON per i parametri, ma è possibile utilizzare anche createObject() dove i parametri vengono indicati nella classica modalità sequenziale.

Il file XAML

Focalizziamo ora la nostra attenzione sul file Scene.xaml. Aprendolo in modifica, come prima cosa è necessario eliminare tutto il testo presente al suo interno ed inserire un oggetto Canvas contenitore (che chiameremo "root"), al suo interno, un altro Canvas (che conterrà l'elenco delle voci da poter trascinare) ed un oggetto Image (con l'immagine di un cestino) che chiameremo rispettivamente "elenco" e "cestino".

Listato 3. Scene.xaml

<Canvas xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Name="root">

  <Canvas Name="elenco" Width="150" Height="450" Background="#E8E8EC" Canvas.ZIndex="1"></Canvas>

  <Image Name="cestino" Source="img/cestino.png" Canvas.ZIndex="0" Width="50" Height="50" Canvas.Left="300" Canvas.Top="20"></Image>

</Canvas>

Questi oggetti sono la base della nostra applicazione: l'elenco delle voci ed un cestino per eliminarle (creare 2 immagini per il cestino: cestino.png e cestino_over.png per evidenziare che il puntatore del mouse si trova su di esso).

Aprendo il file Scene.xaml.js sarà necessario eliminare tutti i riferimenti alla gestione degli eventi del mouse precedentemente inseriti da Visual Studio, e lasciare esclusivamente:

if (!window.SilverlightDragDrop)
  window.SilverlightDragDrop = {};

SilverlightDragDrop.Scene = function() { }

A questo punto, siamo pronti a lanciare per la prima volta l'applicazione.

Figura 3. Prima esecuzione
Prima esecuzione

Ora possiamo dire di aver costruito dei contenitori. È necessario quindi popolarli.

Popolare l'elenco di voci

Per popolare l'elenco con le rispettive voci, verrà utilizzato un Array di stringhe a titolo di esempio, ma logicamente è possibile collegarsi mediante Web Service ad un database e ricevere le informazioni che desideriamo.

Un array come questo:

messaggi = new Array();
messaggi[0]="Mario";
messaggi[1]="Gianni";
messaggi[2]="Marco";
messaggi[3]="Massimiliano";
messaggi[4]="Lorenzo";
messaggi[5]="Andrea";

ci permette di popolare l'elenco effettuando un ciclo su di esso e creando, per ogni voce, un blocco di testo (oggetto TextBlock) che aggiungeremo all'oggetto "elenco" creato precedentemente:

Listato 4. Popolare il Canvas Elenco

function popolaElenco()
{
    // ripulisco l'elenco
    sl.content.FindName("elenco").children.clear();

    for (i=0; i < messaggi.length; i++)
    {
      // creo il blocco di testo con alcune proprietà
      var tb = sl.content.CreateFromXaml(
          "<TextBlock Name='tb" + i + "' Text='" + messaggi[i] + "' />",
          true);

      tb.setValue("Canvas.Left", 20);
      tb.setValue("Canvas.Top", (20*i));

      // aggiungo il blocco di testo all'elenco
      sl.content.FindName("elenco").children.add(tb);
    }
}

Il metodo CreateFromXaml() permette di creare oggetti dinamicamente indicando il loro codice XAML come parametro. Un codice XAML di esempio per un TextBlock potrebbe essere:

<TextBlock Name="nome" Text="testo visualizzato" Width="100" Height="20" FontFamily="Arial" FontSize="10" Foreground="Blue"></TextBlock>

L'oggetto "tb" quindi diventa a tutti gli effetti un oggetto TextBlock con tutte le sue proprietà e metodi. Sarà quindi possibile indicare una posizione mediante setValue() con indicato le proprietà Canvas.Left e Canvas.Top dell'oggetto (Top che aumenta di 20 punti ad ogni ciclo). Fatto questo, aggiungiamo l'oggetto al Canvas contenitore "elenco".

È l'ultima riga che ci permette di aggiungere fisicamente l'oggetto al Canvas elenco: sl è un oggetto che contiene un riferimento al contenitore Silverlight, creato in fase di Load della pagina mediante questa riga (dove SilverlightPlugIn è il nome dell'oggetto Silverlight creato in precedenza):

sl = document.getElementById("SilverlightPlugIn");

Oltre all'ID, al momento della creazione dell'oggetto Silverlight è stata anche indicata una funzione da eseguire al momento del caricamento dell'applicazione (in questo caso onLoad):

[...]
events: {
  onError:null,
  onLoad:onLoad
},    
[...]

Per cui, avviando il debug di uno script del genere:

Listato 5. onLoad

var sl;
var messaggi;
var el_left, el_top;

function onLoad(sender, eventArgs, context)
{
  sl = document.getElementById("SilverlightPlugIn");

  // preparo un array di prova
  messaggi = new Array();
  messaggi[0]="Mario";
  messaggi[1]="Gianni";
  messaggi[2]="Marco";
  messaggi[3]="Massimiliano";
  messaggi[4]="Lorenzo";
  messaggi[5]="Andrea";

  // popolo l'elenco con le voci
  popolaElenco();
}

function popolaElenco()
{
   // ripulisco l'elenco
   sl.content.FindName("elenco").children.clear();
  
   for (i=0; i < messaggi.length; i++)
   {
     // creo il blocco di testo con alcune proprietà
     var tb = sl.content.CreateFromXaml(
       "<TextBlock Name='tb" + i + "' Text='" + messaggi[i] + "' />",
       true);
     tb.setValue("Canvas.Left", 20);
     tb.setValue("Canvas.Top", (20*i));
    
     // aggiungo il blocco di testo all'elenco
     sl.content.FindName("elenco").children.add(tb);
   }
}

vediamo il nostro contenitore popolato con l'elenco di nomi.

Figura 4. L'elenco popolato
L'elenco popolato

Mouse Capture

A questo punto è necessario rendere trascinabili le voci create dinamicamente. Per fare ciò, Silverlight offre una importante funzionalità di Mouse Capture. È possibile conoscere la posizione del puntatore del mouse (finché rimane all'interno di un'applicazione SL) in tempo reale ad ogni movimento. Per attivarla, basta semplicemente richiamare la funzione captureMouse() sull'oggetto che si vuol trascinare. Questa funzione invierà tutte le informazioni sul puntatore all'oggetto stesso. Per un corretto trascinamento sarà necessario attivare questa funzionalità nel momento in cui l'utente clicca sull'oggetto, e interrompere il trascinamento al rilascio del pulsante del mouse.

Attiviamo quindi dei Listener per gestire gli eventi del mouse su ogni voce dell'elenco: ono per gestire la pressione del pulsante sinistro del mouse, uno per il rilascio di quest'ultimo ed infine uno per il trascinamento dell'oggetto.

Listato 6. Aggiungere listener per gli eventi del mouse

function popolaElenco()
{
  // ripulisco l'elenco
  sl.content.FindName("elenco").children.clear();

  for (i=0; i < messaggi.length; i++)
  {
    // creo il blocco di testo con alcune proprietà
    var tb = sl.content.CreateFromXaml(
        "<TextBlock Name='tb" + i + "' Text='" + messaggi[i] + "' />",
        true);
  
    tb.setValue("Canvas.Left", 20);
    tb.setValue("Canvas.Top", (20*i));

    // gestisco gli eventi di spostamento
    tb.addEventListener("mouseleftbuttondown","textBlock_onMouseLeftButtonDown");
    tb.addEventListener("mouseleftbuttonup","textBlock_onMouseLeftButtonUp");
    tb.addEventListener("mousemove","textBlock_onMouseMove");

    // aggiungo il blocco di testo all'elenco
    sl.content.FindName("elenco").children.add(tb);
  }
}

Non ci resta che scrivere le tre funzioni, iniziando con il gestire il Mouse Capture degli oggetti. Appena l'utente clicca su un oggetto (onMouseLeftButtonDown) attiviamo il capture.

Listato 7. Attivare il Mouse Capture

function textBlock_onMouseLeftButtonDown(sender, mouseEventArgs)
{
  // appena l'utente preme il pulsante del mouse,
  // memorizzo la posizione originale dell'elemento trascinato
  el_left = sender.getValue("Canvas.Left");
  el_top = sender.getValue("Canvas.Top");

  // poi attivo il Mouse Capture
  sender.captureMouse();
}

La variabile sender ovviamente contiene l'oggetto TextBlock che in quel momento è stato attivato mediante la pressione del pulsante del mouse (uno dei tb creati dinamicamente). Ed è su questo oggetto che andremo ad attivare il capture. Prima di proseguire però, è importante memorizzare le coordinate di posizione dell'oggetto prima del trascinamento. In caso di rilascio senza un target preciso (il cestino, ad es.) dovremmo posizionare l'oggetto sulle coordinate originali.

Ora è possibile gestire il movimento dell'oggetto, il suo trascinamento mediante l'evento onMouseMove.

Listato 8. Muovere un oggetto con il mouse

function textBlock_onMouseMove(sender, mouseEventArgs)
{
  if (sender.captureMouse())
  {
    // prendo la posizione corrente del puntatore
    var x = mouseEventArgs.getPosition(null).x;
    var y = mouseEventArgs.getPosition(null).y;

    // sposto l'oggetto seguendo il mouse
    sender.setValue("Canvas.Left", x);
    sender.setValue("Canvas.Top", y);
  }
}

Richiamando la proprietà x e y di mouseEventArgs è possibile conoscere la posizione attuale del puntatore del mouse. È quindi possibile indicare all'oggetto TextBlock associato la stessa posizione del cursore, dando l'impressione che segua il mouse nel suo movimento (mediante il setValue delle proprietà Left e Top).

Bene, ora gestiamo invece il rilascio dell'oggetto, e quindi il rilascio del pulsante del mouse. Come prima cosa possiamo interrompere il capture del mouse:

Listato 9. Interrompere il capture del mouse

function textBlock_onMouseLeftButtonUp(sender, mouseEventArgs)
{
  sender.releaseMouseCapture();
}

Avviando il debug dell'applicazione, ora è possibile trascinare una voce dell'elenco in giro per il browser. Ovviamente, al suo rilascio, non accadrà nulla se non vederla posizionata nell'ultimo punto in cui eravamo al momento dell'interruzione del trascinamento.

Dov'è il cestino?

Dobbiamo indicare alla nostra applicazione la posizione del cestino, per poter così effettuare una certa operazione se una voce viene rilasciata su di esso oppure no. Per conoscere la posizione del cestino è sufficiente leggere il valore delle proprietà Canvas.Top e Canvas.Left, ovvero le sue coordinate relative al pannello. Aggiungiamo quindi qualche riga in fase di Load della pagina.

// preparo il cestino
cestino = sl.content.FindName("cestino");
cestino_x = cestino.getValue("Canvas.Left");
cestino_y = cestino.getValue("Canvas.Top");

Nota: queste variabili sono richiamabili ovunque nell'applicazione.

Ora che ne conosciamo le coordinate, possiamo creare una funzioncina per capire se ci troviamo sopra il cestino o meno.

Listato 9. Verifica della collisione con il cestino

function isOnCestino(x, y)
{
  // verifico se le coordinate correnti si trovano sul cestino o meno
  if (
      (x >= cestino_x && x <= cestino_x+(cestino.width)) &&
      (y >= cestino_y && y <= cestino_y+(cestino.height))
  ) return true;
  else return false;
}

Se la coordinata x si trova tra il primo pixel (cestino_x) e l'ultimo (cestino_x + la larghezza dell'immagine del cestino) e la stessa cosa vale per la coordinata y, allora ci troviamo all'interno del cestino.

Fatto questo, ritorniamo sull'evento di rilascio pulsante del mouse (onMouseButtonLeftUp) ed integriamo il codice appena creato:

Listato 10. Funzione di rilascio del mouse

function textBlock_onMouseLeftButtonUp(sender, mouseEventArgs)
{
  // posizione corrente
  var x = mouseEventArgs.getPosition(null).x;
  var y = mouseEventArgs.getPosition(null).y;

  // blocco la gestione dell'evento textBlock_onMouseMove
  sender.releaseMouseCapture();

  // controllo se l'oggetto si trova sul cestino
  if (isOnCestino(x,y))
  {
    if (confirm("Sicuro di voler cancellare la voce " + sender.text + "?"))
    {
      // cancello la voce dall'elenco
      messaggi.splice(sender.name.replace("tb", ""), 1);
      popolaElenco();
    }
    else
    {
      // l'utente non ha confermato la cancellazione
      // riposiziono la voce al suo posto
      sender.setValue("Canvas.Left", el_left);
      sender.setValue("Canvas.Top", el_top);
    }
  }
  else
  {
    // l'elemento non è sul cestino
    // riposiziono la voce al suo posto
    sender.setValue("Canvas.Left", el_left);
    sender.setValue("Canvas.Top", el_top);
  }
}

Come prima cosa catturiamo la posizione corrente del puntatore, poi interrompiamo il capture. Subito dopo, verifichiamo se l'oggetto (sender, non scordiamoci) si trova o meno sul cestino. Se no, impostiamo la posizione originale dell'oggetto (che avevamo indicato con el_left e el_top).

Se invece l'oggetto si trova sul cestino, facciamo apparire un messaggio di conferma per la cancellazione. Se la conferma non viene data, allora riposizioniamo l'oggetto altrimenti effettuiamo la cancellazione nell'array messaggi precedentemente inizializzato. La cancellazione può essere eseguita mediante l'indice dell'oggetto nell'array. Visto che era stato dinamicamente nominato come tbX (dove X è l'indice), con un semplice replace per eliminare le lettere tb e l'utilizzo della funzione JavaScript splice(), il gioco è fatto.

A questo punto non resta che testare la nostra applicazione. Si potrà facilmente notare che le voci ora sono trascinabili e se vengono rilasciate sul cestino, apparirà il messaggio di conferma!

Figura 5. Conferma di cancellazione
La conferma di cancellazione

Rimane solo una cosa da fare: rendere leggermente più interattivo il cestino.. magari evidenziandolo quando un oggetto si trova sopra di esso. Potremmo gestire gli eventi onMouseEnter e onMouseLeave dell'oggetto cestino, ma il capture che viene attivato disabilita questi eventi. È quindi necessario usare il codice che abbiamo già preparato ed implementare un paio di semplici righe. Il nostro obiettivo è l'evento onMouseMove:

Listato 11. Creare l'effetto di "over"

function textBlock_onMouseMove(sender, mouseEventArgs)
{
  if (sender.captureMouse())
  {
    // prendo la posizione corrente del puntatore
    var x = mouseEventArgs.getPosition(null).x;
    var y = mouseEventArgs.getPosition(null).y;

    // sposto l'oggetto seguendo il mouse
    sender.setValue("Canvas.Left", x);
    sender.setValue("Canvas.Top", y);

    // controllo se sono sopra il cestino
    if (isOnCestino(x,y)) sl.content.FindName("cestino").source = "img/cestino_over.png";
    else sl.content.FindName("cestino").source = "img/cestino.png";
  }
}

In questo modo, se siamo sopra al cestino visualizziamo l'immagine "over" altrimenti la solita immagine di default.

Ti consigliamo anche