wpf - parte - que es mvvm en programacion
Construcción de modelos de vista basados en entidades de modelo anidadas en el patrón WPF y MVVM (4)
Tengo un problema para entender cómo crear modelos de vista basados en los siguientes modelos
(Simplifiqué los modelos para ser más claros)
public class Hit
{
public bool On { get; set;}
public Track Track { get; set; }
}
public class Track
{
public ObservableCollection<Hit> Hits { get; set; }
public LinearGradientBrush Color { get; set; }
public Pattern Pattern { get; set; }
}
public class Pattern
{
public string Name { get; set; }
public ObservableCollection<Tracks> Tracks { get; set; }
}
Ahora, mi problema es cómo construir ViewModels ...
Necesito mantener las relaciones originales a través de los modelos, porque tengo un método Serialize () en el patrón que lo serializa en un archivo XML ... (con las pistas y visitas relacionadas)
Para poder vincular el patrón a los controles del usuario y sus plantillas anidadas, también debería tener un PatternViewModel con un <modelo TrackViewModelo> de ObservableCollection, lo mismo para TrackViewModel y HitViewModel ... y necesito tener propiedades de presentación personalizadas en el ver modelos que no son parte del objeto comercial (colores y más ...)
No me parece nada bueno duplicar todas las relaciones de los modelos en los modelos de vista ... y hacer un seguimiento de todas estas relaciones mientras se codifican los modelos de vista también es mucho más propenso a errores.
Alguien tiene un mejor enfoque / solución?
Creo que tuve el mismo problema y si lo haces como "PatternViewModel con un ObservableCollection <TrackViewModel>" también obtienes un gran impacto en tu rendimiento porque comienzas a duplicar datos.
Mi enfoque fue construir, para su expansión, un PatternViewModel con un ObservableCollection <Track>. No es una contradicción para MVVM porque la vista está vinculada a la colección.
De esta manera, puede evitar la duplicación de las relaciones.
Una cosa que he hecho, con cierto éxito, es sacar la ObservableCollection del modelo. Aquí está mi patrón general:
- En los objetos modelo, exponer una propiedad de tipo
IEnumerable<TModel>
que otorga acceso de solo lectura a la colección. Utilice unaList<TModel>
simple anteriorList<TModel>
, no una ObservableCollection, como la colección de respaldo. - Para el código que necesita mutar las colecciones de los modelos (agregar, eliminar, etc.), agregue métodos al objeto modelo. No tiene código externo que manipule directamente la colección; encapsular los métodos internos en el modelo.
- Agregue eventos al modelo para cada tipo de cambio que permita. Por ejemplo, si su modelo solo admite agregar elementos al final de la colección y eliminar elementos, entonces necesitaría un evento ItemAdded y un evento ItemDeleted. Cree un descendiente EventArgs que proporcione información sobre el elemento que se agregó. Dispara estos eventos desde los métodos de mutación.
- En su ViewModel, tenga un
ObservableCollection<TNestedViewModel>
. - Haga que ViewModel enganche los eventos en el modelo. Cada vez que el modelo dice que se agregó un elemento, cree una instancia de ViewModel y agréguelo al ViewbleCollection de ViewModel. Siempre que el modelo indique que se eliminó un elemento, itere el ObservableCollection, encuentre el ViewModel correspondiente y elimínelo.
- Además de los controladores de eventos, asegúrese de que todo el código de colección-mutación se realice a través del modelo: trate el ObservableCollection de ViewModel estrictamente como algo para el consumo de la vista, no como algo que use en el código.
Esto genera una gran cantidad de código duplicado para cada ViewModel diferente, pero es lo mejor que he podido encontrar. Al menos escala en función de la complejidad que necesita: si tiene una colección que es de solo agregado, no tiene que escribir mucho código; si tiene una colección que admite el reordenamiento arbitrario, las inserciones, la clasificación, etc., es mucho más trabajo.
Terminé usando parte de la solución que Joe White sugirió, de una manera levemente diferente
La solución fue dejar los modelos como estaban al principio, y adjuntar a las colecciones un controlador de eventos para CollectionChanged de las colecciones internas, por ejemplo, PatternViewModel sería:
public class PatternViewModel : ISerializable
{
public Pattern Pattern { get; set; }
public ObservableCollection<TrackViewModel> Tracks { get; set; }
public PatternViewModel(string name)
{
Pattern = new Pattern(name);
Tracks = new ObservableCollection<TrackViewModel>();
Pattern.Tracks.CollectionChanged += new NotifyCollectionChangedEventHandler(Tracks_CollectionChanged);
}
void Tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (Track track in e.NewItems)
{
var position = Pattern.Tracks.IndexOf((Track) e.NewItems[0]);
Tracks.Insert(position,new TrackViewModel(track, this));
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Track track in e.OldItems)
Tracks.Remove(Tracks.First(t => t.Track == track));
break;
case NotifyCollectionChangedAction.Move:
for (int k = 0; k < e.NewItems.Count; k++)
{
var oldPosition = Tracks.IndexOf(Tracks.First(t => t.Track == e.OldItems[k]));
var newPosition = Pattern.Tracks.IndexOf((Track) e.NewItems[k]);
Tracks.Move(oldPosition, newPosition);
}
break;
}
}
}
Así que puedo adjuntar el nuevo Color / Estilo / Comando en los modelos de vista para mantener mis modelos base limpios
Y cada vez que agrego / quito / muevo elementos en la colección de modelos base, las colecciones de modelos de vista permanecen sincronizadas entre sí
Afortunadamente, no tengo que administrar muchos objetos en mi aplicación, por lo que los datos duplicados y el rendimiento no serán un problema.
No me gusta demasiado, pero funciona bien, y no es una gran cantidad de trabajo, solo un controlador de eventos para el modelo de vista que contiene otras colecciones de modelos (en mi caso, uno para PatternViewModel para sincronizar TrackViewModels y otro en TrackViewModel para administrar HitViewModels)
Todavía interesado en tus pensamientos o mejores ideas =)
Una solución que he estado considerando, aunque no estoy seguro de si funcionaría perfectamente en la práctica, es usar convertidores para crear un modelo de vista alrededor de su modelo.
Entonces, en su caso, podría vincular Tracks
directamente a (como un ejemplo) un listbox, con un convertidor que crea un nuevo TrackViewModel
desde el Track. Todo el control que vería sería un objeto TrackViewModel
y todos sus modelos se verán en otros modelos.
Aunque no estoy seguro de la actualización dinámica de esta idea, aún no la he probado.