#c_sharp #net #wpf #binding
Пишу проект на 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 cacheTaskEntityModelList;
#endregion
#region ctors
public TaskEntitiesListViewModel() : base()
{
this.cacheTaskEntityModelList = new List();
this.TaskEntities = new ObservableCollectionExtended();
}
#endregion
#region INPCProperties
public ObservableCollectionExtended TaskEntities { get; set; }
#endregion
#region eventSubscribers
[EventSubscription("topic://Authorization/CorrectCredentialsEntered", typeof(OnUserInterface))]
[EventSubscription("topic://Application/ViewUsual", typeof(OnUserInterface))]
public async void InitTaskEntitiesListUsualAsync()
{
if (TaskEntities.Count != 0) TaskEntities.Clear();
var taskEntitiesListFromDAL = await Task.Factory.StartNew(() => Store.GetTaskEntityList(t
=> t.Owner, t => t.Creator, t => t.Base).ToList());
//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 : ObservableCollection
{
private bool _suppressNotification = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable list)
{
if (list == null)
throw new ArgumentNullException("list");
_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
TaskEntities { get; set; }
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;
}
}
}
Ответы
Ответ 1
Если у вас первая привязка проходит с нормальной скоростью, а добавление элементов вызывает тормоза, то вы неправильно работаете с коллекцией. Большие изменения должны происходить пакетно, а единственное уведомление должно быть new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset). Как вариант, если не писать свою коллекцию, то можно менять экземпляр коллекции с соответствующим уведомлением. А вот реализация INotifyPropertyChanged на самой коллекции интересует ItemsControl куда меньше. По коду коллекции: у вас ошибка в коде AddRange. Внутри метода вы вызываете публичный метод Add, которому глубоко наплевать на ваш флаг _suppressNotification и который рассылает всю пачку уведомлений каждый раз. Вам надо вызывать защищённый метод InsertItem. Или можете опуститься на уровень ниже и работать напрямую со свойством Items, но тогда на вашей совести будут вызовы OnPropertyChanged для Count и Item[], а также вызов CheckReentrancy. Хм. Нет. Не должно быть наплевать. Впрочем, вы перекрывате только OnCollectionChanged. Возможно, OnPropertyChanged тоже влияет.
Комментариев нет:
Отправить комментарий