2010-03-28 10 views
24

Mi pregunta genérica es como dice el título, ¿es mejor cargar datos durante la construcción de ViewModel o después a través de algún manejo de evento cargado?MVVM cargar datos durante o después de la construcción de ViewModel?

Supongo que la respuesta es después de la construcción mediante el manejo de algún evento cargado, pero me pregunto cómo se coordina de forma más clara entre ViewModel y View.

Aquí hay más detalles acerca de mi situación y el problema particular que estoy tratando de resolver:

estoy usando el marco MVVM Light, así como la Unidad de DI. Tengo algunas Vistas anidadas, cada una vinculada a un ViewModel correspondiente. Los ViewModels están vinculados al DataContext del control de raíz de cada Vista a través de la idea ViewModelLocator que Laurent Bugnion ha puesto en MVVM Light. Esto permite encontrar ViewModels a través de un recurso estático y controlar la vida útil de ViewModels a través de un marco de inyección de dependencias, en este caso Unity. También permite que Expression Blend vea todo lo relacionado con ViewModels y cómo vincularlos.

De todos modos, tengo una Vista padre que tiene un ComboBox de datos a un ObservableCollection en su ViewModel. El elemento seleccionado de ComboBox también está vinculado (bidireccional) a una propiedad en ViewModel. Cuando la selección del ComboBox cambia, esto es para activar actualizaciones en otras vistas y subvistas. Actualmente estoy logrando esto a través del sistema de mensajería que se encuentra en MVVM Light. Todo esto funciona muy bien y como se esperaba cuando eliges diferentes elementos en el ComboBox.

Sin embargo, el ViewModel obtiene sus datos durante el tiempo de construcción a través de una serie de llamadas a métodos de inicialización. Esto parece ser solo un problema si quiero controlar lo que es el SelectedItem inicial de ComboBox. Utilizando el sistema de mensajería de MVVM Light, actualmente lo configuro donde el colocador de la propiedad SelectedItem de ViewModel es el que difunde la actualización y los otros ViewModels interesados ​​se registran para el mensaje en sus constructores. Parece que actualmente estoy intentando establecer SelectedItem a través del ViewModel en el momento de la construcción, lo que no ha permitido que se construyan sub-ViewModels y se registren todavía.

¿Cuál sería la forma más limpia de coordinar la carga de datos y la configuración inicial de SelectedItem dentro del ViewModel? Realmente quiero seguir poniendo tan poco en el código subyacente de View como sea razonable. Creo que solo necesito una forma para que ViewModel sepa cuando las cosas se han cargado y que luego puede continuar cargando los datos y finalizando la fase de configuración.

Gracias de antemano por sus respuestas.

+1

no se puede tener su evento Loaded llamar a un método en el modelo de vista? – Klinger

+1

Sí, supongo que podría. Probablemente estoy más de pensarlo. Creo que mi vacilación con eso es que he podido enlazar todo hasta ahora declarativamente en el XAML. Establecí el DataContext y luego establecí los enlaces de miembros en un solo lugar. ¿Hay una manera limpia de continuar esto en XAML con el evento Loaded del control vinculado a un método de ViewModel? Por supuesto, tampoco creo que ViewModel deba tener parámetros de manejo de eventos específicos de la UI. – mkmurray

Respuesta

23

Para eventos, debe usar EventToCommand en MVVM Light Toolkit. Usando esto puedes vincular cualquier evento de cualquier elemento ui a un comando de retransmisión. Echa un vistazo a su artículo sobre EventToCommand en

http://blog.galasoft.ch/archive/2009/11/05/mvvm-light-toolkit-v3-alpha-2-eventtocommand-behavior.aspx

Descargar la muestra y echar un vistazo. Es genial. No necesitarás ningún código detrás de eso. Un ejemplo es el siguiente:

<Page x:Class="cubic.cats.Wpf.Views.SplashScreenView" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
     xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300" 
    Title="SplashScreenPage"> 

    <i:Interaction.Triggers> 
     <i:EventTrigger EventName="Loaded"> 
      <cmd:EventToCommand Command="{Binding LoadedCommand}" /> 
     </i:EventTrigger>   
    </i:Interaction.Triggers> 

    <Grid> 
     <Label Content="This is test page" /> 
    </Grid> 
</Page> 

y el modo de vista podría ser así

public class SplashScreenViewModel : ViewModelBase 
    { 
     public RelayCommand LoadedCommand 
     { 
      get; 
      private set; 
     } 

     /// <summary> 
     /// Initializes a new instance of the SplashScreenViewModel class. 
     /// </summary> 
     public SplashScreenViewModel() 
     { 
      LoadedCommand = new RelayCommand(() => 
      { 
       string a = "put a break point here to see that it gets called after the view as been loaded"; 
      }); 
     } 
    } 

si desea que el modelo de vista de tener los EventArgs, se puede establecer sencilla PassEventArgsToCommand true:

<i:Interaction.Triggers> 
      <i:EventTrigger EventName="Loaded"> 
       <cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding LoadedCommand}" /> 
    </i:EventTrigger>   
</i:Interaction.Triggers> 

y el modelo de vista será como

public class SplashScreenViewModel : ViewModelBase 
{ 
    public RelayCommand<MouseEventArgs> LoadedCommand 
    { 
     get; 
     private set; 
    } 

    /// <summary> 
    /// Initializes a new instance of the SplashScreenViewModel class. 
    /// </summary> 
    public SplashScreenViewModel() 
    { 
     LoadedCommand = new RelayCommand<MouseEventArgs>(e => 
     { 
      var a = e.WhateverParameters....; 
     }); 
    } 

} 
+0

Ahh .. Estoy buscando una solución con MVVM Toolkit, es decir, un simple patrón mvvm. ¿Puedes sugerir uno? Gracias –

1

Decidí tener el XAML vinculado de forma declarativa a un controlador de eventos cargado en el código subyacente de la vista, que a su vez acaba de llamar a un método en el objeto ViewModel, a través del elemento raíz de la vista UserControl DataContext.

Era una solución bastante simple, directa y limpia. Supongo que esperaba una forma de vincular el evento Loaded al objeto ViewModel de la misma manera declarativa con ICommands en XAML.

Es posible que haya otorgado a Klinger el crédito de respuesta oficial, pero ha publicado un comentario sobre mi pregunta y no una respuesta. Así que al menos le di un comentario sobre su comentario.

0

Tuve el mismo problema cuando trato con mensajes entre una ventana principal y una ventana secundaria. Simplemente cambie el orden en que se crean sus modelos de vista en su clase ViewModelLocator. Asegúrese de que todos los modelos de vista que dependen de un mensaje se creen antes del modelo de vista que envía el mensaje.

Por ejemplo, en el constructor de la clase ViewModelLocator:

public ViewModelLocator() 
{ 
    if (s_messageReceiverVm == null) 
    { 
     s_messageReceiverVm = new MessageReceiverVM(); 
    } 

    if (s_messageSenderVm == null) 
    { 
     s_messageSenderVm = new MessageSenderVM(); 
    } 
} 
+1

Puede inicializar las máquinas virtuales en ViewModelLocator de esta manera: 'SimpleIoc.Default.Register (true);' –

+0

No entiendo la relevancia de su comentario en mi respuesta. ¿Podría explicar por favor? – bugged87

+1

Acabo de sugerir una forma alternativa (usando Ioc) para crear ViewModels en el constructor ViewModelLocator. –

1

la siguiente solución es similar a la ya proporcionada y aceptado, pero que no utiliza un comando en el modelo de vista para cargar los datos, pero un "método normal". Creo que los comandos son más adecuados para las acciones del usuario (los comandos pueden estar disponibles y no disponibles en tiempo de ejecución), por eso se usa una llamada a método regular, pero también se establece un activador de interacción en la vista.

Sugiero esto: Crear una vista model class. Cree una instancia de la clase de modelo de vista dentro de la xaml de la vista dentro de la propiedad DataContext.

Implemente un método para cargar los datos en su modelo de vista, p. LoadData. Configure la vista para que se llame a este método cuando se carga la vista. Esto se realiza mediante un disparador de interacción en su vista que está vinculado al método en el modelo de vista (se necesitan referencias a "Microsoft.Expression.Interactions" y "System.Windows.Interactivity"):

Ver (xaml):

<Window x:Class="MyWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Test" 
    xmlns:viewModel="clr-namespace:ViewModels" 
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"    
    > 
<Window.DataContext> 
    <viewModel:ExampleViewModel/> 
</Window.DataContext> 
<i:Interaction.Triggers> 
    <i:EventTrigger EventName="Loaded"> 
     <ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/> 
    </i:EventTrigger> 
</i:Interaction.Triggers> 

Esta llamará al método LoadData en el ViewModel en tiempo de ejecución cuando se carga la vista. Aquí es donde cargas tus datos.

public class ExampleViewModel 
{ 
    /// <summary> 
    /// Constructor. 
    /// </summary> 
    public ExampleViewModel() 
    { 
     // Do NOT do complex stuff here 
    } 


    public void LoadData() 
    { 
     // Make a call to the repository class here 
     // to set properties of your view model 
    } 

Si el método en el repositorio es un método asíncrono, puede hacer que el método asíncrono LoadData también, pero esto no es necesario en cada caso.

Por cierto, generalmente no cargaría datos en el constructor del modelo de vista. En el ejemplo anterior, se llama al constructor (parámetro menos) del modelo de vista cuando el diseñador muestra su vista. Hacer cosas complejas aquí puede causar errores en el diseñador al mostrar su vista (por la misma razón no haría cosas complejas en el constructor de vistas).

en algún código escenarios en la vista de los modelos de constructor puede incluso causar problemas en tiempo de ejecución, cuando la vista modelos constructores ejecuta, propiedades de deformación del modelo de vista que están unidos a elementos de la vista, mientras que el objeto de vista no está completamente terminado creando.

Cuestiones relacionadas