El patrón Modelo-Vista-Modelo de vista
Nota:
Este libro electrónico se publicó en la primavera de 2017 y no se ha actualizado desde entonces. Mucho contenido del libro sigue siendo valioso, pero algunos de los materiales están obsoletos.
La experiencia del desarrollador de Xamarin.Forms normalmente supone crear una interfaz de usuario en XAML y, luego, agregar código subyacente que funcione en la interfaz de usuario. A medida que las aplicaciones se modifican y aumentan de tamaño y ámbito, pueden surgir problemas de mantenimiento complejos. Estos problemas incluyen el acoplamiento estricto entre los controles de interfaz de usuario y la lógica de negocios, lo que aumenta el coste de realizar modificaciones de la interfaz de usuario y la dificultad de realizar pruebas unitarias de este código.
El patrón Modelo-Vista-Modelo de vista (MVVM) ayuda a separar limpiamente la lógica de presentación y de negocios de una aplicación de su interfaz de usuario (UI). Mantener una separación limpia entre la lógica de la aplicación y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y facilita la prueba, el mantenimiento y la evolución de una aplicación. También puede mejorar considerablemente las oportunidades de reutilización del código y permite a los desarrolladores y a los diseñadores de la interfaz de usuario colaborar más fácilmente al desarrollar sus respectivas partes de una aplicación.
El patrón MVVM
Hay tres componentes principales en el patrón MVVM: el modelo, la vista y el modelo de vista. Cada uno de ellos sirve para un propósito diferente. La figura 2-1 muestra las relaciones entre los tres componentes.
Figura 2-1: El patrón MVVM
Además de comprender las responsabilidades de cada componente, también es importante comprender cómo interactúan entre sí. En general, la vista "conoce" el modelo de vista y el modelo de vista "conoce" el modelo, pero el modelo desconoce el modelo de vista y el modelo de vista desconoce la vista. Por lo tanto, el modelo de vista aísla la vista del modelo y permite que el modelo evolucione independientemente de la vista.
Las ventajas de usar el patrón MVVM son las siguientes:
- Si hay una implementación de modelo existente que encapsula la lógica de negocios existente, puede ser difícil o arriesgado cambiarla. En este escenario, el modelo de vista actúa como adaptador para las clases de modelo y evita que realice cambios importantes en el código del modelo.
- Los desarrolladores pueden crear pruebas unitarias para el modelo de vista y el modelo, sin usar la vista. Las pruebas unitarias del modelo de vista pueden ejercer exactamente la misma funcionalidad que la vista.
- La interfaz de usuario de la aplicación se puede rediseñar sin tocar el código, siempre que la vista se implemente completamente en XAML. Por lo tanto, una nueva versión de la vista debe funcionar con el modelo de vista existente.
- Los diseñadores y desarrolladores pueden trabajar de manera independiente y simultánea en sus componentes durante el proceso de desarrollo. Los diseñadores pueden centrarse en la vista, mientras que los desarrolladores pueden trabajar en el modelo de vista y los componentes del modelo.
La clave para usar MVVM de manera eficaz consiste en comprender cómo factorizar el código de la aplicación en las clases correctas y en comprender cómo interactúan las clases. En las secciones siguientes se describen las responsabilidades de cada una de las clases del patrón MVVM.
Ver
La vista es responsable de definir la estructura, el diseño y la apariencia de lo que ve el usuario en la pantalla. Idealmente, cada vista se define en XAML, con un código subyacente limitado que no contiene lógica de negocios. Sin embargo, en algunos casos, el código subyacente podría contener lógica de interfaz de usuario que implementa el comportamiento visual que es difícil de expresar en XAML, como es el caso de las animaciones.
En una aplicación de Xamarin.Forms, una vista suele ser una clase Page
derivada o una clase ContentView
derivada. Sin embargo, las vistas también se pueden representar mediante una plantilla de datos, que especifica los elementos de la interfaz de usuario que se usarán para representar visualmente un objeto cuando se muestra. Una plantilla de datos como vista no tiene código subyacente y está diseñada para enlazar a un tipo de modelo de vista específico.
Sugerencia
Evite habilitar y deshabilitar los elementos de la interfaz de usuario en el código subyacente. Asegúrese de que los modelos de vista sean responsables de definir los cambios de estado lógico que afectan a algunos aspectos de la presentación de la vista, por ejemplo, si un comando está disponible, o una indicación de que una operación está pendiente. Por lo tanto, habilite y deshabilite los elementos de la interfaz de usuario enlazando a las propiedades del modelo de vista, en lugar de habilitarlos y deshabilitarlos en el código subyacente.
Hay varias opciones para ejecutar código en el modelo de vista en respuesta a las interacciones de la vista, como un clic de botón o una selección de elementos. Si un control admite comandos, la propiedad Command
del control puede estar enlazada por datos a una propiedad ICommand
del modelo de vista. Cuando se invoca el comando del control, se ejecuta el código del modelo de vista. Además de los comandos, los comportamientos pueden estar asociados a un objeto en la vista y pueden escuchar un comando que se va a invocar o un evento que se va a generar. En respuesta, el comportamiento puede invocar una propiedad ICommand
en el modelo de vista o un método en el modelo de vista.
Modelo de vista
El modelo de vista implementa propiedades y comandos a los que la vista puede enlazar datos, y avisa a la vista los cambios de estado mediante eventos de notificación de cambios. Las propiedades y comandos que proporciona el modelo de vista definen la funcionalidad que ofrece la interfaz de usuario, pero la vista determina cómo se va a mostrar esa funcionalidad.
Sugerencia
Mantenga la capacidad de respuesta de la interfaz de usuario con operaciones asincrónicas. Las aplicaciones móviles deben mantener desbloqueado el subproceso de interfaz de usuario para mejorar la percepción del rendimiento del usuario. Por lo tanto, en el modelo de vista, use métodos asincrónicos para las operaciones de E/S y genere eventos para notificar asincrónicamente las vistas de los cambios de propiedad.
El modelo de vista también es responsable de coordinar las interacciones de la vista con las clases de modelo necesarias. Normalmente hay una relación uno a varios entre el modelo de vista y las clases de modelo. El modelo de vista puede optar por exponer clases de modelo directamente a la vista para que los controles de la vista puedan enlazar datos directamente a ellas. En este caso, las clases de modelo deberán diseñarse para admitir el enlace de datos y los eventos de notificación de cambios.
Cada modelo de vista proporciona datos de un modelo en un formato que la vista puede consumir fácilmente. Para ello, el modelo de vista a veces realiza la conversión de datos. Es una buena idea situar esta conversión de datos en el modelo de vista porque proporciona propiedades que la vista puede enlazar. Por ejemplo, el modelo de vista podría combinar los valores de dos propiedades para facilitar la visualización por parte de la vista.
Sugerencia
Centralice las conversiones de datos en una capa de conversión. También es posible usar convertidores como una capa de conversión de datos independiente que se encuentra entre el modelo de vista y la vista. Esto puede ser necesario, por ejemplo, cuando los datos requieren un formato especial que el modelo de vista no proporciona.
Para que el modelo de vista participe en el enlace de datos bidireccional con la vista, sus propiedades deben generar el evento PropertyChanged
. Los modelos de vista satisfacen este requisito al implementar la interfaz INotifyPropertyChanged
y generar el evento PropertyChanged
cuando se cambia una propiedad.
Para las colecciones, se proporciona la vista descriptiva ObservableCollection<T>
. Esta colección implementa una notificación de cambios en la colección, que evita al desarrollador tener que implementar la interfaz INotifyCollectionChanged
en las colecciones.
Modelo
Las clases de modelo son clases no visuales que encapsulan los datos de la aplicación. Por lo tanto, el modelo se puede considerar como que representa el modelo de dominio de la aplicación, que normalmente incluye un modelo de datos junto con la lógica de validación y de negocios. Algunos ejemplos de objetos de modelo son objetos de transferencia de datos (DTO), objetos CLR antiguos sin formato (POCO) y objetos de entidad y proxy generados.
Las clases de modelo se suelen usar junto con servicios o repositorios que encapsulan el acceso a datos y el almacenamiento en caché.
Conexión de modelos de vista a vistas
Los modelos de vista se pueden conectar a las vistas mediante las funcionalidades de enlace de datos de Xamarin.Forms. Hay muchos enfoques que se pueden usar para construir vistas y ver modelos y asociarlos en tiempo de ejecución. Estos enfoques se dividen en dos categorías, conocidas como composición inicial de vista y composición inicial de modelo de vista. Elegir entre la composición inicial de vista y la composición inicial del modelo de vista es un asunto de preferencia y complejidad. Sin embargo, todos los enfoques comparten el mismo objetivo, que es que la vista tenga asignado un modelo de vista a su propiedad BindingContext.
Con la composición inicial de vista, la aplicación se compone conceptualmente de vistas que se conectan a los modelos de vista de los que dependen. La principal ventaja de este enfoque es que facilita la construcción de aplicaciones de acoplamiento flexible y comprobables por unidades, ya que los modelos de vista no dependen de las propias vistas. También es fácil comprender la estructura de la aplicación siguiendo su estructura visual, en lugar de tener que realizar un seguimiento de la ejecución de código para comprender cómo se crean y asocian las clases. Además, la construcción de primera vista se alinea con el sistema de navegación de Xamarin.Forms que es responsable de construir páginas cuando se produce la navegación, lo que hace que una composición inicial del modelo de vista sea complejo y esté mal alineado con la plataforma.
Con la composición inicial del modelo de vista, la aplicación se compone conceptualmente de modelos de vista, con un servicio responsable de localizar la vista para un modelo de vista. Para algunos desarrolladores, la composición inicial del modelo de vista resulta más natural, ya que la creación de la vista puede abstraerse, lo que les permite centrarse en la estructura lógica sin interfaz de usuario de la aplicación. Además, permite crear modelos de vista mediante otros modelos de vista. Sin embargo, este enfoque suele ser complejo y puede resultar difícil comprender cómo se crean y asocian las distintas partes de la aplicación.
Sugerencia
Mantenga los modelos de vista y las vistas independientes. El enlace de vistas a una propiedad de un origen de datos debe ser la dependencia principal de la vista en su modelo de vista correspondiente. En concreto, no haga referencia a tipos de vista, como Button
y ListView
, desde los modelos de vista. Siguiendo los principios descritos aquí, los modelos de vista se pueden probar de forma aislada, lo que reduce la probabilidad de defectos de software al limitar el ámbito.
En las secciones siguientes se describen los enfoques principales para conectar modelos de vista a vistas.
Creación de un modelo de vista mediante declaración
El enfoque más sencillo es que la vista cree una instancia declarativa de su modelo de vista correspondiente en XAML. Cuando se construye la vista, también se construirá el objeto de modelo de vista correspondiente. Este enfoque se muestra en el ejemplo de código siguiente:
<ContentPage ... xmlns:local="clr-namespace:eShop">
<ContentPage.BindingContext>
<local:LoginViewModel />
</ContentPage.BindingContext>
...
</ContentPage>
Cuando se crea ContentPage
, se crea automáticamente una instancia de LoginViewModel
y se establece como el elemento BindingContext
de la vista.
Esta construcción declarativa y la asignación del modelo de vista por la vista tiene la ventaja de que es simple, pero la desventaja de que requiere un constructor predeterminado (sin parámetros) en el modelo de vista.
Creación de un modelo de vista mediante programación
Una vista puede tener código en el archivo de código subyacente, lo que da lugar a que el modelo de vista se asigne a su propiedad BindingContext
. Esto suele realizarse en el constructor de la vista, como se muestra en el ejemplo de código siguiente:
public LoginView()
{
InitializeComponent();
BindingContext = new LoginViewModel(navigationService);
}
La construcción y asignación mediante programación del modelo de vista dentro del código subyacente de la vista tiene la ventaja de que es simple. Sin embargo, la principal desventaja de este enfoque es que la vista debe proporcionar al modelo de vista las dependencias necesarias. El uso de un contenedor de inserción de dependencias puede ayudar a mantener el acoplamiento flexible entre la vista y el modelo de vista. Para obtener más información, consulte Inserción de dependencias.
Crear una vista definida como una plantilla de datos
Una vista se puede definir como una plantilla de datos y asociarse a un tipo de modelo de vista. Las plantillas de datos se pueden definir como recursos, o bien se pueden definir insertado dentro del control que mostrará el modelo de vista. El contenido del control es la instancia del modelo de vista y la plantilla de datos se usa para representarlo visualmente. Esta técnica es un ejemplo de una situación en la que primero se crea una instancia del modelo de vista, seguido de la creación de la vista.
Crear automáticamente un modelo de vista con un localizador de modelos de vista
Un localizador de modelos de vista es una clase personalizada que administra la creación de instancias de los modelos de vista y su asociación a las vistas. En la aplicación móvil eShopOnContainers, la clase ViewModelLocator
tiene una propiedad adjunta, AutoWireViewModel
, que se usa para asociar modelos de vista con vistas. En el XAML de la vista, esta propiedad adjunta se establece en true para indicar que el modelo de vista debe conectarse automáticamente a la vista, como se muestra en el ejemplo de código siguiente:
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
La propiedad AutoWireViewModel
es una propiedad enlazable que se inicializa en false y, cuando su valor cambia, se llama al controlador de eventos OnAutoWireViewModelChanged
. Este método resuelve el modelo de vista de la vista. En el ejemplo de código siguiente se muestra cómo se logra esto:
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
if (view == null)
{
return;
}
var viewType = view.GetType();
var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = string.Format(
CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null)
{
return;
}
var viewModel = _container.Resolve(viewModelType);
view.BindingContext = viewModel;
}
El método OnAutoWireViewModelChanged
intenta resolver el modelo de vista mediante un enfoque basado en convención. Esta convención supone que:
- Los modelos de vistas están en el mismo ensamblado que los tipos de vista.
- Las vistas están en un espacio de nombres secundario .Views.
- Los modelos de vista están en un espacio de nombres secundario .ViewModels.
- Los nombres del modelo de vistas se corresponden con los nombres de vista y terminan con "ViewModel".
Por último, el método OnAutoWireViewModelChanged
establece el BindingContext
del tipo de vista en el tipo de modelo de vista resuelto. Para obtener más información sobre cómo resolver el tipo de modelo de vistas, consulte Resolución.
Este enfoque tiene la ventaja de que una aplicación tiene una sola clase que es responsable de la creación de instancias de los modelos de vista y su conexión a las vistas.
Sugerencia
Use un localizador de modelos de vista para facilitar la sustitución. Un localizador de modelos de vista también se puede usar como punto de sustitución para las implementaciones alternativas de dependencias, como para pruebas unitarias o datos en tiempo de diseño.
Actualización de las vistas en respuesta a los cambios en el modelo o el modelo de vistas subyacente
Todas las clases de modelo y modelos de vistas que son accesibles para una vista deben implementar la interfaz INotifyPropertyChanged
. La implementación de esta interfaz en un modelo de vista o una clase de modelo permite a la clase proporcionar notificaciones de cambios a los controles enlazados a datos en la vista cuando cambia el valor de propiedad subyacente.
Las aplicaciones deben diseñarse para el uso correcto de la notificación de cambio de propiedad, de modo que se cumplan los siguientes requisitos:
- Generar siempre un evento
PropertyChanged
si cambia el valor de una propiedad pública. No suponer que se puede omitir la generación del eventoPropertyChanged
debido al conocimiento de cómo se produce el enlace XAML. - Generar siempre un evento
PropertyChanged
para las propiedades calculadas cuyos valores usan otras propiedades en el modelo de vista o modelo. - Generar siempre el evento
PropertyChanged
al final del método que realiza un cambio de propiedad o cuando se sabe que el objeto está en un estado seguro. La generación del evento interrumpe la operación invocando los controladores del evento de forma sincrónica. Si este hecho se produce en medio de una operación, podría exponer el objeto a las funciones de devolución de llamada cuando se encuentra en un estado no seguro y actualizado parcialmente. Además, es posible que los eventosPropertyChanged
desencadenen cambios en cascada. Por lo general, los cambios en cascada requieren que se realicen actualizaciones antes de que el cambio en cascada se pueda ejecutar de forma segura. - Nunca generar un evento
PropertyChanged
si la propiedad no cambia. Esto significa que debe comparar los valores antiguos y nuevos antes de generar el eventoPropertyChanged
. - Nunca generar el evento
PropertyChanged
durante el constructor de un modelo de vista si va a inicializar una propiedad. Los controles enlazados a datos de la vista no se habrán suscrito para recibir notificaciones de cambios en este momento. - Nunca generar más de un evento
PropertyChanged
con el mismo argumento de nombre de propiedad dentro de una única invocación sincrónica de un método público de una clase. Por ejemplo, dada una propiedadNumberOfItems
cuya memoria auxiliar es el campo_numberOfItems
, si un método incrementa_numberOfItems
cincuenta veces durante la ejecución de un bucle, solo debe generar una notificación de cambio de propiedad en la propiedadNumberOfItems
una vez, después de que se complete todo el trabajo. Para los métodos asincrónicos, genere el eventoPropertyChanged
para un nombre de propiedad determinado en cada segmento sincrónico de una cadena de continuación asincrónica.
La aplicación móvil eShopOnContainers usa la clase ExtendedBindableObject
para proporcionar notificaciones de cambio, que se muestra en el ejemplo de código siguiente:
public abstract class ExtendedBindableObject : BindableObject
{
public void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
var name = GetMemberInfo(property).Name;
OnPropertyChanged(name);
}
private MemberInfo GetMemberInfo(Expression expression)
{
...
}
}
La clase BindableObject
de Xamarin.Form implementa la interfaz INotifyPropertyChanged
y proporciona un método OnPropertyChanged
. La clase ExtendedBindableObject
proporciona el método RaisePropertyChanged
para invocar la notificación de cambio de propiedad y, al hacerlo, usa la funcionalidad proporcionada por la clase BindableObject
.
Cada clase de modelo de vista de la aplicación móvil eShopOnContainers deriva de la clase ViewModelBase
, que a su vez deriva de la clase ExtendedBindableObject
. Por lo tanto, cada clase de modelo de vista usa el método RaisePropertyChanged
de la clase ExtendedBindableObject
para proporcionar una notificación de cambio de propiedad. En el ejemplo de código siguiente se muestra cómo la aplicación móvil eShopOnContainers invoca la notificación de cambio de propiedad mediante una expresión lambda:
public bool IsLogin
{
get
{
return _isLogin;
}
set
{
_isLogin = value;
RaisePropertyChanged(() => IsLogin);
}
}
Tenga en cuenta que el uso de una expresión lambda de esta manera supone un pequeño costo de rendimiento porque la expresión debe evaluarse para cada llamada. Aunque el costo de rendimiento es pequeño y normalmente no afectaría a una aplicación, los costos pueden acumularse cuando hay muchas notificaciones de cambio. Sin embargo, la ventaja de este enfoque es que proporciona compatibilidad con la seguridad y la refactorización de tipos en tiempo de compilación al cambiar el nombre de las propiedades.
Interacción de la interfaz de usuario mediante comandos y comportamientos
En las aplicaciones móviles, las acciones se suelen invocar en respuesta a una acción del usuario, como un clic de botón, que se puede implementar mediante la creación de un controlador de eventos en el archivo de código subyacente. Sin embargo, en el patrón MVVM, la responsabilidad de implementar la acción radica en el modelo de vista y se debe evitar colocar código en el código subyacente.
Los comandos proporcionan una manera cómoda de representar acciones que se pueden enlazar a controles de la interfaz de usuario. Estos encapsulan el código que implementa la acción y ayudan a evitar que se desacople de su representación visual en la vista. Xamarin.Forms incluye controles que se pueden conectar mediante declaración a un comando y estos controles invocarán el comando cuando el usuario interactúe con el control.
Los comportamientos también permiten que los controles se conecten mediante declaración a un comando. Sin embargo, se pueden usar comportamientos para invocar una acción asociada a un intervalo de eventos generados por un control. Por lo tanto, los comportamientos solucionan muchos de los mismos escenarios que los controles habilitados para comandos, a la vez que proporcionan un mayor grado de flexibilidad y control. Además, también se pueden usar comportamientos para asociar objetos o métodos de comandos a controles que no se han diseñado para interactuar con los comandos.
Implementación de comandos
Los modelos de vista suelen exponer propiedades de comando, para el enlace desde la vista, que son instancias de objeto que implementan la interfaz ICommand
. Un conjunto de controles Xamarin.Forms proporcionan una propiedad Command
, que pueden ser datos enlazados a un objeto ICommand
proporcionado por el modelo de vista. La interfaz ICommand
define un método Execute
, que encapsula la propia operación, un método CanExecute
, que indica si se puede invocar el comando y un evento CanExecuteChanged
que se produce cuando tienen lugar cambios que afectan a la ejecución del comando. Las clases Command
y Command<T>
, proporcionadas por Xamarin.Forms, implementan la interfaz ICommand
, donde T
es el tipo de los argumentos en Execute
y CanExecute
.
Dentro de un modelo de vista, debe haber un objeto de tipo Command
o Command<T>
para cada propiedad pública en el modelo de vista de tipo ICommand
. El constructor Command
o Command<T>
requiere un objeto de devolución de llamada Action
al que se llama cuando se invoca el método ICommand.Execute
. El método CanExecute
es un parámetro de constructor opcional y es una Func
que devuelve un bool
.
En el código siguiente se muestra cómo se construye una instancia Command
, que representa un comando de registro, mediante la especificación de un delegado para el método de modelo de vista Register
:
public ICommand RegisterCommand => new Command(Register);
El comando se expone a la vista mediante una propiedad que devuelve una referencia a un objeto ICommand
. Cuando se llama al método Execute
en el objeto Command
, simplemente se reenvía la llamada al método en el modelo de vista mediante el delegado especificado en el constructor Command
.
Un comando puede invocar un método asincrónico mediante las palabras clave async
y await
al especificar el delegado Execute
del comando. Esto indica que la devolución de llamada es un objeto Task
y se debe esperar. Por ejemplo, el código siguiente muestra cómo se construye una instancia Command
, que representa un comando de inicio de sesión, mediante la especificación de un delegado para el método de modelo de vista SignInAsync
:
public ICommand SignInCommand => new Command(async () => await SignInAsync());
Los parámetros se pueden pasar a las acciones Execute
y CanExecute
mediante la clase Command<T>
para crear una instancia del comando. Por ejemplo, el código siguiente muestra cómo se usa una instancia Command<T>
para indicar que el método NavigateAsync
requerirá un argumento de tipo string
:
public ICommand NavigateCommand => new Command<string>(NavigateAsync);
En las clases Command
y Command<T>
, el delegado al método CanExecute
en cada constructor es opcional. Si no se especifica un delegado, Command
devolverá true
para CanExecute
. Sin embargo, el modelo de vista puede indicar un cambio en el estado de CanExecute
del comando mediante la llamada al método ChangeCanExecute
en el objeto Command
. Esto hace que se genere el evento CanExecuteChanged
. Los controles de interfaz de usuario enlazados al comando actualizarán entonces su estado habilitado para reflejar la disponibilidad del comando enlazado a datos.
Invocación de comandos desde una vista
En el ejemplo de código siguiente se muestra cómo un objeto Grid
de LoginView
enlaza con RegisterCommand
en la clase LoginViewModel
mediante una instancia de TapGestureRecognizer
:
<Grid Grid.Column="1" HorizontalOptions="Center">
<Label Text="REGISTER" TextColor="Gray"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
</Grid>
Un parámetro de comando también se puede definir opcionalmente mediante la propiedad CommandParameter
. El tipo del argumento esperado se especifica en los métodos de destino Execute
y CanExecute
. El objeto TapGestureRecognizer
invocará automáticamente el comando de destino cuando el usuario interactúe con el control asociado. El parámetro de comando, si se proporciona, se pasará como argumento al delegado Execute
del comando.
Implementación de comportamientos
Los comportamientos permiten agregar funcionalidad a los controles de interfaz de usuario sin tener que aplicarles subclases. En su lugar, la función se implementa en una clase de comportamiento y se asocia al control como si fuera parte de este. Los comportamientos permiten implementar código que normalmente tendría que escribir como código subyacente, ya que interactúa directamente con la API del control, de manera que puede asociarse al control de manera concisa y empaquetarse para reutilizarlo en más de una aplicación o vista. En el contexto de MVVM, los comportamientos son un método útil para conectar controles a comandos.
Un comportamiento asociado a un control mediante propiedades asociadas se conoce como comportamiento asociado. El comportamiento puede usar entonces la API expuesta del elemento al que está asociado para agregar funcionalidad a ese control, o a otros controles, en el árbol visual de la vista. La aplicación móvil eShopOnContainers contiene la clase LineColorBehavior
, que es un comportamiento adjunto. Para obtener más información sobre este comportamiento, consulte Mostrar errores de validación.
Un comportamiento Xamarin.Forms es una clase que deriva de la clase Behavior
o Behavior<T>
, donde T
es el tipo del control al que se debe aplicar el comportamiento. Estas clases proporcionan métodos OnAttachedTo
y OnDetachingFrom
, que se deben invalidar para proporcionar lógica que se ejecutará cuando el comportamiento se asocie a los controles y se desasocie de estos.
En la aplicación móvil eShopOnContainers, la clase BindableBehavior<T>
deriva de la clase Behavior<T>
. El propósito de la clase BindableBehavior<T>
es proporcionar una clase base para los comportamientos de Xamarin.Forms que requiera que el elemento BindingContext
del comportamiento se establezca en el control adjunto.
La clase BindableBehavior<T>
proporciona un método OnAttachedTo
reemplazable que establece el objeto BindingContext
del comportamiento, y un método OnDetachingFrom
reemplazable que limpia el objeto BindingContext
. Además, la clase almacena una referencia al control adjunto en la propiedad AssociatedObject
.
La aplicación móvil eShopOnContainers incluye una clase EventToCommandBehavior
, que ejecuta un comando en respuesta a un evento que se produce. Esta clase se obtiene de la clase BindableBehavior<T>
, de modo que el comportamiento puede enlazar con un objeto ICommand
especificado por una propiedad Command
, y ejecutarlo, cuando se usa el comportamiento. En el ejemplo de código siguiente se muestra la clase EventToCommandBehavior
:
public class EventToCommandBehavior : BindableBehavior<View>
{
...
protected override void OnAttachedTo(View visualElement)
{
base.OnAttachedTo(visualElement);
var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();
if (events.Any())
{
_eventInfo = events.FirstOrDefault(e => e.Name == EventName);
if (_eventInfo == null)
throw new ArgumentException(string.Format(
"EventToCommand: Can't find any event named '{0}' on attached type",
EventName));
AddEventHandler(_eventInfo, AssociatedObject, OnFired);
}
}
protected override void OnDetachingFrom(View view)
{
if (_handler != null)
_eventInfo.RemoveEventHandler(AssociatedObject, _handler);
base.OnDetachingFrom(view);
}
private void AddEventHandler(
EventInfo eventInfo, object item, Action<object, EventArgs> action)
{
...
}
private void OnFired(object sender, EventArgs eventArgs)
{
...
}
}
Los métodos OnAttachedTo
y OnDetachingFrom
se usan para registrar y anular el registro de un controlador de eventos para el evento definido en la propiedad EventName
. A continuación, cuando se desencadena el evento, se invoca el método OnFired
, que ejecuta el comando.
La ventaja de usar EventToCommandBehavior
para ejecutar un comando cuando se desencadena un evento es que se pueden asociar comandos con controles que no se han diseñado para interactuar con comandos. Además, el código de control de eventos se traslada a los modelos de vista, donde se pueden realizar pruebas unitarias.
Invocación de comportamientos desde una vista
EventToCommandBehavior
es especialmente útil para asociar un comando a un control que no admite comandos. Por ejemplo, ProfileView
usa EventToCommandBehavior
para ejecutar OrderDetailCommand
cuando se desencadena el evento ItemTapped
en ListView
que se enumera los pedidos del usuario, como se muestra en el código siguiente:
<ListView>
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding OrderDetailCommand}"
EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />
</ListView.Behaviors>
...
</ListView>
En tiempo de ejecución, EventToCommandBehavior
responderá a la interacción con ListView
. Cuando se selecciona un elemento en el control ListView
, se desencadenará el evento ItemTapped
, que ejecutará OrderDetailCommand
en ProfileViewModel
. De manera predeterminada, los argumentos de evento para el evento se pasarán al comando. Estos datos se convierten a medida que se pasan entre el origen y el destino mediante el convertidor especificado en la propiedad EventArgsConverter
, que devuelve Item
de ListView
desde ItemTappedEventArgs
. Por lo tanto, cuando se ejecuta OrderDetailCommand
, la propiedad Order
seleccionada se pasa como un parámetro a la acción registrada.
Para obtener más información sobre los comportamientos, consulte Comportamientos.
Resumen
El patrón Modelo-Vista-Modelo de vista (MVVM) ayuda a separar limpiamente la lógica de presentación y de negocios de una aplicación de su interfaz de usuario (UI). Mantener una separación limpia entre la lógica de la aplicación y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y facilita la prueba, el mantenimiento y la evolución de una aplicación. También puede mejorar considerablemente las oportunidades de reutilización del código y permite a los desarrolladores y a los diseñadores de la interfaz de usuario colaborar más fácilmente al desarrollar sus respectivas partes de una aplicación.
Con el patrón MVVM, la interfaz de usuario de la aplicación y la lógica de presentación y negocios subyacente se separan en tres clases distintas: la vista, que encapsula la lógica de la interfaz de usuario y la interfaz de usuario; el modelo de vista, que encapsula la lógica y el estado de la presentación; y el modelo, que encapsula la lógica de negocios y los datos de la aplicación.