Condividi tramite


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

di Scott Mitchell

Scarica il PDF

Quando si visualizza un elenco lungo 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.

Introduzione

Quando si visualizza un elenco lungo di dati ordinati in cui sono presenti solo una manciata di valori diversi nella colonna ordinata, un utente finale potrebbe trovare difficile distinguere dove, esattamente, si verificano i limiti delle differenze. Ad esempio, nel database sono presenti 81 prodotti, ma solo nove diverse opzioni di categoria (otto categorie univoche e l'opzione NULL ). Si consideri il caso di un utente interessato ad 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 la sua migliore scommessa consiste nell'ordinare i risultati in base alla categoria, che raggruppa tutti i prodotti Pesce insieme. Dopo l'ordinamento in base alla categoria, l'utente deve quindi cercare l'elenco, cercando dove iniziano e terminano i prodotti a raggruppamento di pesce. Poiché i risultati sono ordinati in ordine alfabetico in base al nome della categoria che individuano i prodotti Pesce non è difficile, ma richiede ancora 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 visualizzati nella figura 1 consentono a un utente di trovare più rapidamente un determinato gruppo e identificare i relativi limiti, nonché 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 full-size)

In questa esercitazione verrà illustrato come creare un'interfaccia utente di ordinamento.

Passaggio 1: Creazione di un controllo GridView standard, ordinabile

Prima di esplorare come aumentare GridView per fornire l'interfaccia di ordinamento avanzata, consente di creare prima una griglia standard e ordinabile che elenca i prodotti. Iniziare aprendo la CustomSortingUI.aspx pagina nella PagingAndSorting cartella. Aggiungere gridView alla pagina, impostarne la ID proprietà su ProductListe associarla a un nuovo OggettoDataSource. Configurare ObjectDataSource per usare il ProductsBLL metodo della GetProducts() classe per la selezione dei record.

Configurare quindi GridView in modo che contenga solo gli ProductNameelementi , , CategoryNameSupplierNamee UnitPrice BoundFields e Il controllo CheckBoxField interrotto. Infine, configurare GridView per supportare l'ordinamento selezionando la casella di controllo Abilita ordinamento nella smart tag di GridView (o impostandone la AllowSorting proprietà su true). Dopo aver effettuato 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>

Prendere un momento per visualizzare il nostro progresso finora in un browser. La figura 2 mostra 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 dimensioni complete)

Passaggio 2: Esplorazione delle tecniche per l'aggiunta delle righe separatori

Con il completamento di GridView generico ordinabile, tutto ciò che rimane deve essere in grado di aggiungere le righe separatori in GridView prima di ogni gruppo univoco ordinato. Ma come è possibile inserire tali righe in GridView? In sostanza, è 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 GridView.RowDataBound 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 qui, poiché le righe non possono essere aggiunte a livello di codice al gestore eventi GridView. L'insieme GridView, Rows in realtà, è 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à era necessaria in molte pagine Web o in diversi siti Web. Tuttavia, comporta un po ' di codice e un'esplorazione approfondita nelle profondità dei lavori interni di GridView. Pertanto, 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 GridView dopo il relativo limite: attaccare il problema in modo diverso e merita una discussione.

Aggiunta di righe all'associazione dati 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

Uso i record separatori termini nelle virgolette perché non esiste un record separatore speciale; invece, è necessario contrassegnare in qualche modo che un determinato record nell'origine dati funzioni come separatore anziché una normale riga di dati. Per gli esempi, si associa un'istanza ProductsDataTable a GridView, composta da ProductRows. È possibile contrassegnare un record come riga separatore impostandone la CategoryID proprietà su -1 (poiché tale valore non esiste 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. Eseguire l'iterazione nell'oggetto ProductsRowsProductsDataTable, cercando dove le differenze nella colonna ordinata si trovano
  4. A ogni limite del gruppo, inserire un'istanza di record ProductsRow separatore in DataTable, una a cui CategoryID è impostata -1 (o a qualsiasi designazione è 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, è anche necessario fornire un gestore eventi per l'evento GridView.RowDataBound In questo caso, si verificherà ogni DataRow oggetto e si determina se fosse una riga separatore, una la cui CategoryID impostazione era -1. In tal caso, probabilmente si vuole 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' di lavoro maggiore rispetto a quanto descritto in precedenza, poiché è necessario fornire anche un gestore eventi per l'evento GridView s Sorting e tenere traccia dei SortExpression valori e SortDirection .

Modifica della raccolta di controlli gridView dopo che è stato databound

Anziché inviare messaggi ai 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 è composta da una raccolta di celle. In particolare, l'insieme di controlli GridView contiene un Table oggetto nella relativa 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 controllo dopo la creazione. È possibile assicurarsi che la gerarchia di controllo di GridView sia stata creata per l'ultima volta al rendering della pagina. Pertanto, questo approccio esegue l'override del metodo della Render classe, a quel punto viene aggiornata la Page 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 dimensioni complete)

Per questa esercitazione si userà questo approccio per personalizzare l'esperienza utente di ordinamento.

Nota

Il codice presentato in questa esercitazione si basa sull'esempio fornito nella voce di blog di Teemu Keiski , Riproduzione di un bit con GridView Sort Grouping.

Passaggio 3: Aggiunta delle righe separatori alla gerarchia di controllo gridView s

Poiché si desidera aggiungere solo le righe separatori alla gerarchia di controllo gridView dopo la creazione della gerarchia di controllo e la creazione 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 controllo GridView effettiva in HTML. Il punto più recente possibile a cui è possibile eseguire questa operazione è l'evento della Render classe, che è possibile eseguire l'override Page nella classe code-behind usando la firma del metodo seguente:

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

Quando viene richiamato base.Render(writer) il metodo originale Render della Page classe ogni controllo nella pagina verrà eseguito il rendering, generando il markup in base alla gerarchia di controllo. È pertanto imperativo chiamare base.Render(writer), 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 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 dei gruppi.

Nota

Se si vuole che GridView venga ordinato in base a una colonna specifica quando la pagina viene caricata per la prima volta, chiamare il metodo GridView Sort nella prima pagina visita (ma non nei postback successivi). A questo scopo, aggiungere questa chiamata nel Page_Load gestore eventi all'interno di un if (!Page.IsPostBack) condizionale. Per altre informazioni sul metodo, vedere l'esercitazione Relativa al paging e all'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 per analizzare le righe che cercano 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 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
}

Se GridView non è ancora stato ordinato, la proprietà GridView s SortExpression non sarà stata impostata. Pertanto, si desidera aggiungere solo le righe separatori se questa proprietà ha un valore. In caso contrario, è necessario determinare l'indice della colonna in base al quale sono stati ordinati i dati. Questa operazione viene eseguita eseguendo il ciclo tramite l'insieme GridView s Columns , cercando la colonna la cui SortExpression proprietà equivale alla proprietà GridView s SortExpression . Oltre all'indice della colonna, si afferra anche la proprietà, usata durante la HeaderText visualizzazione delle 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 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);
}

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:

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;

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.