Udostępnij za pośrednictwem


Tworzenie dostosowanego interfejsu użytkownika sortowania (C#)

Autor: Scott Mitchell

Pobierz plik PDF

Podczas wyświetlania długiej listy posortowanych danych można bardzo pomóc w grupowaniu powiązanych danych, wprowadzając wiersze separatora. W tym samouczku zobaczymy, jak utworzyć taki interfejs użytkownika sortowania.

Wprowadzenie

Podczas wyświetlania długiej listy posortowanych danych, w których istnieje tylko kilka różnych wartości w posortowanej kolumnie, użytkownik końcowy może trudno rozpoznać, gdzie, dokładnie, występują granice różnic. Na przykład w bazie danych istnieje 81 produktów, ale tylko dziewięć różnych opcji kategorii (osiem unikatowych kategorii plus NULL opcja). Rozważmy przypadek użytkownika, który jest zainteresowany zbadaniem produktów, które należą do kategorii Owoce morza. Na stronie zawierającej listę wszystkich produktów w jednym elemecie GridView użytkownik może zdecydować, że najlepszym rozwiązaniem jest sortowanie wyników według kategorii, które grupują razem wszystkie produkty z owoców morza. Po sortowaniu według kategorii użytkownik musi wyszukać listę, szukając miejsca rozpoczęcia i zakończenia produktów pogrupowanych na owoce morza. Ponieważ wyniki są uporządkowane alfabetycznie według nazwy kategorii znajdowanie produktów z owoców morza nie jest trudne, ale nadal wymaga ścisłego skanowania listy elementów w siatce.

Aby ułatwić wyróżnianie granic między posortowanych grup, wiele witryn internetowych używa interfejsu użytkownika, który dodaje separator między takimi grupami. Separatory, takie jak pokazane na rysunku 1, umożliwiają użytkownikowi szybkie znalezienie określonej grupy i zidentyfikowanie jej granic, a także ustalenie, jakie odrębne grupy istnieją w danych.

Każda grupa kategorii jest wyraźnie identyfikowana

Rysunek 1. Każda grupa kategorii jest wyraźnie zidentyfikowana (kliknij, aby wyświetlić obraz pełnowymiarowy)

W tym samouczku zobaczymy, jak utworzyć taki interfejs użytkownika sortowania.

Krok 1. Tworzenie standardowego, sortowalnego elementu GridView

Zanim dowiesz się, jak rozszerzyć element GridView w celu zapewnienia ulepszonego interfejsu sortowania, najpierw utwórzmy standardowy, sortowalny element GridView zawierający listę produktów. Zacznij od otwarcia CustomSortingUI.aspx strony w folderze PagingAndSorting . Dodaj element GridView do strony, ustaw jej ID właściwość na ProductList, i powiąż ją z nowym obiektem ObjectDataSource. Skonfiguruj obiekt ObjectDataSource, aby użyć ProductsBLL metody s GetProducts() klasy do wybierania rekordów.

Następnie skonfiguruj kontrolkę GridView tak, aby zawierała ProductNametylko pola , CategoryName, SupplierNamei UnitPrice BoundField oraz pole CheckBoxField zakończone. Na koniec skonfiguruj element GridView do obsługi sortowania, zaznaczając pole wyboru Włącz sortowanie w tagu inteligentnym GridView (lub przez ustawienie jej AllowSorting właściwości na true). Po dodaniu tych dodatków do CustomSortingUI.aspx strony deklaratywne znaczniki powinny wyglądać podobnie do następujących:

<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
    TypeName="ProductsBLL"></asp:ObjectDataSource>

Poświęć chwilę, aby zobaczyć postęp do tej pory w przeglądarce. Rysunek 2 przedstawia posortowany element GridView, gdy jego dane są sortowane według kategorii w kolejności alfabetycznej.

Sortowalne dane gridView są uporządkowane według kategorii

Rysunek 2. Dane elementu GridView sortowalnego są uporządkowane według kategorii (kliknij, aby wyświetlić obraz pełnowymiarowy)

Krok 2. Eksplorowanie technik dodawania wierszy separatora

Po zakończeniu sortowania GridView wszystkie pozostałe elementy mają być w stanie dodać wiersze separatora w siatce przed każdą unikatową posortowaną grupą. Ale jak można wstrzyknąć takie wiersze do GridView? Zasadniczo musimy iterować wiersze GridView, określić, gdzie występują różnice między wartościami w posortowanej kolumnie, a następnie dodać odpowiedni wiersz separatora. Myśląc o tym problemie, wydaje się naturalne, że rozwiązanie leży gdzieś w procedurze obsługi zdarzeń GridView RowDataBound . Jak omówiono w samouczku Niestandardowe formatowanie na podstawie danych , ta procedura obsługi zdarzeń jest często używana podczas stosowania formatowania na poziomie wiersza na podstawie danych wiersza. Jednak program RowDataBound obsługi zdarzeń nie jest tutaj rozwiązaniem, ponieważ nie można dodać wierszy do kontrolki GridView programowo z tego programu obsługi zdarzeń. Kolekcja GridView jest Rows w rzeczywistości tylko do odczytu.

Aby dodać dodatkowe wiersze do kontrolki GridView, mamy trzy opcje:

  • Dodaj te wiersze separatora metadanych do rzeczywistych danych powiązanych z elementem GridView
  • Po powiązaniu elementu GridView z danymi dodaj dodatkowe TableRow wystąpienia do kolekcji kontrolek GridView
  • Tworzenie niestandardowej kontrolki serwera, która rozszerza kontrolkę GridView i zastępuje te metody odpowiedzialne za konstruowanie struktury GridView

Utworzenie niestandardowej kontroli serwera byłoby najlepszym rozwiązaniem, jeśli ta funkcja była potrzebna na wielu stronach internetowych lub w kilku witrynach internetowych. Jednak wymagałoby to sporo kodu i dokładnej eksploracji głębi wewnętrznych prac GridView. W związku z tym nie rozważymy tej opcji dla tego samouczka.

Pozostałe dwie opcje dodawania wierszy separatora do rzeczywistych danych powiązanych z elementem GridView i manipulowania kolekcją kontrolek GridView po jej powiązaniu — atakują problem inaczej i zasługują na dyskusję.

Dodawanie wierszy do danych powiązanych z elementem GridView

Gdy element GridView jest powiązany ze źródłem danych, tworzy dla GridViewRow każdego rekordu zwróconego przez źródło danych. W związku z tym możemy wstrzyknąć wymagane wiersze separatora, dodając rekordy separatora do źródła danych przed powiązaniem go z elementem GridView. Rysunek 3 ilustruje tę koncepcję.

Jedna technika obejmuje dodawanie wierszy separatora do źródła danych

Rysunek 3. Jedna technika obejmuje dodawanie wierszy separatora do źródła danych

Używam rekordów separatora terminów w cudzysłowie, ponieważ nie ma specjalnego rekordu separatora; zamiast tego musimy jakoś oznaczyć flagę, że konkretny rekord w źródle danych służy jako separator, a nie normalny wiersz danych. W naszych przykładach ponownie utworzymy powiązanie wystąpienia z elementem ProductsDataTable GridView, który składa się z ProductRowselementu . Możemy oznaczyć rekord jako wiersz separatora, ustawiając jego CategoryID właściwość na -1 (ponieważ taka wartość nie mogła istnieć normalnie).

Aby skorzystać z tej techniki, należy wykonać następujące kroki:

  1. Programowe pobieranie danych w celu powiązania z elementem GridView (wystąpieniem ProductsDataTable )
  2. Sortowanie danych na podstawie właściwości GridView s SortExpressionSortDirection
  3. Iteracja w ProductsRows obiekcie ProductsDataTable, wyszukując, gdzie leżą różnice w posortowanej kolumnie
  4. W każdej granicy grupy wstrzyknąć wystąpienie rekordu ProductsRow separatora do tabeli DataTable, które ma ustawioną CategoryID wartość -1 (lub dowolne oznaczenie zostało wybrane, aby oznaczyć rekord jako rekord separatora )
  5. Po wstrzyknięciu wierszy separatora programowo powiąż dane z kontrolką GridView

Oprócz tych pięciu kroków musimy również udostępnić procedurę obsługi zdarzeń dla zdarzenia GridView.RowDataBound W tym miejscu sprawdziliśmy każdy DataRow z nich i ustaliliśmy, czy był to wiersz separatora, którego CategoryID ustawienie miało wartość -1. Jeśli tak, prawdopodobnie chcemy dostosować formatowanie lub tekst wyświetlany w komórkach.

Użycie tej techniki do wstrzykiwania granic grup sortowania wymaga nieco więcej pracy niż opisane powyżej, ponieważ należy również udostępnić procedurę obsługi zdarzeń dla zdarzenia GridView Sorting i śledzić SortExpression wartości i SortDirection .

Manipulowanie kolekcją kontrolek GridView po tym, jak została utworzona dane

Zamiast komunikatów o danych przed powiązaniem ich z obiektem GridView, możemy dodać wiersze separatora po powiązaniu danych z elementem GridView. Proces powiązania danych tworzy hierarchię kontrolki GridView, która w rzeczywistości jest po prostu wystąpieniem Table składającym się z kolekcji wierszy, z których każda składa się z kolekcji komórek. W szczególności kolekcja kontrolek GridView zawiera Table obiekt w jego katalogu głównym, GridViewRow (który pochodzi z klasy) dla każdego rekordu TableRow powiązanego DataSource z elementem GridView, oraz TableCell obiekt w każdym GridViewRow wystąpieniu dla każdego pola danych w elemecie DataSource.

Aby dodać wiersze separatora między poszczególnymi grupami sortowania, możemy bezpośrednio manipulować tą hierarchią sterowania po jej utworzeniu. Możemy mieć pewność, że hierarchia kontrolki GridView została utworzona po raz ostatni przez czas renderowania strony. W związku z tym to podejście zastępuje metodę Page klasy s Render , w której końcowa hierarchia kontrolki GridView jest aktualizowana w celu uwzględnienia potrzebnych wierszy separatora. Rysunek 4 ilustruje ten proces.

Alternatywna technika manipulowania hierarchią kontrolki GridView

Rysunek 4. Alternatywna technika manipuluje hierarchią kontrolki GridView (kliknij, aby wyświetlić obraz pełnowymiarowy)

W tym samouczku użyjemy tego ostatniego podejścia, aby dostosować środowisko użytkownika sortowania.

Uwaga

Kod, który przedstawiam w tym samouczku, jest oparty na przykładzie podanym w wpisie w blogu Teemu Keiski, Playing a Bit with GridView Sort Grouping (Granie trochę za pomocą grupowania sortowania GridView).

Krok 3. Dodawanie wierszy separatora do hierarchii kontrolki GridView s

Ponieważ chcemy dodać tylko wiersze separatora do hierarchii kontrolki GridView po utworzeniu hierarchii sterowania i utworzeniu jej po raz ostatni na tej wizycie strony, chcemy wykonać to dodanie na końcu cyklu życia strony, ale zanim rzeczywista hierarchia kontrolki GridView została renderowana w formacie HTML. Ostatnim możliwym punktem, w którym możemy to osiągnąć, jest Page zdarzenie klasy Render , które możemy zastąpić w naszej klasie kodu za pomocą następującego podpisu metody:

protected override void Render(HtmlTextWriter writer)
{
    // Add code to manipulate the GridView control hierarchy
    base.Render(writer);
}

Page Gdy oryginalna Render metoda klasy jest wywoływanabase.Render(writer), każda kontrolka na stronie zostanie renderowana, generując znaczniki na podstawie ich hierarchii sterowania. W związku z tym należy wywołać base.Render(writer)metodę , tak aby strona była renderowana, i że manipulowaliśmy hierarchią sterowania kontrolki GridView przed wywołaniem base.Render(writer)metody , tak aby wiersze separatora zostały dodane do hierarchii kontrolki GridView przed jego renderowaniem.

Aby wstrzyknąć nagłówki grup sortowania, najpierw musimy upewnić się, że użytkownik zażądał sortowania danych. Domyślnie zawartość kontrolki GridView nie jest sortowana i dlatego nie musimy wprowadzać żadnych nagłówków sortowania grup.

Uwaga

Jeśli chcesz, aby element GridView został posortowany według określonej kolumny po pierwszym załadowaniu strony, wywołaj metodę GridView Sort na pierwszej stronie (ale nie w kolejnych postbacks). Aby to zrobić, dodaj to wywołanie w procedurze Page_Load obsługi zdarzeń w ramach warunkowego if (!Page.IsPostBack) . Aby uzyskać więcej informacji na temat metody, zapoznaj się z samouczkiem dotyczącym stronicowania i sortowaniaSort danych raportu.

Przy założeniu, że dane zostały posortowane, następnym zadaniem jest określenie kolumny, według której dane zostały posortowane, a następnie przeskanowanie wierszy, które szukają różnic w wartościach tej kolumny. Poniższy kod gwarantuje, że dane zostały posortowane i znajdzie kolumnę, według której dane zostały posortowane:

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // Determine the index and HeaderText of the column that
        //the data is sorted by
        int sortColumnIndex = -1;
        string sortColumnHeaderText = string.Empty;
        for (int i = 0; i < ProductList.Columns.Count; i++)
        {
            if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression)
                == 0)
            {
                sortColumnIndex = i;
                sortColumnHeaderText = ProductList.Columns[i].HeaderText;
                break;
            }
        }
        // TODO: Scan the rows for differences in the sorted column�s values
}

Jeśli element GridView nie został jeszcze posortowany, właściwość GridView SortExpression nie zostanie ustawiona. W związku z tym chcemy dodać tylko wiersze separatora, jeśli ta właściwość ma pewną wartość. W przeciwnym razie musimy określić indeks kolumny, według której dane zostały posortowane. Jest to realizowane przez pętlę w kolekcji GridView Columns , wyszukując kolumnę, której SortExpression właściwość jest równa właściwości GridView s SortExpression . Oprócz indeksu kolumny pobieramy HeaderText również właściwość, która jest używana podczas wyświetlania wierszy separatora.

W przypadku indeksu kolumny, według której dane są sortowane, ostatnim krokiem jest wyliczenie wierszy elementu GridView. Dla każdego wiersza musimy określić, czy posortowana wartość kolumny różni się od wartości kolumny posortowanej w poprzednim wierszu. Jeśli tak, musimy wstrzyknąć nowe GridViewRow wystąpienie do hierarchii sterowania. Jest to realizowane za pomocą następującego kodu:

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // ... Code for finding the sorted column index removed for brevity ...
        // Reference the Table the GridView has been rendered into
        Table gridTable = (Table)ProductList.Controls[0];
        // Enumerate each TableRow, adding a sorting UI header if
        // the sorted value has changed
        string lastValue = string.Empty;
        foreach (GridViewRow gvr in ProductList.Rows)
        {
            string currentValue = gvr.Cells[sortColumnIndex].Text;
            if (lastValue.CompareTo(currentValue) != 0)
            {
                // there's been a change in value in the sorted column
                int rowIndex = gridTable.Rows.GetRowIndex(gvr);
                // Add a new sort header row
                GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex,
                    DataControlRowType.DataRow, DataControlRowState.Normal);
                TableCell sortCell = new TableCell();
                sortCell.ColumnSpan = ProductList.Columns.Count;
                sortCell.Text = string.Format("{0}: {1}",
                    sortColumnHeaderText, currentValue);
                sortCell.CssClass = "SortHeaderRowStyle";
                // Add sortCell to sortRow, and sortRow to gridTable
                sortRow.Cells.Add(sortCell);
                gridTable.Controls.AddAt(rowIndex, sortRow);
                // Update lastValue
                lastValue = currentValue;
            }
        }
    }
    base.Render(writer);
}

Ten kod rozpoczyna się programowo odwołując Table się do obiektu znajdującego się w katalogu głównym hierarchii kontrolek GridView i tworząc zmienną ciągu o nazwie lastValue. lastValue służy do porównywania wartości kolumny posortowanej w bieżącym wierszu z wartością poprzedniego wiersza. Następnie kolekcja GridView Rows jest wyliczana, a dla każdego wiersza wartość posortowanej kolumny jest przechowywana w zmiennej currentValue .

Uwaga

Aby określić wartość posortowanej kolumny określonego wiersza, należy użyć właściwości komórki Text . Działa to dobrze w przypadku obiektów BoundFields, ale nie będzie działać zgodnie z potrzebami w przypadku pól szablonów, pól CheckBoxFields itd. Wkrótce przyjrzymy się, jak uwzględnić alternatywne pola GridView.

Zmienne currentValue i lastValue są następnie porównywane. Jeśli różnią się one, musimy dodać nowy wiersz separatora do hierarchii sterowania. Jest to realizowane przez określenie indeksu GridViewRow kolekcji obiektów RowsTable, utworzenie nowych GridViewRow i wystąpień, a następnie dodanie TableCell elementu i TableCellGridViewRow do hierarchii sterowania.

Należy pamiętać, że samotny TableCell wiersz separatora jest sformatowany w taki sposób, że obejmuje całą szerokość elementu GridView, jest sformatowany przy użyciu SortHeaderRowStyle klasy CSS i ma jej Text właściwość, tak aby wyświetlała zarówno nazwę grupy sortowania (taką jak Category ) i wartość grupy (na przykład Napoje). lastValue Na koniec jest aktualizowana do wartości currentValue.

Klasa CSS używana do formatowania wiersza SortHeaderRowStyle nagłówka grupy sortowania musi być określona Styles.css w pliku. Możesz korzystać z dowolnych ustawień stylu, które cię podobają; Użyłem następujących elementów:

.SortHeaderRowStyle
{
    background-color: #c00;
    text-align: left;
    font-weight: bold;
    color: White;
}

W bieżącym kodzie interfejs sortowania dodaje nagłówki grup sortowania podczas sortowania według dowolnego pola ograniczenia (zobacz Rysunek 5, który przedstawia zrzut ekranu podczas sortowania według dostawcy). Jednak w przypadku sortowania według dowolnego innego typu pola (takiego jak CheckBoxField lub TemplateField) nagłówki grup sortowania nie są nigdzie znajdowane (zobacz Rysunek 6).

Interfejs sortowania zawiera nagłówki grup sortowania podczas sortowania według pól granic

Rysunek 5. Interfejs sortowania zawiera nagłówki grup sortowania podczas sortowania według pól granic (kliknij, aby wyświetlić obraz pełnowymiarowy)

Brak nagłówków grup sortowania podczas sortowania pola CheckBoxField

Rysunek 6. Brak nagłówków grup sortowania podczas sortowania pola CheckBoxField (kliknij, aby wyświetlić obraz pełnowymiarowy)

Przyczyną braku nagłówków grup sortowania podczas sortowania według pola CheckBoxField jest to, że kod obecnie używa tylko TableCell właściwości s Text do określenia wartości posortowanej kolumny dla każdego wiersza. W przypadku pola CheckBoxFields TableCell właściwość s Text jest pustym ciągiem. Zamiast tego wartość jest dostępna za pośrednictwem kontrolki sieci Web CheckBox znajdującej TableCell się w kolekcji.Controls

Aby obsłużyć typy pól innych niż BoundFields, musimy rozszerzyć kod, w którym currentValue jest przypisana zmienna, aby sprawdzić istnienie kontrolki CheckBox w TableCellControls kolekcji. Zamiast używać metody currentValue = gvr.Cells[sortColumnIndex].Text, zastąp ten kod następującym kodem:

string currentValue = string.Empty;
if (gvr.Cells[sortColumnIndex].Controls.Count > 0)
{
    if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox)
    {
        if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked)
            currentValue = "Yes";
        else
            currentValue = "No";
    }
    // ... Add other checks here if using columns with other
    //      Web controls in them (Calendars, DropDownLists, etc.) ...
}
else
    currentValue = gvr.Cells[sortColumnIndex].Text;

Ten kod sprawdza posortowaną kolumnę TableCell dla bieżącego wiersza, aby określić, czy w kolekcji Controls znajdują się jakiekolwiek kontrolki. Jeśli istnieją, a pierwsza kontrolka to CheckBox, currentValue zmienna jest ustawiona na Wartość Tak lub Nie, w zależności od właściwości CheckBox.Checked W przeciwnym razie wartość jest pobierana z TableCell właściwości s Text . Tę logikę można replikować w celu obsługi sortowania dla dowolnych pól szablonów, które mogą istnieć w elemecie GridView.

Po dodaniu powyższego kodu nagłówki grup sortowania są teraz obecne podczas sortowania według przerwanego pola CheckBoxField (patrz Rysunek 7).

Nagłówki grup sortowania są teraz obecne podczas sortowania pola CheckBoxField

Rysunek 7. Nagłówki grup sortowania są teraz obecne podczas sortowania pola CheckBoxField (kliknij, aby wyświetlić obraz pełnowymiarowy)

Uwaga

Jeśli masz produkty z wartościami NULL bazy danych dla CategoryIDpól , SupplierIDlub UnitPrice , te wartości będą wyświetlane jako puste ciągi w siatce domyślnie, co oznacza, że tekst wiersza separatora dla tych produktów z wartościami NULL będzie odczytywany jak Kategoria: (oznacza to, że nie ma nazwy po Kategorii: podobnie jak kategoria: napoje). Jeśli chcesz wyświetlić tutaj wartość, możesz ustawić właściwość BoundFields NullDisplayText na tekst, który chcesz wyświetlić, lub dodać instrukcję warunkową w metodzie Render podczas przypisywania currentValue wartości do właściwości wiersza separatoraText.

Podsumowanie

Element GridView nie zawiera wielu wbudowanych opcji dostosowywania interfejsu sortowania. Jednak przy użyciu kodu niskiego poziomu można dostosować hierarchię sterowania GridView w celu utworzenia bardziej dostosowanego interfejsu. W tym samouczku pokazano, jak dodać wiersz separatora grup sortowania dla posortowalnego elementu GridView, który łatwiej identyfikuje odrębne grupy i te granice. Aby uzyskać dodatkowe przykłady dostosowanych interfejsów sortowania, zapoznaj się z wpisem na blogu Scott Guthrie s A Few ASP.NET 2.0 GridView Sortuj porady i wskazówki.

Szczęśliwe programowanie!

Informacje o autorze

Scott Mitchell, autor siedmiu książek ASP/ASP.NET i założyciel 4GuysFromRolla.com, współpracuje z technologiami internetowymi firmy Microsoft od 1998 roku. Scott pracuje jako niezależny konsultant, trener i pisarz. Jego najnowsza książka to Sams Teach Yourself ASP.NET 2.0 w ciągu 24 godzin. Można do niego dotrzeć pod adresem mitchell@4GuysFromRolla.com. Lub za pośrednictwem swojego bloga, który można znaleźć na stronie http://ScottOnWriting.NET.