En anteriores posts hemos visto que algunos controles de presentación, como la Barra de Aplicación, no son sencillos de montar con un patrón MVVM (fundamentalmente porque son objetos del shell y no objetos Silverlight).
En este caso vamos a ver como otros controles Silverlight que no se ajustan exactamente al modelo y, aun así, podremos manejar eventos con facilidad utilizando el patrón.
Para ello vamos a basarnos en una página de configuración que estoy montando en mi próximo proyecto.
Como podéis ver, los dos controles inferiores permiten activar y desactivar notificaciones (Live Tiles y Toast Notifications). Ambos controles son de la clase ToggleSwitch.
La clase ToggleSwitch describe eventos para manjar (entre otros) el Click, Checked y Unchecked. Sin embargo, no dispone de la posibilidad de invocar un comando (como sí pasa con Button).
Aquí es donde vamos a trabajar con una funcionalidad incorporada en el SDK 7.1 que nos permitirá asociar fácilmente (y de forma declarativa) eventos a comandos.
Lo primero es hacer referencia en la página al siguiente espacio de nombres.
1:
2: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
3:
A continuación, en cada uno de los controles declararemos “disparadores” (Triggers) que invocarán un comando.
1: <tool:ToggleSwitch Grid.Column="1" x:Name="PermitirTiles" Header="{Binding Source={StaticResource LocationHelper}, Path=Recursos.PermitirActiveTiles}" Content="{Binding Path=TextoActivarTiles}" IsChecked="{Binding Path=ActivarTiles}">
2: <i:Interaction.Triggers>
3: <i:EventTrigger EventName="Checked">
4: <i:InvokeCommandAction Command="{Binding Path=ComandoActivarTiles}" CommandParameter="{Binding ElementName=PermitirTiles, Path=IsChecked}" />
5: </i:EventTrigger>
6:
7: <i:EventTrigger EventName="Unchecked">
8: <i:InvokeCommandAction Command="{Binding Path=ComandoActivarTiles}" CommandParameter="{Binding ElementName=PermitirTiles, Path=IsChecked}" />
9: </i:EventTrigger>
10: </i:Interaction.Triggers>
11: </tool:ToggleSwitch>
Como podéis ver, en este caso se disparan “Triggers” en los eventos Checked y Unchecked del control, en ambos casos se invoca al mismo comando y, en ambos casos pasando como parámetro la propiedad "IsChecked” del propio control. La diferencia es que, dependiendo del evento lanzado, dicha propiedad valdrá true o false.
A continuación en el ModeloVista declaremos las siguientes propiedades (he eliminado algo de código para facilitar el seguimiento).
1: public class ConfiguracionViewModel : ViewModelBase
2: {
3: private ICommand _comandoActivarTiles;
4: private ICommand _comandoActivarToast;
5:
6: /// <summary>
7: /// Initializes a new instance of the ConfiguracionViewModel class.
8: /// </summary>
9: /// <param name="dataService">Instancia de <see cref="IConfiguracionDataService"/> que proporciona acceso al servicio de datos</param>
10: public ConfiguracionViewModel(IConfiguracionDataService dataService)
11: {
12: if (dataService != null)
13: _dataService = dataService;
14:
15: // Creamos el comando para activar /desactivar tiles y el de activar /desactivar toast
16: _comandoActivarTiles = new RelayCommand<bool>((bool p) => ActivarTiles = p);
17: _comandoActivarToast = new RelayCommand<bool>((bool p) => ActivarToast = p);
18:
19: LoadData();
20: }
21:
22: private static readonly string TextoActivarTilesNombreProp = "TextoActivarTiles";
23:
24: /// <summary>
25: /// Texto que se mostrará dependiendo del estado actual de la activación de tiles
26: /// </summary>
27: public string TextoActivarTiles
28: {
29: get { return _textoActivarTiles; }
30: private set
31: {
32: _textoActivarTiles = value;
33: RaisePropertyChanged(TextoActivarTilesNombreProp);
34: }
35: }
36:
37: private static readonly string TextoActivarToastNombreProp = "TextoActivarToast";
38:
39: /// <summary>
40: /// Texto que se mostrará dependiendo del estado actual de la activación de tiles
41: /// </summary>
42: public string TextoActivarToast
43: {
44: get { return _textoActivarToast; }
45: private set
46: {
47: _textoActivarToast = value;
48: RaisePropertyChanged(TextoActivarToastNombreProp);
49: }
50: }
51:
52: /// <summary>
53: /// Propiedad que permite acceder al comando de activación /desactivación de tiles
54: /// </summary>
55: public ICommand ComandoActivarTiles
56: {
57: get { return _comandoActivarTiles; }
58: }
59:
60: /// <summary>
61: /// Propiedad que permite acceder al comando de activación /desactivación de notificaciones Toast
62: /// </summary>
63: public ICommand ComandoActivarToast
64: {
65: get { return _comandoActivarToast; }
66: }
67:
68: private static readonly string ActivarTilesNombreProp = "ActivarTiles";
69:
70: /// <summary>
71: /// Permite acceder al valor que indica si los tiles activos están o no habilitados
72: /// </summary>
73: public bool? ActivarTiles
74: {
75: get { return _preferencias.EnableTileNotif; }
76: set
77: {
78: if (_preferencias.EnableTileNotif == value)
79: return;
80:
81: _preferencias.EnableTileNotif = value;
82: TextoActivarTiles = ObtenerCaptionToggleSwitch(value);
83:
84: RaisePropertyChanged(ActivarTilesNombreProp);
85: }
86: }
87:
88: private static readonly string ActivarToastNombreProp = "ActivarToast";
89:
90: /// <summary>
91: /// Permite acceder al valor que indica si las notificaciones toast están o no habilitadas
92: /// </summary>
93: public bool? ActivarToast
94: {
95: get { return _preferencias.EnableToastNotif; }
96: set
97: {
98: if (_preferencias.EnableToastNotif == value)
99: return;
100:
101: _preferencias.EnableToastNotif = value;
102: TextoActivarToast = ObtenerCaptionToggleSwitch(value);
103:
104: RaisePropertyChanged(ActivarToastNombreProp);
105: }
106: }
107:
108: /// <summary>
109: /// Devuelve una cadena con el título de un ToggleSwitch en función del valor pasado como parámetro.
110: /// </summary>
111: /// <param name="param">Valor booleano asociado al estado del ToggleSwitch</param>
112: /// <returns>Cadena con el título que debe de mostrar el ToggleSwitch</returns>
113: private static string ObtenerCaptionToggleSwitch(bool? param)
114: {
115: return (param.HasValue && param.Value) ? Resources.CaptionActivo : Resources.CaptionInactivo;
116: }
117: }
Esencialmente se crean comandos que cambian el valor del modelo y los literales asociados al estado del control (buscando dichos literales en los recursos).
La clase RelayCommand está declarada en el framework MVVM Light.
El código no está limpio pero probado y funciona.