WPF DataGrid DataContext чрезвычайно медленно - PullRequest
5 голосов
/ 16 августа 2011

У меня есть простое приложение WPF, над которым я работаю, которое выполняет запрос SQL и отображает полученные данные в DataGrid.

Все работает как положено, за исключением того, что производительность ужасна. Время от нажатия кнопки для загрузки данных до фактического отображения данных в DataGrid составляет порядка 3-4 секунд. Это намного быстрее при включенной виртуализации строк, но мне пришлось ее отключить, поскольку мне нужно иметь возможность выполнять операции с ячейками, которые больше не видны после прокрутки. И даже при включенной виртуализации отображение данных происходит медленнее, чем хотелось бы.

Сначала я предположил, что это медленная база данных SQL, но я провел несколько тестов и обнаружил, что считываю все данные с сервера SQL (несколько сотен строк) в таблицу DataTable за доли секунды. Пока я не привяжу DataTable к DataContext DataGrid, все блокируется на несколько секунд.

Так почему DataContext такой медленный? Мой компьютер совершенно новый, поэтому мне сложно понять, почему для заполнения DataGrid требуется какое-то время, учитывая, как быстро я получаю данные.

(я также пытался привязать к ItemSrid DataGrid, а не к DataContext, но производительность была такой же.)

Есть ли альтернативный способ загрузки данных в DataGrid, который имеет более разумную производительность? Даже альтернативу DataGrid стоит изучить в случае необходимости.


Редактировать: По предложению Влада я попробовал другой тест, который обошел SQL-запрос. Вместо этого я заполнил DataTable 1000 строками случайно сгенерированных данных. Без изменений. Данные были сгенерированы и записаны в DataTable менее чем за секунду. Однако для присоединения этого к DataGrid потребовалось более 20 секунд.

Ниже приведен DataGrid XAML, который я использую. Кроме привязки, это очень просто, никакой специальный код не прикреплен к нему.

<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="dataGridWellReadings" GridLinesVisibility="None" CanUserResizeRows="False" SelectionUnit="Cell" AlternatingRowBackground="#FFE0E0E0" RowBackground="#FFF0F0F0" HorizontalScrollBarVisibility="Disabled" SelectedCellsChanged="dataGridWellReadings_SelectedCellsChanged" EnableRowVirtualization="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Date" Binding="{Binding readingDate, StringFormat=yyyy-MM-dd}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Pt" Binding="{Binding readingPt, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Pc" Binding="{Binding readingPc, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Ppl" Binding="{Binding readingPpl, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="MCFD" Binding="{Binding readingMCFD, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Water Produced" Binding="{Binding readingWaterProduced, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Water Hauled" Binding="{Binding readingWaterHauled, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Temperature" Binding="{Binding readingTemperature, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Hours On (actual)" Binding="{Binding readingHoursOnActual, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Hours On (planned)" Binding="{Binding readingHoursOnPlanned, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Clock Cycles" Binding="{Binding readingClockCycles, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

Ответы [ 5 ]

16 голосов
/ 16 августа 2011

Слишком много переменных, чтобы ответить на это с уверенностью. Однако вот несколько вещей, которые вы должны рассмотреть:

  1. Необходим ли объем данных, который вы вводите в сетку? Возможно, вы даете ему слишком много данных, чем пользователь на самом деле будет использовать? Это может замедлить ход событий.

  2. Вы визуализируете столбцы или ячейки со слишком большим количеством шаблонов? Я знаю, что это делает вашу презентацию гибкой, но слишком большое количество шаблонов (или элементов управления) может замедлить процесс.

  3. Много ли у вас преобразователей значений в вашей сетке данных? Требуется ли, чтобы каждая строка или каждый столбец выполняли немного дорогой код для визуализации?

  4. Возможно ли, что у вас есть много вложенных стилей (с использованием BasedOn) и, возможно, более важно, много триггеров в тех стилях, которые крадут время рендеринга для применения?

  5. Используете ли вы много пользовательских элементов управления, особенно вложенных элементов управления, в вашей презентации, которые могут вызывать задержку рендеринга в вашей презентации?

  6. Является ли строка привязки, которую вы используете для ваших клеток, комплексной? Применяете много поисков StringFormat, ElementName или Ancestory? Это способствует замедлению рендеринга.

  7. Возможно ли использовать визуальную кисть для отображения большего количества данных, чем сразу видно пользователю? Это приведет к короткому замыканию логики виртуализации.

  8. Рассматривали ли вы использовать FallBackValue для своих привязок и установить IsAsync в значение true? При установке значения true будет отображаться FallBackValue до тех пор, пока данные не будут готовы.

  9. Используете ли вы много MultiBindings или PriorityBindings в вашем пользовательском интерфейсе, что может привести к замедлению рендеринга при обработке более одного поля или значения?

  10. Являются ли стили, которые вы используете, сложными? Особенно градиентные кисти, рендеринг может быть дорогостоящим, особенно если вы делаете это в каждой строке вашей сетки.

  11. Рассматривали ли вы использование подкачки, чтобы у вас было меньше данных? В конце концов, Наиру, это лучшее решение для подобных проблем, если вы можете заставить пользователей принять его.

  12. Вы смотрите на использование памяти и процессора? Возможно ли, что используемое вами оборудование просто пытается воспроизвести созданный вами пользовательский интерфейс?

  13. Следите ли вы за выходом отладки, чтобы увидеть, есть ли ошибки привязки или другие проглоченные ошибки, способствующие снижению производительности?

  14. Вы улыбались экрану и дарили своему коду хорошее чувство, чтобы он захотел приложить больше усилий для вас? Шучу, но есть много переменных - ага?

  15. Реализованы ли вы команды, чьи обработчики CanExecute вызываются очень часто и, возможно, являются дорогостоящими для выполнения? Это могут быть тихие убийцы производительности.

Опять же, нет 100% ответа на эту проблему. Но это может помочь.

Еще одна вещь, которую следует учитывать, это то, что ваше перечисление может быть наблюдаемым списком - это позволит вам загружать данные по частям. Если вы хотите загрузить первую страницу и в асинхронном процессе добавить следующую страницу и следующую и т. Д., Пользовательский интерфейс должен быть очень близок к загрузке всего сразу, за исключением того, что это будет более быстрый начальный рендеринг. Это может быть сложно, но это еще один вариант для вас. Наблюдаемые списки такие изящные.

Примерно так:

ObservableCollection<User> Users { get; set; }

void LoadUsers()
{
    int _Size = 2;
    int _Page = 0;
    using (System.ComponentModel.BackgroundWorker _Worker
        = new System.ComponentModel.BackgroundWorker())
    {
        _Worker.WorkerReportsProgress = true;
        _Worker.DoWork += (s, arg) =>
        {
            List<User> _Data = null;
            while (_Data == null || _Data.Any())
            {
                _Data = GetData(_Size, _Page++);
                _Worker.ReportProgress(_Page, _Data);
            }
        };
        _Worker.ProgressChanged += (s, e) =>
        {
            List<User> _Data = null;
            _Data = e.UserState as List<User>;
            _Data.ForEach(x => Users.Add(x));
        };
        _Worker.RunWorkerAsync();
    }
}

List<User> GetData(int size, int page)
{
    // never return null
    return m_Context.Users.Take(size).Skip(page).ToList();
}

Вот что я хочу, чтобы вы убрали - привязка в WPF никогда не бывает мгновенной. Вы никогда не будете иметь сложную форму визуализации и связывания без некоторой задержки. Вы можете контролировать боль здесь с некоторыми из методов выше. Тем не менее, вы никогда не можете удалить все это. Тем не менее, связывание в WPF является самой мощной и удивительной технологией связывания. Я когда-либо испытывал.

Удачи!

1 голос
/ 17 сентября 2011

Вы можете обнаружить, что низкая производительность связана не с самим присоединением, а с перерисовкой DataGrid, которая происходит при отображении данных.Упомянутая вами задержка кажется довольно чрезмерной.

У меня была проблема с DataGrid, в которой потребовалось буквально несколько секунд для обновления после изменения размера окна, сортировки столбца и т. Д., И он заблокировал пользовательский интерфейс окна, пока он это делал.(1000 строк, 5 столбцов).

Это привело к проблеме (ошибка?) С расчетами размеров WPF.У меня было это в сетке с RowDefinition Height = "Auto", которая заставляла систему рендеринга пытаться пересчитать размер DataGrid во время выполнения, измеряя размер каждого столбца и строки, предположительно, заполняя всю сетку (как я понимаю).Предполагается, что это нужно как-то разумно, но в данном случае это не так.

Быстрая проверка, чтобы выяснить, не является ли это связанной проблемой - установить свойства высоты и ширины DataGrid.до фиксированного размера на время теста, и попробуйте запустить снова.Если ваша производительность восстановлена, постоянное исправление может быть одним из следующих вариантов:

  • Изменить размеры содержащихся элементов на относительные (*) или фиксированные значения
  • Установить MaxHeight и MaxWidthDataGrid с фиксированным значением, превышающим обычное значение
  • Попробуйте использовать другой тип контейнера с другой стратегией изменения размера (Grid, DockPanel и т. д.).На самом деле, самое простое решение, которое я нашел, было поместить сетку данных в Grid в качестве непосредственного контейнера с DataGrid в качестве единственного элемента
1 голос
/ 08 сентября 2011

Это может быть полезно для некоторых:

У меня была сетка данных, которая медленно связывалась, как оригинальный плакат.

Приложение запрашивало базу данных и выполняло много логики вНекоторое время, а затем потребовалась одна или несколько секунд, чтобы просто связать наблюдаемую коллекцию с сеткой данных.В моем случае оказалось, что хотя большая часть данных была готова, некоторые из них были загружены с отложенной загрузкой (то есть они не загружались до тех пор, пока это не было необходимо - это обычная и полезная часть большинства инструментов ORM, таких как NHibernate, iBatis и т. Д.),В этом не было необходимости, пока не произошло связывание.В моем случае это были не все данные, а только один столбец с ленивой загрузкой.Оказалось, что в WPF уже есть очень простой механизм для обработки чего-то подобного.Установка привязки для этого столбца к следующей решенной проблеме:

<Binding Path="SomeProperty" IsAsync="True" FallbackValue="..." />

Моя сетка данных загружена почти мгновенно.Один столбец содержал только текст «...» в течение нескольких секунд, а затем появились правильные данные.

0 голосов
/ 17 августа 2011

Испытываете ли вы эту медлительность в отладочных или выпусках вашего приложения? С приложением Visual Studio или без него?

Если он находится в сборке Debug с подключенной Visual Studio, то это может быть ошибка привязки данных, записываемая в окно вывода. Я говорю это, так как ранее этим вечером я разрешил значительную паузу / медлительность в ListBox, который отображал более 5000 элементов, что было вызвано шаблоном по умолчанию для ListBoxItem, пытаясь выполнить привязку для VerticalContentAlignment и HorizontalContentAlignment, которые всегда не удалось.

0 голосов
/ 16 августа 2011

Из-за отсутствия просмотра вашего кода я бы предложил установить бесплатную пробную версию профилировщика производительности или памяти (например, http://www.red -gate.com / products / dotnet-development / ).Скорее всего, он очень быстро скажет вам, где находится узкое место.

...