Si si… nessun errore di scrittura. In uno sprazzo di creatività, l’ho battezzato io Cliweb. In pratica, secondo la mia filosofia di sviluppo di applicazioni client, un Cliweb è effettivamente un’applicazione client che si comporta come un’applicazione web. Facciamo un passo indietro: qual’è stata la fortuna del web, quella scintilla di virtù che l’ha portato a travolgere le applicazioni client in un decennio (…anche meno)?
In una parola, semplicità. Il web è nato semplice, anche se adesso gli si vuol far fare “di tutto di più”. Le applicazioni client, noiose, grigie, tutte uguali, ma tutte diverse, annoiavano e fuorviavano l’utente. Il web invece, è alla portata di tutti; ma perchè? Cos’è che lo rende alla portata di tutti, più di un’applicazione client? L’interazione con l’utente. La navigazione e l’approccio punta e clicca hanno confortato l’utente e oggi persone di qualsiasi età, e di qualsiasi livello di istruzione, riescono a consultare informazioni sul web.
Possibile che non si riesca a fare altrettanto anche con il client? Possibile che la tecnologia si sia bloccata alle Windows Forms, che di “navigazione” e “punta e clicca” hanno ben poco? Se insistiamo sulle WinForms, avremo vita veramente difficile: si tratta di realizzare un meccanismo che imiti la navigazione del web in un ambiente che non offre alcun sistema “nativo” per la navigazione attraverso i contenuti.
Ecco quindi che prendendo in esame la tecnologia WPF, si si accorge come questo concetto si sposi molto bene con le “Navigation Application” di WPF. Le navigation application sono delle applicazioni che basano l’interazione con l’utente su concetti prettamente “web”, ovvero:
- Hyperlinks
- Navigazione
- Selezione da liste
- Wizards e percorsi guidati
Questo modello applicativo si sposa molto bene con il pattern MVVM, permettendo di fatto la creazione di applicazioni “casual-user” estremamente efficaci ed intuitive.
In questo articolo vedremo come impiegare il MVVM Toolkit di VS 2008 per creare una Navigation Application che impieghi il pattern MVVM. Nella fattispecie realizzeremo una semplice applicazione per la scelta di un prodotto da un ipotetico distributore automatico.
It’s a long, long story…
Come punto di partenza, analizziamo l’unica user-story che dovremo modellare:
L’utente deve selezionare l’operazione da effettuare con il distributore automatico, nel nostro caso un acquisto. Comparirà una lista di prodotti con i relativi prezzi. L’utente potrà selezionare il prodotto desiderato dalla lista ed acquistarlo, previa conferma dell’operazione.
Ovviamente si tratta di una User Story fasulla, ma ci servirà per capire come MVVM, Navigation Application e WPF possano esserci di aiuto anche nella fase di design della nostra applicazione. Dimenticavo: tutto questo senza ASSOLUTAMENTE curarci della grafica; ci focalizzeremo solamente sulle funzionalità, lasciando la cura della grafica ad un altro team (o ad un altro tempo, nel caso fossimo la classica “one man band”!).
Sketchin’ the Application
Una delle cose più belle: focalizzarsi sul flusso dell’applicazione: vediamo in figura come “disegnare il flusso” dell’applicazione in modo da focalizzare l’attenzione su COSA si vuole che l’utente veda e CON CHE COSA si vuole che l’utente interagisca. Io ho “barato”, in quanto ho utilizzato direttamente l’applicazione per disegnare il flusso. Solitamente utilizzo carta e matita per fare la stessa cosa in modo più “interattivo”.
In pratica avremo:
- Schermata di selezione dell’azione: in questo caso sarà solo una (acquisto) ma in futuro potremo voler espandere le possibilità dell’applicazione
- Schermata di selezione del prodotto da acquistare: lista di prodotti con pulsanti Annulla e Conferma
- Schermata di conferma dell’acquisto: riepilogo dell’azione di acquisto intrapresa, con possibilità di annullare o confermare
- Schermata di risultato dell’azione: conferma dell’azione intrapresa e possibilità di ritornare alla schermata di selezione dell’azione
Quindi, dal punto di vista di MVVM avremo:
- 4 Views: Selezione azione, Selezione prodotto, Riepilogo Acquisto, Conferma Acquisto
- 4 ViewModels Selezione Azione,Selezione prodotto, Riepilogo Acquisto, Conferma Acquisto
- 1 Model: Prodotto
Chiaramente non approfondiremo le azioni di “logica di business” che dovrebbero essere intraprese all’acquisto di un prodotto, in quanto non pertinenti.
Dal punto di vista tecnico, le Views saranno delle Page, mentre il nostro container primario sarà una NavigationWindow. I ViewModels esporranno i dati da collegare alle view ed i comandi con i quali le views potranno interagire; ovviamente saranno delle classi. Infine il model includerà i dati che vogliamo che la nostra entità prodotto contenga.
Modificare il contenuto del progetto MVVM generato
Il MVVM Toolkit produce un semplice progetto che serve da startup per un progetto MVVM. Se vogliamo utilizzare una Navigation Application, però, il progetto così com’è non va bene. Dobbiamo:
- Eliminare le view ed il view model creato automaticamente; la MainView è una Window, e a noi servono Pages.
- Modificare la classe App.cs come segue:
1: public partial class App : Application
2: {
3: private void OnStartup(object sender, StartupEventArgs e)
4: {
5: NavigationWindow _navigationWindow = new NavigationWindow();
6: _navigationWindow.Title = "Navigation Application with MVVM";
7: _navigationWindow.Width = 800;
8: _navigationWindow.Height = 600;
9: // Create the ViewModel and expose it using the View's DataContext
10: Views.MenuView _view = new Views.MenuView();
11: _view.DataContext = new ViewModels.MenuViewModel();
12: //Set Navigation window as current App MainWindow and show it
13: App.Current.MainWindow = _navigationWindow;
14: _navigationWindow.Show();
15: //Navigate to MenuView
16: _navigationWindow.Navigate(_view);
17: }
18: }
Fatto questo creiamo le classi che abbiamo elencato prima e le view individuate nell’analisi della User Story. La grande potenza del pattern MVVM ci permette di focalizzarci solo ed esclusivamente sulle funzionalità, senza perderci nei dettagli grafici (animazioni, suoni, disposizione degli elementi), implementabili in un secondo momento, quando le funzionalità dell’applicazione sono già consolidate. Vediamo insieme la View ed il ViewModel più complicati tra quelli creati: ProductsView e ProductsViewModel.
1: <Page x:Class="Cliweb.Views.ProductsView"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: Title="ProductsView">
5: <Grid>
6: <Grid.RowDefinitions>
7: <RowDefinition Height="80*"/>
8: <RowDefinition Height="20*"/>
9: </Grid.RowDefinitions>
10: <Grid.ColumnDefinitions>
11: <ColumnDefinition/>
12: <ColumnDefinition/>
13: </Grid.ColumnDefinitions>
14: <ListBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="8,8,8,8" ItemsSource="{Binding Products}" SelectedItem="{Binding Path=SelectedProduct}">
15: <ListBox.ItemTemplate>
16: <DataTemplate>
17: <StackPanel Orientation="Vertical">
18: <TextBlock Text="{Binding Path=Name}"></TextBlock>
19: <TextBlock Text="{Binding Path=Price}"></TextBlock>
20: </StackPanel>
21: </DataTemplate>
22: </ListBox.ItemTemplate>
23: </ListBox>
24: <Button Margin="8,8,8,8" Grid.Row="1" Grid.Column="1" Command="{Binding ConfirmCommand}">Conferma</Button>
25: <Button Margin="8,8,8,8" Grid.Row="1" Grid.Column="0" Command="{Binding CancelCommand}">Annulla</Button>
26: </Grid>
27: </Page>
Come possiamo vedere, sia i dati che i comandi sono collegati tramite binding al ViewModel sottostante, impostato tramite la proprietà DataContext della View. Tutta la vista può essere “stravolta”, applicando grafica, template e stili senza minimamente influire sulle funzionalità dell’applicazione che sono effetivamente implementate nel ViewModel.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Collections.ObjectModel;
6: using System.Windows.Input;
7:
8: using Cliweb.Models;
9: using Cliweb.Commands;
10:
11: namespace Cliweb.ViewModels
12: {
13: class ProductsViewModel
14: {
15:
16: private ProductViewModel _selectedProduct;
17: public ProductViewModel SelectedProduct
18: {
19: get { return _selectedProduct; }
20: set { _selectedProduct = value; }
21: }
22:
23: ObservableCollection<ProductViewModel> _products;
24: public ObservableCollection<ProductViewModel> Products
25: {
26: get
27: {
28: if (_products == null)
29: {
30: _products = new ObservableCollection<ProductViewModel>();
31: Services.ProductService _ps = new Services.ProductService();
32: List<Product> _items = _ps.GetProducts();
33: foreach (Product _item in _items)
34: {
35: _products.Add(new ProductViewModel(_item));
36: }
37: }
38: return _products;
39: }
40: }
41:
42: private DelegateCommand _confirmCommand;
43: public ICommand ConfirmCommand
44: {
45: get
46: {
47: if (_confirmCommand == null)
48: {
49: _confirmCommand = new DelegateCommand(Confirm);
50: }
51: return _confirmCommand;
52: }
53: }
54:
55: private void Confirm()
56: {
57: if (_selectedProduct != null)
58: {
59: //Navigate to confirm page
60: Views.ConfirmBuyView _view = new Views.ConfirmBuyView();
61: _view.DataContext = new ViewModels.ConfirmBuyViewModel(_selectedProduct);
62: Services.NavigationService.Navigate(_view);
63: }
64: }
65:
66: private DelegateCommand _cancelCommand;
67: public ICommand CancelCommand
68: {
69: get
70: {
71: if (_cancelCommand == null)
72: {
73: _cancelCommand = new DelegateCommand(Cancel);
74: }
75: return _cancelCommand;
76: }
77: }
78:
79: private void Cancel()
80: {
81: Views.MenuView _view = new Views.MenuView();
82: _view.DataContext = new ViewModels.MenuViewModel();
83: Services.NavigationService.Navigate(_view);
84: }
85: }
86: }
Il ViewModel è leggermente più complesso, ma non di molto. Ci sono i due comandi, ConfirmCommand e CancelCommand, che vengono collegati ai due buttons della view. Questi implementano la navigazione tra le pagine, creando una view, un viewmodel ed assegnando il ViewModel alla proprietà DataContext della View appena creata. Infine la classe NavigationService, mediante il metodo Navigate, effettua fisicamente la navigazione tra la pagina corrente e quella nuova. La proprietà SelectedProduct serve a mantenere traccia del prodotto selezionato nella lista, mentre la ObservableCollection Products si auto-popola al primo binding utilizzando un finto servizio ProductService. Per il resto dei dettagli, vi rimando al codice sorgente dell’esempio, che potete trovare qui.
Conclusioni
Sviluppare un CliWeb è piuttosto divertente. La semplificazione dell’interazione con gli elementi a video lo rende molto interessante come modello per le “casual-user applications”, ovvero quelle applicazioni che devono essere accessibili ad un pubblico piuttosto eterogeneo. Non è assolutamente detto che tutte le applicazioni debbano essere sviluppate con questo modello, ma in determinati casi l’impiego di una tecnica di questo genere renderà sicuramente più confortevole l’uso dell’applicazione ai nostri utenti. Ricapitolando, i tre elementi fondamentali sono:
- Pulsanti
- Selezioni da liste
- Navigazione tra pagine
Con questi tre semplici elementi e possibile effettuare tutti i task che si incontrano in una applicazione di media complessità. Ovviamente dobbiamo scordarci Ribbons, TreeViews, GridViews e controlli complessi. Questi controlli vanno bene per altri tipi di applicazioni, sono assolutamente compatibili con il pattern MVVM, ma non sono adatti ad un accesso “casual-user”.
L’ultimo (non in termini di importanza!) beneficio che possiamo individuare nell’impiego di MVVM in Navigation Applications è l’isolamento delle funzionalità e la testabilità delle stesse. In teoria, infatti, è possibile simulare un intero flusso operativo di navigazione solo utilizzando i ViewModels, per testare il comportamento dell’applicazione.