2009-05-12 827 views
18

A través de la generosa ayuda en this question, armé la siguiente estructura MVVM que muestra los cambios de un modelo en tiempo real en XAML (fecha/hora actual), muy agradable.Modelos gordos, ViewModels delgados y Vistas tontas, el mejor enfoque MVVM?

Una ventaja fresco de esta configuración es que cuando nos fijamos en la vista en modo de diseño de Visual Studio o Blend, que ver la hora de relojería por, lo que significa que en el tiempo de diseño que tiene acceso a los datos en tiempo real de su modelo .

En el proceso de conseguir que esto funcione, me sorprendió ver la mayor parte del movimiento mayor de mi modelo de vista en mi modelo, incluida la aplicación de INotifyPropertyChange. Otro cambio es que I ya no se une a propiedades en el modelo de vista, pero a métodos.

Así Actualmente este es mi sabor favorito de MVVM:

  1. View es tonto:

    • uno ObjectDataProvider para cada objeto que necesita de su modelo
    • cada ObjectDataProvider se asigna a un método en ViewModel (no en una propiedad)
    • no x: propiedades de nombre en elementos XAML
  2. modelo de vista es flaca:

    • la única cosa en su modelo de vista son los métodos en que su visión se une
  3. Modelo es la grasa:

    • el modelo implementa INotifyPropertyChanged en cada una de sus propiedades.
    • para cada método en su ViewModel (por ejemplo, GetCurrentCustomer) hay un método de singleton correspondiente en su modelo (por ejemplo, GetCurrentCustomer).
    • el modelo se encarga de cualquier tiempo real enhebrar funcionalidad como en este ejemplo

Preguntas:

  1. Aquellos de ustedes que han estado implementando MVVM en escenarios reales, es la siguiente la estructura básica que también ha establecido, y si no, ¿cómo varía la suya?
  2. ¿Cómo ampliaría esto para incluir comandos enrutados y eventos enrutados?

El siguiente código funcionará si solo copia el XAML y el código de atrás en un nuevo proyecto de WPF.

XAML:

<Window x:Class="TestBinding99382.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:TestBinding99382" 
    Title="Window1" Height="300" Width="300"> 

    <Window.Resources> 
     <ObjectDataProvider 
      x:Key="DataSourceCustomer" 
      ObjectType="{x:Type local:ShowCustomerViewModel}" 
         MethodName="GetCurrentCustomer"/> 
    </Window.Resources> 

    <DockPanel DataContext="{StaticResource DataSourceCustomer}"> 
     <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> 
      <TextBlock Text="{Binding Path=FirstName}"/> 
      <TextBlock Text=" "/> 
      <TextBlock Text="{Binding Path=LastName}"/> 
     </StackPanel> 
     <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> 
      <TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/> 
     </StackPanel> 

    </DockPanel> 
</Window> 

código subyacente:

using System.Windows; 
using System.ComponentModel; 
using System; 
using System.Threading; 

namespace TestBinding99382 
{ 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 
     } 
    } 

    //view model 
    public class ShowCustomerViewModel 
    { 
     public Customer GetCurrentCustomer() { 
      return Customer.GetCurrentCustomer(); 
     } 
    } 

    //model 
    public class Customer : INotifyPropertyChanged 
    { 
     private string _firstName; 
     private string _lastName; 
     private DateTime _timeOfMostRecentActivity; 
     private static Customer _currentCustomer; 
     private Timer _timer; 

     public string FirstName 
     { 
      get 
      { 
       return _firstName; 
      } 
      set 
      { 
       _firstName = value; 
       this.RaisePropertyChanged("FirstName"); 
      } 
     } 

     public string LastName 
     { 
      get 
      { 
       return _lastName; 
      } 
      set 
      { 
       _lastName = value; 
       this.RaisePropertyChanged("LastName"); 
      } 
     } 

     public DateTime TimeOfMostRecentActivity 
     { 
      get 
      { 
       return _timeOfMostRecentActivity; 
      } 
      set 
      { 
       _timeOfMostRecentActivity = value; 
       this.RaisePropertyChanged("TimeOfMostRecentActivity"); 
      } 
     } 

     public Customer() 
     { 
      _timer = new Timer(UpdateDateTime, null, 0, 1000); 
     } 

     private void UpdateDateTime(object state) 
     { 
      TimeOfMostRecentActivity = DateTime.Now; 
     } 

     public static Customer GetCurrentCustomer() 
     { 
      if (_currentCustomer == null) 
      { 
       _currentCustomer = new Customer 
        { FirstName = "Jim" 
         , LastName = "Smith" 
         , TimeOfMostRecentActivity = DateTime.Now 
        }; 
      } 
      return _currentCustomer; 
     } 

     //INotifyPropertyChanged implementation 
     public event PropertyChangedEventHandler PropertyChanged; 
     private void RaisePropertyChanged(string property) 
     { 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(property)); 
      } 
     } 
    } 
} 
+0

En cuanto a RelayCommands y RoutedCommands, es posible que desee echar un vistazo a estas respuestas: http://stackoverflow.com/questions/650010/mvvm-routed-and-relay-command/6441472#6441472 – Marc

Respuesta

28

Aquí está mi opinión, por lo que vale la pena:

Realmente no estoy de acuerdo con el enfoque usted sugiere (a excepción de la vista tonta). En la vida real, a menudo tendrá que usar un modelo existente: podría ser un código heredado que no tiene el tiempo (o voluntad) de cambiar, o incluso una biblioteca para la que no tiene el código. En mi opinión, el modelo debe ignorar por completo la forma en que se mostrará, y debe ser fácilmente utilizable en una aplicación que no sea de WPF. Por lo tanto, no tiene que implementar ninguna interfaz específica como INotifyPropertyChanged de INotifyCollectionChanged para que se pueda usar en MVVM. Creo que toda la lógica relacionada con la IU debe residir en ViewModel.

Con respecto a RoutedEvents y RoutedCommands, no son realmente adecuados para usar con el patrón MVVM. Por lo general trato de usar tan poco RoutedEvents como sea posible, y no RoutedCommands en absoluto. En cambio, mis ViewModels exponen RelayCommand propiedades que vinculo a la interfaz de usuario en XAML (vea this article por Josh Smith para detalles sobre RelayCommand). Cuando realmente lo necesito para controlar los eventos de un cierto control, uso comportamientos adjunto para asignar los eventos a los comandos ViewModel (echar un vistazo a Marlon Grech's implementation)

Así, en resumen:

  • Ver Dumb
  • grande y modelo de vista inteligente
  • Cualquier modelo que quieren o tienen que utilizar

Por supuesto que es sólo mi enfoque, y puede que no sea el mejor, pero me siento muy a gusto con ella;)

+0

muy interesantes, especialmente con respecto al modelo, que debería poder usar el patrón MVVM para conectar clases de datos de proyectos antiguos que no tienen conocimiento de WPF. Es interesante que pienses que RoutedEvents y RoutedCommands no son realmente adecuados para MVVM, pensé que estaban hechos para ser utilizados en patrones de desacoplamiento como MVVM. Gracias por la respuesta. –

+0

Refactoreé este ejemplo basado en esta retroalimentación, coloque la lógica en ViewModel: http://stackoverflow.com/questions/857820/big-smart-viewmodels-dumb-views-and-any-model-the-best- mvvm-approach –

+0

Llegas al punto ... – Marc

2

Estoy de acuerdo con Thomas. Mi consejo a cualquier persona en WPF architecturing sería:

  • entidades POCO llano sin INotifyPropertyChange, seguimiento de estado, BL, etc.
  • ViewModels simples y pequeños que notifican Vistas justo a tiempo
  • interfaz de usuario reutilizable simple con un sistema de navegación inteligente que evite las jerarquías de datos complejos y ViewModels subyacentes complejas
  • MVVM con vistas al primer enfoque para mantener las dependencias simples
  • operaciones asíncronas con las tareas o Rx
  • Un tema simple
  • sin compleja interfaz de usuario robusta, que sea sencillo, basta con tomar ventaja de la composición WPFs interfaz de usuario y capacidades de unión
  • no dude e código subyacente para generar contenido dinámicamente (formularios, listas, etc.) y ahorrar tiempo significativo en la configuración declarativa del ojo (se aplica a la mayoría de los casos), y para mí es una obligación en 2015. Use métodos de extensión para crear una API Fluent para eso.
Cuestiones relacionadas