Страницы

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

пятница, 10 января 2020 г.

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

#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 тоже влияет.

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

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