Non è una novità, ma è sicuramente il desiderio espresso da molti dei nostri clienti: voglio un'applicazione che "cambi interfaccia" come io desidero.
Detta così sembra semplice, o comunque fattibile, e infatti in molti casi lo è: il web ci ha abituato bene, CSS, user controls, ASP.net sembrano fatti apposta per creare applicazioni "muta-forma".
Non si può dire altrettanto per i clients. Win32, con i suoi pregi e difetti, solo ultimamente ci ha messo a disposizione un blando supporto per i temi, che spesso si limitava a qualche ombra in più su un bottone o un effetto grafico su una lista.
Ritornando al desiderio del nostro cliente, il cambio di interfaccia non è solo un cambio di tema e/o colore, ma un vero e proprio cambio di struttura dell'interfaccia: stesse funzionalità in punti diversi e con approcci diversi o addirittura possibilità di rimuovere alcune funzionalità presenti in una versione "full" per semplificare delle versioni light della stessa applicazione.
Come può aiutarci nel perseguire questo obiettivo il pattern Model-View-ViewModel (sempre a braccetto con WPF, s'intende)?
Se pensiamo bene, WPF ci dà veramente tutto quello che ci serve per riuscire a caricare dinamicamente le interfacce. Per implementare la soluzione userò il MVVM Toolkit per Visual Studio 2008, ma i ragionamenti sono piuttosto “trasversali” ed indipendenti dalla particolare implementazione di MVVM.
Gli obiettivi che ci prefiggiamo saranno i seguenti:
- Vogliamo avere una applicazione "completa" e funzionante priva di interfaccia grafica
- Le interfacce grafiche dovranno risiedere su disco e NON essere compilate
- Non dovranno esserci file di "code-behind" per le nostre interfacce, in quanto l'unica comunicazione con la logica avverrà attraverso databinding e commandbinding
Quindi, parlando in MVVM-lese, dovremmo produrre una dll (o un exe, nel nostro esempio) con all'interno i Model ed i ViewModel. Questi infatti definiscono completamente il dominio (Model) e l'operatività (ViewModel) della nostra applicazione.
Tutte le View dovranno essere esterne rispetto alla nostra applicazione, quindi saranno semplicemente una serie di file XAML. Questo è facilmente realizzabile in questo modo:
- Cancellare il file .cs (code behind) della nostra “View”
- Impostare la proprietà “Build Action” a “Content”
- Impostare la proprietà “Copy to output directory” a “Copy if newer”
Esiste un solo limite a questo approccio, peraltro superabile. Le nostre view non compilate non potranno contenere UserControls (sempre XAML only), in quanto il Framework non mette a disposizione un sistema per referenziare fisicamente i files che risiedono sul disco (un pò come faceva invece il prode ASP.net con i controlli .ascx).
Dopo aver creato il progettino, aggiungiamo un file .config, nel quale aggiungiamo la proprietà Theme, come si può vedere nel seguente snippet:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <appSettings>
4: <!--Standard|Custom-->
5: <add key="Theme" value="Custom"/>
6: </appSettings>
7: </configuration>
Il parametro Theme, assieme alla classe ViewFactory che vediamo nel seguente snippet, decide come verranno create le nostre view, che sono l’unica cosa personalizzabile della nostra applicazione:
1: using System.Configuration;
2: using System.Windows;
3: using System.IO;
4: using System.Windows.Markup;
5:
6: namespace DynamicMVVM.Util
7: {
8: public class ViewFactory
9: {
10: public static System.Windows.Window CreateView(string viewName)
11: {
12: Window _view;
13: using (Stream _stream = File.OpenRead(string.Format("{0}/{1}.xaml",ConfigurationManager.AppSettings["Theme"], viewName)))
14: {
15: _view = (Window)XamlReader.Load(_stream);
16: }
17: return _view;
18: }
19: }
20: }
Con il metodo CreateView potremo creare “al volo” le view da passare come visualizzatori ai nostri ViewModels. Come possiamo vedere nel seguente frammento di codice, nell’evento di Startup della nostra applicazione, dopo aver creato il MainViewModel, utilizziamo il Factory delle View per creare la View sensibile alla configurazione:
1: using System.Windows;
2:
3: namespace DynamicMVVM
4: {
5: public partial class App : Application
6: {
7: private void OnStartup(object sender, StartupEventArgs e)
8: {
9: // Create the ViewModel and expose it using the View's DataContext
10: Window _view = Util.ViewFactory.CreateView("MainView");
11: _view.DataContext = new ViewModels.MainViewModel();
12: _view.Show();
13: }
14: }
15: }
A questo punto, il risultato si può vedere nella figura seguente; a sinistra la view “Standard” e a destra la view “Custom”:
E’ ovvio che questa non è una soluzione di Theming dell’applicazione, in quanto WPF offre già tutti gli strumenti per effettuare theming ad altissimo livello. Con questa soluzione possiamo effettivamente stravolgere le interfacce, impiegando addirittura controlli differenti. Come potrete notare, infatti, nella view “Custom” ho sostituito il controllo ListBox con un a ListView, spostato il pulsante e aggiunto un’immagine. Ovviamente sarebbe possibile fare molto di più, ma questo esempio vuole essere un pò la conclusione del mio post sulle cover dei cellulari e la loro similitudine con MVVM. E’ innegabile, infatti, che applicando questo pattern è possibile *REALMENTE* separare il comportamento dell’applicazione dal suo aspetto grafico, valicando il concetto di “Tema” fino ad arrivare ad una personalizzazione molto spinta. La chiave di lettura è ovviamente come sempre il Binding messo a disposizione da WPF che funge da collante tra le interfacce ed i ViewModels. Il codice è disponibile qui.