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 bardzo przydatne może być grupowanie powiązanych danych przez wprowadzenie wierszy separatora. W tym samouczku pokażemy, jak stworzyć interfejs użytkownika do 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óżnicy. 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 badaniem produktów, które należą do kategorii Owoce morza. Na stronie zawierającej listę wszystkich produktów w jednym elemencie GridView, użytkownik może zdecydować, że najlepszym rozwiązaniem jest sortowanie wyników według kategorii, co pogrupuje razem wszystkie produkty z owoców morza. Po posortowaniu według kategorii użytkownik musi następnie wyszukać, gdzie zaczynają się i kończą produkty pogrupowane 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óżnienie granic między posortowanych grup, wiele witryn internetowych korzysta z interfejsu użytkownika, który dodaje separator między takimi grupami. Separatory, takie jak pokazane na rysunku 1, umożliwiają użytkownikowi szybsze znajdowanie określonej grupy i identyfikowanie 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 o pełnym rozmiarze)

W tym samouczku pokażemy, jak stworzyć interfejs użytkownika do sortowania.

Krok 1. Tworzenie standardowego, sortowalnego obiektu GridView

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

Następnie skonfiguruj kontrolkę GridView tak, aby zawierała tylko pola ProductName, CategoryName, SupplierName i UnitPrice BoundFields oraz pole wyboru Discontinued. Na koniec skonfiguruj kontrolkę 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 znacznik deklaratywny powinien wyglądać podobnie do następującego:

<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ć nasze dotychczasowe postępy w swojej przeglądarce. Rysunek 2 przedstawia posortowany element GridView, gdy jego dane są sortowane według kategorii w kolejności alfabetycznej.

Sortowalne dane kontrolki GridView są uporządkowane według kategorii

Rysunek 2. Sortowanie danych kontrolki GridView jest uporządkowane według kategorii (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Krok 2. Eksplorowanie technik dodawania wierszy separatora

Po ukończeniu ogólnego, sortowalnego GridView, wszystko, co pozostaje, to możliwość dodania wierszy separatora w GridView przed każdą unikatową grupą posortowaną. Jak można jednak wstrzyknąć takie wiersze do kontrolki GridView? Zasadniczo musimy przechodzić przez wiersze kontrolki 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 . Zgodnie z opisem w samouczku Formatowanie niestandardowe na podstawie danych, ten program obsługi zdarzeń jest często używany podczas stosowania formatowania na poziomie wiersza na podstawie jego danych. Jednak RowDataBound procedura obsługi zdarzeń nie jest tutaj rozwiązaniem, ponieważ nie można programowo dodać wierszy do kontrolki GridView w ramach tej procedury. Kolekcja GridView Rows jest 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 kontrolką GridView
  • Po powiązaniu kontrolki GridView z danymi, dodaj dodatkowe wystąpienia TableRow do kolekcji kontrolki 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 sieci Web lub w kilku witrynach sieci Web. Jednak wiązałoby się to z dość dużą ilością kodu i szczegółową eksploracją głębi wewnętrznych funkcji GridView. W związku z tym nie weźmiemy pod uwagę tej opcji dla tego samouczka.

Pozostałe dwie opcje dodawania wierszy separatora do rzeczywistych danych powiązanych z kontrolką 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 kontrolką GridView

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

Jedna technika polega na dodawaniu wierszy separatora do źródła danych

Rysunek 3. Jedna technika polega na dodawaniu wierszy separatora do źródła danych

Używam rekordu separatora jako przenośni, ponieważ nie ma specjalnego rekordu separatora; zamiast tego musimy w jakiś sposób oznaczyć, że konkretny rekord w źródle danych służy jako separator, a nie zwykła linia danych. W naszych przykładach wiążemy wystąpienie ProductsDataTable z elementem GridView, który składa się z ProductRows. Możemy oznaczyć rekord jako wiersz separatora, ustawiając jego CategoryID właściwość na -1 (ponieważ taka wartość nie może istnieć normalnie).

Aby użyć tej techniki, należy wykonać następujące czynności:

  1. Automatyczne pobranie danych w celu powiązania z elementem GridView (instancją ProductsDataTable)
  2. Sortowanie danych na podstawie właściwości GridView s SortExpression i SortDirection
  3. Iterowanie po ProductsRows w ramach ProductsDataTable, w poszukiwaniu różnic w posortowanej kolumnie
  4. Na każdej granicy grupy, wprowadzić wystąpienie rekordu ProductsRow separatora do tabeli DataTable, które ma CategoryID ustawione na -1 (lub jakiekolwiek inne oznaczenie zostało wybrane, aby oznaczyć rekord jako rekord separatora)
  5. Po dodaniu wierszy separatorów, programowo powiąż dane z kontrolką GridView

Oprócz tych pięciu kroków należałoby również podać procedurę obsługi zdarzeń dla zdarzenia GridView RowDataBound . W tym miejscu sprawdzalibyśmy każdy DataRow i ustalali, czy był to wiersz separatora, którego ustawieniem CategoryID było -1. Jeśli tak, prawdopodobnie chcemy dostosować jego formatowanie lub tekst wyświetlany w komórkach.

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

Manipulowanie kolekcji kontrolek GridView po związaniu danych

Zamiast formatowania danych przed powiązaniem ich z kontrolką GridView, możemy dodać wiersze oddzielające po powiązaniu danych z kontrolką GridView. Proces powiązania danych tworzy hierarchię sterowania 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 obiekt Table w katalogu głównym, obiekt GridViewRow, który jest pochodną klasy TableRow, dla każdego rekordu w DataSource powiązanym z kontrolką GridView, oraz obiekt TableCell w każdym wystąpieniu GridViewRow dla każdego pola danych w obiekcie 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 sterowania kontrolki GridView została utworzona po raz ostatni przez czas renderowania strony. W związku z tym to podejście zastępuje metodę klasy PageRender, dzięki czemu ostateczna hierarchia kontrolna GridView jest aktualizowana, aby uwzględniać wymagane wiersze separatora. Rysunek 4 ilustruje ten proces.

Alternatywna technika zarządza hierarchią elementów sterujących 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ć doświadczenie użytkownika związane z sortowaniem.

Uwaga / Notatka

Kod, który przedstawiam w tym samouczku, jest oparty na przykładzie podanym w wpisie na blogu Teemu Keiski, Playing a Bit with GridView Sort Grouping.

Krok 3. Dodawanie wierszy separatora do hierarchii kontrolki GridView

Ponieważ chcemy dodać wiersze separatora do hierarchii kontrolki GridView dopiero po jej utworzeniu i ostatecznym zaktualizowaniu podczas tej wizyty na stronie, chcemy wykonać to działanie na końcu cyklu życia strony, ale zanim rzeczywista hierarchia kontrolki GridView zostanie wyrenderowana do HTML. Ostatnim możliwym punktem, w którym możemy to osiągnąć, jest Page zdarzenie klasy Render, które możemy przesłonić w naszej klasie code-behind 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 Po wywołaniu Render oryginalnej base.Render(writer) metody klasy każda kontrolka na stronie zostanie renderowana, generując znaczniki na podstawie ich hierarchii sterowania. W związku z tym konieczne jest, abyśmy obaj wywołali base.Render(writer), dzięki czemu strona zostanie wyrenderowana, oraz abyśmy manipulowali hierarchią kontroli GridView przed wywołaniem base.Render(writer), tak aby wiersze separatora zostały dodane do hierarchii kontroli GridView przed jego renderowaniem.

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

Uwaga / Notatka

Jeśli chcesz, aby kontrolka GridView była posortowana według określonej kolumny przy pierwszym załadowaniu strony, wywołaj metodę GridView Sort przy pierwszej wizycie na stronie (ale nie przy kolejnych żądaniach zwrotnych). Aby to zrobić, dodaj to wywołanie w procedurze Page_Load obsługi zdarzeń w instrukcji warunkowej if (!Page.IsPostBack). Aby uzyskać więcej informacji na temat metody, zapoznaj się z samouczkiem Sort.

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 w poszukiwaniu różnic w wartościach tej kolumny. Poniższy kod gwarantuje, że dane zostały posortowane i znajdują 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 GridView nie został jeszcze posortowany, wówczas właściwość SortExpression dla GridView nie zostanie ustawiona. W związku z tym chcemy dodać tylko wiersze separatora, jeśli ta właściwość ma pewną wartość. Jeśli tak się dzieje, musimy następnie określić indeks kolumny, według której dane zostały posortowane. Jest to osiągane przez przechodzenie przez kolekcję GridView Columns, wyszukując kolumnę, której właściwość SortExpression jest równa właściwości siatki GridView SortExpression. Oprócz indeksu kolumny pobieramy również właściwość HeaderText, która jest używana podczas wyświetlania wierszy separatorów.

Mając indeks kolumny, według której posortowane są dane, ostatnim krokiem jest wyliczenie wierszy w kontrolce GridView. Dla każdego wiersza musimy określić, czy wartość posortowanej kolumny różni się od wartości posortowanej kolumny w poprzednim wierszu. Jeśli tak, musimy wstrzyknąć nowe GridViewRow wystąpienie do hierarchii sterowania. Jest to realizowane przy użyciu 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ę odwołując się programowo do obiektu Table znajdującego się na początku hierarchii kontrolki GridView i tworzy zmienną ciągu o nazwie lastValue. lastValue służy do porównania wartości posortowanej kolumny w bieżącym wierszu z wartością w poprzednim wierszu. Następnie kolekcja GridView Rows jest wyliczana, a dla każdego wiersza wartość posortowanej kolumny jest przechowywana w zmiennej currentValue .

Uwaga / Notatka

Aby określić wartość określonej kolumny posortowanego wiersza, użyję właściwości komórki Text. Działa to dobrze w przypadku obiektów BoundFields, ale nie będzie działać zgodnie z oczekiwaniami dla pól TemplateFields, CheckBoxFields itd. Wkrótce przyjrzymy się sposobom uwzględnienia alternatywnych pól 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 w kolekcji obiektu TableRows, utworzenie nowych wystąpień GridViewRow i TableCell, a następnie dodanie TableCell i GridViewRow do hierarchii kontrolnej.

Należy pamiętać, że samotny TableCell wiersz separatora jest sformatowany tak, aby obejmował całą szerokość kontrolki GridView, jest sformatowany przy użyciu SortHeaderRowStyle klasy CSS i ma jego Text właściwość, tak aby wyświetlała zarówno nazwę grupy sortowania (np. Kategoria ) i wartość grupy (na przykład Napoje). lastValue Na koniec zostanie zaktualizowany do wartości currentValue.

Klasa CSS, która jest używana do formatowania wiersza nagłówka grupy sortowania SortHeaderRowStyle, powinna być określona w pliku Styles.css. Możesz używać dowolnych ustawień stylu, które cię podobają; Użyto 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 Pole wyboru lub Pole szablonu) nagłówki grup sortowania nie są nigdzie znajdowane (zobacz Rysunek 6).

Interfejs sortowania uwzględnia nagłówki grup sortowania podczas sortowania według pól BoundFields

Rysunek 5. Interfejs sortowania zawiera nagłówki grup sortowania podczas sortowania według pól ograniczenia (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 używa obecnie tylko TableCell właściwości s Text do ustalania wartości posortowanej kolumny dla każdego wiersza. W przypadku pól CheckBoxFields, właściwość 'TableCell s Text' jest pustym ciągiem znaków; wartość jest dostępna za pośrednictwem kontrolki sieciowej CheckBox, która znajduje się w kolekcji 'TableCell s Controls'.

Aby obsłużyć typy pól inne niż BoundFields, musimy rozszerzyć kod, w którym currentValue jest przypisywana zmienna, aby sprawdzić, czy w kolekcji TableCellControls istnieje kontrolka CheckBox. Zamiast używać kodu currentValue = gvr.Cells[sortColumnIndex].Text, zastąp go następującym:

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 znajdują się jakieś kontrolki Controls . Jeśli istnieje, a pierwsza kontrolka to CheckBox, zmienna currentValue jest ustawiona na Tak lub Nie, w zależności od właściwości Checked CheckBox. W przeciwnym razie wartość jest pobierana z TableCell s Text właściwości. Tę logikę można replikować w celu obsługi sortowania dla wszelkich pól szablonu, które mogą istnieć w elemencie GridView.

Po dodaniu powyższego kodu nagłówki grup sortowania są teraz obecne podczas sortowania według przerwanego pola CheckBoxField (zobacz 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 / Notatka

Jeśli masz produkty z wartościami NULL bazy danych dla pól CategoryID, SupplierID lub UnitPrice, te wartości będą domyślnie wyświetlane jako puste ciągi w widoku GridView, co oznacza, że tekst wiersza separatora dla tych produktów z wartościami NULL będzie wyglądał jak Kategoria: (to znaczy, że po Kategoria: nie ma nazwy, tak jak w przypadku 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 właściwości separatora wierszyText.

Podsumowanie

Kontrolka 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 pokazaliśmy, jak dodać wiersz separatora dla grup sortowania w posortowalnym GridView, co ułatwia identyfikację odrębnych grup oraz ich granic. Aby zapoznać się z dodatkowymi przykładami dostosowanych interfejsów sortowania, zajrzyj do wpisu na blogu Scotta Guthrie pt. "A Few ASP.NET 2.0 GridView Sorting Tips and Tricks" (Kilka wskazówek dotyczących sortowania w ASP.NET 2.0 GridView).

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 go uzyskać pod adresem mitchell@4GuysFromRolla.com.