BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Szybkość DataGridView – kilka rad cz.1

02-11-2006 11:00 | User 84985
Kilka porad jak zwiększyć elastyczność i szybkość działania kontrolki DataGridView w naszych aplikacjach.

Wstęp

DataGridView(DGV) jest niezwykle potężną kontrolką, której przydatność w budowie aplikacji desktopowych na platformie dotNET podkreślać nie trzeba. Niezwykła elastyczność i konfigurowalność tego komponentu jest niekwestionowanym atutem, ale i potencjalnym źródłem problemów; powodem obniżenia wydajności. Szybkość działania aplikacji (szczególnie GUIa) jest postrzegana przez klienta(laika) nie tylko jako wykładnia wydajności całego systemu ale i jakości dostarczanych przez nas(profesjonalistów) produktów. Co z tego, że kolega z zespołu za algorytm obsługi transakcji bazodanowych obronił doktorat, skoro interfejs w naszym trójwarstwowym systemie haczy na średniej klasy sprzęcie?
    Artykuł postanowiłem opublikować w dwóch częściach: Pierwsza o konfiguracji samej kontrolki oraz druga – sposób wiązania kontrolki ze źródłem danych.

Style komórek DGV

Duży wpływ na szybkość działania DGV ma sposób, w jaki ustalany jest styl dla konkretnej komórki grida. Sama kontrolka oferuje dosyć elastyczny system stylów, który pozwala na dostosowanie wyglądu (formatowania) komórek do naszych potrzeb. Równie często jak zamierzony wygląd naszej ‘siatki’ otrzymujemy niezły cios prosto między nogi GUIa (spowolnienie, ‘haczenie’). Częstym powodem takiego obrotu sprawy jest niewłaściwe wykorzystanie mechanizmu dziedziczenia styli (ang. style inheritance ), a raczej brak jego wykorzystania.

             Rozważmy następujący przykład:

Form1view

Rys 1. Okno Grida


Powyższy styl DGV można osiągnąć poprzez podpięcie do zdarzenia
System . Data . DataGridView . CellFormatting następującej metody : 

[Kod C#]

  private void dataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
 
{
     
  if (e.RowIndex % 2 == 1)   // parzyste wiersze mają niebieskie tło
           
e.CellStyle.BackColor = Color.Lavender;
       
if (e.ColumnIndex == 1)    // druga kolumna – zmiana czcionki
           
e.CellStyle.Font = this.boldUnderlineFont;
       
if (e.ColumnIndex == 2)    // trzecia kolumna – zmiana pozycjonowania tekstu
           
e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
  }

Jest to niezmiernie nieefektywne rozwiązanie: Dla każdej komórki grida wywoływana jest metoda dataGridView_CellFormatting , w której styl jest ustawiany ‘na sztywno’ w zależności od wartości ColumnIndex oraz RowIndex. Konstrukcja systemu stylów w DGV powoduje, że każda modyfkacja  propercji CellStyle danej komórki powoduje utworzenie nowego obiektu typu DataGridViewCellStyle ( o ile wcześniej nie został przypisany na sztywno). Zatem każda komórka dysponują własnym obiektem stylu, a DGV z rys. 1  zawiera góra 6 stylów. W przypadku dyżych danych problem może zaważyć na wydajności DGV.
    Kolejnym podejściem jest podmienianie stylów w tym samym zdarzeniu. Rozwiązanie polega na zdefiniowaniu zmiennych typu
DataGridViewCellStyle reprezentujących wygląd komórek i przypisywaniu ich do konretych już komórek:

     

[Kod C#]
    //definiujemy zmienne

    DataGridViewCellStyle underlineBoldFontStyle;
    DataGridViewCellStyle rightAlignStyle;
    DataGridViewCellStyle alternatingUnderlineBoldFontStyle;
    DataGridViewCellStyle alternatingRightAlignStyle;
    DataGridViewCellStyle alternatingRegularStyle;

    // metoda formatowania komórki w której podmieniamy style

    private void dataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
    {
        if (e.RowIndex % 2 == 1) //co drugi wiersz ma niebieskie tło
       
{
            if (e.ColumnIndex == 1)
                e.CellStyle = this.alternatingUnderlineBoldFontStyle;
            elseif (e.ColumnIndex == 2)
                e.CellStyle = this.alternatingRightAlignStyle;
            else
   
            e.CellStyle = this.alternatingRegularStyle;
        }
        else
       
{
            if (e.ColumnIndex == 1)
                e.CellStyle = this.underlineBoldFontStyle;
            if (e.ColumnIndex == 2)
                e.CellStyle = this.rightAlignStyle;
        }
    }

    //ustawianie własności stylów
    privatevoid setUpStyles()
    {
       this.underlineBoldFontStyle = new DataGridViewCellStyle(this.dataGridView.DefaultCellStyle);
       this.underlineBoldFontStyle.Font = this.boldUnderlineFont;
       this.underlineBoldFontStyle.BackColor = Color.White;

       this.rightAlignStyle = new DataGridViewCellStyle(this.dataGridView.DefaultCellStyle);
       this.rightAlignStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
       this.rightAlignStyle.BackColor = Color.White;

       this.alternatingUnderlineBoldFontStyle = new DataGridViewCellStyle(this.dataGridView.DefaultCellStyle);
       this.alternatingUnderlineBoldFontStyle.Font = this.boldUnderlineFont;
       this.alternatingUnderlineBoldFontStyle.BackColor = Color.Lavender;

         this.alternatingRightAlignStyle = new DataGridViewCellStyle(this.dataGridView.DefaultCellStyle);
       this.alternatingRightAlignStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
       this.alternatingRightAlignStyle.BackColor = Color.Lavender;

         this.alternatingRegularStyle = new DataGridViewCellStyle(this.dataGridView.DefaultCellStyle);

       this.alternatingRegularStyle.BackColor = Color.Lavender;

    }
//…

W takim przypadku obiekty stylów dzielone są pomiędzy komórki DGV. Jest dobrze, jednak styl ustawiany jest nadal w metodzie obsługi zdarzenia formatowania komórki (DGV. CellFormatting) .Nadal możemy spodziewać się pewnego obniżenia wydajności.
    Z odsieczą idzie nam DGV z systemem dziedziczenia stylów jako tajną bronią. Okazuje się bowiem, że wygląd komórki nie zależy jedynie od wartości propercji Style obiektu DataGridViewCellStyle powiązanego z tą komórką ale i od pewnych właściwości wiersza, kolumny i samego DGV do którego należy. Konkretnie:
  1. DataGridViewCell.Style
  2. DataGridViewRow.DefaultCellStyle
  3. AlternatingRowsDefaultCellStyle (only for cells in rows with odd index numbers)
  4. RowsDefaultCellStyle
  5. DataGridViewColumn.DefaultCellStyle
  6. DataGridView.DefaultCellStyle

Ustalanie własności (czcionki, koloru, pozycjonowania tekstu…) polega na przejściu listy w dół i zwrócenie pierwszej napotkanej wartości (różnej od null, Color.Empty etc.),  która różna jest od tej z DataGridView.DefaultCellStyle .
    Wracając do przykładu: Chcąc osiągnąć rezultat jak na rys 1 przy wykorzystaniu dziedziczenia stylów wystarczy wywołać poniższą metodę, która ustawi właściwości stylów odpowiednich elementów DGV:

[Kod C#]

private void setUpDgvStyles()
    {
        this.dataGridView.Dock = DockStyle.Fill;
        this.dataGridView.AlternatingRowsDefaultCellStyle.BackColor = Color.Lavender;
        this.dataGridView.Columns["bbb"].DefaultCellStyle.Font = this.boldUnderlineFont;
        this.dataGridView.Columns["ccc"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
   
}

    Rozwiązanie jest eleganckie, zwięzłe i, co najważniejsze, efektywne. Czasami zadarza się jednak, że obsługa zdarzenia DGV. CellFormatting jest nieunikniona (każdy scenariusz dynamicznego formatowania wymaga wykorzystania tego zdarznia). W takim wypadku obsługa DGV. CellFormatting powinna dotyczyć tylko tej części formatowania, której nie można zapewnić w czasie kompilacji (zaznaczanie błędów, formatowanie zależne od zawrtości komórek etc).
    Dobrą praktyką przy ustawianiu stylów jest przenoszenie wspólnych własności komórek jak najniżej powyżej zamieszczonej listy. Przykładowo jeśli chcielibyśmy całego DGV wypełnić czerwoną czcionką a tylko jedną kolumnę pozostawić czarną to lepszym (czyt. szybszym) rozwiązaniem będzie ustalenie koloru czerownego na poziomie DGV ( DGV.DefaultCellStyle.BackColor ) a czarnego na poziomie konkretnej kolumny ( DGVColumn.DefaultCellStyle.BackColor ). Odnoszenie się do propercji Style obiektu DGVCell też nie jest mile widziane. Sprawdzenie, czy komórka ma styl poprzez propercję DataGridViewCell . HasStyle może nas uchronić przez niechcianym mnożeniem obiektów stylu. Dodatkowo dziedziczenie stylów powoduje, że wartość DGVCell.Style może nie zawierać pełnego opisu stylu jaki komórka posiada. To jak komórka wygląda w DGV można sprawdzić poprzez propercję DGVCell . InheritedStyle. Jej użycie nie generuje obiektu typy DGVCellStyle .
    Więcej informacji na temat dziedziczenia stylów w kontrolce DGV można oczywiście znaleźć w sieci (msdn, DGV FAQ – tu bardzo obrazowo i przejrzyście wyjaśnione).

Menu kontekstowe dla elementów DataGridView

To, że dane widać to za mało. Aplikacja pozbawiona możlowości edycji danych to oczywisty enwenement. Najlepiej jeśli sposób edycji(dostępu do funkcji programu odpowiedzialnych za operowanie na danych) jest intuicyjny i szybki. DGV umożliwia wykorzytanie menu kontekstowego do wygodnego  wywoływania funkcji programu. Każda elememt DGV posiada propercję ContextMenuStrip, której możemy przypisać obiekt reprezentujący menu kontekstowe.
    Podobnie jak w przypadku stylów przypisywanie osobnego obiektu  
ContextMenuStrip dla każdej komórki jest zgoła rozrzutne i z pewnością przyczyni się do spowolnienia działania kontrolki DGV. Można przypisać globalne menu kontekstowe na poziomie całego DGV. Elementy takie ja DGVColumn oraz DGVRow również posiadają propercję ContextMenuStrip, którą można się posłużyć by zdefiniować menu kontekstowe powiązane z grupą komórek należącą odpowiednio do kolumny lub wiersza.
    Najlepszym rozwiązaniem w kontekscie poruszanego w artykule problemu jest obsługa zdarzeń
CellContextMenuStripNeeded oraz RowContextMenuStripNeeded . Oba zdarzenia przyjmują jako parametr obiekt typu DataGridViewCellContextMenuStripNeededEventArgs , który umożliwia wyświetlenie menu kontekstowego zależnego od wiersza i kolumny komórki.

               

[Kod C#]
ContextMenuStrip context;

  public Form1()
      : base()
    {
        //…
        this
.setUpDgvContextMenu();
        this
.dataGridView.CellContextMenuStripNeeded += new      DataGridViewCellContextMenuStripNeededEventHandler(dataGridView_CellContextMenuStripNeeded);
   
}

     private void SetContextMenuAviability(int row, int col)
    {
        this.context.Items[0].Enabled = col == 2;
        this.context.Items[1].Enabled = (col == 1) && (row % 2 == 0);
    }

     void dataGridView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
    {
        SetContextMenuAviability(e.RowIndex, e.ColumnIndex);
        e.ContextMenuStrip = this.context;
    }

    privatevoid setUpDgvContextMenu()
    {
        context = newContextMenuStrip();
        context.Items.Add("menu1");
        context.Items.Add("menu2");
        context.Items.Add("menu3");
   
}

System wierszy dzielonych (ang.share rows)

W DGV wiersze współdzielą ze sobą instancje klasy DGVRow tak często, jak tylko jest to możliwe. Pozwala to na zaoszczędzenie znacznych ilości pamięci. Wspóldzielenie obiektu DGVRow nie zawsze może się odbyć. Obiekt DGVRow może być wspólny dla wielu wierszy DGV tylko w przypadku, gdy stan jego komórek można ustalić na podstawie własności wiersza i kolumny, do której należą. Jeśli nie wykorzystujemy data-bindingu ani trybu wirtualnego do wiązania kontrolki z danymi dzielenie wierszy również nie jest możliwe.
    Aby zwiększyć szybkość działania DGV należy mieć na uwadze, że pewne działania mogą spowodować, że wiersz przestaje dzielić obiekt
DGVRow z innym. Przykładowo edycja zawartości konkretnej komórki powoduję, że wiersz zawierający tę komórkę otrzymuje własną kopię dzielonego obiektu. Ponieważ jest to nieuniknione zawarte tu wskazówki mają sens jedynie w przypadku gdy ilość danych jest relatywnie duża w stosunku do tych edytowanych przez użytkownika.
     Do sprawdzienia czy dany wiersz dzieli obiekt
DGVRow można posłużyć się propercją Index obiektu zwracanego przez metodę DGVRowCollection.SharedRow(Int32) . Jej wartość w przypadku obiektów dzielonych jest równa -1. Przydatne jest również zdarzenie DGV.RowUnshered , które podnoszone jest za każdym razem gdy wiersz otrzymuję własną instancję DGVRow (przestaje dzielić DGVRow z innymi wierszami DGV).
    Kilka poniższych  wskazówek może uchonić DGV przed niepotrzebnym mnożeniem obiektów
DGVRow :

  • Należy unikać indeksowania kolekcji wierszy oraz wykorzystania iteratora (w pętlach foreach). Metody DGV operujące na wierszcha zazwyczaj przyjmują numer wiersza a nie jego instancję. Namtomiast handlery zdarzeń przyjmują propercje wiersza, których dotyczą zdarzenia.
  • Indeksowanie kolekcji komórek również powoduję, że wiersz przestaje dzielić obiekt DGVRow .
  • Jeśli musimy odnieść się bezpośrednio do obiektu DGVRow konkretnego wiersza powinniśmy starać się wykorzystać metodę DGVRowCollection.SharedRow(Int32) , która zwraca obiekt dzielony. Pamiętać należy, że zmiana obiektu dzielonego dotyczy wszystkich wierszy, które go dzielą.
  • Tryb selekcji DGVSelectionMode.CellSelect także przyczynia się do mnożenia obiektów DGVRow. Jak tylko to możliwe należy stosować DGVSelectionMode.FullRowSelect lub DGVSelectionMode.FullColumnSelect.
  • Wykorzystanie zdarzeń DGVRowCollection.CollectionChange oraz DGV.RowStateChange również powodują, że wiersze przestają dzielić między sobą instancję DGVRow . Wywołanie metod DGVRowCollection.OnCollectionChange(…) oraz DGV.OnRowStateChange(…)  odnosi identyczny skutek (metody te podnoszą powyższe zdarzenia).
  • Nie należy odwoływać się do kolekcji DGV.SelectedCells gdy wartość propercji DGV.SelectionMode jest różny od DGVSelectionMode.CellSelect
  • Wywołanie metody DGV.AreAllCellSelected(Boolean) może spowodować, że wszystkie wiersze otrzymają włąsny obiekt DGVRow .
  • Wywołanie DGV.SelectAll() podczas gdy tryb selekcji jest ustawiony na DGVSelectionMode.CellSelect także powoduje, że wiersze przestają dzielić obiekt DGVRow .
  • Ustawienie propercji Selected oraz ReadOnly   w komórce na false podczas gdy wartość w  kolumnie jest ustawiona na true powoduję, że wszystkie wiersze przestają dzielić obiekt DGVRow.
  • Identyczny skutek ma odwołanie do propercji DGVRowCollection.List .
  • Wywołanie Sort(IComparer) również.

Praca z elementami zaznaczonymi

To, w jaki sposób zajmujemy się obsługą elemtnów zaznaczonych w DGV ma znaczenie dla wydajności. Praca bezpośrednio na kolekcji DGV.SelectedCells jest bardzo kosztowna. Lepiej pod tym względem wypadają kolekcje DGV.SelectedRows oraz DGV.SelectedColumns . Podobnie jest z trybem selekcji: Wartość DGV.SelectionMode ustawiona na DGVSelectionMode.CellSelect nie jest mile widziana biorąc pod uwagę prędkość działania DGV.
    Przydatną funkcjonalność oferuje nam metoda
DGV.AreAllCellSelected , która sprawdza czy została zaznaczona cała zawartość grida. Niestety wywołanie jej może spowodować, że wiersze DGV przestają dzielić obiekt DGVRow. Natomiast aby sprawdzić ilość zaznaczonych elemetnów lepiej, zamist  odwoływać się do propercji Count odpwiedniej kolekcji, jest użyć metod DGV.GetCellCount , DGVRowCollection.GetRowCount , DGVColumnCollection.GetColumnCount p odając jako parametr DGVElementStates.Selected .

Automatyczne dostosowanie szerokości kolumn

DGV pozwala na automatyczne dostoswanie szerokości kolumn tak aby zawartość w komórkach nie była mogła pojawić się w całości. Aby tego dokonać DGV musi przeanalizować zawartość każdej komórce, co w połączeniu z dużą ilością danych może źle odbić się na prędkości działania.
                Aby uniknąć utraty wydajności działania należy:

  • W przypadku gdy używamy automatycznego dostosowania szerokości wierszy najlepiej zmieniać rozmiar komórek na podstawie tylko widocznych wierszy:
    • DGV . AutoSizeColumnsMode oraz DGV.AutoSizeRowsMode przypisać wartości DisplayedCells lub DisplayedCellsExceptHeaders odpowiednich enumeratorów.
    • DGV.RowHeadersWidthSizeMode przypisać wartość AutoSizeToDisplayedHeaders lub AutoSizeToFirstHeader enumeratora DGVRowHeaderWidthSizeMode 
  • Dla osiągnięcia maksymalnej wydajności należy posłużyć się ręcznym dostosowaniem szerokości kolumn zamiast automatycznego.

Podsumowanie

Każda z podanych wyżej wskazówek może pomóc w zwiększeniu prędkości oraz elastyczności DGV w naszej aplikacji. Oczywiste jest, że zastosowanie wszystkich zaleceń, porad i sztuczek nie jest możliwe, i wcale o to nie chodzi. Pracując z DGV należy jedynie mieć na uwadze, że różne rozwiązania tego samego problemu mają zarówno zalety jak i wady. Należy tak programować aby osiągnąć poziom co najmniej ugodowy między wydajności, przejrzystością rozwiązania a spełnieniem stawianych nam wymagań.

Załączniki:

Podobne artykuły

Komentarze 1

dartar
dartar
246 pkt.
Junior
21-01-2010
oceń pozytywnie 0
OK, ale dodalbym jeszcze cos na temat mozliwosci przystawki Windows do obslugi kolejek komunikatow. Naprawde sie przydaje.
pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.