WPF/MVVM: delegación de una colección de modelos de dominio a un modelo de vista
dns delegates (3)
Una colección de modelo de dominio (normalmente una Lista o IEnumerable) se delega a un ViewModel.
Eso significa que mi CustomerViewModel tiene una colección de órdenes de tipo List o IEnumerable.
Ningún cambio en la lista es reconocido por el control vinculado. Pero con ObservableCollection es.
Este es un problema en el patrón de diseño de MVVM.
¿Cómo tratas con ello?
ACTUALIZACIÓN : muestra de cómo lo hago:
public class SchoolclassViewModel : ViewModelBase
{
private Schoolclass _schoolclass;
private ObservableCollection<PupilViewModel> _pupils = new ObservableCollection<PupilViewModel>();
public SchoolclassViewModel(Schoolclass schoolclass)
{
_schoolclass = schoolclass;
_schoolclass.Pupils = new List<Pupil>();
foreach (var p in schoolclass.Pupils)
Pupils.Add(new PupilViewModel(p));
}
public Schoolclass GetSchoolclass
{
get { return _schoolclass; }
}
public int ID { get; set; }
public string SchoolclassName
{
get { return _schoolclass.SchoolclassName;}
set
{
if(_schoolclass.SchoolclassName != value)
{
_schoolclass.SchoolclassName = value;
this.RaisePropertyChanged("SchoolclassName");
}
}
}
public ObservableCollection<PupilViewModel> Pupils
{
get{ return _pupils;}
set
{
_pupils = value;
this.RaisePropertyChanged("Pupils");
}
}
}
De acuerdo, continuaré y agregaré mis pensamientos como una respuesta en lugar de en los comentarios. :)
Creo que la verdad es que esta es solo la realidad de la forma en que WPF y el enlace de datos funcionan. Para que funcione el enlace de datos bidireccional, las colecciones necesitan un medio para notificar los controles que están vinculados a ellos, y las listas y colecciones estándar utilizadas en la mayoría de los objetos de dominio no lo soportan / no deberían. Como mencioné en un comentario, el requisito de implementar INotifyPropertyChanged para propiedades que no son de recopilación es otro requisito que un objeto de dominio estándar no puede cumplir.
Los objetos de dominio no están destinados a ser modelos de vista, y por esta razón es posible que tenga que mapear entre los dos tipos de objetos. Esto no es diferente a tener que mapear entre objetos de dominio y objetos de acceso a datos. Cada tipo de objeto tiene una función diferente en el sistema, y cada uno debe diseñarse específicamente para respaldar su propio rol en el sistema.
Dicho todo esto, la idea de Agies de usar AOP para generar automáticamente clases de proxy es muy interesante, y es algo que pretendo investigar.
Lo que hago es, en lugar de usar ObservableCollection en mi modelo de dominio, usar mi propio tipo de colección que implementa la interfaz INotifyCollectionChanged entre otras interfaces útiles estándar y personalizadas. Mi forma de pensar es que, como Rockford Lhotka sugiere en su libro, la notificación de cambio es útil para algo más que una capa de presentación, ya que otros objetos comerciales dentro de la capa de dominio a menudo necesitan algún tipo de notificación cuando el estado cambia en otro objeto.
Con esta metodología, puede crear su propio tipo de colección que aún tenga los beneficios de la notificación de cambio y los comportamientos personalizados que necesite. La clase base para su colección podría implementarse como un código puramente de infraestructura y luego podría crearse una subclase que podría contener lógica de negocios utilizando la tecnología de estratificación de subtipo usada en este libro . Así que, al final, podría tener una colección que pueda ajustar los tipos de IEnumerable <> y proporcionar los elementos de notificación de cambio que busca, tanto para su modelo de dominio como para el código de presentación.
Me ocupo de esto al no hacerlo de la manera que describes.
Si necesito presentar un objeto Foo
y sus objetos Bar
relacionados en la vista, FooViewModel
generalmente implementará una propiedad Bars
de tipo ObservableCollection<BarViewModel>
.
Tenga en cuenta que esto es independientemente de si la clase Foo
subyacente tiene o no una propiedad Bars
de tipo IEnumerable<Bar>
. La clase Foo
podría no. Es posible que la aplicación ni siquiera necesite poder iterar sobre todos los objetos de Bar
para un Foo
, excepto en la interfaz de usuario.
Editar
Cuando mi vista es una representación simple del modelo de objetos de la aplicación, hago las cosas como lo hace en su muestra. El código en mi constructor es generalmente un poco más compacto:
_Bars = new ObservableCollection<BarViewModel>(
_Foo.Bars.Select(x => new BarViewModel(x)));
pero es esencialmente lo mismo.
Pero esto supone que Foo
realmente expone una propiedad de Bars
. Puede que no. O tal vez solo algunos objetos Bar
deberían aparecer en la vista. O tal vez deberían aparecer entremezclados con otros objetos, y el FooViewModel
debería exponer un CompositeCollection
de algún tipo.
El punto que estoy haciendo es que el modelo de vista es un modelo de la vista . Esto no necesariamente tiene una correspondencia directa con el modelo de objeto subyacente.
Para elegir un ejemplo simple: mi programa puede darle al usuario una forma de colocar elementos en cinco categorías diferentes arrastrándolos y soltándolos en cinco controles ListBox
diferentes. En última instancia, hacer esto establece una propiedad Category
en el objeto Item
. Mi modelo de vista va a tener una colección de objetos CategoryViewModel
, cada uno con una propiedad de tipo ObservableCollection<ItemViewModel>
, de modo que es fácil implementar los elementos entre las colecciones.
El problema es que es posible que ni siquiera haya una clase de categoría en el modelo de objetos de la aplicación, y mucho menos una colección de objetos de Category
. Item.Category
podría ser una propiedad del tipo string
. CategoryViewModel
no refleja el modelo de objetos de la aplicación. Solo existe para admitir la vista en la interfaz de usuario.