Uao, che titolo altisonante che ho tirato fuori dal cilindro questo pomeriggio.
Vi illustro lo scenario.
Immaginatevi un piccola applicazione mobile (Pocket PC o Windows Mobile, non importa) composta essenzialmente da 5 form diverse: Form1, Form2, Form3, Form4 e, sorpresa, Form5. L’applicazione parte mostrando Form1. Su ciascuna form ci sono due pulsanti – Next e Previous – per poter navigare avanti ed indietro da una forma all’altra. Gli stati possibili all’interno dell’applicazione sono i seguenti, con le varie possibilità di navigazione…
Partenza=Form1, Indietro=Form1, Avanti=Form2
Partenza=Form2, Indietro=Form1, Avanti=Form3
Partenza=Form3, Indietro=Form2, Avanti=Form4
Partenza=Form4, Indietro=Form3, Avanti=Form5
Partenza=Form5, Indietro=Form4, Avanti=Form5
Vediamo un po’ di codice per capire di cosa sto parlando. Questa è la classe Form1.
1 public partial class Form1 : Form
2 {
3 TransactionManager manager = new TransactionManager();
4
5 public Form1()
6 {
7 InitializeComponent();
8 }
9
10 private void previousButton_Click(object sender, EventArgs e)
11 {
12 manager.CurrentForm = this;
13 manager.NextIndex = 1;
14 manager.MoveToTransaction();
15 }
16
17 private void nextButton_Click(object sender, EventArgs e)
18 {
19 manager.CurrentForm = this;
20 manager.NextIndex = 2;
21 manager.MoveToTransaction();
22 }
23 }
Faccio uso di quello che ho chiamato TransactionManager, ovvero una classe che ha la responsabilità di farmi navigare avanti o indietro in base alla “posizione” in cui mi trovo. Se clicco sul previousButton e mi trovo su Form1, rimango su Form1. Se clicco su nextButton e mi trovo su Form1, vado su Form2, e così via, secondo lo schema (chiamiamolo così) che ho indicato sopra. Vediamo il codice di TransactionManager per capire un po’ di cose.
1 class TransactionManager : ITransaction
2 {
3 Form _currentForm;
4 int _nextIndex;
5
6 public Form CurrentForm
7 {
8 get { return _currentForm; }
9 set { _currentForm = value; }
10 }
11
12 public int NextIndex
13 {
14 get { return _nextIndex; }
15 set { _nextIndex = value; }
16 }
17
18 public void MoveToTransaction()
19 {
20 PDAManager.ShowWaitCursor(true);
21
22 string formName = "FormsNavigator.Form" + (_nextIndex).ToString();
23 Form newForm = (Form)Assembly.GetExecutingAssembly().CreateInstance(formName);
24
25 if (newForm != null)
26 {
27 newForm.Show();
28 newForm.BringToFront();
29
30 if (_currentForm.GetType() != typeof(Form1))
31 {
32 _currentForm.Close();
33 }
34 }
35
36 PDAManager.ShowWaitCursor(false);
37 }
38 }
Qua ci divertiamo.
La classe TransactionManager implementa l’interfaccia ITransactionManager, che prevede due proprietà pubbliche (CurrentForm e NextIndex) e l’implementazione di un metodo MoveToTransaction(). Quest’ultimo è fondamentale: visualizza la clessidra sullo schermo del palmare. Poi viene creata tramite Reflection l’istanza del nuovo form da visualizzare: mi spiego meglio. Supponiamo di essere su Form2 e supponiamo di cliccare sul pulsante Next: il flusso di esecuzione entra nel metodo MoveToTransaction() e viene calcolato il nome del nuovo form: nel nostro caso FormsNavigator.Form3 (linea 22 del codice qui sopra). Il nuovo form viene creato (linea 23) e mostrato sullo schermo. Se il vecchio form non è Form1, lo chiudo, altrimenti rimane lì dov’è: Form1 è l’entry-point di questa applicazione, se lo chiudessi chiuderei tutto, perciò lo evito. La clessidra viene tolta e l’utente si ritrova con il nuovo form davanti.
Aggiungiamo un po’ di asincronia
Il codice qui sopra è semplice: permette di navigare avanti ed indietro sulle 5 form dell’applicazione. Supponiamo adesso che il passaggio da una form all’altra implichi il salvataggio di una qualche informazione su database locale, magari un SQL Server Compact Edition. Nulla ci vieta di mettere TextBox, ComboBox o altri controlli su ciascuna delle 5 form. Va da sè che il passaggio da uno stato all’altro deve prima occuparsi del salvataggio di queste informazioni da qualche parte, magari – come dicevo prima – un database locale. Questo caso può anche essere relativamente veloce, ma se la mole di dati cresce, oppure se utilizzo un database remoto, il tempo di attesa potrebbe anche crescere. Se c’è una cosa che non mi piace, è far aspettare l’utente per una cosa di cui non gliene può fregare di meno. Mi piacerebbe che la navigazione fosse veloce, che l’utente possa viaggiare da Form1 a Form2 senza aspettare troppo, mentre qualcun’altro sta facendo il lavoro sporco senza bloccare la UI, e senza far aspettare nessuno. Ho risolto così.
Alla classe TransactionManager di prima ho aggiunto un metodo privato:
1 void saveTransactionDataAsync()
2 {
3 System.Threading.Thread.Sleep(5000);
4 string msg = string.Format("Ho finito di salvare i dati della tx {0}", _nextIndex.ToString());
5 MessageBox.Show(msg);
6 }
L’implementazione per questo post non fa nulla di interessante: il thread corrente viene messo in pausa per 5 secondi, poi visualizzo una MessageBox all’utente che lo informa che l’operazione di salvataggio dati è stata terminata con successo. Ho poi modificato l’implementazione del metodo MoveToTransaction che abbiamo visto prima, inserendo alla linea 21 le seguenti due linee di codice:
1 Thread thread = new Thread(saveTransactionDataAsync);
2 thread.Start();
In pratica, le operazioni su database vengono effettuate in un thread separato, mentre quello principale chiude ed apre le form molto velocemente, senza che l’utente si accorga di quello che sta succedendo in modo più “nascosto”.
Ci sono molte altre cose da dire, ma penso che per dare l’idea e per fornire qualche spunto può essere più che sufficiente. I commenti sono ben accetti.
Technorati Tags: .NET Framework Compact Framework Programming async