wpf - curso - mvvm en ios
MVVM y colecciones de máquinas virtuales (2)
En esta situación, simplemente hago que el modelo exponga ObservableCollection
s en lugar de List
s. No hay una razón particular por la que no debería. El ObservableCollection
encuentra en el espacio de nombres System.Collections.ObjectModel
del ensamblaje del System
, por lo que no hay dependencias extra irrazonables, de todos modos tendrá el System
todos modos. List
está en mscorlib
, pero eso es tanto un artefacto histórico como cualquier cosa.
Esto simplifica enormemente las interacciones model-viewmodel, no veo una razón para no hacerlo, usar List
s en el modelo simplemente crea muchos códigos desagradables de placa de caldera. Después de todo, estás interesado en los eventos.
Además, ¿por qué su HouseVM
un ObservableCollection<PeopleVM>
, en lugar de ObservableCollection<People>
? Las máquinas virtuales son para enlazar vistas, por lo que creo que todo lo que es vinculante para su ObservableCollection<PeopleVM>
está realmente interesado en las People
, de lo contrario, está vinculando dentro de un enlace, ¿o hay alguna razón específica por la que esto es útil? En general, no tengo una VM que exponga otras máquinas virtuales, pero tal vez sea solo yo.
Editar sobre las 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 atrapado con tener que encadenar varias actualizaciones, aunque me pregunto si realmente estás haciendo el mismo trabajo manualmente que el evento en un ObservableCollection
, a menos que haya entendido mal algunas de eso
Personalmente, como dije, mantendría las máquinas virtuales simples, y haré que expongan el mínimo y no expongan otras máquinas virtuales. Puede tomar algún tipo de rediseño y hacer que algunas partes sean un poco difíciles (por ejemplo, Converter
, sin embargo, terminas con un diseño simple y fácil de manejar con algunas irritaciones fáciles de manejar en los bordes.
Me parece que su ruta actual va a ser muy compleja bastante rápido y, lo más importante, difícil de seguir ... Sin embargo, YMMV, es solo mi experiencia :)
Tal vez mover algo de la lógica a servicios explícitos podría ayudar?
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, particularmente con respecto a la actualización de las colecciones Model y ViewModel con adiciones y eliminaciones?
La House
modelo contiene una colección de People
modelo (normalmente una List<People>
).
Ver modelo HouseVM
contiene el objeto House que se envuelve y un modelo ObservableCollection of view PeopleVM
( ObservableCollection<PeopleVM>
). Tenga en cuenta que terminamos aquí con HouseVM sosteniendo dos colecciones (que requieren sincronización):
1. HouseVM.House.List<People>
2. HouseVM.ObservableCollection<PeopleVM>
Cuando la casa se actualiza con nuevas personas (agregar) o personas se van (eliminan), ese evento ahora debe manejarse en ambas colecciones, la colección de personas de House Model Y la VM HouseVM PeopleVM ObservableCollection.
¿Es esta estructura correcta MVVM?
¿Hay alguna forma de evitar tener que hacer la doble actualización de Agrega y quita?
Su enfoque general es perfectamente correcto MVVM, tener un ViewModel exponiendo una colección de otros ViewModels es un escenario muy común, que utilizo en todas partes. No recomendaría exponer artículos directamente en un ViewModel, como dijo nicodemus13, ya que terminará con su enlace de vista a modelos sin ViewModels en el medio para los artículos de su colección. Entonces, la respuesta a su primera pregunta es: Sí, esto es válido MVVM.
El problema que está abordando en su segunda pregunta es la sincronización entre la lista de modelos de personas en su modelo de casa y la lista de personas ViewModels en su casa ViewModel. Tienes que hacer esto manualmente. Entonces, no, no hay forma de evitar esto.
Lo que puede hacer: Implementar una ObservableCollection<T>
, ViewModelCollection<T>
, que ViewModelCollection<T>
sus cambios a una colección subyacente. Para obtener una sincronización bidireccional, también haga que la colección del modelo sea una ObservableCollection <> y regístrese en el evento CollectionChanged
en su ViewModelCollection.
Esta es mi implementación. Utiliza un servicio de ViewModelFactory, etc., pero solo eche un vistazo al 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));
}
}