Страницы

Поиск по вопросам

понедельник, 25 февраля 2019 г.

Почему DataGrid такой медленный, или как придать приличный вид GridView?

Пишу проект на 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; } } }


Ответ

Если у вас первая привязка проходит с нормальной скоростью, а добавление элементов вызывает тормоза, то вы неправильно работаете с коллекцией. Большие изменения должны происходить пакетно, а единственное уведомление должно быть new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset). Как вариант, если не писать свою коллекцию, то можно менять экземпляр коллекции с соответствующим уведомлением. А вот реализация INotifyPropertyChanged на самой коллекции интересует ItemsControl куда меньше.
По коду коллекции: у вас ошибка в коде AddRange. Внутри метода вы вызываете публичный метод Add, которому глубоко наплевать на ваш флаг _suppressNotification и который рассылает всю пачку уведомлений каждый раз. Вам надо вызывать защищённый метод InsertItem. Или можете опуститься на уровень ниже и работать напрямую со свойством Items, но тогда на вашей совести будут вызовы OnPropertyChanged для Count и Item[], а также вызов CheckReentrancy
Хм. Нет. Не должно быть наплевать. Впрочем, вы перекрывате только OnCollectionChanged. Возможно, OnPropertyChanged тоже влияет.

Комментариев нет:

Отправить комментарий