c# - para - mvvm conceptos



Colecciones de sincronización MVVM (7)

Bueno, antes que nada, no creo que haya una sola "forma correcta" de hacer esto. Depende completamente de tu aplicación. Hay formas más correctas y menos correctas.

Dicho eso, me pregunto por qué tendrías que mantener estas colecciones " sincronizadas ". ¿Qué escenario estás considerando que los haría perder sincronía? Si observa el código de muestra del artículo de MSDN de Josh Smith en MV-VM , verá que la mayoría de las veces, los Modelos se mantienen sincronizados con ViewModels simplemente porque cada vez que se crea un Modelo, también se crea un ViewModel. . Me gusta esto:

void CreateNewCustomer()
{
    Customer newCustomer = Customer.CreateNewCustomer();
    CustomerViewModel workspace = new CustomerViewModel(newCustomer, _customerRepository);
    this.Workspaces.Add(workspace);
    this.SetActiveWorkspace(workspace);
}

Me pregunto, ¿qué impide que crees un AppleModelView cada vez que creas un Apple ? Esa me parece ser la forma más fácil de mantener estas colecciones "sincronizadas", a menos que haya entendido mal su pregunta.

https://ffff65535.com

¿Existe una forma estandarizada para sincronizar una colección de objetos Model con una colección de objetos ModelView coincidentes en C # y WPF? Estoy buscando algún tipo de clase que mantenga sincronizadas las dos colecciones siguientes, suponiendo que solo tengo algunas manzanas y puedo guardarlas todas en la memoria.

Otra forma de decirlo, quiero asegurarme de que si agrego Apple a la colección Apples me gustaría tener un AppleModelView agregado a la colección AppleModelViews. Podría escribir el mío escuchando el evento CollectionChanged de cada colección. Parece una situación común que alguien más inteligente que yo haya definido "la manera correcta" de hacerlo.

public class BasketModel
{
    public ObservableCollection<Apple> Apples { get; }
}

public class BasketModelView
{
    public ObservableCollection<AppleModelView> AppleModelViews { get; }
}


Me gusta mucho la solución de 280Z28. Solo un comentario. ¿Es necesario hacer los bucles para cada NotifyCollectionChangedAction? Sé que los documentos de las acciones indican "uno o más elementos", pero como ObservableCollection no admite agregar o eliminar rangos, creo que esto nunca sucederá.


OK. Tengo un enamoramiento nerd con esta respuesta, así que tuve que compartir esta fábrica abstracta que agregué para apoyar mi inyección de ctor.

using System;
using System.Collections.ObjectModel;

namespace MVVM
{
    public class ObservableVMCollectionFactory<TModel, TViewModel>
        : IVMCollectionFactory<TModel, TViewModel>
        where TModel : class
        where TViewModel : class
    {
        private readonly IVMFactory<TModel, TViewModel> _factory;

        public ObservableVMCollectionFactory( IVMFactory<TModel, TViewModel> factory )
        {
            this._factory = factory.CheckForNull();
        }

        public ObservableCollection<TViewModel> CreateVMCollectionFrom( ObservableCollection<TModel> models )
        {
            Func<TModel, TViewModel> viewModelCreator = model => this._factory.CreateVMFrom(model);
            return new ObservableVMCollection<TViewModel, TModel>(models, viewModelCreator);
        }
    }
}

Que se basa en esto:

using System.Collections.ObjectModel;

namespace MVVM
{
    public interface IVMCollectionFactory<TModel, TViewModel>
        where TModel : class
        where TViewModel : class
    {
        ObservableCollection<TViewModel> CreateVMCollectionFrom( ObservableCollection<TModel> models );
    }
}

Y esto:

namespace MVVM
{
    public interface IVMFactory<TModel, TViewModel>
    {
        TViewModel CreateVMFrom( TModel model );
    }
}

Y aquí está el verificador nulo para completar:

namespace System
{
    public static class Exceptions
    {
        /// <summary>
        /// Checks for null.
        /// </summary>
        /// <param name="thing">The thing.</param>
        /// <param name="message">The message.</param>
        public static T CheckForNull<T>( this T thing, string message )
        {
            if ( thing == null ) throw new NullReferenceException(message);
            return thing;
        }

        /// <summary>
        /// Checks for null.
        /// </summary>
        /// <param name="thing">The thing.</param>
        public static T CheckForNull<T>( this T thing )
        {
            if ( thing == null ) throw new NullReferenceException();
            return thing;
        }
    }
}

Puede que no entienda exactamente sus requisitos, sin embargo, la forma en que he manejado una situación similar es usar el evento CollectionChanged en el ObservableCollection y simplemente crear / destruir los modelos de vista según sea necesario.

void OnApplesCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{    
  // Only add/remove items if already populated. 
  if (!IsPopulated)
    return;

  Apple apple;

  switch (e.Action)
  {
    case NotifyCollectionChangedAction.Add:
      apple = e.NewItems[0] as Apple;
      if (apple != null)
        AddViewModel(asset);
      break;
    case NotifyCollectionChangedAction.Remove:
      apple = e.OldItems[0] as Apple;
      if (apple != null)
        RemoveViewModel(apple);
      break;
  }

}

Puede haber algunos problemas de rendimiento cuando agrega / elimina muchos elementos en un ListView.

Hemos resuelto esto al: Extender el ObservableCollection para tener un AddRange, RemoveRange, métodos BinaryInsert y agregar eventos que notifiquen a otros que la colección está siendo modificada. Junto con un CollectionViewSource extendido que desconecta temporalmente la fuente cuando se cambia la colección, funciona muy bien.

HTH,

Dennis


Restablecer una colección a un valor predeterminado o para que coincida con un valor objetivo es algo que he golpeado con bastante frecuencia

Escribí una pequeña clase de ayuda de métodos misceláneos que incluye

public static class Misc
    {
        public static void SyncCollection<TCol,TEnum>(ICollection<TCol> collection,IEnumerable<TEnum> source, Func<TCol,TEnum,bool> comparer, Func<TEnum, TCol> converter )
        {
            var missing = collection.Where(c => !source.Any(s => comparer(c, s))).ToArray();
            var added = source.Where(s => !collection.Any(c => comparer(c, s))).ToArray();

            foreach (var item in missing)
            {
                collection.Remove(item);
            }
            foreach (var item in added)
            {
                collection.Add(converter(item));
            }
        }
        public static void SyncCollection<T>(ICollection<T> collection, IEnumerable<T> source, EqualityComparer<T> comparer)
        {
            var missing = collection.Where(c=>!source.Any(s=>comparer.Equals(c,s))).ToArray();
            var added = source.Where(s => !collection.Any(c => comparer.Equals(c, s))).ToArray();

            foreach (var item in missing)
            {
                collection.Remove(item);
            }
            foreach (var item in added)
            {
                collection.Add(item);
            }
        }
        public static void SyncCollection<T>(ICollection<T> collection, IEnumerable<T> source)
        {
            SyncCollection(collection,source, EqualityComparer<T>.Default);
        }
    }

que cubre la mayoría de mis necesidades, la primera sería probablemente la más aplicable ya que también convertirías tipos

nota: esto solo sincroniza los elementos en la colección, no los valores dentro de ellos


Utilizo colecciones construidas de forma automática y lazyily:

public class BasketModelView
{
    private readonly Lazy<ObservableCollection<AppleModelView>> _appleViews;

    public BasketModelView(BasketModel basket)
    {
        Func<AppleModel, AppleModelView> viewModelCreator = model => new AppleModelView(model);
        Func<ObservableCollection<AppleModelView>> collectionCreator =
            () => new ObservableViewModelCollection<AppleModelView, AppleModel>(basket.Apples, viewModelCreator);

        _appleViews = new Lazy<ObservableCollection<AppleModelView>>(collectionCreator);
    }

    public ObservableCollection<AppleModelView> Apples
    {
        get
        {
            return _appleViews.Value;
        }
    }
}

Usando la siguiente ObservableViewModelCollection<TViewModel, TModel> :

namespace Client.UI
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics.Contracts;
    using System.Linq;

    public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
    {
        private readonly ObservableCollection<TModel> _source;
        private readonly Func<TModel, TViewModel> _viewModelFactory;

        public ObservableViewModelCollection(ObservableCollection<TModel> source, Func<TModel, TViewModel> viewModelFactory)
            : base(source.Select(model => viewModelFactory(model)))
        {
            Contract.Requires(source != null);
            Contract.Requires(viewModelFactory != null);

            this._source = source;
            this._viewModelFactory = viewModelFactory;
            this._source.CollectionChanged += OnSourceCollectionChanged;
        }

        protected virtual TViewModel CreateViewModel(TModel model)
        {
            return _viewModelFactory(model);
        }

        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
            case NotifyCollectionChangedAction.Add:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                }
                break;

            case NotifyCollectionChangedAction.Move:
                if (e.OldItems.Count == 1)
                {
                    this.Move(e.OldStartingIndex, e.NewStartingIndex);
                }
                else
                {
                    List<TViewModel> items = this.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToList();
                    for (int i = 0; i < e.OldItems.Count; i++)
                        this.RemoveAt(e.OldStartingIndex);

                    for (int i = 0; i < items.Count; i++)
                        this.Insert(e.NewStartingIndex + i, items[i]);
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);
                break;

            case NotifyCollectionChangedAction.Replace:
                // remove
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);

                // add
                goto case NotifyCollectionChangedAction.Add;

            case NotifyCollectionChangedAction.Reset:
                Clear();
                for (int i = 0; i < e.NewItems.Count; i++)
                    this.Add(CreateViewModel((TModel)e.NewItems[i]));
                break;

            default:
                break;
            }
        }
    }
}




mvvm