Пишу проект на WPF. На одном из View необходимо выводить клиенту довольно большой по объему список объектов в виде таблицы. Идеально для этого подошёл бы DataGrid. Я не очень-то люблю "изобретать велосипеды", поэтому нашёл множество уже готовых решений (те же контролы от Syncfusion). Я уж было очень сильно обрадовался, однако столкнулся с серьёзной проблемой: привязывая коллекцию большого объёма (более сотни объектов) к DataGrid, процесс рендеринга самого контрола сильно затягивается (и это при том, что, унаследовавшись ObservableCollection, я добавил метод AddRange(), чтобы при добавлении каждого объекта не вызывалось событие интерфейса INotifyPropertyChanged). Если добавлять 200-300 объектов в коллекцию за раз и привязать их к DataGrid, процесс затянется уж очень надолго и приложение может вовсе зависнуть. Получение данных из базы и формирование модельных объектов происходят во вторичном потоке. Думал, что проблема в количестве объектов при их создании, однако, после использования профайлера, я понял, что все тормоза начинаются именно после вызова AddRange() на привязанной коллекции. Возможно, проблема в том, что каждый модельный объект реализует INPC? И содержит в себе ряд таких же вложенных объектов, также реализующих этот интерфейс?
Как бы там ни было, я попробовал заменить DataGrid на GridView, и, в принципе, всё стало работать довольно быстро. Однако GridView не предоставляет множества преимуществ, которые имеются у DataGrid (начиная от элементарной сортировки и заканчивая готовым красивым оформлением). Теперь меня интересует, как придать более или менее приличный вид GridView. Например, как придать ему стиль "Metro"?
Или же есть какая-то возможность, чтобы оптимизировать работу DataGrid? Ну не верится мне, что на рендеринг сотни модельных объектов уходит так много времени и это так затормаживает UI.
UPDATE:
ViewModel:
public class TaskEntitiesListViewModel : StoreUsingViewModel, IPageViewModel
{
#region fields
private TaskEntityModel currentTask;
private List
var taskEntitiesListFromDAL = await Task.Factory.StartNew(() => Store.GetTaskEntityList
//wrapping each TaskEntity to TaskEntityModel
if (cacheTaskEntityModelList.Count != 0) cacheTaskEntityModelList.Clear();
taskEntitiesListFromDAL.ForEach(t => cacheTaskEntityModelList.Add(new TaskEntityModel(t)));
TaskEntities.AddRange(cacheTaskEntityModelList.Where(t => t.IsOver == false).Take(100).ToList());
}
#endregion
#region IPageViewModel
public string Name { get { return "TaskEntitiesList"; } }
#endregion
#region BaseViewModel
public override void OnDataChangedGlobalEvent()
{
InitTaskEntitiesListUsualAsync();
}
#endregion
}
Код коллекции:
public class ObservableCollectionExtended
public void AddRange(IEnumerable
_suppressNotification = true;
foreach (T item in list)
Add(item);
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ClearRange()
{
_suppressNotification = true;
ClearItems();
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
Во вьюшке в качестве ItemsSource для DataGrid используется public ObservableCollectionExtended
UPDATE 2
код View:
UPDATE 3
Model:
public class TaskEntityModel : BaseModel
{
#region fields
private string prop1;
private string prop2;
private string prop3;
private string prop4;
private string prop5;
private DateTime dateProp1;
private DateTime dateProp2;
private DateTime dateProp3;
private DateTime dateProp4;
#endregion
#region ctors
public TaskEntityModel(TaskEntity taskEntity) : base(taskEntity)
{
//filling fields
}
#endregion
#region INPCProperties
public string Prop1
{
get { return prop1; }
set
{
if (value != prop1)
{
prop1 = value;
OnPropertyChanged("Prop1");
}
}
}
public DateTime DateProp1
{
get
{
if (dateProp1 == null)
dateProp1 = DateTime.Now;
return dateProp1;
}
set
{
if (value != dateProp1)
{
dateProp1 = value;
OnPropertyChanged("DateProp1");
OnPropertyChanged("DateProp2");
}
}
}
//and so on
#endregion
}
public abstract class BaseModel : ObservableObject
{
#region fields
protected int ID;
#endregion
public int UID
{
get { return ID; }
set
{
if(value != ID)
{
ID = value;
OnPropertyChanged("UID");
}
}
}
public BaseModel() { }
public BaseModel(BaseEntity baseEntity)
{
if(baseEntity != null)
{
this.ID = baseEntity.ID;
}
}
}
Ответ
Если у вас первая привязка проходит с нормальной скоростью, а добавление элементов вызывает тормоза, то вы неправильно работаете с коллекцией. Большие изменения должны происходить пакетно, а единственное уведомление должно быть new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset). Как вариант, если не писать свою коллекцию, то можно менять экземпляр коллекции с соответствующим уведомлением. А вот реализация INotifyPropertyChanged на самой коллекции интересует ItemsControl куда меньше.
По коду коллекции: у вас ошибка в коде AddRange. Внутри метода вы вызываете публичный метод Add, которому глубоко наплевать на ваш флаг _suppressNotification и который рассылает всю пачку уведомлений каждый раз. Вам надо вызывать защищённый метод InsertItem. Или можете опуститься на уровень ниже и работать напрямую со свойством Items, но тогда на вашей совести будут вызовы OnPropertyChanged для Count и Item[], а также вызов CheckReentrancy
Хм. Нет. Не должно быть наплевать. Впрочем, вы перекрывате только OnCollectionChanged. Возможно, OnPropertyChanged тоже влияет.
Комментариев нет:
Отправить комментарий