MVVM y colecciones de máquinas virtuales


Un senario común: Un modelo con una colección de modelos de artículos.
Por ejemplo, una Casa con una colección de Personas.

¿Cómo estructurar esto correctamente para MVVM, en particular con respecto a la actualización de las colecciones Model y ViewModel con adiciones y eliminaciones?

Model House contiene una colección de model People (normalmente a List<People>).
View model HouseVM contiene el objeto House que envuelve y un ObservableCollection of view model PeopleVM (ObservableCollection<PeopleVM>). Tenga en cuenta que termina aquí con el HouseVM sosteniendo dos colecciones (que requieren sincronización):
1. HouseVM.House.List<People>
2. HouseVM.ObservableCollection<PeopleVM>

Cuando la Casa se actualiza con Personas nuevas (agregar) o Personas dejan (quitar), ese evento ahora debe manejarse en ambas colecciones, la colección de Personas de la Casa Modelo Y la colección observable de VM HouseVM PeopleVM.

¿Es correcta esta estructura MVVM?
¿Hay alguna manera de evitar tener que hacer la doble actualización para Agregar y Quitar?

Author: Ricibob, 2013-04-05

2 answers

Su enfoque general es MVVM perfectamente bien, tener un ViewModel exponer una colección de otros ViewModels es un escenario muy común, que uso por todas partes. No recomendaría exponer artículos directamente en un ViewModel, como dijo nicodemus13, ya que terminas con tu enlace de vista a modelos sin ViewModels en el medio para los artículos de tu colección. Entonces, la respuesta a su primera pregunta es: Sí, esto es MVVM válido.

El problema que está abordando en su segunda pregunta es la sincronización entre la lista de personas a las modelos en su casa de la modelo y la lista de personas ViewModels en su casa ViewModel. Tienes que hacerlo manualmente. Por lo tanto, no hay manera de evitar esto.

introduzca la descripción de la imagen aquí

Lo que puede hacer: Implementar un ObservableCollection<T>, ViewModelCollection<T>, lo que empuja sus cambios a una colección subyacente. Para obtener sincronización bidireccional, haz de la colección del modelo una ObservableCollection también y regístrate en el evento CollectionChanged en tu ViewModelCollection.

Esto es mi implementación. Utiliza un servicio ViewModelFactory y así sucesivamente, pero solo echar un vistazo a la principal general. Espero que ayude...

/// <summary>
/// Observable collection of ViewModels that pushes changes to a related collection of models
/// </summary>
/// <typeparam name="TViewModel">Type of ViewModels in collection</typeparam>
/// <typeparam name="TModel">Type of models in underlying collection</typeparam>
public class VmCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
    where TViewModel : class, IViewModel
    where TModel : class

{
    private readonly object _context;
    private readonly ICollection<TModel> _models;
    private bool _synchDisabled;
    private readonly IViewModelProvider _viewModelProvider;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="models">List of models to synch with</param>
    /// <param name="viewModelProvider"></param>
    /// <param name="context"></param>
    /// <param name="autoFetch">
    /// Determines whether the collection of ViewModels should be
    /// fetched from the model collection on construction
    /// </param>
    public VmCollection(ICollection<TModel> models, IViewModelProvider viewModelProvider, object context = null, bool autoFetch = true)
    {
        _models = models;
        _context = context;

        _viewModelProvider = viewModelProvider;

        // Register change handling for synchronization
        // from ViewModels to Models
        CollectionChanged += ViewModelCollectionChanged;

        // If model collection is observable register change
        // handling for synchronization from Models to ViewModels
        if (models is ObservableCollection<TModel>)
        {
            var observableModels = models as ObservableCollection<TModel>;
            observableModels.CollectionChanged += ModelCollectionChanged;
        }


        // Fecth ViewModels
        if (autoFetch) FetchFromModels();
    }

    /// <summary>
    /// CollectionChanged event of the ViewModelCollection
    /// </summary>
    public override sealed event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add { base.CollectionChanged += value; }
        remove { base.CollectionChanged -= value; }
    }

    /// <summary>
    /// Load VM collection from model collection
    /// </summary>
    public void FetchFromModels()
    {
        // Deactivate change pushing
        _synchDisabled = true;

        // Clear collection
        Clear();

        // Create and add new VM for each model
        foreach (var model in _models)
            AddForModel(model);

        // Reactivate change pushing
        _synchDisabled = false;
    }

    private void ViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Return if synchronization is internally disabled
        if (_synchDisabled) return;

        // Disable synchronization
        _synchDisabled = true;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var m in e.NewItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>())
                    _models.Add(m);
                break;

            case NotifyCollectionChangedAction.Remove:
                foreach (var m in e.OldItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>())
                    _models.Remove(m);
                break;

            case NotifyCollectionChangedAction.Reset:
                _models.Clear();
                foreach (var m in e.NewItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>())
                    _models.Add(m);
                break;
        }

        //Enable synchronization
        _synchDisabled = false;
    }

    private void ModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (_synchDisabled) return;
        _synchDisabled = true;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var m in e.NewItems.OfType<TModel>()) 
                    this.AddIfNotNull(CreateViewModel(m));
                break;

            case NotifyCollectionChangedAction.Remove:
                    foreach (var m in e.OldItems.OfType<TModel>()) 
                        this.RemoveIfContains(GetViewModelOfModel(m));
                break;

            case NotifyCollectionChangedAction.Reset:
                Clear();
                FetchFromModels();
                break;
        }

        _synchDisabled = false;
    }

    private TViewModel CreateViewModel(TModel model)
    {
        return _viewModelProvider.GetFor<TViewModel>(model, _context);
    }

    private TViewModel GetViewModelOfModel(TModel model)
    {
        return Items.OfType<IViewModel<TModel>>().FirstOrDefault(v => v.IsViewModelOf(model)) as TViewModel;
    }

    /// <summary>
    /// Adds a new ViewModel for the specified Model instance
    /// </summary>
    /// <param name="model">Model to create ViewModel for</param>
    public void AddForModel(TModel model)
    {
        Add(CreateViewModel(model));
    }

    /// <summary>
    /// Adds a new ViewModel with a new model instance of the specified type,
    /// which is the ModelType or derived from the Model type
    /// </summary>
    /// <typeparam name="TSpecificModel">Type of Model to add ViewModel for</typeparam>
    public void AddNew<TSpecificModel>() where TSpecificModel : TModel, new()
    {
        var m = new TSpecificModel();
        Add(CreateViewModel(m));
    }
}
 46
Author: Marc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-23 08:12:25

En esta situación simplemente hago que el modelo exponga ObservableCollections en lugar de Lists. No hay ninguna razón en particular por la que no debería. El ObservableCollection está en el espacio de nombres System.Collections.ObjectModel del ensamblado System, por lo que no hay dependencias adicionales irrazonables, casi seguramente tiene System de todos modos. List está en mscorlib, pero eso es tanto un artefacto histórico como cualquier otra cosa.

Esto simplifica las interacciones modelo-viewmodel masivamente, no puedo ver una razón para no hacerlo, usando List s en el modelo simplemente crea un montón de código de placa de caldera desagradable. Usted está interesado en los eventos, después de todo.

También, ¿por qué es su HouseVM envolver un ObservableCollection<PeopleVM> en lugar de ObservableCollection<People>? Las máquinas virtuales son para vincularse a vistas, por lo que creo que lo que es vinculante para su ObservableCollection<PeopleVM> está realmente interesado en People, de lo contrario está vinculando-dentro-de-un-enlace, o hay una razón específica por la que esto es útil? Generalmente no tendría una máquina virtual que exponga otras máquinas virtuales, pero tal vez sea solo yo.

Editar acerca de bibliotecas / WCF

No veo por qué tener un modelo en una biblioteca, o incluso expuesto por un servidor WCF debería afectar si generan eventos o no, me parece perfectamente válido (obviamente, el servicio WCF no expondrá los eventos directamente). Si no te gusta esto, creo que estás atascado con tener que encadenar varias actualizaciones, aunque me pregunto si en realidad estás haciendo manualmente el mismo trabajo que el evento haría en un ObservableCollection, a menos que haya malinterpretado algunos de se.

Personalmente, como dije, mantendría las máquinas virtuales simples, y las haría exponer lo mínimo y no exponer otras máquinas virtuales. Puede tomar un poco de rediseño y hacer que ciertas partes un poco de un dolor (por ejemplo, Converters, sin embargo, usted termina con un diseño simple, fácil de manejar con algunas irritaciones fáciles de manejar en los bordes.

Me parece que su ruta actual va a terminar muy compleja con bastante rapidez y, lo más importante, difícil de seguir... Sin embargo, YMMV, es sólo mi experiencia :)

Tal vez mover algo de la lógica a los servicios explícitos podría ayudar?

 4
Author: nicodemus13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-05 09:46:18