Création d’une interface utilisateur de tri personnalisée (C#)
par Scott Mitchell
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.
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 ProductList
et 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
, SupplierName
et 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.
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.
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 :
- Récupérer par programmation les données à lier à GridView (un
ProductsDataTable
instance) - Trier les données en fonction des propriétés et
SortDirection
des propriétés GridViewSortExpression
- Itérer à travers dans
ProductsRows
leProductsDataTable
, en recherchant où se trouvent les différences dans la colonne triée - À chaque limite de groupe, injectez un enregistrement
ProductsRow
de séparateur instance dans le DataTable, un enregistrement dont la valeur estCategoryID
définie-1
(ou toute autre désignation a été décidé pour marquer un enregistrement en tant qu’enregistrement de séparateur) - 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.
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).
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)
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).
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 CategoryID
champs , SupplierID
ou 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.