Uno delle cose che mancano in Silverlight, sin dalla versione 1.0, è la possibilità di gestire l'evento DoubleClick del Mouse. Pur se Silverlight è ricco di eventi che riguardano il mouse è carente secondo due punti di vista. Il tasto destro del mouse non è gestibile, ma questo lo si deve alla presenza di un menù di contesto di configurazione del plugin. Ma l'assenza di doppio click è davvero singolare e talvolta fastidiosa. Perciò, ora che è uscita la beta 1 di Silverlight 2.0, e preso atto che la mancanza è ancora presente ho pensato di creare una classina che mi venga in aiuto quando ho la necessità di distinguere tra singolo e doppio click.

La questione è piuttosto semplice. Abbiamo a disposizione sono l'evento MouseLeftButtonUp e da esso dobbiamo discriminare ed è piuttosto ovvio che la variabile da tenere in considerazione sia il tempo. Potremmo immaginare che quando arriva un primo evento dobbiamo attendere un certo numero di millisecondi (diciamo 300 circa). Se entro questo tempo arriva un secondo evento siamo in presenza di un doppio click mentre al loro scadere possiamo considerare di avere a che fare con un single click.

Gestire questa condizione comporta però un bel po' di lavoro e soprattutto saremmo costretti a scrivere questo flusso svariate volte, almeno una volta per ciascun elemento per il quale abbiamo la necessità di gestire il doppio click. Perciò da buoni programmatori, convinti che il nostro lavoro sia di scrivere poco codice, dobbiamo realizzare una classe che ci aiuti ogni volta che ne abbiamo bisogno.

La classe che ho realizzato e che trovate in allegato a questo post prende in carico tutti gli eventi di click che arrivano da una determinata sorgente e applica la logica cui ho accennato poco fa. Poi. alla scadenza dei 300 millisecondi si incarica di sollevare a sua volta un altro evento corrispondente al singolo o doppio click. Purtroppo, dato che per ottenere l'eventuale secondo click dobbiamo restituire immediatamente il controllo al chiamante, siamo costretti a lanciare un thread che si incarichi di attendere il timeout e lanciare l'evento Click (quello singolo) nel caso in cui non arrivi più nulla.  Ecco il corpo principale del MouseClickManager:

   1: /// <summary>
   2: /// Handles the click.
   3: /// </summary>
   4: /// <param name="sender">The sender.</param>
   5: /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
   6: public void HandleClick(object sender, MouseButtonEventArgs e)
   7: {
   8:     lock(this)
   9:     {
  10:         if (this.Clicked)
  11:         {
  12:             this.Clicked = false;
  13:             OnDoubleClick(sender, e);
  14:         }
  15:         else
  16:         {
  17:             this.Clicked = true;
  18:             ParameterizedThreadStart threadStart = new ParameterizedThreadStart(ResetThread);
  19:             Thread thread = new Thread(threadStart);
  20:             thread.Start(e);
  21:         }
  22:     }
  23: }
  24:  
  25: /// <summary>
  26: /// Resets the clicked flag after timeout.
  27: /// </summary>
  28: /// <param name="state">The state.</param>
  29: private void ResetThread(object state)
  30: {
  31:     Thread.Sleep(this.Timeout);
  32:  
  33:     lock (this)
  34:     {
  35:         if (this.Clicked)
  36:         {
  37:             this.Clicked = false;
  38:             OnClick(this, (MouseButtonEventArgs)state);
  39:         }
  40:     }
  41: }

Nel metodo HandleClick dapprima acquisiamo un lock. E' una precauzione necessaria perchè come vedremo il flag Clicked diventa una risorsa condivisa fra due thread diversi e quindi il lock ci serve ad evitare problemi di concorrenza su di essa. A questo punto i casi sono due: se troviamo Clicked settato a false significa che siamo in presenza del primo click e quindi dopo aver settato il flag a true avviamo un thread secondario. Se invece Clicked è già settato significa che è arrivato il secondo click perciò si azzera il flag e poi si provvede a sollevare l'evento b. Nel thread secondario innanzitutto si attendono i millisecondi configurati nella proprietà Timeout e al loro scadere se il flag è ancora settato lo si azzera e si solleva l'evento Click. Fermatevi un attimo a pensarci su se la cosa non è così chiara... comunque vi assicuro che funziona.

Ora c'è un problema da gestire. Dato che l'evento Click viene sollevato da un thread secondario quando questo arriva all'interfaccia ci troveremo nella spiacevole condizione di non essere sincronizzati con il thread principale che appunto detiene l'interfaccia stessa. Questo significa che se tutto va bene... non funzionerà nulla :) Abbiamo bisogno di fare il marshaling del contesto per rendere l'evento Click fruibile dall'interfaccia. Questo è un problema comune all'ambiente Windows Forms che si ripresenta anche all'interno del plugin di Silverlight. Ecco il corpo del metodo OnClick deputato a sollevare l'evento:

   1: /// <summary>
   2: /// Called when click occure.
   3: /// </summary>
   4: /// <param name="sender">The sender.</param>
   5: /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
   6: private void OnClick(object sender, MouseButtonEventArgs e)
   7: {
   8:     MouseButtonEventHandler handler = Click;
   9:  
  10:     if (handler != null)
  11:         this.Control.Dispatcher.BeginInvoke(handler, sender, e);
  12: }

L'oggetto rappresentato dalla proprietà Control è l'istanza del controllo che dovrà ricevere l'evento. Questo deve essere passato al costruttore della classe assieme al timeout desiderato. Il dispatcher si occuperà per noi di gestire il marshaling del contesto e il gioco è fatto.

Usare la classe MouseClickManager è semplice. Se ne crea un'istanza passandole un riferimento al controllo che la contiene e un timeout. Poi si agganciano i due eventi DoubleClick e Click e infine si fa in modo che all'interno dell'evento MouseLeftButtonUp venga chiamato il metodo HandleClick e si passano ad esso gli argomenti sender e args.

La scelta del timeout è soggettiva. Sarebbe opportuno poter configurare la soglia in base all'abilità dell'utente. Comunque da qualche esperimento che ho fatto si evince che 200 millisecondi sono troppo pochi mentre 400 sono troppi e si inizia ad apprezzare il ritardo dell'evento Click rispetto al clieck vero e proprio.

Download: http://blog.boschin.it/download/mouseclickmanager.zip (1 KB)

 

Technorati Tag: ,

Aggiungi Commento