Partager via


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

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 les données associées en introduisant des lignes de séparateur. Dans ce tutoriel, nous allons voir comment créer une telle interface utilisateur de tri.

Présentation

Lorsque vous affichez 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 trouver difficile de discerner où, exactement, les limites de 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 ). Considérez le cas d’un utilisateur qui s’intéresse à l’examen des produits qui appartiennent à la catégorie Fruits de mer. À partir d'une page qui répertorie tous les produits d'un seul GridView, l'utilisateur pourrait estimer que sa meilleure option consiste à trier les résultats par catégorie, ce qui regroupera tous les produits de fruits de mer ensemble. Après le tri par catégorie, l’utilisateur doit ensuite parcourir la liste, à la recherche de l’endroit où les produits groupés de fruits de mer commencent et se terminent. Étant donné que les résultats sont classés par ordre alphabétique selon le nom de la catégorie, trouver les produits de fruits de mer n’est pas difficile, mais il faut néanmoins examiner attentivement la liste des éléments dans la grille.

Pour 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 de taille complète)

Dans ce tutoriel, nous allons voir comment créer une telle 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 la propriété ID à ProductList, et liez-le à un nouvel ObjectDataSource. Configurez ObjectDataSource pour utiliser la ProductsBLL méthode s de GetProducts() classe pour sélectionner des enregistrements.

Ensuite, configurez le GridView de sorte qu'il ne contienne que les ProductName, CategoryName, SupplierName, et UnitPrice BoundFields et le CheckBoxField Discontinued. Enfin, configurez GridView pour prendre en charge le tri en cochant la case Activer le tri dans la balise active 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’ici 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 de GridView triables sont classées par catégorie

Figure 2 : Les données de GridView triables sont classées par catégorie (cliquez pour afficher l’image de taille complète)

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

Une fois le GridView générique triable terminé, tout ce qui reste est de pouvoir ajouter les lignes de séparateur dans GridView avant chaque groupe trié unique. Mais comment ces lignes peuvent-elles être injectées dans GridView ? Essentiellement, nous devons effectuer une itération dans les lignes de GridView, déterminer où se produisent les différences entre les valeurs de la colonne triée, puis ajouter la ligne de séparation appropriée. Lorsque vous pensez à ce problème, il semble naturel que la solution se trouve quelque part dans le gestionnaire d’événements RowDataBound GridView. Comme nous l’avons vu dans le didacticiel sur la 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 la ligne. Toutefois, le RowDataBound gestionnaire d’événements n’est pas la solution ici, car les lignes ne peuvent pas être ajoutées à GridView par programmation à partir de ce gestionnaire d’événements. La collection GridView Rows , en fait, est en lecture seule.

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

  • Ajoutez 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 ces 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 implique un peu de code et une exploration approfondie des profondeurs des travaux internes de GridView. Par conséquent, nous ne considérerons pas cette option pour ce didacticiel.

Les deux autres options, à savoir l'ajout de lignes de séparateur aux données réelles liées au GridView et la manipulation de la collection de contrôles du GridView après avoir été lié, abordent 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 enregistrement 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 le 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 le terme « enregistrements séparateurs » entre guillemets, car il n’y a pas d’enregistrement séparateur spécial ; nous devons à la place noter 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 allons lier une ProductsDataTable instance à GridView, qui est composée de ProductRows. Nous pouvons marquer un enregistrement comme étant une ligne de séparation en définissant sa propriété CategoryID sur -1 (car une telle valeur ne pourrait normalement pas exister).

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

  1. Récupérer par programmation les données à lier à GridView (une ProductsDataTable instance)
  2. Trier les données en fonction des propriétés SortExpression et SortDirection du GridView
  3. Itérer dans ProductsRows pour ProductsDataTable, en recherchant où se situent les différences dans la colonne triée.
  4. À chaque limite de groupe, injectez une instance d'enregistrement de séparateur ProductsRow dans le DataTable, celle dont le CategoryID est défini sur -1 (ou quelle que soit la désignation qui a été décidée pour marquer un enregistrement comme enregistrement séparateur)
  5. Après avoir injecté les lignes de séparateur, 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 allons vérifier chacun DataRow et déterminer s’il s’agissait d’une ligne de séparation, une dont CategoryID le paramètre était -1. Dans ce cas, nous voulons probablement ajuster sa mise en forme ou le texte affiché dans la ou 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 GridView Sorting et suivre les valeurs SortExpression et SortDirection.

Manipulation de la collection de contrôles GridView après avoir été liée aux données

Au lieu de modifier les données avant de les lier au GridView, nous pouvons ajouter les lignes de séparateur après que les données ont été liées au GridView. Le processus de liaison de données crée la hiérarchie de contrôle 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 (dérivé de la TableRow classe) pour chaque enregistrement lié DataSource à GridView et un TableCell objet dans chaque GridViewRow instance pour chaque champ de données du DataSource.

Pour ajouter des lignes de séparateur 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 de GridView a été créée pour la dernière fois au moment où la page est affichée. Par conséquent, cette approche remplace la Page méthode de Render classe, à quel moment la hiérarchie de contrôle finale de GridView est mise à jour pour inclure les lignes de séparateur nécessaires. La figure 4 illustre ce processus.

Une autre technique manipule la hiérarchie de contrôle gridView

Figure 4 : Une autre technique manipule la hiérarchie de contrôle gridView (cliquez pour afficher l’image de taille complète)

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

Remarque

Le code que je présente dans ce tutoriel est basé sur l’exemple fourni dans l’entrée de blog de Teemu Keiski, Jouer un peu avec le tri et le regroupement GridView.

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

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

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

Lorsque la Page méthode originale Render de la classe est appelée base.Render(writer), chacun des contrôles de la page sera affiché, générant 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 , de sorte que la page soit rendue et que nous manipulons 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’elles ne soient rendues.

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.

Remarque

Si vous souhaitez que GridView soit trié par une colonne particulière au premier chargement de la page, appelez la méthode GridView Sort lors de la première visite de la page (mais pas lors des rechargements ultérieurs). Pour ce faire, ajoutez cet appel dans le Page_Load gestionnaire d’événements sous une condition if (!Page.IsPostBack). Pour plus d’informations sur la méthode, reportez-vous au didacticiel sur la pagination et le 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 à 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 recherchent la colonne selon laquelle les données ont été triées :

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

Si GridView n’a pas encore été trié, la propriété GridView SortExpression n’a pas été définie. Par conséquent, nous voulons uniquement ajouter les lignes de séparateur si cette propriété a une valeur. Si c’est le cas, nous devons ensuite déterminer l’index de la colonne par laquelle les données ont été triées. Pour ce faire, parcourez la collection GridView Columns et recherchez la colonne pour laquelle la propriété SortExpression est égale à la propriété SortExpression de GridView. En plus de l’index de colonne, nous récupérons également la propriété HeaderText, qui est utilisée lors de l’affichage des lignes de séparation.

Avec l’index de la colonne par laquelle les données sont triées, l’étape finale 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. Dans ce cas, nous devons injecter une nouvelle GridViewRow instance dans la hiérarchie de contrôle. Pour ce faire, utilisez le code suivant :

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

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

Remarque

Pour déterminer la valeur de la colonne triée de la rangée particulière, j'utilise la propriété de la cellule Text. Cela fonctionne bien pour BoundFields, mais ne fonctionnera pas comme vous le souhaitez pour TemplateFields, CheckBoxFields, et ainsi de suite. Nous allons examiner comment prendre en compte les autres champs GridView sous peu.

Les variables currentValue 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 de GridViewRow dans la collection d'objets TableRows, créez de nouvelles instances de GridViewRow et TableCell, puis ajoutez le TableCell et le GridViewRow à la hiérarchie de contrôle.

Notez que la seule ligne de séparation TableCell est formatée de telle sorte qu’elle s’étend sur toute la largeur du GridView, est mise en forme à l’aide de la classe CSS SortHeaderRowStyle et possède sa propriété Text de sorte qu’elle affiche à la fois le nom du groupe de tri (par exemple, Catégorie) et la valeur du groupe (par exemple, Boissons). Enfin, lastValue est mis à jour à 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 intéressent ; J’ai utilisé les éléments suivants :

.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 un objet BoundField (voir la figure 5, qui affiche une capture d’écran lors du tri par fournisseur). Toutefois, lors du tri par un autre type de champ (par exemple, CheckBoxField ou TemplateField), les en-têtes de groupe de tri ne sont pas trouvés (voir la figure 6).

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

Figure 5 : L’interface de tri inclut des en-têtes de groupe de tri lors du tri par BoundFields (cliquez pour afficher l’image de taille complète)

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

Figure 6 : Les en-têtes de groupe de tri sont manquants lors du tri d’un CheckBoxField (cliquez pour afficher l’image de taille complète)

La raison pour laquelle les en-têtes de groupe de tri sont manquants lors du tri par un 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 ; à la place, 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 pour vérifier l’existence d’un CheckBox dans la TableCell collection s Controls . Au lieu d’utiliser currentValue = gvr.Cells(sortColumnIndex).Text, remplacez ce code par les éléments suivants :

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

Ce code examine la colonne triée TableCell de la ligne active pour déterminer s’il existe des contrôles dans la collection Controls. S'il y en a, et que le premier contrôle est une case à cocher, la currentValue variable est réglée sur Oui ou Non, en fonction de la propriété de la case à cocherChecked. 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 supprimé (voir la 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 désormais présents lors du tri d’un checkBoxField (cliquez pour afficher l’image de taille complète)

Remarque

Si vous avez des produits avec des valeurs de base de données dans les champs NULL, CategoryID, SupplierID, ou UnitPrice, ces valeurs apparaîtront par défaut sous forme de chaînes vides dans la vue GridView. Cela signifie que le texte de la ligne de séparation pour ces produits avec les valeurs NULL sera lu comme « Catégorie : » (c’est-à-dire qu’il n’y a pas de nom après « Catégorie : » comme dans « 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 de la currentValue propriété de Text la ligne de séparation.

Résumé

GridView n’inclut pas de nombreuses 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 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 ces limites de groupes. Pour obtenir d’autres exemples d’interfaces de tri personnalisées, consultez l’article de blog de Scott Guthrie intitulé A Few ASP.NET 2.0 GridView Sorting 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 en tant que consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 en 24 heures. On peut le joindre à mitchell@4GuysFromRolla.com.