Conseils pour développer des rapports RDLC dans Visual Studio
La création d’un rapport de données est une fonctionnalité classique des logiciels en particulier ceux à destination de professionnels. Or, les technologies pour le faire ne sont pas toujours connues. Examinons le cas de Visual Studio.
Dans sa philosophie de proposer un AGL complet, Microsoft incorpore dans Visual Studio ce qu’il faut pour réaliser des rapports de données. Cela se faisait initialement au moyen de Crystal Reports. Depuis la version 2005 et le framework .NET, Visual Studio intègre sa propre technologie de conception et génération de rapports, très proche de celle de SSRS.
Le concepteur de rapports de Visual Studio est de premier abord intuitif, aisé à prendre à main. Il est en fait assez difficile à maîtriser. Voici quelques généralités sur ces rapports, suivies de conseils pour une bonne conception et éviter les mauvais surprises au rendu final (en PDF par ex.)
Généralités
Type de fichier
Selon le type de projet il faut référencer la librairie suivante :
- Windows : Microsoft.ReportViewer.WinForms
- Web : Microsoft.ReportViewer.WebForms
Un rapport est alors un fichier de type “Report”, d’extension “.rdlc” :
Par défaut, un fichier RDLC s’ouvre avec le Report Designer. Il s’agit en fait d’un fichier XML :
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns:rd="https://schemas.microsoft.com/SQLServer/reporting/reportdesigner"
xmlns="https://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition">
<Description>XXX</Description>
<!-- ... -->
</Report>
Eléments constitutifs
Un rapport se compose des éléments suivants :
- Comme un document Word : corps, entête et pied de page :
- Chacun peut accueillir des contrôles :
- Des données : paramètres, datasets, images embarquées :
Page
La page en sortie est définie dans Report Properties > Page Setup
:
On peut ainsi définir l’unité de mesure dans la page :
- Les centimètres : cm
- Les pouces : in
Note :
- Le Designer n’est pas 100% WYSIWYG : contrairement à ce que l’on a dans Word en particulier en mode Page, les sauts et séparateurs de pages n’y sont pas figurés.
- Header/Footer : on peut ne pas les afficher en 1ere et/ou dernier page mais la place est réservée !
- Le moteur de rendu peut chercher à compresser en hauteur le rapport : si une TextBox est cachée (propriété Hidden=True), il est possible que tous les éléments en dessous soit remontés, ce qui peut casser la mise en page souhaitée. Il faudra alors utiliser des contrôles pour caler les autres à leur place, par exemple un Rectangle vide mais visible, de même hauteur que le texte caché, ou bien un Rectangle avec un saut de page :
Définition des images
Autant le rendu des textes est bon, celui des images n’est pas très fin. En effet, la définition est de 96 DPI, soit 795 pixels pour 21 cm, la largeur d’une feuille A4.
D’autre part, les images peuvent être embarquées dans le rapport. Alors, elles sont compressées assez fortement. Le rendu final une fois imprimé peut être assez décevant. Il est donc préférable de « sortir » le texte des images pour le mettre dans des TextBox puis de faire des essais pour trouver la taille d’image qui passe le mieux.
Déplacement et redimensionnement
Les éléments peuvent être déplacés et redimensionnés de manière classique :
- A la souris :
- Avec calage sur les autres éléments figurés par des lignes bleues :
- Ou sans en maintenant la touche Ctrl appuyée, comme pour empêcher une fenêtre de se « docker » dans Visual Studio.
- Avec calage sur les autres éléments figurés par des lignes bleues :
- Au clavier :
- Ctrl+Flèche pour déplacer
- Ctrl+Maj+Flèche pour redimensionner
- En modifiant les properties
Location
etSize
:
Pour aligner (en position ou en taille) plusieurs éléments entre eux, il peut être pratique d’utiliser la barre d’outils Report Formatting
.
L’élément de référence (par rapport auquel caler les autres) est alors le premier sélectionné. C’est celui dont les points d’attache sont sur fond blanc :
Enfin, pour centrer un/des élément(s) horizontalement ou verticalement dans son élément parent, utiliser le menu
Format > Center in Form
:
Note : Faire attention aux éléments suivants :
- L’agrandissement ou le déplacement d’un contrôle en dépassant la bordure de son parent (rectangle ou page body) agrandit ce dernier automatiquement. On peut vite se retrouver avec une page de 22 cm de large s’imprimant au final sur 2 pages A4 !
- Le déplacement d’un rectangle container par modification de sa propriété Location ne déplace pas les contrôles enfants !
Visibilité conditionnelle
Il est possible de masquer un contrôle dynamiquement grâce à l’usage d’une « expression de visibility » mapée à la propriété Hidden du contrôle :
Les expressions n’étant pas toujours bien évaluées, il est plus sûr d’avoir des expressions de ce type : =IIf(Parameters!ReportParameter1.Value = "", True, False)
Z-Index
Contrairement à la fenêtre d’outil Document Outline
du designer de WinForms, son homologue pour le Designer de RDLC ne permet pas de monter ou de descendre le Z-Index d’un contrôle en modifiant l’ordre d’ajout des contrôles dans le container parent. On peut certes se servir des menus Bring Forward
et Send Backward
mais ils ne sont pas accessibles aisément :
- uniquement via un clic-droit sur le contrôle, parmi le menu
Layout
- il n’y a pas d’entrée dans le menu Format
- l’ajout des boutons correspondants dans une barre d’outil ne marche pas, les boutons restant grisés.
Par contre, les boutons Bring to Front
et Send to Back
sont accessibles dans la barre d’outils Report Formatting
. On peut donc les utiliser en structurant le rapport grâce à des rectangles pour regrouper des contrôles. Avec un peu de pratique, on arrive facilement, sans trop de clics, à obtenir le bon empilage de contrôles.
Astuces
Report Properties
Pour afficher la boîte de dialogue Report properties
, on peut :
- Utiliser le bouton dans la barre de commandes Layout :
- Faire un clic-droit dans une zone libre du rapport et sélectionner le menu correspondant :
Code custom
Dans les expressions, on peut appeler des fonctions personnalisées, ce qui peut être plus propres et lisibles, et permet la réutilisation de portion de code. Ces fonctions sont définies dans Report properties > Code
et s’appelent dans une expression en les préfixant avec Code.
.
A noter qu’il n’y a pas de coloration syntaxique à cet endroit ! On peut s’en sortir en utilisant Excel pour concevoir et tester nos fonctions, modulo le fait que leur “VBA” diffère un peu :
- Type par défaut :
- Excel : Variant
- RDLC : Object
- Spécifique à Excel :
Debug.Print
➜ A commenter.* Is Nothing
etIsEmpty(*)
➜ UtiliserIsNothing(*)
dans les RDLC.- Pour remplacer
IsNothing(*)
des RDLC, on peut ajouter cette fonction dans Excel :
Function IsNothing(vValue) As Boolean
If IsObject(vValue) Then
IsNothing = vValue Is Nothing
Else
IsNothing = IsEmpty(vValue)
End If
End Function
Touche [Échap]
La touche Échap permet de :
– Sortir une TextBox du mode édition, mode par défaut lorsque l’on clique sur le texte qu’elle contient ou en son centre.
– Faire « remonter » la sélection dans la hiérarchie enfant → parent des contrôles.
TextBox
Une TextBox permet d’afficher du texte en mode riche i.e. avec une mise en forme, sur plusieurs lignes, mais uniquement écrit horizontalement. Elle supporte l’alignement vertical et une marge intérieure (padding) :
Sa valeur peut combiner du texte statique avec des expressions, avec deux cas de figure simples :
- Texte statique
- Expression unique, modifiable directement par clic-droit :
- Ces deux cas se concurrencent :
- Du fait du mode « Expression unique », on ne peut pas avoir un simple texte “=”, mêmes avec des espaces. C’est considéré comme une expression invalide. On peut s’en sortir avec l’expression =”=” mais on perd en lisibilité du rapport.
- En même temps, lorsque le texte contient de la mise en forme, on n’a pas le mode « Expression unique ». Dans l’exemple suivant, la parenthèse est en gras :
- On en déduit une astuce pour afficher un “=” sans avoir à coder une expression : il suffit de faire suivre le signe égal par un espace en gras.
TextBox et PlaceHolder
Le cas de figures plus complexe consiste à combiner textes statiques et d’expressions. Chaque expression correspond alors à un “PlaceHolder” :
Une fois l’expression saisie, le placeholder créé est généralement symbolisé par un «Expr» dans le texte. Il suffit de double cliquer dessus ou de faire un clic-droit pour le modifier :
Alors, on a la possibilité d’attribuer un Label, ce qui permet d’augmenter encore la lisibilité du rapport :
Ce cas mixant textes statiques et expressions est à privilégier par rapport à une expression globale construisant le texte final, du type ="Année = " & Parameters!Periode.Value
, toujours en vue d’améliorer la lisibilité et donc la maintenabilité du rapport.
On peut aller encore plus loin en produisant du HTML en sortie d’expression. Ce dernier sera correctement interprété lorsque l’on coche le type de markup « HTML » dans les propriétés du PlaceHolder.
A noter que les expressions avec juste un paramètre sont déjà bien nommées :
Tablix
Pour éditer les propriétés d’une tablix, cliquer à l’intérieur pour éditer une cellule puis faire un clic droit dans le coin supérieur gauche :
Header/Footer – SubReport
- Les paramètres globaux
Globals!PageNumber
etGlobals!TotalPages
sont utilisables uniquement dans un header ou dans un footer. - Il est possible de ne pas imprimer l’entête / le pied de page sur la première et/ou dernière page. Par contre, la place reste réservée sur la page : cela fera juste une zone vide lors de l’impression et le corps de page commencera en dessous.
- Quand un rapport est utilisé en tant que subreport, ses header et footer et ses marges sont ignorés – ce sont ceux du rapport principal qui sont utilisés.
Appel C# de génération au format PDF en mode déconnecté
Il est possible de remplir un DataSet depuis le code appelant, sans besoin de connecter le rapport à une DataSource. On peut même DataSource et DataSet à la main directement dans le XML du fichier RDLC :
<DataSources>
<DataSource Name="DummyDataSource">
<ConnectionProperties>
<DataProvider />
<ConnectString />
</ConnectionProperties>
<rd:DataSourceID>28be852c-851b-4e60-8fcc-a6dcbd085955</rd:DataSourceID>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="ListeAssocies">
<Query>
<DataSourceName>DummyDataSource</DataSourceName>
<CommandText />
</Query>
<Fields>
<Field Name="Nom">
<DataField>Nom</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="Adresse">
<DataField>Adresse</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
</Fields>
</DataSet>
</DataSets>
Alors, pour générer le rapport (ici au format PDF), on alimente les données (paramètres et datasets) directement dans le code :
string[] streamids;
string mimeType;
string encoding;
string filenameExtension;
var reportViewer = new ReportViewer();
reportViewer.LocalReport.ReportPath = "..\\..\\Report1.rdlc";
// Paramètres
reportViewer.LocalReport.SetParameters(new ReportParameter("Nom", ""));
reportViewer.LocalReport.SetParameters(new ReportParameter("Adresse", ""));
// DataSet "ListeAssocies"
DataTable tmp = new DataTable();
DataColumn dc1 = tmp.Columns.Add("Nom");
DataColumn dc2 = tmp.Columns.Add("Adresse");
var row = tmp.NewRow();
row.SetField(dc1, "xxx");
row.SetField(dc2, "xxx");
tmp.Rows.Add(row);
row = tmp.NewRow();
row.SetField(dc1, "yyy");
row.SetField(dc2, "yyy");
tmp.Rows.Add(row);
reportViewer.LocalReport.DataSources.Add(new ReportDataSource("ListeIntervenants", tmp));
// Génération au format PDF
byte[] bytes = reportViewer.LocalReport.Render(
"PDF", null, out mimeType, out encoding, out filenameExtension,
out streamids, out warnings);
using (FileStream fs = new FileStream("report1.pdf", FileMode.Create))
{
fs.Write(bytes, 0, bytes.Length);
}
Fenêtres flottantes
Faire attention aux fenêtres flottantes, non dockées, dans Visual Studio. Elles deviennent inutilisables dès qu’on ouvre et referme une des fenêtres de dialogue de propriétés d’un élément dans le RDLC, telles que les Report Properties
. C’est comme si ces fenêtres de dialogue, modales, étaient toujours ouverts. La seule solution est alors de redémarrer Visual Studio !