Creazione di un'interfaccia utente per l'ordinamento personalizzato (VB)

di Scott Mitchell

Scarica il PDF

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 diverse opzioni di categoria (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 controllo GridView, l'utente potrebbe decidere di scegliere di ordinare i risultati in base alla categoria, che raggruppa tutti i prodotti Pesce 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 che trova i prodotti Seafood non è difficile, ma richiede comunque di analizzare attentamente l'elenco di elementi nella griglia.

Per evidenziare i limiti tra i 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.

Ogni gruppo di categorie è chiaramente identificato

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 un controllo GridView standard ordinabile

Prima di esplorare come aumentare GridView per fornire l'interfaccia di ordinamento avanzata, è prima possibile creare un controllo 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à ProductListsu e associarla a un nuovo ObjectDataSource. Configurare ObjectDataSource per l'uso del ProductsBLL metodo della GetProducts() classe per la selezione dei record.

Configurare quindi GridView in modo che contenga ProductNamesolo , CategoryName, SupplierNamee UnitPrice BoundFields e CheckBoxField discontinued. Infine, configurare 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>

Prenditi un attimo per visualizzare i nostri progressi finora in un browser. Nella figura 2 viene illustrato il controllo GridView ordinabile quando i dati vengono ordinati in base alla categoria in ordine alfabetico.

I dati di GridView ordinabili sono ordinati per categoria

Figura 2: I dati ordinabili di GridView sono ordinati per categoria (fare clic per visualizzare l'immagine a dimensione intera)

Passaggio 2: Esplorazione delle tecniche per l'aggiunta delle righe di separatore

Con il completamento di GridView generico ordinabile, tutto ciò che rimane consiste nell'aggiungere le righe separatori in GridView prima di ogni 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 Sulla 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
  • Creare un controllo server personalizzato che estende il controllo GridView ed esegue l'override di tali metodi responsabili della costruzione della struttura di 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 piuttosto un po 'di codice e un'esplorazione approfondita delle profondità dei lavori interni di GridView. Di conseguenza, non si considererà questa opzione per questa esercitazione.

Le altre due opzioni aggiungono righe separatori ai dati effettivi associati a GridView e modificano la raccolta di controlli di GridView dopo il limite, attaccano il problema in modo diverso e meritano una discussione.

Aggiunta di righe ai dati associati a GridView

Quando GridView è associato a un'origine dati, crea un GridViewRow oggetto per ogni record restituito dall'origine dati. È quindi possibile inserire le righe separatori necessarie aggiungendo record separatori all'origine dati prima di associarlo a GridView. La figura 3 illustra questo concetto.

Una tecnica prevede l'aggiunta di righe separatori all'origine dati

Figura 3: Una tecnica prevede l'aggiunta di righe separatori all'origine dati

Io uso i record separatori termini tra virgolette perché non c'è alcun record separatore speciale; è necessario in qualche modo contrassegnare che un determinato record nell'origine dati funge da separatore anziché come riga di dati normale. Per gli esempi, viene eseguito il binding di un'istanza ProductsDataTable a GridView, costituita 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:

  1. Recuperare i dati a livello di codice da associare a GridView (un'istanza ProductsDataTable )
  2. Ordinare i dati in base alle proprietà e SortDirection di SortExpression GridView
  3. Scorrere in ProductsRows , ProductsDataTablecercando dove si trovano le differenze nella colonna ordinata
  4. In ogni limite di gruppo inserire un'istanza di record ProductsRow separatore in DataTable, una su cui è CategoryID impostata -1 (o su qualsiasi designazione sia stata deciso di contrassegnare un record come record separatore)
  5. Dopo aver inserito le righe separatori, 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, si controllano ognuna DataRow e si determina se si tratta di una riga separatore, 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ù lavoro rispetto a quanto descritto in precedenza, perché è necessario fornire anche un gestore eventi per l'evento gridView e Sorting tenere traccia dei SortExpression valori e SortDirection .

Modifica della raccolta di controlli di GridView dopo che è stato databound

Anziché inviare i dati prima di associarlo 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 è costituita da una raccolta di celle. In particolare, l'insieme di controlli gridView contiene un Table oggetto alla 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 in DataSource.

Per aggiungere righe separatori tra ogni gruppo di ordinamento, è possibile modificare direttamente questa gerarchia di controlli dopo la creazione. È possibile assicurarsi che la gerarchia di controllo 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 Page metodo della Render classe, a quel punto viene aggiornata la gerarchia di controllo finale di GridView per includere le righe separatori necessarie. La figura 4 illustra questo processo.

Una tecnica alternativa modifica la gerarchia di controllo di GridView

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.

Nota

Il codice che sto presentando in questa esercitazione si basa sull'esempio fornito nel post di blog di Teemu Keiski , Playing a Bit with GridView Sort Grouping ( Riproduzione di un bit con gridView Sort Grouping).

Passaggio 3: Aggiunta delle righe separatori alla gerarchia di controllo di GridView

Poiché si vogliono aggiungere solo le righe separatori alla gerarchia di controllo di GridView dopo la creazione e la creazione della gerarchia di controllo per l'ultima volta in tale pagina, si vuole eseguire questa aggiunta alla fine del ciclo di vita della pagina, ma prima del rendering della gerarchia di controlli GridView effettiva in HTML. L'ultimo punto possibile in cui è possibile eseguire questa operazione è l'evento Page della Render classe , che è possibile eseguire l'override nella classe code-behind usando la firma del metodo seguente:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
   ' Add code to manipulate the GridView control hierarchy
   MyBase.Render(writer)
End Sub

Quando viene richiamato base.Render(writer) il Page metodo originale Render della classe, verrà eseguito il rendering di ognuno dei controlli nella pagina, generando il markup in base alla gerarchia dei controlli. Pertanto, è fondamentale chiamare base.Render(writer)entrambi , in modo che venga eseguito il rendering della pagina e che venga modificata la gerarchia di controllo di GridView prima di chiamare base.Render(writer), in modo che le righe separatori siano state aggiunte alla gerarchia di controllo di GridView prima che venga eseguito il 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.

Nota

Se si desidera che GridView venga ordinato in base a una determinata colonna quando la pagina viene caricata per la prima volta, chiamare il metodo gridView nella Sort prima pagina visita (ma non per i postback successivi). A tale scopo, aggiungere questa chiamata nel Page_Load gestore eventi all'interno di un'istruzione if (!Page.IsPostBack) condizionale. Per altre informazioni sul metodo, vedere l'esercitazione Sul paging e l'ordinamento dei dati delSort 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 non è ancora stato ordinato, la proprietà gridView non SortExpression sarà stata impostata. Pertanto, si desidera aggiungere le righe separatori solo se questa proprietà ha un valore. In caso affermativo, è necessario determinare l'indice della colonna in base al 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à di GridView.SortExpression Oltre all'indice della colonna, si afferra anche la HeaderText proprietà , usata per visualizzare le righe separatori.

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 di controllo di 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. La raccolta di GridView viene Rows quindi enumerata e per ogni riga il valore della colonna ordinata viene archiviato nella currentValue variabile .

Nota

Per determinare il valore della colonna ordinata della riga specifica, utilizzare la proprietà della cella.Text Questo funziona bene per BoundFields, ma non funzionerà come desiderato per TemplateFields, CheckBoxFields e così via. A breve si esaminerà come tenere conto dei campi GridView alternativi.

Le currentValue variabili e lastValue vengono quindi confrontate. Se differiscono, è necessario aggiungere una nuova riga separatore alla gerarchia dei controlli. Questa operazione viene eseguita determinando l'indice di nell'insieme dell'oggetto , creando nuove GridViewRow istanze e TableCell e quindi aggiungendo e GridViewRowTableCell alla gerarchia dei controlli.GridViewRowTableRows

Si noti che la riga separatore s solitaria TableCell è formattata in modo che si estende sull'intera larghezza di GridView, venga formattata usando la classe CSS e abbia la SortHeaderRowStyle relativa Text proprietà in modo che mostri sia il nome del gruppo di ordinamento (ad esempio Category ) che il valore del gruppo (ad esempio Beverages). 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 Styles.css file. È possibile usare qualsiasi impostazione di stile vi piace; Ho usato quanto segue:

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

Con il codice corrente, l'interfaccia di ordinamento aggiunge intestazioni di gruppo di ordinamento durante l'ordinamento in base a qualsiasi BoundField (vedere la figura 5, che mostra uno screenshot durante l'ordinamento in base al fornitore). Tuttavia, quando si esegue l'ordinamento 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).

L'interfaccia di ordinamento include intestazioni di gruppo di ordinamento durante l'ordinamento in base a BoundFields

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)

Le intestazioni del gruppo di ordinamento non sono presenti durante l'ordinamento di un campo CheckBox

Figura 6: Le intestazioni del gruppo di ordinamento non sono presenti durante l'ordinamento di un campo CheckBox (fare clic per visualizzare l'immagine a dimensione intera)

Il motivo per cui le intestazioni del gruppo di ordinamento non sono presenti durante l'ordinamento in base a un controllo CheckBoxField è dovuto al fatto che il codice utilizza 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'insiemeTableCell.Controls

Per gestire i tipi di campo diversi da BoundFields, è necessario aumentare il codice in cui è assegnata la variabile per verificare l'esistenza currentValue di un controllo CheckBox nella TableCell raccolta di .Controls Anziché usare currentValue = gvr.Cells(sortColumnIndex).Text, sostituire questo codice con quanto segue:

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. In caso affermativo, e il primo controllo è un controllo CheckBox, la currentValue variabile è 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 tutti i campi template che possono esistere in GridView.

Con l'aggiunta del codice precedente, le intestazioni del gruppo di ordinamento sono ora presenti durante l'ordinamento in base al campo CheckBox interrotto (vedere la figura 7).

Le intestazioni del gruppo di ordinamento sono ora presenti durante l'ordinamento di un campo CheckBox

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)

Nota

Se sono presenti prodotti con NULL valori di database per i CategoryIDcampi , SupplierIDo UnitPrice , tali valori verranno visualizzati come stringhe vuote in GridView per impostazione predefinita, ovvero il testo della riga separatore per tali prodotti con NULL valori verrà letto come Category: (ovvero, non vi è alcun nome dopo Category: like with Category: Beverages). 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 Text riga separatore.

Riepilogo

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 di controllo di GridView per creare un'interfaccia più personalizzata. In questa esercitazione è stato illustrato come aggiungere una riga separatore di gruppi di ordinamento per un controllo GridView ordinabile, che identifica più facilmente i gruppi distinti e i limiti di tali gruppi. Per altri esempi di interfacce di ordinamento personalizzate, vedere La voce di blog Suggerimenti e consigli per l'ordinamento di Scott Guthrie s A Few ASP.NET 2.0 GridView .

Buon programmatori!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, lavora 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 all'indirizzo mitchell@4GuysFromRolla.com. o tramite il suo blog, disponibile all'indirizzo http://ScottOnWriting.NET.