MVVM Light: Comandos

Aquí vamos con la segunda parte del tutorial.

Para este tutorial vamos a crear un ejemplito muy muy simple pero que nos ayudará a entender los conceptos básicos que es lo que realmente buscamos.

Para empezar vamos a ir a la página de MVVM Light y bajarnos la última versión (la versión 3 a dia de hoy)

Página de MVVM Light

Que no os asuste la instalación, simplemente teneis que bajar los binarios y los templates para la versión de visual studio que estéis usando. Como extra también podéis bajar los snippets lo cual recomiendo.

Cuando bajeis los 3 ficheros, lo descomprimís y solo es copiarlo donde corresponde, nada dificil (Si teneis problemas dejadme un comentario).

Una vez tienes MVVM Light instalado vamos a crear una nueva aplicación MVVM Light para WPF (Para Silverlight o Windows Phone 7 sería lo mismo realmente).

El template de MVVM Light es bastante bastante completito, nos vienen los directorios que tenemos que usar (con la excepción de Views aunque no sé por qué) y además nos crea ya una ventana con su ViewModel asociado. Además de un diccionario de estilos (MainSkin.xaml) en el cual podemos meter nuestros estilos de la ventana.

Por último, nos crea un ViewModelLocator el cual instancia en el App.xaml. ¿Para qué sirve esto?

ViewModelLocator es el encargado de instanciar y mantener una referencia de cada ViewModel que existe en nuestra aplicación. Lo cual nos da ciertas ventajas, entre ellas poder asignar el DataContext de cada View en el xaml y no mediante código, lo cual nos permitirá diseñar nuestra aplicación en tiempo de diseño sin hacer ninguna floritura extra.

Ya en la próxima parte os explicaré como añadir más ViewModels al ViewModelLocator así que por ahora vamos a ignorarlo y seguir adelante.

Empezamos borrando el MainSkin.xaml puesto que no lo vamos a usar en este simple ejemplo.

Creamos en el modelo una clase llamada “Person” y le pones este código:

using System.ComponentModel;

namespace EjemploMVVML.Model
{
    public class Person : INotifyPropertyChanged
    {
        private string _name;
        private int _age;

        public Person()
        {

        }

        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                RaisePropertyChanged("Name");
            }
        }

        public int Age
        {
            get { return _age; }
            set
            {
                _age = value;
                RaisePropertyChanged("Age");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(
                    this,
                    new PropertyChangedEventArgs(propertyName)
                    );
            }
        }

        #endregion
    }
}

Como veis está en inglés puesto que luego vamos a usar este ejemplo en el artículo de localización, además es una MUY buena practica escribir todo el código en inglés (variables, comentarios, clases, todo).

Esta clase implementa INotifyPropertyChanged puesto que el modelo necesita notificar sus cambios de cara al DataBinding.

Normalmente yo suelo crear una clase llamada Notifier la cual implementa esta interfaz y luego heredo el modelo hereda de esta clase (siempre que no necesitemos heredar del modelo de otra clase y tengamos que implementar esta interfaz a mano como en el ejemplo).

Vamonos con la interfaz, os dejo el código, algo en plan rápido y guarrete ya que este artículo no tiene nada que ver con interfaces :P

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        x:Class="EjemploMVVML.MainWindow"
        Title="Ejemplo MVVM"
        Height="460"
        Width="343"
        Background="#FFA7F9FF" ResizeMode="NoResize">

	<Window.DataContext>
		<Binding Path="Main" Source="{StaticResource Locator}"/>
	</Window.DataContext>

	<Grid x:Name="LayoutRoot">
		<Label Content="Insert your name:" Height="23" HorizontalAlignment="Left" Margin="30,31,0,0" VerticalAlignment="Top" Width="123" />
		<TextBox Height="25" Margin="30,58,82,0" VerticalAlignment="Top"/>
		<Label Content="Insert your age:" HorizontalAlignment="Left" Margin="30,87,0,0" VerticalAlignment="Top"/>
		<TextBox Margin="30,116.96,82,0" VerticalAlignment="Top" Height="25"/>
		<Button Content="Accept" Margin="0,155,82,0" VerticalAlignment="Top" HorizontalAlignment="Right" Width="74"/>
		<TextBox Margin="30,212.98,82,0" IsReadOnly="True" Height="25" VerticalAlignment="Top" d:LayoutOverrides="HorizontalAlignment, Height"/>
		<Button Content="Reset" HorizontalAlignment="Left" Margin="8,0,0,8" VerticalAlignment="Bottom" Width="75"/>
	</Grid>
</Window>

Lo importante de aquí, es ver como se ha definido el DataContext en la ventana. Hemos cogido la referencia del ViewModel que nos interesa (el MainViewModel) desde el ViewModelLocator. ¿Chulo verdad? :)

Ahora vamonos al MainViewModel, uhm, ya tiene código por defecto… (Le he quitado algun comentario para simplificar el ejemplo):

using GalaSoft.MvvmLight;

namespace EjemploMVVML.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        public string Welcome
        {
            get
            {
                return "Welcome to MVVM Light";
            }
        }

        public MainViewModel()
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real"
            }
        }

        ////public override void Cleanup()
        ////{
        ////    // Clean up if needed

        ////    base.Cleanup();
        ////}
    }
}

Cada ViewModel heredará de ViewModelBase el cual nos provee de ciertos helpers que nos facilitarán la vida. Por ejemplo dicho ViewModelBase implementa ya el INotifyPropertyChanged y además nos provee de un método en el cual podemos hacer limpieza en caso de que sea necesaria (Una especie de destructor).

Además tenemos una propiedad bastante util, IsInDesignMode y ya el constructor viene listo para usarlo. Simplemente podemos colocar código dentro de IsInDesignMode para hacer uso del “Design-time DataContext” (Revisar el artículo si quereis saber qué es esto) y luego el código real dentro del else.

Como ya sabemos usar el Design-time DataContext y no tiene nada que ver con MVVM Light, vamos a borrar eso y escribir directamente el código real.

Vamos a borrar el método Cleanup y borrar la propiedad “Welcome” y dejar solo el costructor vacío.

Para empezar vamos a crear una instancia de la clase Person.

Normalmente el instanciar una clase que va a ser expuesta a la vista necesita 3 pasos:

Crear la instancia:

private Person _aPerson;

Instanciarla en el constructor:

_aPerson = new Person();

y crearle su propiedad con notificación de cambios:

public Person APerson
{
    get { return _aPerson; }
    set
    {
        _aPerson = value;
        RaisePropertyChanged("APerson");
    }
}

Ya tenemos lista la instancia de Person para ser bindeada por la vista.

Vamos a bindear las 2 cajas (las de nombre y edad) a sus respectivas propiedades de la instancia de Person:

<TextBox Height="25" Margin="30,58,82,0" VerticalAlignment="Top"
         Text="{Binding APerson.Name}"/>
<TextBox Margin="30,116.96,82,0" VerticalAlignment="Top" Height="25"
         Text="{Binding APerson.Age}"/>

Nada nuevo aquí, solo hacemos los binding como estamos acostumbrados a hacer.

También vamos a necesitar bindear el tercer TextBox el cual contendrá un pequeño saludo.

Para ello creamos una propiedad en el ViewModel:

private string _greeting = string.Empty;

public string Greeting
{
    get { return _greeting; }
    set
    {
        _greeting = value;
        RaisePropertyChanged("Greeting");
    }
}

y bindeamos la tercera caja:

<TextBox Margin="30,212.98,82,0" IsReadOnly="True" Height="25" VerticalAlignment="Top"
         Text="{Binding Greeting}"/>

Ahora, si ejecutamos la aplicación…

Vemos que no hace nada, los botones no responden (¡Sorpresa!)

¿Cómo podemos solucionar esto?

Ah claro, creamos un evento por cada botón y asunto solucionado.

Pero… Habíamos dicho que todo el trabajo referente a datos se hacía en el ViewModel y no en el Code-Behind de la vista… Bueno, delegamos la acción al ViewModel desde el Code-Behind y ya está.

Vale, funciona, pero… ¿No es un poco guarrete esto que proponemos? Sí, sin duda.

WPF y Silverlight ofrecen algo más especializado para estas cosas que los propios eventos, ese algo se llama “Commands” o “Comandos” en castellano.

¿Qué ofrecen estos _Commands_ como ventaja frente a los eventos? Muchas. Las iremos viendo a lo largo del resto del artículo.

Para empezar, los comandos no se definen en el Code-Behind, sino directamente en el DataContext, que en nuestro caso es en el MainViewModel. Con esto, nuestro código será mucho más limpio puesto que no tenemos que delegar el trabajo desde el Code-Behind y asi dejamos a éste para lo que realmente está, para hacer todo lo referente a la vista y no a los datos.

Así que vamos al lío.

¿Cómo se crea un comando?

Originalmente quizá crear un comando no era tan facil como crear un evento, pero gracias a la comunidad tenemos unas clases que harán que crear comandos sea incluso más facil que crear eventos.

MVVM Light incluye una de esas clases, RelayCommand

Usar un RelayCommand es lo más facil del mundo, empezamos por crear el comando en sí como una propiedad autocompletada del MainViewModel:

public RelayCommand AcceptCommand { get; private set; }

Los comandos por convención incluyen la palabra Command al final.

Ahora creamos el comando en el Constructor:

AcceptCommand = new RelayCommand(Accept);

El constructor del RelayCommand recibe un delegado (Comunmente llamado el parametro Execute pues será el que ejecute código) del tipo Action el cual espera un método que no acepte parámetros y tampoco devuelva nada, así que le hemos pasado el método “Accept” al RelayCommand, así que vamos a crear dicho método (sin parametro y devolviendo void):

private void Accept()
{
	Greeting = string.Format("Hello, my name is {0} and Im {1} years old.",
		APerson.Name, APerson.Age);
}

Ya solo nos queda bindear el botón Accept al comando:

<Button Content="Accept" Margin="0,155,82,0" VerticalAlignment="Top" HorizontalAlignment="Right" Width="74"
        Command="{Binding AcceptCommand}"/>

Tan fácil como esperabas :)

Ejecutamos la aplicación y… Funciona!

Vamos a crear ahora el comando para el botón reset, seguro que ya sabréis hacerlo por vosotros mismos…

public RelayCommand ResetCommand { get; private set; }
ResetCommand = new RelayCommand(Reset);
private void Reset()
{
	APerson = new Person();
	Greeting = string.Empty;
}
<Button Content="Reset" HorizontalAlignment="Left" Margin="8,0,0,8" VerticalAlignment="Bottom" Width="75"
		Command="{Binding ResetCommand}"/>

Creo que no harán falta las explicaciones aquí, ¿Verdad?

Ahora probemos una cosa… Abrimos la aplicación y le damos a Accept…

Uh, esto ya no mola tanto… ¡Ah! Ya lo tengo, pongo el botón como disabled y cuando rellene los dos textbox lo pongo enabled… Uhm, la idea es buena, pero… ¿Cómo lo implemento? Podría crear unos eventos que comprobaran que los textbox tienen información y luego desbloquear el botón o algo. Nah, muy dificil, mejor compruebo a la hora de hacer click de que haya info, en caso de no haberla que suelte un MessageBox diciendo que has de rellenar las cajas o mejor, no hago nada, se supone que si no escribes algo no va a valer.

Bueeeno, vamos a hacerlo bien, que lo importante en una aplicación con interfaces es la experiencia de usuario (UX).

Los comandos tienen la capacidad de saber cuando pueden ser ejecutados y cuando no. La forma que tienen de avisar de que no pueden ser ejecutados es simplemente bloqueando el control (¡Justo lo que queremos!).

Para ello, RelayCommand acepta otro delegado (éste comunmente llamado CanExecute), en este caso del tipo Func el cual no acepta ningun valor y a la vez devuelve un bool, el cual si es true el comando se puede ejecutar y si es false, pues lo contrario.

Antes que nada, necesitamos una forma de comprobar si los atributos de la clase están “rellenos” o no, para ello vamos a implementar una pequeña propiedad en la clase Person:

public bool IsValid
{
	get
	{
		return _name != string.Empty &amp;&amp; _age > 0;
	}
}

Vamos a modificar nuestro AcceptCommand

En el constructor lo cambiamos por:

AcceptCommand = new RelayCommand(Accept, CanAccept);

La convención también suele decir que los métodos para comprobar si un comando se puede ejecutar o no van prefijados por Can.

Ahora escribámos el método:

private bool CanAccept()
{
	return APerson.IsValid;
}

Si ejecutamos el código, vemos que el botón Accept está bloqueado:

Para desbloquearlo deberemos rellenar los 2 textboxes primero.

NOTA: Por defecto el binding se hace cuando el campo pierde el foco, así que hasta que no le quitemos el foco a la ultima caja que rellenemos, no se activará el control. Esto puede ser modificado cambiando el modo del binding a PropertyChanged.

Una de las cosas buenas de los RelayCommands es que aceptan un delegado, y en este caso el CanAccept es simplemente una linea…

Así que vamos a borrar el método CanAccept entero y vamos a modificar la inicialización del RelayCommand para que reciba una lambda:

AcceptCommand = new RelayCommand(Accept, () => APerson.IsValid);

Te quitas 4 líneas de encima.

Vamos a meterle una nueva feature a la aplicación, una que nos diga el número de caracteres que tiene el saludo.

Vamos a la vista y debajo del bóton reset insertamos:

<TextBox Margin="30,0,82,120.04" VerticalAlignment="Bottom" RenderTransformOrigin="0.7,-1.23" Height="25" IsReadOnly="True"/>
<Button Content="Count" HorizontalAlignment="Right" Margin="0,0,82,86.04" VerticalAlignment="Bottom" Width="75"/>

Nada extraño, bueno, código feo de Blend, pero no nos preocupa eso para el tutorial (pero si para nuestras aplicaciones reales ehh ;) )

¿Cómo vamos a contar los carácteres del mensaje? En el mundo real simplemente bindeariamos la nueva caja a “Greeting.Length” y asunto solucionado, pero como esto no es el mundo real y sí un artículo de aprendizaje, vamos a usar otro método el cual nos será util para otras muchas cosas en dicho mundo real :P

Los comandos nos permiten la posibilidad de enviar un parámetro con ellos, así que vamos a aprovechar eso para enviar el numero de caracteres directamente sacado del textbox del Greeting.

Para ello empezamos dandole un nombre a dicho TextBox:

<TextBox x:Name="Greet" Margin="30,212.98,82,0" Height="25" IsEnabled="True" VerticalAlignment="Top"
         Text="{Binding Greeting}" IsReadOnly="True" />

Ahora nos vamos con la creación del comando:

public RelayCommand<int?> CountCommand { get; private set; }

Como veis, ahora usamos una versión genérica del RelayCommand que acepta un parámetro pero…. ¿Por qué int? y no un int normal?

Atención a esto, ya que probablemente esto solo sea suficiente para leer el artículo.

El RelayCommand va a intentar comparar el parámetro con null, un tipo primitivo no puede almacenar null, así que comparar un int contra null dará una preciosa excepción la mar de maja.

Entonces existen versiones nullables de los tipos primitivos, para usar esos tipos hay que colocar la interrogación detrás del tipo, int?, double?, bool?, etc.

Con decir que yo pasé horas investigando esto e incluso terminé reescribiendo parte de la clase para que no reventara… Oye, ya no hacía falta pasarle ningún nullable, pero vamos, ya que se como va, os lo explico bien :)

Pasemos a lo siguiente, inicializar el comando:

CountCommand = new RelayCommand<int?>(Count, noused => Greeting != string.Empty);

¿Qué es eso de noused? El parámetro que pasamos con el comando lo recibirá tanto el “Execute” como el “CanExecute” y en este caso, el “CanExecute” no necesita para nada ese parámetro, aun asi, tiene que recibirlo, asi que le pongo de nombre “noused” para dar a entender que no lo necesitamos. Aparte de eso, comprobamos que ya se haya creado el mensaje del greeting para activar el botón.

Antes de implementar el método Count necesitamos una propiedad donde alojar el mensaje con el numero de caracteres, tal y como hicimos con la propiedad Greeting:

private string _countMessage = string.Empty;

public string CountMessage
{
	get { return _countMessage; }
	set
	{
		_countMessage = value;
		RaisePropertyChanged("CountMessage");
	}
}

y ahora la bindeamos a la caja:

<TextBox Margin="30,0,82,120.04" VerticalAlignment="Bottom" RenderTransformOrigin="0.7,-1.23" Height="25" IsReadOnly="True"
		 Text="{Binding CountMessage}"/>

Ahora vamos a crear el método “Count”:

private void Count(int? characters)
{
	CountMessage = string.Format("Greeting has {0} characters.", characters);
}

Lo unico que queda es bindear el comando al botón:

<Button Content="Count" HorizontalAlignment="Right" Margin="0,0,82,86.04" VerticalAlignment="Bottom" Width="75"
		Command="{Binding CountCommand}" CommandParameter="{Binding Text.Length, ElementName=Greet}"/>

¿No es difícil verdad? Hacemos uso de CommandParameter para enviar un parámetro, en este caso el numero de carácteres del textbox “Greet”.

Todavía queda algo más. No todos los controles implementan comandos y normalmente estos comandos son ejecutados cuando haces click en el control. No siempre va a ser eso lo que necesitas y muchas veces directamente no vas a tener opción a ningun tipo de comando. Así que en vez de tirar de eventos, podemos usar una librería que trae MVVM Light, EventToCommand, con esta librería podemos “transformar” cualquier evento a un comando.

Para evitar hacer más complejo el ejemplo, vamos a suponer que los botones no tienen comandos y queremos transformar su evento “Click” en un comando.

Para ello tenemos que importar estos namespaces en nuestra ventana:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"

NOTA: Si estais usando Visual Studio 2008 con WPF3.5 has de cambiar uno de los namespaces:

xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"

Bueno, ahora nos vamos a un botón cualquiera (por ejemplo reset) y lo dejamos así:

<Button Content="Reset" HorizontalAlignment="Left" Margin="8,0,0,8" VerticalAlignment="Bottom" Width="75">
	<i:Interaction.Triggers>
		<i:EventTrigger EventName="Click">
			<cmd:EventToCommand Command="{Binding ResetCommand, Mode=OneWay}"/>
		</i:EventTrigger>
	</i:Interaction.Triggers>
</Button>

No es tan complejo, ¿no? Hemos seleccionado el tipo de evento que estará bindeado al comando y luego hemos dicho cual es el comando :) No hemos tenido que modificar el ViewModel siquiera.

Esto suele venir bien por ejemplo para ejecutar un comando cada vez que seleccionas un elemento en un ListBox, ya que SelectionChanged no puede ser bindeado a un comando por defecto.

Y bueno, hasta aquí la explicación de comandos, más largo de lo que esperaba, y espero que no se olvide nada.

Os dejo un zip con el código fuente (Visual Studio 2010) para los que no querás hacerlo mientras lo leeis (¡Mal! :P )

Código fuente

Espero vuestros comentarios (que va a ser que no habrán) y nos vemos en la próxima parte.

Tags: , , ,

41 comentarios

  1. Miguel Angel Santiago

    Enhorabuena por este fantástico post. Me ha ayudado muchísimo en saber manejar el MVVM de WPF. En particular, estoy intentando usarlo en mis aplicaciones de MS Surface.

    En particular, la funcionalidad que me está dando problemas es EventToCommand; sería sensacional si me funcionara bien. Estoy aplicándola a los eventos de los controles propios de Surface (imagino que estos heredan también de FrameworkElement).

    Lo que hacía hasta ahora era:

    <s:SurfaceButton s:Contacts.ContactDown="OnContactDown"

    y ahora el objetivo es seguir el patrón MVVM:


    Sin embargo, no me salta el Comando. He comprobado que mi código está bien sin más que sustituirlo por el evento Click y así me detectaría los eventos del ratón. Pero el objetivo es manejor los eventos de Contact de la Surface.

    Sabrías decirme que estoy haciendo mal? ¿O tengo que hacer algún paso adicional?

    Muchas gracias!

  2. Hola Miguel, no había visto tu comentario, no sé por qué lo pasó a spam el akismet :P

    Desde la ignorancia no sé como funciona MS Surface. Primero te aconsejaría crear un proyecto nuevo de MS Surface con un simple botón e implementarle EventToCommand para ver si funciona. En caso afirmativo, yo miraría el código del botón y ver si está bien implementado el comando en el ViewModel, si realmente tu vista conoce el ViewModel (a veces una pequeña tontería nos da hora de dolores de cabeza). Sobretodo el tema del EventToCommand -> RelayCommand ya que el problema con el tema del databinding es que no nos salta ninguna excepción cuando nos equivocamos y puede que pienses que está bien dicho DataBinding y en realidad no.

    ¿No ofrecen los botones comandos por defecto?

    Por otro lado, puede ser que Laurent (el que está detrás de MVVM Light) no haya considerado la MS Surface y no sea compatible vete tu a saber por qué. Voy a investigar un poco y a unas malas le pego un aviso por twitter a ver que me comenta.

  3. Miguel Angel Santiago

    Hola Fox,

    Estuve mirando un poco más de este tema y, de hecho, a Laurent le dejé también un post: http://bit.ly/2kGKK
    Como bien suponías, yo tenía un fallo en el ViewModel, pero al final conseguí que me funcionara :-) Tampoco estoy demasiado puesto en la MS Surface y, claro, cualquier problemilla lo empiezas a relacionar con incompatibilidades del MVVM Light en la Surface.

    A lo que me preguntas, miraré a ver si los botones de Surface tiene una property Command. Hasta ahora lo estaba haciendo usando el EventToCommand sobre el evento Click y funciona perfecto! De hecho, tiene que ser así pues un SurfaceWindow es igualmente un FrameworkElement, por lo que no debería ser diferente al manejo de los botones de WPF estándar.

    El problema se complicaba cuando quería usar otro tipo de eventos más específicos de Surface que no los tuviera de forma nativa un control (e.g. ContactDown sobre un Rectangle). Al final encontré una solución que puse en el post de Laurent http://bit.ly/aWcEOS, pues el problema era que no se estaba resolviendo correctamente el nombre del evento en EventToCommand.

    Estoy todavía un poco verde en el manejo del MVVM, así seguiré peleando con ello. Enhorabuena por la claridad de los posts que pusiste de MVVM. Seguro que te preguntaré alguna que otra cosilla más.

    Un saludo.

  4. Me alegro de que lo solucionaras Miguel. A mi me suele pasar parecido a tí, cuando empecé a usar versiones tempranas de MVVM Light o VS 2010 empecé a achacar algunos problemas a bugs por usar betas. Muchas veces el fallo es algo tontísimo que se nos olvida de poner.

    No te preocupes si al principio te lias. Conforme vayas practicando te irán saliendo trucos que puedes usar para facilitarte la vida en futuros programas.

    Gracias por comentar.

  5. Miguel Angel Santiago

    Hola Fox,

    Quería preguntarte una duda. Mirando la mayor parte de ejemplos de MVVM, siempre usan un ListBox que se carga mediante Binding del ItemSource a una propiedad ObservableCollection.

    Sin embargo, me estaba preguntado otra cosa. Si tengo un Canvas principal y quiero cargar dinámicamente UserControls hijos (Children del Canvas) según diferentes criterios. ¿Cómo he de configurar la vista principal para hacer Bindings? Estos UserControls tendrán claro su View y ViewModel. Imagina la pantalla de la Surface (el Canvas) al que le voy añadiendo controles de usario por diferentes motivos.

    Y aprovecho para otra cosa en la misma línea. ¿Cómo me recomiendas hacer la comunicación:

    * View Model padre – View Model UserControls hijos?
    * entre los diferentes View Model UserControls hijos?

    Muchas gracias por adelantado!

  6. Uhm. Sobre lo primero:

    Nunca he usado Canvas, siempre es mejor usar un panel que de más opciones. Aquí la verdad es que no puedo darte ninguna solución. O bien metes dentro del canvas un panel que alojará a los UC o bien te buscas la forma de crear algún algoritmo que lo haga dinámicamente.

    Sobre lo segundo.

    Si la vista principal necesita acceder a opciones de los UC, necesitarás usar Dependency Properties. Ahora bien, si el ViewModel principal necesita acceder a los ViewModel de los UserControl, puedes usar el Messenger.

    Lo que si tienes que mirar es el tema de varios ViewModel repetidos. Si usas el Locator solo vas a tener una instancia y no muchas. Quizás te venga bien, quizá no.

    Siento no poder ayudarte mucho, pero cada diseño abre puertas a unas soluciones y cierra otras. No hay ninguna solución estandar a las cosas. Simplemente prueba, hazlo funcionar y cuando sepas más si quieres lo refactorizas a algo mejor.

  7. Miguel Angel Santiago

    Muchas gracias por tu rápida contestación!

    Respecto a lo primero, si en vez de Canvas, hablara de Grid me encontraría en una situación similar para insertar UC dinámicamente no?

    Imagina el ejemplo más sencillo. Tengo un Window formado por una Grid donde quiero insertar diferentes instancias de un UC según evoluciona mi programa. Hasta ahora siempre usaba Grid.Children.Add(UC) en el CodeBehind. Pero como ahora estoy pensando en MVVM, me estaba preguntando cómo simular ese “Add” mediante una operación de Binding en la View. Y, como te decía, siempre me encontraba los ejemplos de ListBox.

    No obstante, ¿qué otro tipo de panel sugieres para insertar mis UCs?

  8. No, tienes razón, lo he pensado mal. Todo depende de como quieres que esten colocados dichos UCs.

    Tienes que pillar un objeto que acepte una lista de objetos y ya insertarlos ahi. Como bien dices, ListBox o cualquier elemento de ese tipo. Si no quieres que sea una simple lista llena de tus UCs tendrías que modificar graficamente algún control de este tipo para que tenga otra forma, no sé si me explico.

    O dicho de otra forma, si quieres colocar tus UCs haciendo un circulo, tendrás que coger un control que tenga ItemsSource y modificarlo gráficamente para que sea con forma de círculo.

    La verdad, no deja de ser un hablar por hablar porque realmente no sé que quieres hacer.

  9. Miguel Angel Santiago

    Muchas gracias Fox! Por supuesto que te has explicado perfectamente.

    Mira, mientras tanto, he mirado alguna cosilla más y he encontrado este enlace: http://bit.ly/6CYo6t
    que justo es la misma idea que me decías de modificar el template de las lista de objetos. De hecho, sugiere usar directamente el tipo ItemsControl, y hacer que se convierta en un grid modificando su plantilla.

    Lo que quiero hacer es que mi aplicación de Surface (Canvas) está inicialmente vacía. Cuando pongo un objeto físico sobre la mesa, debe aparecer un UC asociado a ese objeto. Si pongo un segundo objeto, pues aparece una segunda instancia del UC…

    Y estaba empeñado en hacerlo por Binding y me estaba liando con el método tradicional de Grid.Children.Add(UC). ¿Cómo lo ves?

    PS: Para no sobre dimensionar este post de tu blog, ¿sugieres que pongamos dudas sobre algún otro lugar de tu site?

  10. Estoy pensando en crear algún foro donde la gente pueda preguntar (y ayudar también), aunque aún tengo que pensar si funcionará o no jeje.

    Creo que no puedo ayudarte más, no he tocado un surface en la vida y no se como trabaja. Vas a tener que probar con el tema de modificar un ItemsControl a ver si te funciona.

  11. Miguel Angel Santiago

    ́nimate con el foro! Pero entiendo que tienes que ver si te da tiempo.

    De lo de la Surface, básicamente es WPF con controles de usuario especialmente diseñado para la Surface y con eventos táctiles. Por eso, lo de decir Surface es un mero detalle, que en sí la clave de funcionamiento.

    Un saludo y que pases un buen fin de semana!

  12. La idea no es ver si me da tiempo, al fin y al cabo no soy experto programador, solo estudiante. La idea es que todos colaboren tanto en preguntas como en respuestas para que crezca. Pero entiendo que es algo dificil.

  13. Miguel Angel Santiago

    Estoy liado con lo que te comentaba antes y me surgía una nueva duda. ¿Puedo de alguna forma tener un ItemsControl con ItemSource diferentes? Imagina que en mi ItemsControl quiero meter UC distintos. O, más fácil, un ListBox con filas que son de DataTemplates diferentes.

    Muchas gracías Fox!

  14. No. No puedes hacer eso. Tendrás que inventarte alguna forma de crear algun wrapper para todos esos UC y usar eso como ItemsSource.

  15. Miguel Angel Santiago

    La verdad que me estoy liando un poco, porque parece que al usar MVVM se me está complicando algo tan sencillo como debería ser añadir elementos a una Grid.

    A ver si quizás no te lo estoy explicando bien. ¿Cómo harías tú algo tan sencillo como esto: Tienes una venta con un contenedor Grid vacío. Empieza la aplicación. Cuando pasa 1 segundo, a la Grid le añades un Botón. Cuando pasan 2 segundos, le añades un Picture. Cuando pasan 3 segundos, le añades otro botón diferente. Y así cada segundo se va añadiendo un control diferente.

    Te agradecería mucho me pudieras pasar un pequeño ejemplo de cómo harías algo como eso en MVVM.

    Muchas gracias por todo y perdona por toda la molestia.

  16. Miguel Angel Santiago

    En lo de antes, olvidaba decir que el hecho de estos elementos se vayan añadiendo es, por ejemplo, debido a que estoy cargando unos datos del modelo. En definitiva, que lo estoy haciendo desde MainViewModel y necesitaría informar a la vista MainView (la del Grid) que vaya cargando los controles que te comentaba antes.

  17. En un comentario de un post que ya nada tiene que ver no puedo darte tanta información. Todo depende de estudiar la situación. Yo crearía un timer o algo en el code-behind de la vista y vaya creando esos controles.

    Lo siento Miguel, no puedo ayudarte en cada duda que te vaya surgiendo en tu app o llenaremos esto de mensajes de preguntas.

  18. Hola Fox!

    Estoy siguiendo los pasos para aprender el uso del MVVM en silverlight pero en el xaml me marca un error con el Command del boton ya que me dice que “La propiedad Command no fue encontrada en el tipo Boton” me estara faltando algun namespace?

    Saludos

  19. Uhm, Qué versión de Silverlight? y.. Si pegas un ejemplo de un boton con un comando que te dice que no va… Para ir descartando.

  20. Hola

    Estoy usando SL 3

    Me subraya Command con azul y dice “The property ‘Command ‘ was not found in type ‘Button’”.

  21. Uhm, parece que se añadió en Silverlight 4 Kronos, así que actualizate :)

  22. Excelente explicación!, muy clara y al punto.

    Felicitaciones!

  23. Gracias Walter, me alegro que te guste.

  24. Hola fox:

    Tengo un problema con este tema (comandos): estoy desarrollando una aplicación en la cual tengo dos modelos de vista para dos user control, necesito ejecutar un comando antes que un evento es eso posible?
    He realizado pruebas y primeramente se ejecuta el código del evento, enseguida el código del comando, estoy utlizando galasoft con el EventToCommand.

  25. Charly Machine

    Que tal fox.

    el día viernes dejé una pregunta en tu blog, y no veo ni mi mensaje ni la respuesta pasa algo?

  26. Hola Charly, lo siento, estaba fuera de casa y no miraba el panel de control, los comentarios se moderan si es el primero jeje.

    Una solución sencilla a tu problema es ejecutar el comando en el evento en plan:

    void evento (….)
    {
    // codigo del evento
    // ejecutar comando
    }

    A primera vista es lo unico que se me ocurre, en el pasado ya intenté hacer eso y recurrí a esta solución.

  27. El problema es que tendría que acceder al contexto vdd (ViewModel)?, bueno pues creo que tendré que hacerlo de esa manera.

    Gracias.

  28. Si claro, pero no es algo feo ni extraño, la vista SI sabe quien es su viewmodel, lo feo sería al revés.

    Normalmente tengo una propiedad del tipo del ViewModel en plan

    private MainViewModel ViewModel
    {
    get { return DataContext as MainViewModel; }
    }

    Con esto ya tengo acceso al viewmodel de forma sencilla :)

  29. Hola,

    tu post me ha parecido muy interesante. Explicas de una forma sencilla (si toda esta locura se puede catalogar como ‘sencilla’) el modelo MVVM. Es muy parecido (o lo mismo) que el modelo MVP en Java, quizá este modelo esté más orientado a WPF.

    Quizá estaría bien hablar de la clase DependencyObject, para que los modelos deriven de ella, e implementar las propiedades como DependencyProperties, que hace el trabajo de INotifyPropertyChanged de una forma más acorde con WPF.

    Gracias por tu trabajo, un saludo.

  30. Hola Jero.

    Realmente todos los patrones suelen derivar de mv* (Model-view-xxxx) Solo que cada lenguaje tiene su forma de implementarlo.

    MVC en Java tiene su forma, MVP en WPF está de otra….

    MVVM es la misma cosa que las otras dos solo que aprovecha las ventajas que da WPF (como comandos por ejemplo).

    Tendré que mirar lo que dices tú, pero como te habrás dado cuenta llevo un tiempo paradillo aquí, empecé a mirarme web (de lo cual no controlo nada) y mi idea era escribir también artículos de WEB cuando aprendiera cosas curiosas. Tengo a WPF algo más paradillo, tendré que retomarlo para seguir escribiendo.

  31. Antes de nada felicitarte por tu artículo, la verdad es que se te da bien enseñar y más en un tema tan confuso como son los patrones de diseño. Tengo una duda como manejo los eventos de ratón (por ejemplo capturar si el usuario le da a la rueda del botón para disparar un commando que haga lo q sea), en el code-behind de la vista o hay alguna manera de hacerlo desde el controlador?.

    Salu2 y sigue así q lo estas haciendo muyyyyy bien.

  32. Hola Fox, muy buen post!

    Tengo un problema en el que quizas me puedas ayudar:

    Hice el seguimiento del desarrollo, pero en lugar de WPF use Silverlight. Lo que ocurrio es que al cambiar el nombre y la edad, no se actualizo el objeto Person en el view model.

    Esto por que es? en vez de Command’s deberia usar otro “mecanismo”?

    Saludos!

  33. ¿Qué version de SL usas? Solo la 4 soporta el bindeo de commands (creo). En un principio te debería de funcionar tal cual, algo fallará. No te dice ningun error o algun bind que esté mal?

  34. Ahora mismo no se como he llegado a este blog, pero si lo hubiese descubierto antes, mis 15 días de tortura con el MVVM habrían sido más productivos. Muchas gracias Fox por tu trabajo y por el interés en difundir tus conocimientos de forma clara, sencilla y en castellano, a los que estamos empezando con esta movida.

    Una preguntilla… como me gusta enredar he intentado transformar el botón count en EventToCommand:

    Es incapaz de contar los caracteres, puede ser que MVVM Light no acepta CommandParameter, la sintaxis es incorrecta o yo que se… solo llevo 15 días en esto ;-)

    Un saludo.

  35. Esto es lo que he utilizado pero no se publicó, problemas de tags.

    cmd:EventToCommand Command=”{Binding CountCommand, Mode=OneWay}” CommandParameter=”{Binding Text.Lenght, ElementName=Greet}”

  36. Ni te molestes, por la mañana todo se ve mejor, incluso los errores de sintaxis que el IntelliSense se come: Text.Lenght (Length)

    Un saludo

  37. Hola Fernando. Acabo de ver tus mensajes.

    MVVM puede ser al principio confuso, yo recuerdo haber desistido de el un par de veces porque no le veía la “gracia” por decirlo de alguna forma. Una vez que aprendes a usarlo y consigues tener un esqueleto usando MVVM, lo demás viene rodado.

    Por otro lado, quiero escribir algunas cosas más sobre MVVM Light como usar inyección de dependencias (no es directamente relacionado, pero al menos enseñaré como usarlo junto a MVVM Light).

    Y nada más, me alegro de que mis posts sean útiles.

  38. hola, buenisimo el post…
    he tratado de hacer el cambio de even to command pero al agregar la referencia me tira error,
    busque los arhcivos y los encontre
    he tratado de agregarla como referencia no me deja…

    sabes cual puede ser el problema? ya trabajo con SL4 y Mvvm Light

  39. sorry, ya lo descubri… porque tu proyecto es en WPF4 y el mio en SL4

  40. Qué problema te da luisterio? ACabo de probar una aplicación SL4 con MVVM Light y usando EventToCommand y perfecto.

Dejar un comentario