willempipi Ответов: 5

DataGridView настройка источника данных происходит слишком медленно


Привет,

У меня сейчас действительно странная проблема. Я создал пользовательский элемент управления с 2 gridviews и 2 пользовательскими полосами прокрутки на нем, gridHeader сверху, gridData снизу. Первый gridview (gridHeader) используется для ввода пользователем поискового запроса в каждом отдельном столбце. Второй gridview (gridData) используется для просмотра результирующих данных. Для каждого столбца в gridHeader я также создаю элемент управления фильтром, который будет помещен поверх columnheader, с помощью этого элемента управления фильтром пользователь также может фильтровать данные, проверяя включение/выключение уникальных значений в списке столбца(нажмите на маленькую кнопку в столбце, и вы увидите уникальные значения).

Управление работает и зарекомендовало себя, но оно слишком медленное. Загрузка некоторых источников данных может занять до 1000 мс. После проверки кода путем добавления таймеров я понял, что проблема заключается не в тяжелой фильтрации, которую я делаю, а в том, что она вызвана gridviews при назначении datatable, а также при добавлении элементов управления фильтра в заголовки столбцов.

Чтобы как-то решить вторую проблему, я начал с того, что не удалил FilterColumnClass, когда он не был обнаружен, и оставил их в памяти, так что вторая загрузка будет быстрее. Но если я назначаю источник данных заголовка-gridview, заполненный только 1 строкой, это занимает около 180 МС, в то время как когда я назначаю источник данных data-gridview, заполненный 100 строками, это занимает всего около 10 мс.

Когда заголовок-сетка назначается, 1 событие получает triggerd, который ничего не делает из-за флага "_StopEventHandling", который установлен в true.

Я поместил тайминги в качестве комментария в код, 1-е означает; в первый раз, когда вы назначаете datatable элементу управления, 2-е означает; во второй раз, когда вы назначили datatable. Время в МС, которое вы видите,-это время, которое потребовалось compaired до начала функции или compaired до последнего снимка времени.

Вот часть кода о котором идет речь:

public class SomeCustomUserControl
{
    private DataTable _AllData = null;
    private DataTable _ViewData = null;
        
    private List<FilterColumnClass> _FilterColumns = new List<FilterColumnClass>();
        
    private bool _StopEventHandling = false;
    private bool _DataBound = false;
    private bool _DataBoundToSendSelected = false;
        
    private int _LastSortColumnIndex = -1;
    private bool _LastSortColumnDir = false;

    public void SetDataSource(DataTable value)
    {
        // Set all filtercontrols to invisible
        foreach (FilterColumnClass filter in _FilterColumns)
        {
            filter.FilterCheckControl.Visible = false;
        }

        // Assigning the received datatable to a local variable
        _AllData = value;

        // Do the databinding
        DataBind();
    }

    private void DataBind()
    {
        if (_AllData != null)
        {
            // Letting the control know we've started databinding the data.
            _DataBound = true;

            // Letting the control know it should NOT handle any events from the grids
            _StopEventHandling = true; 

            // Included in example: scroll down!
            PrepareGridViews(); 
 
            // 1st: 0ms 2nd: 0ms

            // Included in example: scroll down!
            PrepareFilterColumns(); 
 
            // 1st: 472ms 2nd: 252ms
 
            // ================ !!! HERE !!! ====================
            // Creates a new datatable according the filters
            _ViewData = FilterRows(_AllData, _FilterColumns); 
            gridData.DataSource = _ViewData;  
            // ================ !!! HERE !!! ====================
            // THIS IS REALLY FAST: 1st: 12ms 2nd: 10ms
            // The _ViewData datatable contains around 100 records

            // Sorts the new datasource according the last sort
            if (_LastSortColumnIndex != -1)
            {
                if (gridHeader.Columns.Count > _LastSortColumnIndex)
                {
                    try
                    {
                        if (_LastSortColumnDir)
                            gridData.Sort(gridData.Columns[gridHeader.Columns[_LastSortColumnIndex].Name], ListSortDirection.Ascending);
                        else
                            gridData.Sort(gridData.Columns[gridHeader.Columns[_LastSortColumnIndex].Name], ListSortDirection.Descending);
                    }
                    catch
                    {
                    }
                }
            }

            // 1st: 0ms 2nd: 0ms

            // Sets the columns to the users preferences, like column-name, column-width and column-order
            if (gridData.Columns.Count > 0) Personalize_Columns();

            // 1st: 194ms 2nd: 187ms

            // Sets the custom scrollbars min, max and value settings
            PrepareScrollBars();

            // 1st: 0ms 2nd: 1ms

            // Resets the vertical position of the custom scrollbars
            SetVerticalPosition(vScrollBar1.Value);

            // 1st: 0ms 2nd: 0ms

            // Resets the horizontal position of the custom scrollbars
            foreach (DataGridViewColumn col in gridHeader.Columns)
            {
                if (col.DisplayIndex == 0)
                {
                    _LastScrollingColumnIndex = -1;
                    SetHorizontalPosition(col.Index);
                }
            }
            // Checks if the parent-control wants to mark some items in the grid
            AskMarkedItems();

            // 1st: 196ms 2nd: 36ms

            // Letting the control know we're done databinding the data
            _DataBoundToSendSelected = true;

            // Letting the control know it should continue handling the events from the grids
            _StopEventHandling = false;
        }
        else
        {
            // Reset the grids
            gridHeader.DataSource = null;
            gridData.DataSource = null;
            _ViewData = null;
        }
 
        // TOTAL 1st: 874ms 2nd: 486ms
    }

    private void PrepareGridViews()
    {
        // Speeds up the gridviews
        SetDoubleBuffered(gridData, true); 
        SetDoubleBuffered(gridHeader, true);

        // Templating
        gridHeader.RowTemplate.Height = PublicFunctions.UniversalHeight - 6; 
        gridData.RowTemplate.Height = PublicFunctions.UniversalHeight - 6;

        // No columnheader because we use the one from gridHeader
        gridData.ColumnHeadersVisible = false; 
    }
    private void PrepareFilterColumns()
    {
        // Index counter for the column order
        int index = 0;

        // Suspend the layout to speed up the adding of the filter controls
        this.SuspendLayout();

        foreach (DataColumn col in _AllData.Columns)
        {
            // First try to find the the filter column class, if it is already added
            FilterColumnClass filter = _FilterColumns.Find(delegate(FilterColumnClass item) { return item.ColumnName == col.ColumnName; });

            if (filter != null)
            {
                // filter column class found, reset the index to be sure
                filter.Index = index;
                filter.FilterCheckControl.Visible = true;
            }
            else
            {
                // filter column not found, create one and add it to this control
                FilterColumnClass new_filter = new FilterColumnClass();
                new_filter.ColumnName = col.ColumnName;
                new_filter.Index = index;
                new_filter.FilterCheckControl.Visible = true;
                new_filter.FilterCheckControl.eventAskCurrentDataTable += new AskDataTableByInt(FilterCheckControl_eventAskCurrentDataTable);
                new_filter.FilterCheckControl.eventAskSourceDataTable += new AskDataTableByInt(FilterCheckControl_eventAskSourceDataTable);
                new_filter.FilterCheckControl.eventApplyFilter += new EventHandler(FilterCheckControl_eventApplyFilter);

                _FilterColumns.Add(new_filter);
                this.Controls.Add(new_filter.FilterCheckControl);
            }
            index++;
        }

        // Adding of filter controls complete resume the layout
        this.ResumeLayout();

        // ================ !!! HERE !!! ====================
        // 1st: 256ms 2nd: 46ms 
        // ================ !!! HERE !!! ====================

        // Set the filter columns that are not found to invisible
        foreach (FilterColumnClass filter in _FilterColumns)
        {
            bool found = false;
            foreach (DataColumn col in _AllData.Columns)
            {
                if (filter.ColumnName == col.ColumnName)
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                filter.FilterCheckControl.Visible = false;
            }
        }
        // 1st: 0ms 2nd: 0ms

        // Create a datatable for the header with empty strings
        DataTable header = new DataTable("FilterRow");

        // Add all columns of the datasource to it
        foreach (DataColumn col in _AllData.Columns)
        {
            header.Columns.Add(new DataColumn(col.ColumnName, typeof(string)));
        }

        // 1st: 0ms 2nd: 0ms

        // Create a empty row for it
        DataRow filterrow = header.NewRow();

        foreach (FilterColumnClass filter in _FilterColumns)
        {
            // If the filter control is visible we have to take over the old value of that filter.
            if (filter.FilterCheckControl.Visible)
            {
                try
                {
                    filterrow.ItemArray[filter.Index] = filter.FilterTextString;
                }
                catch (Exception ex)
                {
                    PublicFunctions.ReportBug(ex, _Shared);
                }
            }
        }

        // Add the filter row to the header datatable
        header.Rows.Add(filterrow);

        // 1st: 0ms 2nd: 0ms

        // ================ !!! HERE !!! ====================
        // Assign the header datatable to the grid
        gridHeader.DataSource = header; 
        // ================ !!! HERE !!! ====================
        // THIS IS REALLY SLOW 1st: 162ms 2nd: 171ms
        // The header datatable contains 1 row

        // TOTAL 1st: 418ms 2nd: 217ms
    }

    private void SetDoubleBuffered(DataGridView grid, bool setting)
    {
        Type dgvType = grid.GetType();
        PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
        pi.SetValue(grid, setting, null);
    }
}


Я надеюсь, что вы, ребята, можете видеть сквозь большое количество кода.

BobJanova

Насколько велик _AllData? Фильтрация-это не очень умный процесс (он получает каждую строку, передает ее в ваш код и спрашивает, Хотите ли вы ее получить), а на умеренных наборах данных (порядка 1000) он, вероятно, будет слишком медленным.

Ответ может заключаться в том, чтобы фильтровать данные с помощью запроса, а не в коде.

willempipi

Спасибо, но, как я уже сказал, процесс фильтрации(выполняемый функцией "FilterRows(_AllData, _FilterColumns);") не является проблемой, он занимает всего около 10 мс. Проблема заключается в присвоении datatable сетке заголовка(содержащей только 1 строку), которая занимает около 180 МС, и добавлении filtercontrols к usercontrol (это.Управления.Добавить(new_filter.FilterCheckControl);) который занимает около 250 мс.

Фильтрация, которую я делаю, далеко не универсальна и сложна для выполнения в запросе, и я использую свою фильтрацию для данных, которые уже отфильтрованы. _AllData будет содержать не более 1000 строк.

5 Ответов

Рейтинг:
39

willempipi

Я НАШЕЛ РЕШЕНИЕ!!!!

Основная причина того, что привязка datatable к сетке данных происходит так медленно, заключается в том, что рисование имен столбцов занимает много времени. Простая строка кода:

gridHeader.ColumnHeadersVisible = false;

Ускоряет процесс привязки данных от 180-300 МС к 4 мс!!!

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


Member 11531718

компонента DataGrid.ColumnHeadersVisible = False
Элемент управления DataGrid.Источник Данных = MyDataTable
компонента DataGrid.ColumnHeadersVisible = True

PaladinDataMatt

Совершенно потрясающе, спасибо.

Fernando J. Garcia

Привет,

Я знаю, что эта нить старая, но все еще очень актуальна!

Хотя предложенное решение работает. Я обнаружил, что настоящая причина заключается не в том, что заголовки видны, а в том, что AutoSizeColumnMode и autoSizeRowsMode имеют значение, отличное от None.

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

Надеюсь, это поможет!

Фернандо (нандостиль)

Рейтинг:
2

wizardzz

Почему вы делаете это.suspendlayout (), а затем resumelayout() перед выполнением настройки источника данных заголовка datatable? Что произойдет, если вы приостановите макет для всех параметров источника данных?


willempipi

Прочитал где-то, что это может быть решением, так что это был тест.

Рейтинг:
2

Fernando J. Garcia

Привет,

Я знаю, что эта нить старая, но все еще очень актуальна!

Хотя предложенное решение работает. Я обнаружил, что настоящая причина заключается не в том, что заголовки видны, а в том, что AutoSizeColumnMode и autoSizeRowsMode имеют значение, отличное от None.

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

Надеюсь, это поможет!

Фернандо (нандостиль)


Рейтинг:
2

Ahsann90

Просто удалить и добавить новый элемент.


CHill60

Неужели? И ты опоздал почти на 3 года

Рейтинг:
1

Henry Minute

Я как раз собирался упомянуть об этом. SuspendLayout() и вещь тоже.

Кроме того, в (очень) быстром тесте изменение:

FilterColumnClass new_filter = new FilterColumnClass();
  new_filter.ColumnName = col.ColumnName;
  new_filter.Index = index;
  new_filter.FilterCheckControl.Visible = true;

к:
FilterColumnClass new_filter = new FilterColumnClass(col.ColumnName, index);


и установка new_filter.FilterCheckControl.Visible = true; в конструкторе, так как вы делаете это для всех экземпляров.

Дал незначительное (3% иш) улучшение.

Код действительно должен быть запущен через профилировщик, хотя для более точного определения узкого места.


willempipi

Спасибо;) 200 мс jet, чтобы сэкономить. Не могли бы вы подумать вместе, почему это занимает 180 МС:

// Создайте datatable для заголовка с пустыми строками
Заголовок DataTable = new DataTable("FilterRow");

// Добавьте в него все столбцы источника данных
по каждому элементу (объект datacolumn Коль в _AllData.Столбцы)
{
заголовок.Столбцы.Add(new DataColumn(col.ColumnName, typeof(string)));
}

// 1-й: 0мс 2-й: 0мс

// Создайте для него пустую строку
DataRow filterrow = заголовок.Невров();

foreach (FilterColumnClass filter in _FilterColumns)
{
// Если элемент управления фильтром виден, мы должны взять на себя старое значение этого фильтра.
если (фильтр.FilterCheckControl.Видно)
{
пробовать
{
фильтрроу.ItemArray[фильтр.Индекс] = фильтр.FilterTextString;
}
поймать (исключение бывший)
{
Общественные функции.ReportBug(ex, _Shared);
}
}
}

// Добавьте строку фильтра в заголовок datatable
header.Rows.Add(filterrow);

// 1-й: 0мс 2-й: 0мс

// ================ !!! Сюда !!! ====================
// Присвоить сетке заголовок datatable
сеточник.Источник данных = заголовок;
// ================ !!! Сюда !!! ====================
// Это действительно медленно 1-й: 162 МС 2-й: 171 МС

Henry Minute

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