El patrón Model-View-ViewModel
Nota:
Este libro electrónico se publicó en la primavera de 2017 y no se ha actualizado desde entonces. Hay mucho en el libro que sigue siendo valioso, pero algunos de los materiales están obsoletos.
La Xamarin.Forms experiencia del desarrollador normalmente implica crear una interfaz de usuario en XAML y, a continuación, agregar código subyacente que funciona en la interfaz de usuario. A medida que se modifican las aplicaciones y aumenta el tamaño y el á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 costo de realizar modificaciones de la interfaz de usuario y la dificultad de probar este código unitaria.
El patrón Model-View-ViewModel (MVVM) ayuda a separar limpiamente la lógica de negocios y presentación 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 puede facilitar 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 diseñadores de 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 sirve para un propósito distinto. En la figura 2-1 se muestran 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 un nivel alto, la vista "conoce" el modelo de vista y el modelo de vista "conoce" el modelo, pero el modelo no es consciente del modelo de vista y el modelo de vista no es consciente de 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 arriesgada cambiarla. En este escenario, el modelo de vista actúa como adaptador para las clases de modelo y le permite evitar realizar 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 forma 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 consiste eficazmente en comprender cómo factorizar el código de la aplicación en las clases correctas y 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 animaciones.
En una Xamarin.Forms aplicación, una vista suele ser una Page
clase derivada o 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 enlazarse 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 son responsables de definir los cambios de estado lógico que afectan a algunos aspectos de la presentación de la vista, como 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 para ver las propiedades del modelo, 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 del Command
control puede estar enlazada a datos a una ICommand
propiedad en el modelo de vista. Cuando se invoca el comando del control, se ejecutará el código del modelo de vista. Además de los comandos, los comportamientos se pueden adjuntar a un objeto en la vista y pueden escuchar un comando que se va a invocar o que se genere un evento. En respuesta, el comportamiento puede invocar un ICommand
en el modelo de vista o un método en el modelo de vista.
ViewModel
El modelo de vista implementa propiedades y comandos a los que la vista puede enlazar datos y notifica a la vista los cambios de estado a través de 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 interfaz de usuario con capacidad de respuesta 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 formulario que la vista puede consumir fácilmente. Para ello, el modelo de vista a veces realiza la conversión de datos. Colocar esta conversión de datos en el modelo de vista es una buena idea porque proporciona propiedades a las que la vista se 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 PropertyChanged
evento . Los modelos de vista satisfacen este requisito implementando la INotifyPropertyChanged
interfaz y generando el PropertyChanged
evento cuando se cambia una propiedad.
En el caso de las colecciones, se proporciona el uso descriptivo ObservableCollection<T>
de las vistas. Esta colección implementa una notificación modificada de recopilación, lo que impide que el desarrollador tenga que implementar la INotifyCollectionChanged
interfaz 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 negocios. Algunos ejemplos de objetos de modelo son objetos de transferencia de datos (DTO), Objetos CLR antiguos sin formato (POCOs) 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 la primera composición de la vista, y la primera composición del modelo de vista. Elegir entre la primera composición de la vista y la primera composición del modelo de vista es un problema de preferencia y complejidad. Sin embargo, todos los enfoques comparten el mismo objetivo, que es para que la vista tenga asignado un modelo de vista a su propiedad BindingContext.
Con la primera composición de la 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 acoplables unitarias de forma flexible, 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 primera construcción de la vista se alinea con el Xamarin.Forms sistema de navegación responsable de construir páginas cuando se produce la navegación, lo que hace que un modelo de vista sea primero complejo y mal alineado con la plataforma.
Con la composición 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. La primera composición del modelo de vista se siente más natural para algunos desarrolladores, ya que la creación de la vista se puede abstraer, lo que les permite centrarse en la estructura lógica que no es de 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 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 limitando 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>
ContentPage
Cuando se crea , se crea automáticamente una instancia de LoginViewModel
y se establece como BindingContext
.
Esta construcción declarativa y asignación del modelo de vista por la vista tiene la ventaja de que es simple, pero tiene la desventaja de que requiere un constructor predeterminado (sin parámetros) en el modelo de vista.
Crear un modelo de vista mediante programación
Una vista puede tener código en el archivo de código subyacente que da lugar a que el modelo de vista se asigne a su BindingContext
propiedad. 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 sencillo. 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 plantilla de datos
Una vista se puede definir como una plantilla de datos y asociarla a un tipo de modelo de vista. Las plantillas de datos se pueden definir como recursos o se pueden definir insertadas 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, seguida 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 ViewModelLocator
clase 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 AutoWireViewModel
propiedad es una propiedad enlazable que se inicializa en false y, cuando su valor cambia, se llama al OnAutoWireViewModelChanged
controlador de eventos. 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 OnAutoWireViewModelChanged
método intenta resolver el modelo de vista mediante un enfoque basado en convenciones. Esta convención supone que:
- Los modelos de vista están en el mismo ensamblado que los tipos de vista.
- Las vistas se encuentran en . Visualiza el espacio de nombres secundario.
- Los modelos de vista se encuentran en . ViewModels espacio de nombres secundario.
- Los nombres de modelo de vista se corresponden con los nombres de vista y terminan con "ViewModel".
Por último, el OnAutoWireViewModelChanged
método 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 vista, vea 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 implementaciones alternativas de dependencias, como para pruebas unitarias o datos en tiempo de diseño.
Actualizar vistas en respuesta a los cambios en el modelo de vista subyacente o el modelo
Todas las clases de modelo y modelo que son accesibles para una vista deben implementar la INotifyPropertyChanged
interfaz . La implementación de esta interfaz en un modelo de vista o clase de modelo permite a la clase proporcionar notificaciones de cambios a los controles enlazados a datos de la vista cuando cambia el valor de la propiedad subyacente.
Las aplicaciones deben diseñarse para el uso correcto de la notificación de cambio de propiedad, cumpliendo los siguientes requisitos:
- Generar siempre un
PropertyChanged
evento si cambia el valor de una propiedad pública. No suponga que se puede omitir la generación del evento debido a la forma en que se produce elPropertyChanged
enlace XAML. - Generar siempre un
PropertyChanged
evento para las propiedades calculadas cuyos valores se usan en otras propiedades del modelo de vista o modelo. - Generar siempre el
PropertyChanged
evento 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 esto ocurre 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 parcialmente actualizado. Además, es posible que los eventos desencadenePropertyChanged
los cambios en cascada. Por lo general, los cambios en cascada requieren que se completen las actualizaciones antes de que el cambio en cascada sea seguro para ejecutarse. - Nunca genere un
PropertyChanged
evento si la propiedad no cambia. Esto significa que debe comparar los valores antiguos y nuevos antes de generar elPropertyChanged
evento. - No genere nunca el evento durante el
PropertyChanged
constructor de un modelo de vista si va a inicializar una propiedad. Los controles enlazados a datos en la vista no se habrán suscrito para recibir notificaciones de cambio en este momento. - Nunca genere más de un
PropertyChanged
evento 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 unaNumberOfItems
propiedad cuyo almacén de respaldo es el_numberOfItems
campo, si un método aumenta_numberOfItems
cincuenta veces durante la ejecución de un bucle, solo debe generar una notificación de cambio de propiedad en laNumberOfItems
propiedad una vez, una vez completado todo el trabajo. En el caso de los métodos asincrónicos, genere elPropertyChanged
evento 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 ExtendedBindableObject
clase para proporcionar notificaciones de cambios, 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 de BindableObject
Xamarin.Form implementa la INotifyPropertyChanged
interfaz y proporciona un OnPropertyChanged
método . La ExtendedBindableObject
clase proporciona el método para invocar la RaisePropertyChanged
notificación de cambio de propiedad y, al hacerlo, usa la funcionalidad proporcionada por la BindableObject
clase .
Cada clase de modelo de vista de la aplicación móvil eShopOnContainers se deriva de la ViewModelBase
clase , que a su vez deriva de la ExtendedBindableObject
clase . Por lo tanto, cada clase de modelo de vista usa el RaisePropertyChanged
método de la ExtendedBindableObject
clase 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 implica un pequeño costo de rendimiento porque la expresión lambda 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 seguridad y 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 invocan normalmente 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 reside 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 los controles de la interfaz de usuario. Encapsulan el código que implementa la acción y ayudan a evitar que se desacopla 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, los comportamientos se pueden usar para invocar una acción asociada a un intervalo de eventos generados por un control . Por lo tanto, los comportamientos abordan muchos de los mismos escenarios que los controles habilitados para comandos, al tiempo que proporcionan un mayor grado de flexibilidad y control. Además, los comportamientos también se pueden usar para asociar objetos de comando o métodos con controles que no se diseñaron específicamente para interactuar con 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 ICommand
interfaz . Xamarin.Forms Varios controles proporcionan una Command
propiedad , que puede ser datos enlazados a un ICommand
objeto proporcionado por el modelo de vista. La ICommand
interfaz define un Execute
método, que encapsula la propia operación, un CanExecute
método , que indica si se puede invocar el comando y un CanExecuteChanged
evento que se produce cuando se producen cambios que afectan a si el comando debe ejecutarse. Las Command
clases y Command<T>
, proporcionadas por Xamarin.Forms, implementan la ICommand
interfaz , 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 Command
constructor o Command<T>
requiere un Action
objeto de devolución de llamada al que se llama cuando se invoca el ICommand.Execute
método . El CanExecute
método es un parámetro de constructor opcional y es un Func
que devuelve .bool
El código siguiente muestra cómo se construye una Command
instancia de , que representa un comando register, especificando un delegado para el método de Register
modelo de vista:
public ICommand RegisterCommand => new Command(Register);
El comando se expone a la vista a través de una propiedad que devuelve una referencia a .ICommand
Cuando se llama al Execute
método en el Command
objeto , simplemente reenvía la llamada al método en el modelo de vista a través del delegado especificado en el Command
constructor.
Un comando puede invocar un método asincrónico mediante las async
palabras clave y await
al especificar el delegado del Execute
comando. Esto indica que la devolución de llamada es y Task
debe esperarse. Por ejemplo, el código siguiente muestra cómo se construye una Command
instancia de , que representa un comando de inicio de sesión, especificando un delegado para el método de SignInAsync
modelo de vista:
public ICommand SignInCommand => new Command(async () => await SignInAsync());
Los parámetros se pueden pasar a las Execute
acciones y CanExecute
mediante la Command<T>
clase para crear instancias del comando. Por ejemplo, el código siguiente muestra cómo se usa una Command<T>
instancia para indicar que el NavigateAsync
método requerirá un argumento de tipo string
:
public ICommand NavigateCommand => new Command<string>(NavigateAsync);
En las Command
clases y Command<T>
, el delegado al CanExecute
método 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 del CanExecute
comando llamando al ChangeCanExecute
método en el Command
objeto . Esto hace que se genere el CanExecuteChanged
evento . Los controles de la interfaz de usuario enlazados al comando actualizarán su estado habilitado para reflejar la disponibilidad del comando enlazado a datos.
Invocar comandos desde una vista
En el ejemplo de código siguiente se muestra cómo un Grid
objeto de la LoginView
clase enlaza a RegisterCommand
en la LoginViewModel
clase mediante una TapGestureRecognizer
instancia de :
<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 CommandParameter
propiedad . El tipo del argumento esperado se especifica en los Execute
métodos y CanExecute
de destino. Invocará TapGestureRecognizer
automáticamente el comando de destino cuando el usuario interactúe con el control adjunto. El parámetro de comando, si se proporciona, se pasará como argumento al delegado del Execute
comando.
Implementación de comportamientos
Los comportamientos permiten agregar la funcionalidad a los controles de interfaz de usuario sin tener que subclasarlos. 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 le 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 tal forma que se puede asociar concisamente al control y empaquetar para su reutilización en más de una vista o aplicación. En el contexto de MVVM, los comportamientos son un enfoque útil para conectar controles a comandos.
Un comportamiento asociado a un control a través de propiedades adjuntas se conoce como comportamiento adjunto. Después, el comportamiento puede usar la API expuesta del elemento al que está asociado para agregar funcionalidad a ese control, u otros controles, en el árbol visual de la vista. La aplicación móvil eShopOnContainers contiene la LineColorBehavior
clase , que es un comportamiento adjunto. Para obtener más información sobre este comportamiento, vea Mostrar errores de validación.
Un Xamarin.Forms comportamiento es una clase que deriva de la Behavior
clase o Behavior<T>
, donde T
es el tipo del control al que se debe aplicar el comportamiento. Estas clases proporcionan OnAttachedTo
métodos y OnDetachingFrom
, que se deben invalidar para proporcionar lógica que se ejecutará cuando el comportamiento se adjunte y se desasocie de los controles.
En la aplicación móvil eShopOnContainers, la BindableBehavior<T>
clase deriva de la Behavior<T>
clase . El propósito de la BindableBehavior<T>
clase es proporcionar una clase base para Xamarin.Forms los comportamientos que requieren que el BindingContext
comportamiento se establezca en el control adjunto.
La BindableBehavior<T>
clase proporciona un método reemplazable OnAttachedTo
que establece el BindingContext
de comportamiento y un método reemplazable OnDetachingFrom
que limpia .BindingContext
Además, la clase almacena una referencia al control adjunto en la propiedad AssociatedObject
.
La aplicación móvil eShopOnContainers incluye una EventToCommandBehavior
clase , que ejecuta un comando en respuesta a un evento que se produce. Esta clase deriva de la BindableBehavior<T>
clase para que el comportamiento pueda enlazar y ejecutar un ICommand
especificado por una Command
propiedad cuando se consume 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 OnAttachedTo
métodos y OnDetachingFrom
se usan para registrar y anular el registro de un controlador de eventos para el evento definido en la EventName
propiedad . A continuación, cuando se desencadena el evento, se invoca el OnFired
método , que ejecuta el comando .
La ventaja de usar EventToCommandBehavior
para ejecutar un comando cuando se desencadena un evento es que los comandos se pueden asociar a controles que no se diseñaron para interactuar con comandos. Además, esto mueve el código de control de eventos para ver los modelos, donde se puede probar unitariamente.
Invocar comportamientos desde una vista
EventToCommandBehavior
es especialmente útil para adjuntar un comando a un control que no admite comandos. Por ejemplo, usa ProfileView
EventToCommandBehavior
para ejecutar OrderDetailCommand
cuando se desencadena el ItemTapped
evento en el ListView
que se enumeran 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 ListView
, se activará el ItemTapped
evento , que ejecutará OrderDetailCommand
en .ProfileViewModel
De forma predeterminada, los argumentos de evento para el evento se pasan al comando . Estos datos se convierten a medida que se pasan entre el origen y el destino por el convertidor especificado en la EventArgsConverter
propiedad , que devuelve el Item
de ListView
desde ItemTappedEventArgs
. Por lo tanto, cuando OrderDetailCommand
se ejecuta , el objeto seleccionado Order
se pasa como parámetro a la acción registrada.
Para obtener más información sobre los comportamientos, consulte Comportamientos.
Resumen
El patrón Model-View-ViewModel (MVVM) ayuda a separar limpiamente la lógica empresarial y de presentación 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 puede facilitar 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 diseñadores de 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 presentación subyacente y la lógica de negocios se separan en tres clases independientes: 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.