Vous avez déjà très certainement eu à imprimer des documents grâce à WPF. Cela se résumant souvent à un PrintDialog et à l’appel sa méthode PrintVisual. Mais cette dernière ne permet qu’une impression sur une seule et même page, si le contenu est trop grand, celui-ci risque d’être tronqué lors de l’impression.
Nous pourrions alors diviser par nous-même le contenu en plusieurs groupes de contrôles et appeler la méthode PrintVisual pour chaque “groupe”. Comme exemple, j’ai pris une ListView contenant 3000 éléments, et sur chaque page, je peux en contenir 45, imaginez que vous deviez imprimer les 67 pages par 67 appels de la méthode PrintVisual et donc autant de tâches d’impressions envoyées à l’imprimante. Ne serais-ce que pour son aspect bricolage, cette méthode est à proscrire.
Heureusement, il nous est possible d’imprimer sur plusieurs pages de manière plus élégante, nous utiliserons toujours une instance de PrintDialog, mais cette fois-ci nous appellerons la méthode PrintDocument, celle-ci a de même deux paramètres, une chaine de caractère représentant la description de l’impression et un objet de type DocumentPaginator. Si nous regardons la documentation sur le MSDN, nous voyons que cette classe est abstraite, nous allons donc créer une nouvelle classe dans notre projet que nous ferrons hériter de DocumentPaginator
Nous verrons l’implémentation détaillée des méthodes et propriétés un plus loin, mais ce qui va surtout nous intéresser ici est la propriété PageCount et la méthode GetPage(…). Comme leurs nom l’indiquent, la propriété PageCount doit retourner le nombre de pages à imprimer et la méthode GetPage retournera un objet de type DocumentPage qui représentera physiquement une page imprimée. Un petit coup d’œil sur la documentation MSDN nous montre que le constructeur de DocumentPage prend en paramètre un Visual. En regardant le diagramme de classe, on voit que UIElement hérite de Visual, nous allons donc ici simplement utiliser un UserControl pour représenter le visuel de notre DocumentPage, celui ci contiendra uniquement une GridView avec deux colonnes dans notre cas, l’extrait de code suivant montre le conteneur parent de notre UserControl.
Chaque page sera totalement indépendante et recevra à sa construction les seuls éléments qu’elle doit imprimer. Le code-behind de notre UserControl sera donc le suivant :
La propriété constante ElementHeight nous servira ici dans la méthode statique RowPerPage retournant le nombre d’éléments que peut contenir une page, par soucis de simplicité, nous divisons juste la hauteur disponible par la hauteur d’un élément pour obtenir ce nombre.
A présent que nous avons notre “page” retournons à notre OneFor4Paginator. Nous commençons par ajouter deux champs à notre classes, _pageSize de type System.Windows.Size et _rowsPerPage de type int. A chaque modification de la propriété PageSize, nous mettons à jour _pageSize et nous appelons la méthode de classe RowPerPage pour mettre de même à jour notre champ _rowsPerPage. Nous nous occupons ensuite de remplir les propriétés IsPageCountValid et PageCount, dans cet article, nous retournerons toujours la valeur true pour la validation du nombre de pages, quant aux nombres de pages, nous retournons simplement le nombre d’éléments à imprimer par le nombre de d’éléments par page ( vous suivez jusque là ).
Quant à la méthode GetPage, nous récupérons la ligne par laquelle nous devons commencer à imprimer grâce au numéro de page passé en paramètre ( plus par pratique que par logique, ce numéro démarre à 0 ), nous calculons le nombres d’éléments à imprimer en prenons soin de vérifier s’il reste assez d’éléments pour remplir une page et éviter une belle “ArgumentException”, puis nous appelons le constructeur de notre page. Avant de passer notre UserControl en paramètre du DocumentPage à retourner, il nous faut appeller successivement les méthodes du framework Measure et Arrange sur notre UserControl, celle-ci s’occuperons de placer tout le contenu de notre UserControl, sans elles, nous imprimerons des pages entièrement blanches…
Il ne nous reste plus qu’à instancier un PrintDialog et appeller la méthode PrintDocument avec en paramètre un OneFor4Paginator créée précédemment.
Voilà donc une façon simple d’imprimer des documents de plusieurs pages grâce à WPF, par souci de rapidité, nous avons créé notre page en code XAML, ce qui a comme “défaut” d’imprimer quelque chose ressemblant à de simples impression écran de notre logiciel. Mais nous aurions pu tout aussi bien ré implémenter la méthode OnRender de notre UserControl et “dessiner” notre page à la main grâce au DrawingContext, avec un peu d’habitude et de la refactorisation de code, cette manière de faire peut s’avérer tout aussi rapide que la création en code xaml tout en imprimant un “vrai” document sans la sensation d’impression écran…
Aucun arbre n’a du être coupé pour tester le code de cet article, par respect de la nature, utilisez au maximum les imprimantes virtuelles pour vos tests. Microsoft XPS Document Writer ne donne certes pas toujours des aperçus réalistes mais d’autres logiciels comme PDF Créator nous permettent d’obtenir un aperçu très proche des vrais impressions.
Les recherches pour cet article ont été faites dans le cadre d’un stage pour l’entreprise SACEO et son logiciel Opisto : www.opisto.fr
Qu’est-ce que ModelOneFor4?!?
C’est une simple classe de modele de donnée que j’ai créée avec un champ « Id » et un champ « Nom »:
public class ModelOneFor4
{
public int Id {get;set;}
public String Nom {get;set;}
}
Bonjour
Le binding ElementName=Page ne fait référene à rien ?
Erreur dans le constructeur OneFor4Page ListeItems ne doit pas prendre de s la propriété à initialiser étant ListeItem.
J’ai rectifié le constructeur et éliminé dans le binding la mention ElementtName=Page, mais je n’imprime que les Headers, rien de la ListeItem dont le contenu est bien présent dans la variable page. ??
Bien entendu j’ai fourni dans l’appel PrintDocument la liste d’Items dans le format adéquat.
Merci si vous pouvez m’éclairer
Finalement j’ai trouvé : il manque après : page. Measure(PageSize) la déclaration page.UpdateLayout();
Aprés l’avoir ajouté c’est OK
Rajouté page.UpDateLayout() et c’est OK
Manquait aussi l’appel dans le constructeur à DataContext