Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Quando si visualizza un lungo elenco di dati ordinati, può essere molto utile raggruppare i dati correlati introducendo righe separatori. In questa esercitazione verrà illustrato come creare un'interfaccia utente di ordinamento di questo tipo.
Introduzione
Quando si visualizza un lungo elenco di dati ordinati in cui sono presenti solo pochi valori diversi nella colonna ordinata, un utente finale potrebbe trovare difficile distinguere dove, esattamente, si verificano i limiti della differenza. Ad esempio, nel database sono presenti 81 prodotti, ma solo nove opzioni di categoria diverse (otto categorie univoche più l'opzione NULL
). Si consideri il caso di un utente interessato a esaminare i prodotti che rientrano nella categoria Pesce. Da una pagina che elenca tutti i prodotti in un singolo GridView, l'utente potrebbe decidere che l'opzione migliore è ordinare i risultati per categoria, il che raggrupperà tutti i prodotti ittici insieme. Dopo l'ordinamento per categoria, l'utente deve quindi eseguire la ricerca nell'elenco, cercando dove iniziano e terminano i prodotti raggruppati per pesce. Poiché i risultati sono ordinati alfabeticamente in base al nome della categoria, trovare i prodotti di pesce non è difficile, ma richiede comunque una scansione attenta dell'elenco di elementi nella griglia.
Per evidenziare i limiti tra gruppi ordinati, molti siti Web usano un'interfaccia utente che aggiunge un separatore tra tali gruppi. I separatori come quelli mostrati nella figura 1 consentono a un utente di trovare più rapidamente un determinato gruppo e di identificarne i limiti, nonché di verificare quali gruppi distinti esistono nei dati.
Figura 1: Ogni gruppo di categorie è chiaramente identificato (fare clic per visualizzare l'immagine a dimensione intera)
In questa esercitazione verrà illustrato come creare un'interfaccia utente di ordinamento di questo tipo.
Passaggio 1: Creazione di una visualizzazione standard GridView ordinabile
Prima di esplorare come potenziare il GridView per fornire un'interfaccia di ordinamento avanzata, creiamo prima un GridView standard ordinabile che elenca i prodotti. Per iniziare, aprire la CustomSortingUI.aspx
pagina nella PagingAndSorting
cartella . Aggiungere un controllo GridView alla pagina, impostarne la ID
proprietà su ProductList
e associarla a un nuovo ObjectDataSource. Configurare ObjectDataSource per l'uso del metodo ProductsBLL
della classe GetProducts()
per la selezione dei record.
Configurare quindi il GridView in modo che contenga solo i ProductName
, CategoryName
, SupplierName
, e UnitPrice
BoundFields e il CheckBoxField interrotto. Configurare infine GridView per supportare l'ordinamento selezionando la casella di controllo Abilita ordinamento nello smart tag GridView (o impostandone la AllowSorting
proprietà su true
). Dopo aver apportato queste aggiunte alla CustomSortingUI.aspx
pagina, il markup dichiarativo dovrebbe essere simile al seguente:
<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>
Dedicare un po' di tempo a visualizzare i nostri progressi fino a questo punto in un browser. La figura 2 mostra gridView ordinabile quando i relativi dati vengono ordinati in base alla categoria in ordine alfabetico.
Figura 2: I dati di GridView ordinabili sono ordinati per categoria (fare clic per visualizzare l'immagine a dimensione intera)
Passaggio 2: Esplorazione delle tecniche per l'aggiunta di righe di separatore
Con il GridView generico e ordinabile completo, tutto ciò che rimane è aggiungere le righe separatrici nel GridView prima di ciascun gruppo ordinato univoco. Ma come è possibile inserire tali righe in GridView? Essenzialmente, è necessario scorrere le righe di GridView, determinare dove si verificano le differenze tra i valori nella colonna ordinata e quindi aggiungere la riga separatore appropriata. Quando si pensa a questo problema, sembra naturale che la soluzione si trovi da qualche parte nel gestore eventi di RowDataBound
GridView. Come illustrato nell'esercitazione Formattazione personalizzata basata sui dati , questo gestore eventi viene comunemente usato quando si applica la formattazione a livello di riga in base ai dati della riga. Tuttavia, il RowDataBound
gestore eventi non è la soluzione, perché le righe non possono essere aggiunte a GridView a livello di codice da questo gestore eventi. La raccolta di Rows
GridView, infatti, è di sola lettura.
Per aggiungere altre righe a GridView, sono disponibili tre opzioni:
- Aggiungere queste righe separatori di metadati ai dati effettivi associati a GridView
- Dopo che GridView è stato associato ai dati, aggiungere altre
TableRow
istanze alla raccolta di controlli GridView - Crea un controllo server personalizzato che estende il controllo GridView e sovrascrive quei metodi responsabili della costruzione della struttura del GridView.
La creazione di un controllo server personalizzato sarebbe l'approccio migliore se questa funzionalità fosse necessaria in molte pagine Web o in diversi siti Web. Tuttavia, comporta un po ' di codice e un'esplorazione approfondita delle profondità dei lavori interni di GridView. Pertanto, non si considererà questa opzione per questa esercitazione.
Le altre due opzioni aggiungono righe di separatore ai dati effettivi associati a GridView e modificano la raccolta di controlli del GridView dopo che è stato associato - affrontano il problema in modo diverso e meritano una discussione.
Aggiungere righe ai dati collegati al GridView
Quando GridView è associato a un'origine dati, crea un oggetto GridViewRow
per ogni record restituito dall'origine dati. Pertanto, è possibile inserire le righe separatrici necessarie aggiungendo record separatori all'origine dati prima di associarla a GridView. La figura 3 illustra questo concetto.
Figura 3: Una tecnica prevede l'aggiunta di righe separatori all'origine dati
Uso il termine record separatore tra virgolette perché non esiste un record separatore speciale; in qualche modo è necessario contrassegnare che un determinato record nell'origine dati funge da separatore anziché come riga di dati normale. Nei nostri esempi, stiamo eseguendo il binding di un'istanza ProductsDataTable
al GridView, che è composto da ProductRows
. È possibile contrassegnare un record come riga separatore impostandone la CategoryID
proprietà su -1
(poiché tale valore non poteva esistere normalmente).
Per usare questa tecnica, è necessario eseguire i passaggi seguenti:
- Ottenere i dati a livello programmatico da associare al GridView (un'istanza di
ProductsDataTable
) - Ordinare i dati in base alle proprietà
SortExpression
eSortDirection
di GridView - Iterare attraverso il
ProductsRows
nelProductsDataTable
, cercando dove si trovano le differenze nella colonna ordinata - In ogni limite di gruppo, inserire un'istanza di record
ProductsRow
separatore in DataTable, in cui ilCategoryID
è impostato su-1
(o su qualsiasi designazione sia stata decisa per contrassegnare un record come record separatore) - Dopo l'inserimento delle righe separatrici, associare i dati a livello di codice a GridView.
Oltre a questi cinque passaggi, è necessario fornire anche un gestore eventi per l'evento GridView.RowDataBound
In questo caso, controlleremmo ognuno DataRow
e determineremmo se si tratta di una riga separatrice, cioè una la cui CategoryID
impostazione era -1
. In tal caso, è probabile che si voglia modificare la formattazione o il testo visualizzato nelle celle.
L'uso di questa tecnica per inserire i limiti del gruppo di ordinamento richiede un po' più di lavoro rispetto a quanto descritto in precedenza, perché è necessario fornire anche un gestore eventi per l'evento Sorting
di GridView e monitorare i valori SortExpression
e SortDirection
.
Manipolazione della raccolta di controlli del GridView dopo che è stata eseguita l'associazione dati
Invece di inviare messaggi ai dati prima di associarli a GridView, è possibile aggiungere le righe separatori dopo che i dati sono stati associati a GridView. Il processo di data binding crea la gerarchia di controllo di GridView, che in realtà è semplicemente un'istanza Table
composta da una raccolta di righe, ognuna delle quali è composta da una raccolta di celle. In particolare, l'insieme di controlli di GridView contiene un Table
oggetto nella radice, un GridViewRow
oggetto (derivato dalla TableRow
classe ) per ogni record nell'oggetto DataSource
associato a GridView e un TableCell
oggetto in ogni GridViewRow
istanza per ogni campo dati nell'oggetto DataSource
.
Per aggiungere righe separatori tra ogni gruppo di ordinamento, è possibile modificare direttamente questa gerarchia di controlli dopo averla creata. È possibile assicurarsi che la gerarchia dei controlli di GridView sia stata creata per l'ultima volta dal momento in cui viene eseguito il rendering della pagina. Pertanto, questo approccio esegue l'override del metodo della classe Page
, a quel punto la gerarchia finale del controllo GridView viene aggiornata per includere le righe separatrici necessarie. La figura 4 illustra questo processo.
Figura 4: Una tecnica alternativa modifica la gerarchia di controllo di GridView (fare clic per visualizzare l'immagine a dimensione intera)
Per questa esercitazione si userà questo secondo approccio per personalizzare l'esperienza utente di ordinamento.
Annotazioni
Il codice che sto presentando in questa esercitazione è basato sull'esempio fornito nel post del blog di Teemu Keiski, Sperimentare un po' con GridView Sort Grouping.
Passaggio 3: Aggiunta delle righe separatore alla gerarchia di controllo di GridView
Poiché si vogliono aggiungere solo le righe separatrici alla gerarchia di controllo di GridView dopo che la sua gerarchia di controllo è stata creata per l'ultima volta durante la visita alla pagina, si vuole eseguire questa aggiunta alla fine del ciclo di vita della pagina, ma prima che la reale gerarchia di controllo di GridView venga renderizzata in HTML. Il più tardi possibile punto in cui possiamo eseguire questa operazione è l'evento Page
della classe Render
, che possiamo sovrascrivere nella nostra classe code-behind utilizzando la seguente firma del metodo:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Add code to manipulate the GridView control hierarchy
MyBase.Render(writer)
End Sub
Quando il metodo originale Page
della classe viene invocato, ogni controllo nella pagina sarà reso, generando il markup in base alla gerarchia dei controlli. Pertanto, è fondamentale chiamare base.Render(writer)
, in modo che venga eseguito il rendering della pagina e che sia stata modificata la gerarchia dei controlli di GridView prima di chiamare base.Render(writer)
, in modo che le righe separatori siano state aggiunte alla gerarchia dei controlli gridView prima del rendering.
Per inserire le intestazioni del gruppo di ordinamento, è prima necessario assicurarsi che l'utente abbia richiesto l'ordinamento dei dati. Per impostazione predefinita, il contenuto di GridView non è ordinato e pertanto non è necessario immettere intestazioni di ordinamento di gruppo.
Annotazioni
Se si desidera che il GridView venga ordinato in base a una determinata colonna quando la pagina viene caricata per la prima volta, chiamare il metodo del GridView Sort
alla prima visita della pagina (ma non nei postback successivi). A tal fine, aggiungere questa chiamata nel gestore eventi Page_Load
all'interno di un'istruzione condizionale if (!Page.IsPostBack)
. Per ulteriori informazioni sul metodo, fare riferimento all'esercitazione sul paging e l'ordinamento dei dati del Sort
report.
Supponendo che i dati siano stati ordinati, l'attività successiva consiste nel determinare la colonna in cui sono stati ordinati i dati e quindi analizzare le righe cercando differenze nei valori di tale colonna. Il codice seguente garantisce che i dati siano stati ordinati e trovino la colonna in base alla quale sono stati ordinati i dati:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Only add the sorting UI if the GridView is sorted
If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
' Determine the index and HeaderText of the column that
'the data is sorted by
Dim sortColumnIndex As Integer = -1
Dim sortColumnHeaderText As String = String.Empty
For i As Integer = 0 To ProductList.Columns.Count - 1
If ProductList.Columns(i).SortExpression.CompareTo( _
ProductList.SortExpression) = 0 Then
sortColumnIndex = i
sortColumnHeaderText = ProductList.Columns(i).HeaderText
Exit For
End If
Next
' TODO: Scan the rows for differences in the sorted column�s values
End Sub
Se GridView deve ancora essere ordinato, la proprietà GridView non è stata ancora impostata. Pertanto, si vuole aggiungere le righe separatore solo se questa proprietà ha un valore. In caso affermativo, è necessario determinare l'indice della colonna in base alla quale sono stati ordinati i dati. Questa operazione viene eseguita eseguendo un ciclo nell'insieme gridViewColumns
, cercando la colonna la cui SortExpression
proprietà è uguale alla proprietà gridView.SortExpression
Oltre all'indice della colonna, si recupera anche la HeaderText
proprietà, che viene usata durante la visualizzazione delle righe separatrici.
Con l'indice della colonna in base al quale vengono ordinati i dati, il passaggio finale consiste nell'enumerare le righe di GridView. Per ogni riga è necessario determinare se il valore della colonna ordinata è diverso dal valore della colonna ordinata della riga precedente. In tal caso, è necessario inserire una nuova GridViewRow
istanza nella gerarchia dei controlli. Questa operazione viene eseguita con il codice seguente:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Only add the sorting UI if the GridView is sorted
If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
' ... Code for finding the sorted column index removed for brevity ...
' Reference the Table the GridView has been rendered into
Dim gridTable As Table = CType(ProductList.Controls(0), Table)
' Enumerate each TableRow, adding a sorting UI header if
' the sorted value has changed
Dim lastValue As String = String.Empty
For Each gvr As GridViewRow In ProductList.Rows
Dim currentValue As String = gvr.Cells(sortColumnIndex).Text
If lastValue.CompareTo(currentValue) <> 0 Then
' there's been a change in value in the sorted column
Dim rowIndex As Integer = gridTable.Rows.GetRowIndex(gvr)
' Add a new sort header row
Dim sortRow As New GridViewRow(rowIndex, rowIndex, _
DataControlRowType.DataRow, DataControlRowState.Normal)
Dim sortCell As 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
End If
Next
End If
MyBase.Render(writer)
End Sub
Questo codice inizia facendo riferimento a livello di codice all'oggetto Table
trovato nella radice della gerarchia dei controlli gridView e creando una variabile stringa denominata lastValue
.
lastValue
viene utilizzato per confrontare il valore della colonna ordinata della riga corrente con il valore della riga precedente. Successivamente, l'insieme gridView s Rows
viene enumerato e per ogni riga il valore della colonna ordinata viene archiviato nella currentValue
variabile .
Annotazioni
Per determinare il valore della colonna ordinata della riga particolare utilizzo la proprietà della cella Text
. Questo funziona bene per BoundFields, ma non funzionerà come desiderato per TemplateFields, CheckBoxFields e così via. A breve verrà illustrato come tenere conto dei campi GridView alternativi.
Le currentValue
variabili e lastValue
vengono quindi confrontate. Se sono diverse, è necessario aggiungere una nuova riga separatore alla gerarchia dei controlli. Questa operazione viene eseguita determinando l'indice di GridViewRow
nella collezione dell'oggetto Table
Rows
, creando nuove istanze di GridViewRow
e TableCell
, e quindi aggiungendo TableCell
e GridViewRow
alla gerarchia dei controlli.
Si noti che la riga separatore solitaria TableCell
è formattata in modo da estendersi sull'intera larghezza di GridView, è formattata usando la classe CSS SortHeaderRowStyle
e ha la sua proprietà Text
in modo da mostrare sia il nome del gruppo di ordinamento (ad esempio Categoria) sia il valore del gruppo (ad esempio Bevande). Infine, lastValue
viene aggiornato al valore di currentValue
.
La classe CSS utilizzata per formattare la riga SortHeaderRowStyle
di intestazione del gruppo di ordinamento deve essere specificata nel file Styles.css
. È possibile usare qualsiasi impostazione di stile che più vi piace; Ho usato quanto segue:
.SortHeaderRowStyle
{
background-color: #c00;
text-align: left;
font-weight: bold;
color: White;
}
Con il codice attuale, l'interfaccia di ordinamento aggiunge intestazioni di gruppi di ordinamento quando si ordina in base a qualsiasi BoundField (si veda la figura 5, che mostra uno screenshot dell'ordinamento per fornitore). Tuttavia, quando si ordina in base a qualsiasi altro tipo di campo (ad esempio checkBoxField o TemplateField), le intestazioni del gruppo di ordinamento non sono disponibili (vedere la figura 6).
Figura 5: L'interfaccia di ordinamento include intestazioni di gruppo di ordinamento durante l'ordinamento in base a BoundFields (fare clic per visualizzare l'immagine a dimensione intera)
Figura 6: Le intestazioni del gruppo di ordinamento mancano quando si ordina un campo CheckBoxField (fare clic per visualizzare l'immagine a dimensione intera)
Il motivo per cui mancano le intestazioni del gruppo di ordinamento durante l'ordinamento in base a un oggetto CheckBoxField è dovuto al fatto che il codice usa solo la TableCell
proprietà s Text
per determinare il valore della colonna ordinata per ogni riga. Per CheckBoxFields, la TableCell
proprietà s Text
è una stringa vuota, ma il valore è disponibile tramite un controllo Web CheckBox che si trova all'interno dell'insieme TableCell
s Controls
.
Per gestire i tipi di campo diversi da BoundFields, è necessario modificare il codice in cui la variabile currentValue
viene assegnata per verificare l'esistenza di un CheckBox nella raccolta TableCell
Controls
. Anziché usare currentValue = gvr.Cells(sortColumnIndex).Text
, sostituire questo codice con il codice seguente:
Dim currentValue As String = String.Empty
If gvr.Cells(sortColumnIndex).Controls.Count > 0 Then
If TypeOf gvr.Cells(sortColumnIndex).Controls(0) Is CheckBox Then
If CType(gvr.Cells(sortColumnIndex).Controls(0), CheckBox).Checked Then
currentValue = "Yes"
Else
currentValue = "No"
End If
' ... Add other checks here if using columns with other
' Web controls in them (Calendars, DropDownLists, etc.) ...
End If
Else
currentValue = gvr.Cells(sortColumnIndex).Text
End If
Questo codice esamina la colonna TableCell
ordinata per la riga corrente per determinare se sono presenti controlli nella Controls
raccolta. Se ci sono, e il primo controllo è un controllo CheckBox, la currentValue
variabile viene impostata su 'Sì' o 'No', a seconda della proprietà CheckBox.Checked
In caso contrario, il valore viene ricavato dalla TableCell
proprietà s Text
. Questa logica può essere replicata per gestire l'ordinamento per qualsiasi oggetto TemplateFields che può esistere in GridView.
Con l'aggiunta di codice precedente, le intestazioni del gruppo di ordinamento sono ora presenti durante l'ordinamento in base al campo CheckBox sospeso (vedere la figura 7).
Figura 7: Le intestazioni del gruppo di ordinamento sono ora presenti durante l'ordinamento di un campo CheckBox (fare clic per visualizzare l'immagine a dimensione intera)
Annotazioni
Se si dispone di prodotti con valori di database per i campi NULL
, CategoryID
o SupplierID
, tali valori verranno visualizzati come stringhe vuote in GridView per impostazione predefinita, il che significa che il testo della riga separatore per tali prodotti con valori UnitPrice
verrà letto come Categoria: (ovvero, non esiste alcun nome dopo Categoria: come con Categoria: Bevande). Se si desidera visualizzare un valore qui, è possibile impostare la proprietà BoundFields NullDisplayText
sul testo che si desidera visualizzare oppure aggiungere un'istruzione condizionale nel metodo Render quando si assegna alla currentValue
proprietà della riga separatore.Text
Riassunto
GridView non include molte opzioni predefinite per personalizzare l'interfaccia di ordinamento. Tuttavia, con un po' di codice di basso livello, è possibile modificare la gerarchia dei controlli gridView per creare un'interfaccia più personalizzata. In questo tutorial abbiamo visto come aggiungere una riga separatrice per i gruppi di ordinamento in un GridView ordinabile, che identifica in modo più semplice i gruppi distinti e i loro confini. Per altri esempi di interfacce di ordinamento personalizzate, consulta l'articolo del blog di Scott Guthrie dal titolo Suggerimenti e trucchi per l'ordinamento del GridView in ASP.NET 2.0.
Buon programmatori!
Informazioni sull'autore
Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, ha lavorato con le tecnologie Web Microsoft dal 1998. Scott lavora come consulente indipendente, formatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2.0 in 24 ore. Può essere raggiunto a mitchell@4GuysFromRolla.com.