Модераторы: gambit, Kefir, Partizan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> ObservableCollection. Задачка, Поиск оптимального решения 
:(
    Опции темы
apor
Дата 20.9.2013, 10:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 13.5.2009

Репутация: нет
Всего: нет



Прошу помочь в решении проблемы. Суть в следующем: имеется ObservableCollection типа PriceData (поле "Код товара", поле "Цена товара"). Коллекция заполняется (или обновляется) примерно раз в секунду (в последующем может и раз в 100-200 миллисекунд). Коды товаров известны заранее - их порядка 100 тысяч и они уже содержатся в коллекции, а цена изменяется у каждого товара, который содержится в очередной порции новых данных (их приходит примерно 300 - 600 за одно обновление). В GridView при каждом приходе новых данных следует отображать две колонки: "Код товара", "Цена товара". Связываем коллекцию с помощью ItemsSourse. А проблема в следующем: в GridView следует отображать только новые пришедшие данные цены, т.е. коды статичны, а цена изменяется. Но в GridView при новой порции данных старые данные остаются, а они нам не нужны. Нужно отображать только свежие данные.

Код

 <ListView x:Name="lstPrice" Margin="18,12,12,6" DataContext="{Binding}" ItemsSource="{Binding}" Grid.Column="2">
            <ListView.View>
                <GridView x:Name="gvPrice">
                    <GridViewColumn Header="Code" Width="45" DisplayMemberBinding="{Binding Path=Code}"/>
                    <GridViewColumn Header="Price" Width="55" DisplayMemberBinding="{Binding Path=Price}"/>
                </GridView>
            </ListView.View>
        </ListView>


Код на VB, но примеры можно и в С#
Код

Public Class PriceData
    Private _code As Double
    Private _price As Double

    Public Sub New(ByVal code As Double, ByVal price As Double)
        _code = code
        _price = price
    End Sub

    Public Property Code() As Double
        Get
            Return _code
        End Get
        Set(ByVal value As Double)
            _code = value
        End Set
    End Property

    Public Property Price() As Double
        Get
            Return _price
        End Get
        Set(ByVal value As Double)
            _price = value
        End Set
    End Property
End Class

Код

Dim obsCol As New ObservableCollection(Of PriceData)

Public Sub New()
    InitializeComponent()
    lstPrice.ItemsSource = obsCol 'Связываем данные

    For i = 100000 To 1 Step -1 'Предварительное заполнение коллекции кодами 
        obsCol.Add(New PriceData(i, Nothing))
    Next
End Sub

'...далее в другом потоке изменяем цены новыми пришедшими
Me.Dispatcher.Invoke(DispatcherPriority.Background, New Action(
  Function()
  For i = 0 To 300
      obsCol.Item(code) = New PriceData(code, price)
  Next
  Return 0
  End Function))

Повторю, что загвоздка в отображении "старых" данных - они остаются в коллекции. Отображать в GridView нужно только новые пришедшие данные. Вся проблема упирается в отзывчивость UI и производительность. Можно было удалять недействительные данные методом перебора, но, если данных много, то это не решение проблемы. Возможно, я выбрал неправильный подход, но надеюсь на предложения.
Я еще делал так:
Код

Dim listData As New List(Of PriceData)

Public Sub New()
    InitializeComponent()

    For i = 100000 To 1 Step -1 'Предварительное заполнение списка кодами 
        listData .Add(New PriceData(i, Nothing))
    Next
End Sub

'...далее в другом потоке...
Dim obsCol As New ObservableCollection(Of PriceData)(listData) 'Чтобы не заполнять каждый раз коллекцию
 For i = 0 To 300
      obsCol.Item(code) = New PriceData(code, price)
  Next

Me.Dispatcher.Invoke(DispatcherPriority.Background, New Action(
  Function()
      lstPrice.ItemsSource = obsCol 'Связываем данные
  Return 0
  End Function))

В данном случае проблема со старыми данными отпадает, но UI замерзает при каждом обновлении где-то на 0,5 сек.

Это сообщение отредактировал(а) apor - 20.9.2013, 10:40
PM MAIL   Вверх
dzaraev
Дата 20.9.2013, 12:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 149
Регистрация: 12.1.2013

Репутация: нет
Всего: 1



Честно говоря не совсем понятна проблема. Когда вы вызываете индексатор ObservableCollection.Item[], то коллекция генерирует событие CollectionChanged, с аргументом, у которого свойство Action установлено в значение NotifyCollectionChangedAction.Replace. 
ListView на это обязательно реагирует и обновляет соответствующий визуальный элемент. Вот такой код у меня работает как и должен:
Код

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ListView 
            x:Name="lstPrice" 
            Margin="18,12,12,6" 
            DataContext="{Binding}" 
            ItemsSource="{Binding}" 
            >
            <ListView.View>
                <GridView x:Name="gvPrice">
                    <GridViewColumn Header="Code" Width="45" DisplayMemberBinding="{Binding Path=Code}"/>
                    <GridViewColumn Header="Price" Width="55" DisplayMemberBinding="{Binding Path=Price}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button 
            Grid.Row="1" 
            Padding="5,5" 
            Margin="2"
            Click="Button_Click"
            >
            Fill
        </Button>
    </Grid>

Код

public partial class MainWindow : Window
    {
        private readonly ObservableCollection<PriceData> _priceDataCollection;

        public MainWindow()
        {
            InitializeComponent();


            _priceDataCollection = new ObservableCollection<PriceData>();
            lstPrice.ItemsSource = _priceDataCollection; 
            for(int i = 100000; i > 1; i--)
            {
                _priceDataCollection.Add(new PriceData(i, default(double)));
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Dispatcher.BeginInvoke(new Action(UpdatePrices));
        }

        private void UpdatePrices()
        {
            var newPrices = new double[] {10, 12, 32, 423, 44, 5, 77, 100};
            for (int i = 0; i < newPrices.Length; i++)
            {
                var price = newPrices[i];
                _priceDataCollection[i] = new PriceData(100000 - i, price);
            }
        }
    }

Код

public class PriceData
    {
        public PriceData(double code, double price)
        {
            Code = code;
            Price = price;
        }

        public double Code { get; set; }
        public double Price { get; set; }
    }


Далее, на счет подхода. Правильным является то, что вы обновляете отдельные изменившиеся элементы, а не всю коллекцию (как вы пытались делать в конце). Однако то, что вы создаёте новый объект PriceData - не оптимальное решение, т.к. ежесекундно вы создаёте огромное количество объектов, и столько же объектов становятся ненужными, следовательно помимо операции создания объектов, нагружается еще и сборщик мусора.
Могу посоветовать реализовать в классе PriceData интерфейс INotifyPropertyChanged, и при обновлении цен менять состояние соответствующего объекта PriceData, а он в свою очередь будет уведомлять UI, что нужно обновиться (через событие PropertyChanged).
Подробнее об этом вы можете прочитать, если погуглите паттерн MVVM для WPF. Естественно, изменять PriceData при таком варианте можно только из UI потока, т.к. иначе генерация события PropertyChanged приведёт к исключению - WPF будет ругаться, что вы полезли к UI из чужого потока.
Вот такие изменения в коде нужно сделать:
Код

public class PriceData : INotifyPropertyChanged
    {
        private double _code;
        private double _price;

        public PriceData(double code, double price)
        {
            Code = code;
            Price = price;
        }

        public double Code
        {
            get { return _code; }
            set
            {
                _code = value;
                OnPropertyChanged("Code");
            }
        }

        public double Price
        {
            get { return _price; }
            set
            {
                _price = value;
                OnPropertyChanged("Price");
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Код

private void UpdatePrices()
        {
            var newPrices = new double[] {10, 12, 32, 423, 44, 5, 77, 100};
            for (int i = 0; i < newPrices.Length; i++)
            {
                _priceDataCollection[i].Price = newPrices[i];
            }
        }


Это сообщение отредактировал(а) dzaraev - 20.9.2013, 12:28
--------------------
Если вопрос решён - помечайте тему.  
PM MAIL   Вверх
apor
Дата 20.9.2013, 14:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 13.5.2009

Репутация: нет
Всего: нет



Благодарю за ответ. Чтобы была понятна суть проблемы, я немного изменил код выше:
Код

private void UpdatePrices()
        {
            var newPrices = new int[2,4] { { 10, 12, 20, 35 }, { 99, 18, 45, 86 } };
            //var newPrices = new int[2,2] { { 7, 14 }, { 125, 3 } };
            for (int i = 0; i < newPrices.GetLength(1); i++)
            {
                _priceDataCollection[newPrices[0, i] - 1].Price = newPrices[1, i];
            }
        }

Первый newPrices эмулирует приход данных, второй - приход второй партии данных и т.д. Для начала жмем кнопку, затем откомментируем вторую строку newPrices и снова жмем кнопку. В итоге: напротив кодов появятся цены первого обновления данных и, якобы, второго. Мы наблюдаем как старые цены, так и новые. В этом и заключается проблема: нужно наблюдать только данные последнего обновления (125, 3). Так как цены быстро меняются, следует визуально наблюдать только свежие цены.
PM MAIL   Вверх
dzaraev
Дата 23.9.2013, 06:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 149
Регистрация: 12.1.2013

Репутация: нет
Всего: 1



Цитата(apor @  20.9.2013,  14:41 Найти цитируемый пост)
Чтобы была понятна суть проблемы,

Эмм, честно говоря понятней не стало  smile Вы написали код, который меняет отдельные цены в _priceDataCollection, и эти изменения никак не пересекаются. У вас при первом newPrices и i==0 - индекс_коллекции равен 6, т.е. для товара с кодом 99991 вы установили цену 99. При втором newPrices и i==0 вы устанавливаете уже другому товару (99994) другую цену (125). Так что же при этом должно исчезать? Все цены, установленные предыдущим newPrices? Но вы ведь не обновляете старые элементы, с новым newPrices вы обновляете новые элементы, старые естественно остаются как и были, их никто нигде не менял. smile 
--------------------
Если вопрос решён - помечайте тему.  
PM MAIL   Вверх
apor
Дата 23.9.2013, 09:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 13.5.2009

Репутация: нет
Всего: нет



Цитата

 Так что же при этом должно исчезать? Все цены, установленные предыдущим newPrices? Но вы ведь не обновляете старые элементы, с новым newPrices вы обновляете новые элементы, старые естественно остаются как и были, их никто нигде не менял.


Возможно, я не совсем правильно изложил суть вопроса, но именно такое поведение и  должно выполняться.
Вся проблема заключается в большом количестве элементов. Если все обновлять перед приходом следующей партии данных, то временная задержка будет существенной при высокочастотном обновлении данных.
PM MAIL   Вверх
dzaraev
Дата 23.9.2013, 10:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 149
Регистрация: 12.1.2013

Репутация: нет
Всего: 1



Подождите, у меня вопрос - а если цена товара с кодом 99999 например не изменилась, т.е. в новом newPrices её нет - разве её надо удалять? т.е. старая цена вообще стирается что ли?  smile 
Как бы то ни было, поскольку
Цитата

Вся проблема заключается в большом количестве элементов.

то тут могу посоветовать использовать виртуализацию данных или UI.
Виртуализация UI позволит вам отображать и обновлять только те элементы, которые видит пользователь: http://www.zagstudio.com/blog/497#.Uj_py8BnrsN
Виртуализация данных позволит вообще не держать в памяти контрола данные, которые ему не надо показывать (потому что их не видит пользователь): http://www.zagstudio.com/blog/498#.Uj_pzsBnrsN

Плюс ко всему, если лень возиться с виртуализацией, попробуйте применить вариант с INotifyPropertyChanged. Поскольку WPF поддерживает асинхронное обновление отдельных свойств, вы можете даже производить обновление из другого потока. Т.е. попробуйте код:
Код

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //Dispatcher.BeginInvoke(new Action(UpdatePrices)); //не обязательно

            Task task = new Task(UpdatePrices);
            task.Start();
        }

        private void UpdatePrices()
        {
            //Мы здесь в другом потоке, но UI не падает,
            //т.к. WPF умеет обновлять отдельные свойства асинхронно
            var newPrices = new double[] {10, 12, 32, 423, 44, 5, 77, 100};
            for (int i = 0; i < newPrices.Length; i++)
            {
                _priceDataCollection[i].Price = newPrices[i];
            }
        }

Но не забываёте, что WPF умеет асинхронно менять только отдельные свойства, коллекции он уже обновить не сможет. Т.е. нельзя асинронно менять ObservableCollection, на который подписан ваш контрол.

Это сообщение отредактировал(а) dzaraev - 23.9.2013, 10:26
--------------------
Если вопрос решён - помечайте тему.  
PM MAIL   Вверх
apor
Дата 23.9.2013, 11:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 13.5.2009

Репутация: нет
Всего: нет



Цитата

т.е. в новом newPrices её нет - разве её надо удалять? т.е. старая цена вообще стирается что ли? 

Задача довольно специфическая, т.к. при приходе данных производятся нужные манипуляции с ними и при новом обновлении эти данные уже устаревают, а т.к. они уже не актуальны - они больше не нужны.

В любом случае, спасибо за помощь, есть над чем подумать.
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | WPF и Silverlight | Следующая тема »


 




[ Время генерации скрипта: 0.0914 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.