Vytvoření vlastního uživatelského rozhraní pro řazení (C#)
Při zobrazení dlouhého seznamu seřazených dat může být velmi užitečné seskupit související data zavedením oddělovače řádků. V tomto kurzu se dozvíte, jak vytvořit takové uživatelské rozhraní pro řazení.
Úvod
Při zobrazení dlouhého seznamu seřazených dat, kde je v seřazených sloupcích jen několik různých hodnot, může být pro koncového uživatele obtížné rozpoznat, kde přesně dochází k rozdílům. V databázi je například 81 produktů, ale pouze devět různých kategorií (osm jedinečných kategorií plus NULL
možnost). Vezměte v úvahu případ uživatele, který má zájem o zkoumání produktů, které spadají do kategorie Mořské plody. Na stránce, která obsahuje seznam všech produktů v jednom GridView, se uživatel může rozhodnout, že nejlepší tip je seřadit výsledky podle kategorie, která seskupí všechny produkty z mořských plodů dohromady. Po seřazení podle kategorie musí uživatel projít seznam a hledat, kde začínají a končí produkty seskupené z mořských plodů. Vzhledem k tomu, že výsledky jsou seřazeny abecedně podle názvu kategorie, není nalezení produktů z mořských plodů obtížné, ale stále vyžaduje důkladnou kontrolu seznamu položek v mřížce.
Aby bylo možné zvýraznit hranice mezi seřazenými skupinami, mnoho webů používá uživatelské rozhraní, které mezi tyto skupiny přidává oddělovač. Oddělovače, jako jsou ty zobrazené na obrázku 1, umožňují uživateli rychleji najít konkrétní skupinu a identifikovat její hranice a také zjistit, jaké jedinečné skupiny v datech existují.
Obrázek 1: Každá skupina kategorií je jasně identifikována (kliknutím zobrazíte obrázek v plné velikosti)
V tomto kurzu se dozvíte, jak vytvořit takové uživatelské rozhraní pro řazení.
Krok 1: Vytvoření standardního seřaditelného objektu GridView
Než prozkoumáme, jak rozšířit GridView o vylepšené rozhraní pro řazení, pojďme nejprve vytvořit standardní, řaditelný GridView, který obsahuje seznam produktů. Začněte otevřením CustomSortingUI.aspx
stránky ve PagingAndSorting
složce. Přidejte objekt GridView na stránku, nastavte jeho ID
vlastnost na ProductList
a vytvořte vazbu s novým objektem ObjectDataSource. Nakonfigurujte ObjectDataSource tak, aby pro výběr záznamů používal metodu ProductsBLL
třídy s GetProducts()
.
Dále nakonfigurujte Objekt GridView tak, aby obsahoval ProductName
pouze , CategoryName
SupplierName
, a UnitPrice
BoundFields a Ukončené pole CheckBoxField. Nakonec nakonfigurujte GridView tak, aby podporoval řazení zaškrtnutím políčka Povolit řazení v inteligentní značce GridView (nebo nastavením jeho AllowSorting
vlastnosti na true
). Po provedení těchto přidání na CustomSortingUI.aspx
stránku by deklarativní kód měl vypadat nějak takto:
<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>
Podívejte se na náš dosavadní pokrok v prohlížeči. Obrázek 2 znázorňuje řaditelný objekt GridView, když jsou jeho data seřazena podle kategorií v abecedním pořadí.
Obrázek 2: Data typu GridView s řazením jsou seřazená podle kategorie (kliknutím zobrazíte obrázek v plné velikosti)
Krok 2: Prozkoumání technik pro přidání řádků oddělovače
Po dokončení obecného objektu GridView, který lze seřadit, zbývá jen přidat řádky oddělovače v GridView před každou jedinečnou seřazenou skupinu. Ale jak mohou být takové řádky vloženy do GridView? V podstatě potřebujeme iterovat řádky GridView s, určit, kde dochází k rozdílům mezi hodnotami v seřazených sloupcích, a pak přidat příslušný řádek oddělovače. Při úvahách o tomto problému se zdá přirozené, že řešení leží někde v obslužné rutině události GridView s RowDataBound
. Jak jsme probrali v kurzu Vlastní formátování založené na datech , tato obslužná rutina události se běžně používá při použití formátování na úrovni řádků na základě dat řádků. Obslužná rutina RowDataBound
události zde však není řešením, protože řádky nelze přidat do objektu GridView programově z této obslužné rutiny události. Kolekce GridView s Rows
je ve skutečnosti jen pro čtení.
Pokud chcete do objektu GridView přidat další řádky, máme tři možnosti:
- Přidejte tyto řádky oddělovače metadat ke skutečným datům, která jsou svázaná s objektem GridView.
- Po vazbě objektu GridView na data přidejte do kolekce ovládacích prvků GridView s další
TableRow
instance. - Vytvoření vlastního serverového ovládacího prvku, který rozšiřuje ovládací prvek GridView a přepíše metody zodpovědné za vytvoření struktury GridView s
Vytvoření vlastního serverového ovládacího prvku by bylo nejlepší, pokud by tato funkce byla potřebná na mnoha webových stránkách nebo na několika webech. To by však vyžadovalo poměrně málo kódu a důkladné prozkoumání do hloubky vnitřních fungování GridView. Proto tuto možnost v tomto kurzu nebudeme brát v úvahu.
Další dvě možnosti přidání oddělovacích řádků do skutečných dat vázaných na GridView a manipulace s gridView s kolekce ovládacích prvků po jeho vazbě - útok na problém odlišně a zaslouží si diskuzi.
Přidání řádků k datům vázaným na GridView
Když je Objekt GridView vázán na zdroj dat, vytvoří GridViewRow
pro každý záznam vrácený zdrojem dat. Proto můžeme vložit potřebné oddělovací řádky přidáním záznamů oddělovače do zdroje dat před jeho vazbou na GridView. Tento koncept znázorňuje obrázek 3.
Obrázek 3: Jedna technika zahrnuje přidání oddělovačů řádků do zdroje dat
Používám termín oddělovač záznamů v uvozovkách, protože neexistuje žádný zvláštní oddělovač záznamu; místo toho musíme nějak označit příznakem, že konkrétní záznam ve zdroji dat slouží jako oddělovač, nikoli jako normální řádek dat. V našich příkladech svážeme instanci s objektem ProductsDataTable
GridView, který se skládá z objektu ProductRows
. Záznam můžeme označit jako řádek oddělovače nastavením jeho CategoryID
vlastnosti na -1
(protože taková hodnota nemohla normálně existovat).
Abychom mohli tuto techniku využít, museli bychom provést následující kroky:
- Programové načtení dat pro vazbu na GridView (
ProductsDataTable
instance) - Řazení dat na základě vlastností GridView
SortExpression
SortDirection
- Iterujte v
ProductsDataTable
souboru a vyhledejteProductsRows
, kde leží rozdíly v seřazených sloupcích. - Na hranici každé skupiny vložte instanci oddělovače záznamu
ProductsRow
do tabulky DataTable, která je nastavenáCategoryID
na-1
(nebo jakékoli označení bylo rozhodnuto označit záznam jako oddělovač záznamu). - Po vložení oddělovačů řádky, programově svázat data s GridView
Kromě těchto pěti kroků bychom také potřebovali poskytnout obslužnou rutinu události pro událost GridView s RowDataBound
. Tady každý z nich DataRow
zkontrolujeme a určíme, jestli se jedná o řádek oddělovače, jehož CategoryID
nastavení bylo -1
. Pokud ano, pravděpodobně bychom chtěli upravit jeho formátování nebo text zobrazený v buňkách.
Použití této techniky pro vložení hranic skupin řazení vyžaduje trochu více práce, než je uvedeno výše, protože potřebujete také poskytnout obslužnou rutinu události pro událost GridView s Sorting
a sledovat SortExpression
hodnoty a SortDirection
.
Manipulace se kolekcí ovládacích prvků GridView s po příchozím datu
Místo zasílání zpráv o datech před jejich vazbou na GridView, můžeme přidat řádky oddělovače poté , co byla data svázána s GridView. Proces datové vazby vytváří hierarchii ovládacích prvků GridView s, což je ve skutečnosti jednoduše Table
instance složená z kolekce řádků, z nichž každý se skládá z kolekce buněk. Konkrétně kolekce ovládacích prvků GridView s obsahuje Table
objekt v kořenovém adresáři, GridViewRow
objekt (který je odvozen z TableRow
třídy) pro každý záznam ve DataSource
vazbě na GridView a TableCell
objekt v každé GridViewRow
instanci pro každé datové pole v objektu DataSource
.
Pokud chcete přidat oddělovací řádky mezi každou skupinu řazení, můžeme s touto hierarchií ovládacích prvků po jejím vytvoření přímo manipulovat. Můžeme si být jistí, že hierarchie ovládacích prvků GridView s byla naposledy vytvořena v době vykreslení stránky. Proto tento přístup přepíše metodu Page
třídy s Render
, v tomto okamžiku se aktualizuje hierarchie ovládacích prvků GridView s tak, aby zahrnovala potřebné oddělovací řádky. Tento proces znázorňuje obrázek 4.
Obrázek 4: Alternativní technika manipuluje s hierarchií ovládacích prvků GridView (kliknutím zobrazíte obrázek v plné velikosti)
V tomto kurzu použijeme tento druhý přístup k přizpůsobení uživatelského prostředí řazení.
Poznámka
Kód, který prezentuji v tomto kurzu, je založený na příkladu uvedeném v položce blogu Teemu Keiski s Hrou bitů se seskupováním řazení GridView.
Krok 3: Přidání oddělovače řádků do hierarchie ovládacích prvků GridView s
Vzhledem k tomu, že chceme přidat oddělovací řádky do hierarchie ovládacích prvků GridView s až po vytvoření a vytvoření hierarchie ovládacích prvků při poslední návštěvě této stránky, chceme toto přidání provést na konci životního cyklu stránky, ale před vykreslení skutečné hierarchie ovládacích prvků GridView do HTML. Poslední možný bod, ve kterém toho můžeme dosáhnout, je Page
událost třídy s Render
, kterou můžeme přepsat v naší třídě kódu na pozadí pomocí následujícího podpisu metody:
protected override void Render(HtmlTextWriter writer)
{
// Add code to manipulate the GridView control hierarchy
base.Render(writer);
}
Při vyvolání Page
base.Render(writer)
původní Render
metody třídy s se vykreslí každý ovládací prvek na stránce a vygeneruje značky na základě jejich hierarchie ovládacích prvků. Proto je nutné, abychom oba volali base.Render(writer)
, aby stránka byla vykreslena a abychom před voláním base.Render(writer)
manipulovali s hierarchií ovládacích prvků GridView s tak, aby oddělovací řádky byly přidány do hierarchie ovládacích prvků GridView s před jeho vykreslení.
Pokud chcete vložit hlavičky skupiny řazení, musíme nejprve zajistit, aby uživatel požádal o seřazení dat. Ve výchozím nastavení není obsah GridView s seřazen, a proto nemusíme zadávat záhlaví řazení skupin.
Poznámka
Pokud chcete, aby byl GridView při prvním načtení stránky seřazen podle konkrétního sloupce, zavolejte metodu GridView s Sort
při první návštěvě stránky (ale ne při následných postbackech). Chcete-li toho dosáhnout, přidejte toto volání do obslužné rutiny Page_Load
události v rámci podmíněného if (!Page.IsPostBack)
. Další Sort
informace o metodě najdete v kurzu Stránkování a řazení dat sestavy.
Za předpokladu, že jsou data seřazená, je naším dalším úkolem určit, podle kterého sloupce byla data seřazena, a pak prohledávat řádky a hledat rozdíly v hodnotách daného sloupce. Následující kód zajistí, že jsou data seřazená, a najde sloupec, podle kterého byla data seřazena:
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
}
Pokud GridView ještě není seřazen, GridView s SortExpression
vlastnost nebyla nastavena. Proto chceme přidat řádky oddělovače pouze v případě, že tato vlastnost má nějakou hodnotu. Pokud ano, musíme dále určit index sloupce, podle kterého byla data seřazena. Toho lze dosáhnout tak, že procházíme kolekci GridView s Columns
a vyhledáte sloupec, jehož SortExpression
vlastnost se rovná vlastnosti GridView s SortExpression
. Kromě indexu sloupce uchopíme HeaderText
také vlastnost, která se používá při zobrazení řádků oddělovače.
S indexem sloupce, podle kterého jsou data seřazena, je posledním krokem výčet řádků GridView. U každého řádku musíme zjistit, jestli se hodnota seřazeného sloupce liší od hodnoty seřazeného sloupce předchozího řádku. Pokud ano, musíme do řídicí hierarchie vložit novou GridViewRow
instanci. K tomu slouží následující kód:
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);
}
Tento kód začíná programovým odkazem na Table
objekt nalezený v kořenovém adresáři hierarchie ovládacích prvků GridView a vytvoří řetězcovou proměnnou s názvem lastValue
. lastValue
slouží k porovnání hodnoty seřazeného sloupce aktuálního řádku s hodnotou s předchozím řádkem. Dále je výčet kolekce GridView s Rows
a pro každý řádek je hodnota seřazeného sloupce uložena currentValue
v proměnné .
Poznámka
K určení hodnoty konkrétního seřazeného sloupce řádku používám vlastnost buňky s Text
. To funguje dobře pro BoundFields, ale nebude fungovat podle potřeby pro TemplateFields, CheckBoxFields atd. Za chvíli se podíváme na to, jak zohlednit alternativní pole GridView.
Proměnné currentValue
a lastValue
se pak porovnávají. Pokud se liší, musíme do hierarchie ovládacích prvků přidat nový řádek oddělovače. Toho se dosahuje určením indexu objektu GridViewRow
Table
v kolekci objektů Rows
, vytvořením nových GridViewRow
instancí a TableCell
a následným přidáním TableCell
a GridViewRow
do hierarchie ovládacích prvků.
Všimněte si, že oddělovací řádek TableCell
je naformátován tak, aby přesahoval celou šířku GridView, je formátován pomocí SortHeaderRowStyle
třídy CSS a má svou Text
vlastnost tak, že zobrazuje název skupiny řazení (například Category ) a hodnotu skupiny (například Beverages ). lastValue
Nakonec se aktualizuje na hodnotu currentValue
.
Třída CSS použitá k formátování řádku SortHeaderRowStyle
záhlaví řazení skupiny musí být zadána Styles.css
v souboru. Nebojte se použít jakýkoli styl nastavení, které vás osloví; Použil(a) jsem následující:
.SortHeaderRowStyle
{
background-color: #c00;
text-align: left;
font-weight: bold;
color: White;
}
S aktuálním kódem přidává rozhraní pro řazení hlavičky skupin řazení při řazení podle libovolného BoundField (viz Obrázek 5, který ukazuje snímek obrazovky při řazení podle dodavatele). Při řazení podle jakéhokoli jiného typu pole (například CheckBoxField nebo TemplateField) ale záhlaví skupin řazení nikde nenajdete (viz Obrázek 6).
Obrázek 5: Rozhraní pro řazení zahrnuje při řazení podle boundfields záhlaví skupin (kliknutím zobrazíte obrázek v plné velikosti)
Obrázek 6: Při řazení CheckBoxField chybí záhlaví skupiny řazení (kliknutím zobrazíte obrázek v plné velikosti)
Důvod, proč při řazení podle CheckBoxField chybí záhlaví skupiny řazení, je, že kód aktuálně používá pouze TableCell
vlastnost s Text
k určení hodnoty seřazeného sloupce pro každý řádek. Pro CheckBoxFields TableCell
je vlastnost s Text
prázdný řetězec. Místo toho je hodnota k dispozici prostřednictvím ovládacího prvku CheckBox web, který se nachází v kolekci TableCell
s Controls
.
Abychom mohli zpracovávat jiné typy polí než BoundFields, potřebujeme rozšířit kód, kterému currentValue
je proměnná přiřazena, abychom zkontrolovali existenci CheckBoxu v kolekci TableCell
s Controls
. Místo příkazu currentValue = gvr.Cells[sortColumnIndex].Text
nahraďte tento kód následujícím kódem:
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;
Tento kód prozkoumá seřazený sloupec TableCell
pro aktuální řádek a určí, jestli kolekce Controls
obsahuje nějaké ovládací prvky. Pokud ano a první ovládací prvek je CheckBox, currentValue
proměnná je nastavena na Ano nebo Ne v závislosti na vlastnosti CheckBox s Checked
. V opačném případě se hodnota převezme z TableCell
vlastnosti s Text
. Tuto logiku lze replikovat pro zpracování řazení pro všechny TemplateFields, které mohou existovat v GridView.
S výše uvedeným přidáním kódu jsou teď při řazení podle ukončeného pole CheckBoxField k dispozici záhlaví skupin řazení (viz Obrázek 7).
Obrázek 7: Při řazení CheckBoxField jsou teď k dispozici záhlaví skupiny řazení (kliknutím zobrazíte obrázek v plné velikosti)
Poznámka
Pokud máte produkty s databázovými NULL
hodnotami pro CategoryID
pole , SupplierID
nebo UnitPrice
, tyto hodnoty se ve výchozím nastavení zobrazí jako prázdné řetězce v GridView, což znamená, že text řádku oddělovače pro tyto produkty s NULL
hodnotami bude vypadat jako Category: (to znamená, že neexistuje název za Category: like with Category: Beverages ). Pokud chcete, aby se zde zobrazila hodnota, můžete buď nastavit vlastnost BoundFields NullDisplayText
na text, který chcete zobrazit, nebo můžete přidat podmíněný příkaz v render metoda při přiřazení currentValue
k vlastnosti oddělovače řádkuText
.
Souhrn
GridView neobsahuje mnoho předdefinovaných možností pro přizpůsobení rozhraní řazení. S trochou kódu nízké úrovně je však možné upravit hierarchii ovládacích prvků GridView a vytvořit více přizpůsobené rozhraní. V tomto kurzu jsme viděli, jak přidat řádek řazení oddělovače skupin pro seřaditelný Objekt GridView, který snadněji identifikuje jedinečné skupiny a jejich hranice. Další příklady přizpůsobených rozhraní pro řazení najdete na blogu Scott Guthrie s A Few ASP.NET 2.0 GridView Tipy a triky pro řazení .
Všechno nejlepší na programování!
O autorovi
Scott Mitchell, autor sedmi knih o ASP/ASP.NET a zakladatel 4GuysFromRolla.com, pracuje s webovými technologiemi Microsoftu od roku 1998. Scott pracuje jako nezávislý konzultant, školitel a spisovatel. Jeho nejnovější kniha je Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Můžete ho zastihnout na mitchell@4GuysFromRolla.comadrese . nebo prostřednictvím jeho blogu, který najdete na adrese http://ScottOnWriting.NET.