Partager via


Création d’une interface utilisateur de tri personnalisée (C#)

par Scott Mitchell

Télécharger le PDF

Lors de l’affichage d’une longue liste de données triées, il peut être très utile de regrouper des données associées en introduisant des lignes de séparation. Dans ce tutoriel, nous allons voir comment créer une interface utilisateur de tri.

Introduction

Lors de l’affichage d’une longue liste de données triées où il n’y a qu’une poignée de valeurs différentes dans la colonne triée, un utilisateur final peut avoir du mal à déterminer où, exactement, les limites de la différence se produisent. Par exemple, il existe 81 produits dans la base de données, mais seulement neuf choix de catégorie différents (huit catégories uniques plus l’option NULL ). Prenons le cas d’un utilisateur qui souhaite examiner les produits qui appartiennent à la catégorie Fruits de mer. À partir d’une page qui répertorie tous les produits dans un seul GridView, l’utilisateur peut décider qu’il vaut mieux trier les résultats par catégorie, ce qui regroupera tous les produits de fruits de mer. Après avoir trié par catégorie, l’utilisateur doit ensuite parcourir la liste, à la recherche de l’endroit où commencent et se terminent les produits groupés de fruits de mer. Étant donné que les résultats sont classés par ordre alphabétique par le nom de la catégorie, trouver les produits de la mer n’est pas difficile, mais cela nécessite toujours une analyse étroite de la liste des éléments dans la grille.

Pour aider à mettre en évidence les limites entre les groupes triés, de nombreux sites web utilisent une interface utilisateur qui ajoute un séparateur entre ces groupes. Les séparateurs comme ceux présentés dans la figure 1 permettent à un utilisateur de trouver plus rapidement un groupe particulier et d’identifier ses limites, ainsi que de déterminer quels groupes distincts existent dans les données.

Chaque groupe de catégories est clairement identifié

Figure 1 : Chaque groupe de catégories est clairement identifié (cliquez pour afficher l’image en taille réelle)

Dans ce tutoriel, nous allons voir comment créer une interface utilisateur de tri.

Étape 1 : Création d’un GridView standard triable

Avant d’explorer comment augmenter GridView pour fournir l’interface de tri améliorée, nous allons d’abord créer un GridView standard et triable qui répertorie les produits. Commencez par ouvrir la CustomSortingUI.aspx page dans le PagingAndSorting dossier . Ajoutez un GridView à la page, définissez sa ID propriété sur ProductListet liez-la à un nouvel ObjetDataSource. Configurez ObjectDataSource pour utiliser la méthode de classe ProductsBLL s GetProducts() pour sélectionner des enregistrements.

Ensuite, configurez GridView de sorte qu’il contienne uniquement les ProductName, CategoryName, SupplierNameet UnitPrice BoundFields et le Champ CheckBoxField discontinué. Enfin, configurez GridView pour prendre en charge le tri en cochant la case Activer le tri dans la balise active de GridView (ou en définissant sa AllowSorting propriété sur true). Après avoir effectué ces ajouts à la CustomSortingUI.aspx page, le balisage déclaratif doit ressembler à ce qui suit :

<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>

Prenez un moment pour voir notre progression jusqu’à présent dans un navigateur. La figure 2 montre le GridView triable lorsque ses données sont triées par catégorie par ordre alphabétique.

Les données gridView triables sont classées par catégorie

Figure 2 : Les données triables gridView sont classées par catégorie (cliquez pour afficher l’image en taille réelle)

Étape 2 : Exploration des techniques d’ajout des lignes de séparateur

Une fois le GridView générique et triable terminé, il ne reste plus qu’à pouvoir ajouter les lignes de séparation dans le GridView avant chaque groupe trié unique. Mais comment ces lignes peuvent-elles être injectées dans GridView ? Essentiellement, nous devons itérer au sein des lignes de GridView, déterminer où les différences se produisent entre les valeurs de la colonne triée, puis ajouter la ligne de séparation appropriée. Lorsque vous réfléchissez à ce problème, il semble naturel que la solution se trouve quelque part dans le gestionnaire d’événements gridView RowDataBound . Comme nous l’avons vu dans le didacticiel Mise en forme personnalisée basée sur les données , ce gestionnaire d’événements est couramment utilisé lors de l’application de la mise en forme au niveau des lignes en fonction des données de ligne. Toutefois, le RowDataBound gestionnaire d’événements n’est pas la solution ici, car les lignes ne peuvent pas être ajoutées au GridView par programmation à partir de ce gestionnaire d’événements. En fait, la collection gridView Rows est en lecture seule.

Pour ajouter des lignes supplémentaires à GridView, nous avons trois choix :

  • Ajouter ces lignes de séparateur de métadonnées aux données réelles liées à GridView
  • Une fois que GridView a été lié aux données, ajoutez des instances supplémentaires TableRow à la collection de contrôles GridView
  • Créer un contrôle serveur personnalisé qui étend le contrôle GridView et remplace les méthodes responsables de la construction de la structure de GridView

La création d’un contrôle serveur personnalisé serait la meilleure approche si cette fonctionnalité était nécessaire sur de nombreuses pages web ou sur plusieurs sites web. Toutefois, cela impliquerait un peu de code et une exploration approfondie des profondeurs du fonctionnement interne de GridView. Par conséquent, nous ne considérerons pas cette option pour ce tutoriel.

Les deux autres options en ajoutant des lignes de séparation aux données réelles liées à GridView et en manipulant la collection de contrôles de GridView après sa liaison, attaquent le problème différemment et méritent une discussion.

Ajout de lignes aux données liées à GridView

Lorsque GridView est lié à une source de données, il crée un GridViewRow pour chaque enregistrement retourné par la source de données. Par conséquent, nous pouvons injecter les lignes de séparateur nécessaires en ajoutant des enregistrements de séparateur à la source de données avant de la lier à GridView. La figure 3 illustre ce concept.

Une technique implique l’ajout de lignes de séparateur à la source de données

Figure 3 : Une technique implique l’ajout de lignes de séparateur à la source de données

J’utilise les enregistrements de séparateur de termes entre guillemets, car il n’y a pas d’enregistrement de séparateur spécial ; Nous devons plutôt signaler qu’un enregistrement particulier dans la source de données sert de séparateur plutôt qu’une ligne de données normale. Pour nos exemples, nous relisons un ProductsDataTable instance à GridView, qui est composé de ProductRows. Nous pouvons marquer un enregistrement en tant que ligne de séparation en affectant à -1 sa CategoryID propriété la valeur (car une telle valeur ne pouvait pas exister normalement).

Pour utiliser cette technique, nous devons effectuer les étapes suivantes :

  1. Récupérer par programmation les données à lier à GridView (un ProductsDataTable instance)
  2. Trier les données en fonction des propriétés et SortDirection des propriétés GridView SortExpression
  3. Itérer à travers dans ProductsRows le ProductsDataTable, en recherchant où se trouvent les différences dans la colonne triée
  4. À chaque limite de groupe, injectez un enregistrement ProductsRow de séparateur instance dans le DataTable, un enregistrement dont la valeur est CategoryID définie -1 (ou toute autre désignation a été décidé pour marquer un enregistrement en tant qu’enregistrement de séparateur)
  5. Après avoir injecté les lignes de séparation, liez par programmation les données à GridView

En plus de ces cinq étapes, nous devons également fournir un gestionnaire d’événements pour l’événement GridView RowDataBound . Ici, nous avons case activée chacun DataRow et déterminer s’il s’agissait d’une ligne de séparation, une ligne dont CategoryID le paramètre était -1. Si c’est le cas, nous voulons probablement ajuster sa mise en forme ou le texte affiché dans les cellules.

L’utilisation de cette technique pour injecter les limites du groupe de tri nécessite un peu plus de travail que décrit ci-dessus, car vous devez également fournir un gestionnaire d’événements pour l’événement Sorting GridView et effectuer le SortExpression suivi des valeurs et SortDirection .

Manipulation de la collection de contrôles GridView après le flux de données

Au lieu d’envoyer des messages aux données avant de les lier à GridView, nous pouvons ajouter les lignes de séparation une fois que les données ont été liées à GridView. Le processus de liaison de données crée la hiérarchie de contrôle de GridView, qui en réalité est simplement une Table instance composée d’une collection de lignes, chacune composée d’une collection de cellules. Plus précisément, la collection de contrôles GridView contient un Table objet à sa racine, un GridViewRow (qui est dérivé de la TableRow classe) pour chaque enregistrement dans le DataSource lié au GridView, et un TableCell objet dans chaque GridViewRow instance pour chaque champ de données dans le DataSource.

Pour ajouter des lignes de séparation entre chaque groupe de tri, nous pouvons manipuler directement cette hiérarchie de contrôle une fois qu’elle a été créée. Nous pouvons être sûrs que la hiérarchie de contrôle GridView a été créée pour la dernière fois au moment du rendu de la page. Par conséquent, cette approche remplace la Page méthode de classe s Render , à laquelle la hiérarchie de contrôle finale de GridView est mise à jour pour inclure les lignes de séparation nécessaires. La figure 4 illustre ce processus.

Une autre technique manipule la hiérarchie de contrôles de GridView

Figure 4 : Une autre technique manipule la hiérarchie de contrôles GridView (cliquez pour afficher l’image en taille réelle)

Pour ce tutoriel, nous allons utiliser cette dernière approche pour personnaliser l’expérience utilisateur de tri.

Notes

Le code que je présente dans ce tutoriel est basé sur l’exemple fourni dans l’entrée de blog de Teemu Keiski , Playing a Bit with GridView Sort Grouping.

Étape 3 : Ajout des lignes de séparateur à la hiérarchie de contrôles GridView

Étant donné que nous voulons uniquement ajouter les lignes de séparation à la hiérarchie de contrôle GridView après que sa hiérarchie de contrôle a été créée et créée pour la dernière fois lors de cette visite de page, nous voulons effectuer cet ajout à la fin du cycle de vie de la page, mais avant que la hiérarchie de contrôle GridView réelle ait été rendue en HTML. Le dernier point possible à partir duquel nous pouvons effectuer cette opération est l’événement Page de classe s Render , que nous pouvons remplacer dans notre classe code-behind à l’aide de la signature de méthode suivante :

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

Lorsque la méthode d’origine Render de la Page classe est appeléebase.Render(writer), chacun des contrôles de la page est rendu, ce qui génère le balisage en fonction de leur hiérarchie de contrôles. Par conséquent, il est impératif que nous appelons base.Render(writer)tous les deux , afin que la page soit rendue et que nous manipulions la hiérarchie de contrôle gridView avant d’appeler base.Render(writer), afin que les lignes de séparation aient été ajoutées à la hiérarchie de contrôle gridView avant qu’elle ne soit rendue.

Pour injecter les en-têtes de groupe de tri, nous devons d’abord vérifier que l’utilisateur a demandé que les données soient triées. Par défaut, le contenu de GridView n’est pas trié. Par conséquent, nous n’avons pas besoin d’entrer d’en-têtes de tri de groupe.

Notes

Si vous souhaitez que gridView soit trié par une colonne particulière lors du premier chargement de la page, appelez la méthode GridView lors de Sort la première visite de page (mais pas dans les publications ultérieures). Pour ce faire, ajoutez cet appel dans le gestionnaire d’événements Page_Load dans un if (!Page.IsPostBack) conditionnel. Pour plus d’informations sur la méthode, reportez-vous au didacticiel Pagination et tri des données de Sort rapport.

En supposant que les données ont été triées, notre tâche suivante consiste à déterminer la colonne par laquelle les données ont été triées, puis à analyser les lignes à la recherche de différences dans les valeurs de cette colonne. Le code suivant garantit que les données ont été triées et recherche la colonne par laquelle les données ont été triées :

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
}

Si le GridView n’a pas encore été trié, la propriété GridView n’a SortExpression pas été définie. Par conséquent, nous ne voulons ajouter les lignes de séparation que si cette propriété a une certaine valeur. Si c’est le cas, nous devons ensuite déterminer l’index de la colonne selon laquelle les données ont été triées. Pour ce faire, effectuez une boucle dans la collection GridView Columns , en recherchant la colonne dont SortExpression la propriété est égale à la propriété de SortExpression GridView. En plus de l’index de colonne, nous récupérons également la HeaderText propriété, qui est utilisée lors de l’affichage des lignes de séparation.

Avec l’index de la colonne selon laquelle les données sont triées, la dernière étape consiste à énumérer les lignes du GridView. Pour chaque ligne, nous devons déterminer si la valeur de la colonne triée diffère de la valeur de colonne triée de la ligne précédente. Si c’est le cas, nous devons injecter une nouvelle GridViewRow instance dans la hiérarchie de contrôle. Pour ce faire, utilisez le code suivant :

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);
}

Ce code commence par référencer par programmation l’objet Table trouvé à la racine de la hiérarchie de contrôles GridView et en créant une variable de chaîne nommée lastValue. lastValue est utilisé pour comparer la valeur de colonne triée de ligne actuelle avec la valeur de ligne précédente. Ensuite, la collection gridView s Rows est énumérée et, pour chaque ligne, la valeur de la colonne triée est stockée dans la currentValue variable.

Notes

Pour déterminer la valeur de la colonne triée de ligne particulière, j’utilise la propriété s de Text cellule. Cela fonctionne bien pour BoundFields, mais ne fonctionnera pas comme vous le souhaitez pour TemplateFields, CheckBoxFields, etc. Nous allons voir comment prendre en compte d’autres champs GridView sous peu.

Les currentValue variables et lastValue sont ensuite comparées. S’ils diffèrent, nous devons ajouter une nouvelle ligne de séparateur à la hiérarchie de contrôle. Pour ce faire, déterminez l’index du GridViewRow dans la Table collection de l’objet, Rows créez GridViewRow des instances et, TableCell puis ajoutez le TableCell et GridViewRow à la hiérarchie de contrôle.

Notez que la ligne de séparation s seule TableCell est mise en forme de telle sorte qu’elle s’étende sur toute la largeur de GridView, qu’elle est mise en forme à l’aide de la SortHeaderRowStyle classe CSS et qu’elle a sa Text propriété telle qu’elle affiche à la fois le nom du groupe de tri (par exemple, Category ) et la valeur s du groupe (comme Beverages ). Enfin, lastValue est mis à jour avec la valeur de currentValue.

La classe CSS utilisée pour mettre en forme la ligne SortHeaderRowStyle d’en-tête du groupe de tri doit être spécifiée dans le Styles.css fichier. N’hésitez pas à utiliser les paramètres de style qui vous plaisent ; J’ai utilisé ce qui suit :

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

Avec le code actuel, l’interface de tri ajoute des en-têtes de groupe de tri lors du tri par boundField (voir la figure 5, qui montre une capture d’écran lors du tri par fournisseur). Toutefois, lors du tri par n’importe quel autre type de champ (par exemple, CheckBoxField ou TemplateField), les en-têtes de groupe de tri sont introuvables (voir figure 6).

L’interface de tri inclut les en-têtes de groupe de tri lors du tri par BoundFields

Figure 5 : L’interface de tri inclut les en-têtes de groupe de tri lors du tri par BoundFields (cliquez pour afficher l’image en taille réelle)

Les en-têtes de groupe de tri sont manquants lors du tri d’un champ CheckBoxField

Figure 6 : Les en-têtes de groupe de tri sont manquants lors du tri d’un champ CheckBoxField (cliquez pour afficher une image de taille réelle)

La raison pour laquelle les en-têtes de groupe de tri sont manquants lors du tri par checkBoxField est que le code utilise actuellement uniquement la TableCell propriété s Text pour déterminer la valeur de la colonne triée pour chaque ligne. Pour CheckBoxFields, la TableCell propriété s Text est une chaîne vide ; au lieu de cela, la valeur est disponible via un contrôle Web CheckBox qui réside dans la TableCell collection s Controls .

Pour gérer les types de champs autres que BoundFields, nous devons augmenter le code où la currentValue variable est affectée à case activée pour l’existence d’une CheckBox dans la TableCell collection sControls. Au lieu d’utiliser currentValue = gvr.Cells[sortColumnIndex].Text, remplacez ce code par ce qui suit :

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;

Ce code examine la colonne TableCell triée de la ligne active pour déterminer s’il existe des contrôles dans la Controls collection. S’il existe, et que le premier contrôle est une CheckBox, la currentValue variable est définie sur Oui ou Non, en fonction de la propriété CheckBox s Checked . Sinon, la valeur est extraite de la TableCell propriété s Text . Cette logique peut être répliquée pour gérer le tri pour tous les TemplateFields qui peuvent exister dans GridView.

Avec l’ajout de code ci-dessus, les en-têtes de groupe de tri sont désormais présents lors du tri par le CheckBoxField abandonné (voir figure 7).

Les en-têtes de groupe de tri sont maintenant présents lors du tri d’un CheckBoxField

Figure 7 : Les en-têtes de groupe de tri sont maintenant présents lors du tri d’un champ CheckBoxField (cliquez pour afficher une image de taille réelle)

Notes

Si vous avez des produits avec NULL des valeurs de base de données pour les CategoryIDchamps , SupplierIDou UnitPrice , ces valeurs s’affichent sous forme de chaînes vides dans gridView par défaut, ce qui signifie que le texte de ligne de séparation pour ces produits avec NULL des valeurs se lira comme Catégorie : (autrement dit, il n’y a pas de nom après Catégorie : Boissons). Si vous souhaitez afficher une valeur ici, vous pouvez définir la propriété BoundFields NullDisplayText sur le texte que vous souhaitez afficher ou ajouter une instruction conditionnelle dans la méthode Render lors de l’affectation du currentValue à la propriété s de la ligne de Text séparation.

Résumé

GridView n’inclut pas beaucoup d’options intégrées pour personnaliser l’interface de tri. Toutefois, avec un peu de code de bas niveau, il est possible d’ajuster la hiérarchie de contrôle de GridView pour créer une interface plus personnalisée. Dans ce tutoriel, nous avons vu comment ajouter une ligne de séparateur de groupe de tri pour un GridView triable, qui identifie plus facilement les groupes distincts et les limites de ces groupes. Pour obtenir d’autres exemples d’interfaces de tri personnalisées, case activée l’entrée de scott Guthrie sur le blog A Few ASP.NET 2.0 GridView Triing Tips and Tricks.

Bonne programmation !

À propos de l’auteur

Scott Mitchell, auteur de sept livres ASP/ASP.NET et fondateur de 4GuysFromRolla.com, travaille avec les technologies Web Microsoft depuis 1998. Scott travaille comme consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 in 24 Heures. Il est accessible à l’adressemitchell@4GuysFromRolla.com . ou via son blog, qui peut être trouvé à l’adresse http://ScottOnWriting.NET.