Dialogo Modal en MVVM

Los Diálogos Modales están entre los elementos más complejos de usar en el patrón MVVM principalmente por la necesidad de separación de ocupaciones que se debe de cumplir.

Ciertamente se puede manejar de una manera fácil el manejo de los Diálogos Modales desde la vista y en el caso que deseemos hacer la llamada desde el ViewModel, podemos usar eventos, pero ciertamente no es lo recomendable.
Para solucionar el problema se tiene las siguientes opciones:
-          Usar el patrón Mediador
-          Implementar el dialogo como un servicio
En el presente post voy a usar la segunda opción pero omitiendo más que todo el uso del Inversor de Control, ciertamente no es una solución al 100% completa por que se genera tiene un ratio mayor de acoplabilidad, pero hay que tomar en cuenta que en la mayoría de los proyectos no es primordial tal característica y la inversión de tiempo requerida (más que todo en proyectos que poseen mucho código legado o que han sido desarrollados sin tomar en cuenta MVVM y se desea migrar al patrón) es por demás alta; en otras palabras la relación costo - beneficio no es la óptima.
El proyecto que vamos a crear es sencillo, es un formulario con una TextBox y un botón, pulsando el botón se abrirá un dialogo para guardar en un archivo de texto el contenido del TextBox:
De tal manera que primero vamos a crear el ViewModel, donde definiremos una propiedad que se enlace con el TextBox que será del tipo String, luego crearemos un comando que se encargue de escuchar el evento de clic sobre el botón:
        private string _text;
        public string Text
        {
            get
            {
                return _text;
            }
            set
            {
                _text = value;
                NotifyPropertyChanged( ( ) => this.Text );
            }
        }

        #region SaveText Command
        public DelegateCommand SaveTextCommand
        {
            get;
            private set;
        }
 
        void SaveText_Execute( object parameters )
        {
        }
 
        bool SaveText_CanExecute( object parameters )
        {
            return true;
        }
        #endregion

No hay que olvidar que nuestro ViewModel debe implementar la interface “INotifyPropertyChanged”:
        
#region INotifyPropertyChanged Implementation
        public event PropertyChangedEventHandler PropertyChanged;
 
        public void NotifyPropertyChanged( string propertyName )
        {
            if ( this.PropertyChanged != null )
                this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
        }
 
        public void NotifyPropertyChanged( Expression<Func<object>> property )
        {
            MemberExpression memberExpression;
            if ( property.Body is UnaryExpression )
                memberExpression = ( property.Body as UnaryExpression ).Operand as MemberExpression;
            else
                memberExpression = property.Body as MemberExpression;
 
            if ( memberExpression == null )
                Debug.Assert( false, "It must not happen" );
 
            var propertyInfo = memberExpression.Member as PropertyInfo;
            if ( propertyInfo == null )
                Debug.Assert( false, "It must not happen" );
 
            this.NotifyPropertyChanged( propertyInfo.Name );
        }
        #endregion
Nuestro siguiente paso será crear la Vista, como se vio en la imagen superior en el XAML, enlazaremos nuestra Vista con el ViewModel:

<Button Content="Save in a File" Height="52" HorizontalAlignment="Left" Margin="12,115,0,0" Name="btnSave" VerticalAlignment="Top" Width="306" Command="{Binding SaveTextCommand}" />
<TextBox Height="90" HorizontalAlignment="Left" Margin="68,19,0,0" Name="txtText" VerticalAlignment="Top" Width="250" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Text="{Binding Text}" />
<Label Content="Text:" Height="28" HorizontalAlignment="Left" Margin="12,19,0,0" Name="lblText" VerticalAlignment="Top" />

Y por último en el Code-BeHind completaremos el enlace:
        private ViewModel viewModel;
        public MainWindow( )
        {
            InitializeComponent( );
            viewModel = new ViewModel( );
            this.DataContext = viewModel;
        }
Ya con la aplicación montada, procederemos a crear el servicio del Dialogo Modal, para lo que crearemos una Interface denominada “IModalDialogService” que será como sigue:
    public interface IModalDialogService
    {
        string Show( string fileExportName, string message, string filter = "All files (*.*)|*.*", bool restoreDirectory = true );
    }
El siguiente paso será crear la implementación de la Interface, creando una clase que lo implemente, para nuestro caso la llamaremos “ModalDialogService” que será como sigue:
public class ModalDialogService : IModalDialogService
    {
        private static SaveFileDialog saveDialog = new SaveFileDialog( );
 
        private bool WriteFile( string fileName, string message )
        {
            try
            {
                System.IO.File.WriteAllText( fileName, message );
                return true;
            }
            catch ( UnauthorizedAccessException e )
            {
                MessageBox.Show( e.Message, "Unauthorized Access Exception", MessageBoxButton.OK, MessageBoxImage.Error );
                return false;
            }
            catch ( IOException e )
            {
                MessageBox.Show( e.Message, "I/O Exception", MessageBoxButton.OK, MessageBoxImage.Error );
                return false;
            }
        }
 
        public string Show( string fileExportName, string message, string filter = "All files (*.*)|*.*", bool restoreDirectory = true )
        {
            string fileName = string.Empty;
            saveDialog.FileName = fileExportName;
            saveDialog.Filter = filter;
            saveDialog.RestoreDirectory = restoreDirectory;
 
            bool? dialogResult = saveDialog.ShowDialog( );
            if ( dialogResult == true )
                fileName = saveDialog.FileName;
 
            if ( fileName == string.Empty )
                return "";
 
            WriteFile( fileName, message );
 
            return fileName;
        }
    }

Ahora analicemos un poco del código que acabamos de escribir; lo primero a ponerse a pensar es porque hemos creado una interface cuando podíamos haber creado de plano solo la clase que maneje el dialogo, y la respuesta es que al crear una interface obtenemos la flexibilidad necesaria para poder crear más de un tipo de dialogo fácilmente  que pueda ser usado a lo largo de toda la aplicación disminuyendo la acoplacion entre componentes , además  es una previsión para el momento que deseemos usar un Inversor de Control.
El siguiente lugar a analizar es en la clase que implementa la interface, donde se realizan las acciones de crear el SaveFileDialog, establecer sus opciones básicas y realizar la acción de escribir el archivo de texto en disco.
Con el servicio de Dialogo listo, procedemos a crear el dialogo en el ViewModel, para lo cual vamos a agregar las siguientes líneas de código al ViewModel:
        private readonly IModalDialogService messageBoxService;

        /// 
        /// Constructor
        /// 
        public ViewModel( )
        {
            messageBoxService = new ModalDialogService( );
            SaveTextCommand = new DelegateCommand( SaveText_Execute, SaveText_CanExecute );
        }
Por ultimo agregamos la llamada al método show en el método SaveText_Execute de la siguiente manera:
this.messageBoxService.Show( "TestFileName", this.Text, "Text files (*.txt)|*.txt;|All files (*.*)|*.*" );
Ahora si lanzamos la aplicación podremos ver cómo funciona:
Suerte!
Codigo Fuente: Aqui

Comentarios

Entradas populares de este blog

Conectarse a un Web Service en PHP con WPF

Ejecutar comandos en el CMD de Windows desde C#

Usar Log4Net en un proyecto ASP.NET MVC