none
Ребята, киньте кто-нибудь, пожалуйста, ссылку на пример, где выполняется привязка WPF Datagrid к LINQ to List<t> RRS feed

  • Вопрос

  • Ребята, киньте кто-нибудь, пожалуйста, ссылку на пример, где выполняется привязка WPF Datagrid к LINQ to List<t>, содержащий небольшое количество записей (порядка 100), причём содержимое List<T> должно изменяться с большой частотой и LINQ выполняется тоже с высокой частотой (с интервалом примерно от 60 до 100 миллисекунд между выполнением запросов). Структура каждой записи примерно такая:

    public class OrdersLogRecord
    {
            #region Fields
    
            /// <summary>
            /// Номер заявки
            /// </summary>
            private Int64 id_ord;
            /// <summary>
            /// Идентификатор торговой сессии
            /// </summary>
            private Int32 sess_id;
            /// <summary>
            /// Код клиента
            /// </summary>
            private String client_code;
            /// <summary>
            /// Время изменения состояния заявки
            /// </summary>
            private Int32 status;
            /// <summary>
            /// Действие с заявкой
            /// </summary>
            private SByte action;
            /// <summary>
            /// Уникальный числовой идентификатор инструмента
            /// </summary>
            private Int32 isin_id;
            /// <summary>
            /// Направление сделки - покупка или продажа.
            /// </summary>
            private SByte dir;
            /// <summary>
            /// Цена
            /// </summary>
            private Double price;
    
            #endregion
    }



    • Изменено TownSparrow 24 сентября 2012 г. 9:22
    24 сентября 2012 г. 9:20

Ответы

Все ответы

  • Добрый день.

    Покажите фрагмент где вы устанавливаете источник данных, в чем бы у вас не была проблема, скорее всего она там.

    Пара рекомендаций, для биндинга лучше всего использовать объекты потомки DependencyObject. Также, для хранения коллекций применяемых при биндинге, лучше использовать ObservableCOllection<>.

    Пример, если нужен, могу выложить вечером. Или уточните в чем у вас проблема, постараюсь помочь.

    24 сентября 2012 г. 10:08
    Отвечающий
  • Минуту, Алексей, сейчас....

    24 сентября 2012 г. 10:29
  • Я хочу отобразить в главном окне приложения так называемый агрегированный стакан. Вот его класс:

    /// <summary>
    /// Класс OpenBook представляет агрегированный стакан,
    /// по одному торгуемому инструменту.
    /// </summary>
    public class OpenBook : IComparable<OpenBook>
    {
            #region Fields
    
            /// <summary>
            /// Список записей в стакане по торгуемому инструменту
            /// (покупка и продажа).
            /// </summary>
            private List<OpenBookRecord> openBookRecords;
            /// <summary>
            /// Код торгуемого инструмента.
            /// </summary>
            private Int64 isin_Id;
            /// <summary>
            /// Флаг, показывающий, что в стакане произошли изменения true.
            /// </summary>
            private Boolean f_Changed;
    
            #endregion
    
            #region Properties
    
            /// <summary>
            /// Возвращает или задаёт список записей в стакане по торгуемому инструменту.
            /// (покупка и продажа).
            /// </summary>
            public /*Observable*/List<OpenBookRecord> OpenBookRecords
            {
                get { return openBookRecords; }
                set { openBookRecords = value; }
            }
            /// <summary>
            /// Возвращает или задаёт код торгуемого инструмента.
            /// </summary>
            public Int64 InstrumentCode
            {
                get { return isin_Id; }
                set { isin_Id = value; }
            }
            /// <summary>
            /// Возвращает или задаёт флаг, показывающий,
            /// что в стакане произошли изменения true.
            /// </summary>
            public Boolean IsChanged
            {
                get { return f_Changed; }
                set { f_Changed = value; }
            }
    
            #endregion
    
            #region Methods
    
            /// <summary>
            /// Создаёт экземпляр класса OpenBook.
            /// </summary>
            public OpenBook()
            {
                // Создаём список, который будет хранить записи
                // в стакане по инструменту.
                openBookRecords = new List<OpenBookRecord>();
                // Задаём условия сортировки записей в стакане
                // по направлению котировки и убыванию цены
                DomComparer sorterByDirectionAndPrice = new DomComparer();
                // и сортируем записи в стакане согласно этим условиям.
                openBookRecords.Sort(sorterByDirectionAndPrice);
            }
    
            /// <summary>
            /// Добавляет запись в стакан по инструменту.
            /// </summary>
            /// <param name="addedRecord"></param>
            public void AddRecordToOpenBook(OpenBookRecord addedRecord)
            {
                // Индекс записи, по которому будет производится
                // её вставка в список.
                Int32 idx;
    
                if (addedRecord != null)
                {
                    // Если новое значение цены не нулевое
                    if (addedRecord.Price > 0)
                    {
                        // Задаём условия для проверки - есть ли эта запись в стакане
                        DomComparer sorterByDirectionAndPrice = new DomComparer();
                        // Выполняем эту проверку
                        idx = openBookRecords.BinarySearch(addedRecord, sorterByDirectionAndPrice);
                        // В обоих случаях выполняем вставку записи
                        if (idx >= 0)
                        {
                            openBookRecords.Insert(idx, addedRecord);
                        }
                        else
                        {
                            openBookRecords.Insert(~idx, addedRecord);
                        }
                    }
                }
                // Показываем, что состояние стакана по инструменту изменилось.
                f_Changed = true;
            }
    
            /// <summary>
            /// Удаляет запись из стакана по инструменту.
            /// </summary>
            /// <param name="removedRecord"></param>
            public void RemoveRecordFromOpenBook(OpenBookRecord removedRecord)
            {
                // Удалить запись из списка, представляющего стакан по инструменту.
                openBookRecords.Remove(removedRecord);
                // Показать, что стакан по инструменту был изменен
                // (всвязи с удалением записи).
                f_Changed = true;
            }
    
            /// <summary>
            /// Предикат, указывающий порядок сортировки по умолчанию
            /// стаканов по инструменту в списке стаканов. Согласно
            /// которому стаканы по инструментам сортируются по возрастанию
            /// значения кода инструмента.
            /// </summary>
            /// <param name="tmpOpenBook"></param>
            /// <returns></returns>
            public int CompareTo(OpenBook tmpOpenBook)
            {
                return isin_Id.CompareTo(tmpOpenBook.isin_Id);
            }
    
            #endregion
    }

    Основным элементом агрегированного стакана является список List<OpenBookRecord>, записи в который поступают с большой частотой. Ниже привожу класс OpenBookRecord:

    /// <summary>
    ///  Класс OpenBookRecord представляет запись
    ///  из таблицы агрегированного стакана.
    /// </summary>
    public class OpenBookRecord : IComparable<OpenBookRecord>, INotifyPropertyChanged
        {
            #region Events
    
            /// <summary>
            /// Событие "Изменилось значение свойства"
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            #endregion
    
            #region Fields
    
            /// <summary>
            /// Значение ReplID записи.
            /// </summary>
            private Int64 replId;
            /// <summary>
            /// Значение ReplRev записи.
            /// </summary>
            private Int64 replRev;
            /// <summary>
            /// Цена котировки.
            /// </summary>
            private Double price;
            /// <summary>
            /// Объем агрегированной котировки.
            /// </summary>
            private Int64 volume;
            /// <summary>
            /// Направление котировки (покупка/продажа).
            /// </summary>
            private SByte dir;
            /// <summary>
            /// Ссылка на стакан, содержащий эту запись
            /// и поле кода инструмента.
            /// </summary>
            private OpenBook residence;
    
            #endregion
            //*
            #region PropertyChanged Event Raiser
    
            /// <summary>
            ///  Вызывает событие "Изменилось значение свойства".
            /// </summary>
            /// <param name="propertyName"></param>
            private void NotifyPropertyChanged(String propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            #endregion
            //*/
            #region Properties
    
            /// <summary>
            /// Возвращает или задаёт значение ReplID записи.
            /// </summary>
            public Int64 ReplId
            {
                get { return replId; }
                set
                {
                    if (replId != value)
                    {
                        replId = value;
                        NotifyPropertyChanged("ReplId");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт значение ReplRev записи.
            /// </summary>
            public Int64 ReplRev
            {
                get { return replRev; }
                set
                {
                    if (replRev != value)
                    {
                        replRev = value;
                        NotifyPropertyChanged("ReplRev");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт цену котировки.
            /// </summary>
            public Double Price
            {
                get { return price; }
                set
                {
                    if (price != value)
                    {
                        price = value;
                        NotifyPropertyChanged("Price");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт объем агрегированной котировки.
            /// </summary>
            public Int64 Volume
            {
                get { return volume; }
                set
                {
                    if (volume != value)
                    {
                        volume = value;
                        NotifyPropertyChanged("Volume");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт 
            /// </summary>
            public SByte Dir
            {
                get { return dir; }
                set
                {
                    if (dir != value)
                    {
                        dir = value;
                        NotifyPropertyChanged("Dir");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт ссылку на стакан, содержащий эту запись
            /// и поле кода инструмента.
            /// </summary>
            public OpenBook Residence
            {
                get { return residence; }
                set
                {
                    if (residence != value)
                    {
                        residence = value;
                        NotifyPropertyChanged("Residence");
                    }
                }
            }
    
            #endregion
    
            #region Methods
    
            /// <summary>
            /// Предикат, указывающий порядок сортировки по умолчанию записей в стакане.
            /// Согласно этому предикту - записи сортируются по возрастанию значения
            /// в поле ReplID
            /// </summary>
            /// <param name="cmpRecord"></param>
            /// <returns></returns>
            public int CompareTo(OpenBookRecord cmpRecord)
            {
                return replId.CompareTo(cmpRecord.replId);
            }
    
            #endregion
    }

    В настоящий момент, я пытаюсь использовать для биндинга ObservableCollection. Я определил производный от неё класс:

    /// <summary>
    /// Класс ObservableOpenBook представляет содержимое агрегированного стакана,
    /// предназначенное для визуализации в окне приложения.
    /// </summary>
    public class ObservableOpenBook : ObservableCollection<OpenBookRecord>
    {
        /// <summary>
        /// Создаёт экземпляр класса ObservableOpenBook.
        /// </summary>
        public ObservableOpenBook()
             : base()
        {
        }
    }

    Затем определяю её в классе главного окна:

    /// <summary>
    /// Представление стакана по выбранному для торгов инструменту,
    /// содержимое которого предназначено для визуализации в окне приложения.
    /// </summary>
    private static ObservableOpenBook displayedOpenBook = new ObservableOpenBook();

    Затем, в классе главного окна определил свойство возвращающее ObservableOpenBook:

    /// <summary>
    /// Возвращает стакан по выбранному торгуемому инструменту
    /// для отображения его содержимого в окне приложения.
    /// </summary>
    public static ObservableOpenBook GetDisplayedOpenBook
    {
       get { return displayedOpenBook; }
    }

    Затем, в классе ReplicationDataProvider возвращаю ObservableOpenBook для разметки XAML.

    /// <summary>
    /// Класс ReplicationDataProvider предоставляет данные из потоков репликации
    /// для их визуального отображения в окнах приложения.
    /// </summary>
    public class ReplicationDataProvider
    {
       /// <summary>
       /// Предоставлякт данные из агрегированного стакана по инструменту.
       /// </summary>
       /// <returns></returns>
       public ObservableOpenBook GetDisplayedOpenBook()
       {
                return MainWindow.GetDisplayedOpenBook;
       }
    }

    В XAML главного окна пишу:

    <Window.Resources>
            <ObjectDataProvider x:Key="ReplicationDataProvider" ObjectType="{x:Type local:ReplicationDataProvider}"/>
            <ObjectDataProvider x:Key="OpenbookRecords" ObjectInstance="{StaticResource ResourceKey=ReplicationDataProvider}" MethodName="GetDisplayedOpenBook"/>
        </Window.Resources>

    а ниже там, где в XAML определяется DataGrid пишу:

    <!--Табличная сетка для представления данных из агрегированного стакана-->
    <DataGrid Name="dgOrdersAggr" Grid.Row="0" Grid.Column="0" Margin="10" AutoGenerateColumns="False"
                              IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False"
                              CanUserResizeRows="False" CanUserReorderColumns="False"
                          ItemsSource="{Binding Source={StaticResource ResourceKey=OpenbookRecords}}">
       <DataGrid.Columns>
           <DataGridTextColumn Header="Цена" Binding="{Binding Path=Price}"/>
           <DataGridTextColumn Header="Объём" Binding="{Binding Path=Volume}"/>
       </DataGrid.Columns>
    </DataGrid>

    Теперь далее. Для взаимодействия с сервером репликации биржи РТС, я использую их библиотеку P2ClientGate. В обработчике eё события StreamDataEnd, возникающем по окончанию очередной транзакции пишу периодическое заполнение ObservableCollection, являющейся источником данных для визуализации стакана на экране (обработчик работает в отдеоьном треде, а не в UI):

    // Найти стакан, соответствующий выбранному торгуемому инструменту
    OpenBook tmpOb = totalOpenBook.GetOpenBookByInstrument(this.futTradedInstrumentID);
    // Если стакан найден,
    if (tmpOb != null)
    {
        // Если его содержимое изменилось во время последней транзакции,
        if (tmpOb.IsChanged)
        {
            // Если стакан содержит записи:
            if (tmpOb.OpenBookRecords.Count > 0)
            {
                // Вывести на экран содержимое стакана по выбранному инструменту.
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
                {
                    // Очистить предыдущее содержимое стакана.
                    displayedOpenBook.Clear();
                    // Заполнить стакан в окне приложения.
                    for (int i = 0; i < tmpOb.OpenBookRecords.Count; i++)
                        displayedOpenBook.Add(tmpOb.OpenBookRecords[i]);
                 });
                 // После очередного наполнения экранного буфера стакана записями,
                 // сбрасываем флаг выполнения изменений в стакане.
                 // Стакан после этого считается консистентным, пока не будет
                 // изменён во время следующей транзакции.
                 tmpOb.IsChanged = false;
            }
            else
            {
                // Очистить предыдущее содержимое стакана.
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
                {
                     displayedOpenBook.Clear();
                });
                tmpOb.IsChanged = false;
            }
        }
    }

    Здесь я привел только то, что относится к визуализации стакана по инструменту. Кроме этого, в этом же обработчике, вызывается функция отрисовки свечного графика по порции полученной во время транзакции информации и тоже через this.Dispatcher.BeginInvoke, но в других строках. Но на отрисовку графика я не жалуюсь. А вот теперь о моём несчастье. Оно заключается в том, что если я включаю отрисовку стакана, то она тормозит (причём ужасно) работу потока UI, т.е пользовательского интерфейс. График продолжает отрисовываться нормально, но скроллинг датагрида стакана, скроллинг области отрисовки графика и переход на другие TabItem в TabControl'е там же в главном окне, чтобы посмотреть датагриды с заявками и сделками, становится попросту невозможен. Кнопки на панели инструментов - тоже зависают. Я уже бьюсь сегодня с этим целый день с утра.







    • Изменено TownSparrow 24 сентября 2012 г. 13:52
    24 сентября 2012 г. 11:29
  • Привет.

    Данный вопрос еще является актуальным? Спрашиваю потому-что сейчас идет обсуждение другого вашего схожего вопроса - Периодические очистка и заполнение ObservableCollection очень ощутимо подвешивает UI  и там вы уже используете источник данных для DataGrid.


    Для связи [mail]

    • Помечено в качестве ответа TownSparrow 27 сентября 2012 г. 9:03
    26 сентября 2012 г. 13:19
  • Нет, уже не актуально. Прошу извинения за задержку. Можно снять вопрос с форума. Ещё раз извиняюсь.


    • Изменено TownSparrow 27 сентября 2012 г. 9:02
    27 сентября 2012 г. 9:02