Impression et sortie PDF dans les applications métier Windows — choisir entre System.Drawing.Printing, WPF et bibliothèques de rapports

· · CSharp, .NET, WinForms, WPF, Impression, PDF, Rapports, Développement Windows, Conseil technique

Imprimer depuis une application métier Windows ne consiste jamais seulement à « dessiner avec PrintDocument ». Sauts de page absents, marges décalées, aperçu différent du papier ou boîte de dialogue alors qu’un PDF était attendu proviennent presque toujours d’une implémentation commencée sans comprendre le mécanisme d’impression.

Cet article organise les quatre options — System.Drawing.Printing WinForms, FlowDocument/FixedDocument WPF, bibliothèques PDF et rapports générés par Excel/Word — selon les besoins.

1. L’essentiel d’abord

  • Pour une liste simple ou une extension de WinForms existant, PrintDocument suffit. Les sauts de page reposent sur la répétition de PrintPage jusqu’à ce que HasMorePages soit false; sans cela, seule la première page est imprimée. 12
  • Le DPI d’impression diffère entièrement du DPI écran. PageSettings.PrinterResolution est la résolution de l’imprimante ; HardMarginX/HardMarginY sont la zone physique non imprimable et Margins la marge logique. Réutiliser directement les coordonnées écran fausse la mise en page. 345
  • WPF distingue le chemin GDI et le chemin XPS. WPF utilise nativement XPS et convertit automatiquement vers GDI si l’imprimante ne prend pas XPSDrv en charge. 67
  • FlowDocument est fluide, FixedDocument fixe. Le premier privilégie la lisibilité et se relaye selon la fenêtre/résolution ; le second la fidélité à l’affichage ou à l’imprimante. 89
  • Format et bac passent par PrintDialog, PrintTicket et PrintQueue. Validez et fusionnez la demande avec les capacités réelles de l’imprimante via PrintQueue.MergeAndValidatePrintTicket. 1011
  • Microsoft Print to PDF est interactif. Il demande par défaut l’emplacement d’enregistrement ; il ne convient donc pas aux lots PDF sans surveillance. 12
  • Microsoft ne prend pas en charge l’automatisation Office dans un serveur ou service. Office suppose un bureau interactif et un profil utilisateur ; une exécution non interactive peut être instable ou se bloquer. 13
  • Les services Windows s’exécutent en Session 0. Ils ne peuvent pas afficher de boîtes de dialogue et ne doivent pas dépendre de l’imprimante par défaut de la session utilisateur. 14
  • Une fuite d’objets GDI finit par faire tomber le processus. Les handles GDI ont une limite finie par processus, configurable par GDIProcessHandleQuota. 15

Table de décision — besoin × approche

Besoin Approche appropriée Pourquoi
Quelques pages d’une liste ou d’un bordereau simple, WinForms PrintDocument Classes standard suffisantes et réutilisation du dessin GDI+ existant
Rapports à nombreux traits, plusieurs formats ou mise en page complexe Bibliothèque de rapports ou mise en page fixe avec FixedDocument Calcul de coordonnées, sauts de page et typographie japonaise sont coûteux à développer
Impression par lot importante sans surveillance PrintDocument.Print() programmatique / soumission directe à XpsDocumentWriter Zéro boîte de dialogue et pas de dépendance à l’imprimante par défaut
PDF seulement, sans impression Génération directe avec une bibliothèque PDF Sans boîte de dialogue, imprimante par défaut ni état du spooler
Aperçu obligatoire et circuit de validation interne PrintPreviewDialog WinForms, DocumentViewer WPF Interface standard de revue avant impression
Réutilisation d’un modèle Excel existant Génération directe Open XML SDK ou automatisation COM limitée Réutilise les actifs, mais évitez COM dans un service résident

2. Le minimum sur le fonctionnement de l’impression Windows

L’architecture Windows combine spooler et pilote par imprimante. L’application appelle des fonctions indépendantes du périphérique et le travail peut aller vers laser, traceur, imprimante matricielle, fax, etc. 16

  • Chemin GDI : une application Win32 GDI envoie les commandes comme EMF ou laisse le pilote rendre l’image avant le spooler. Les applications WinForms traditionnelles utilisent ce chemin. 166
  • Chemin XPS : les pilotes XPS et WPF envoient un document XPS au spooler ; si l’imprimante n’est pas XPSDrv, Windows convertit en GDI. 67

Un écran et une impression ne se ressemblent pas lorsque les DPI sont confondus : l’écran est autour de 96 DPI, l’imprimante souvent autour de 600 DPI. Construisez plutôt les coordonnées en centièmes de pouce ou en unité réelle. Tenez aussi compte de la zone physique non imprimable : HardMarginX/HardMarginY limitent l’impression même si Margins est réglé à zéro. 345

3. WinForms : System.Drawing.Printing en pratique

L’impression WinForms repose sur PrintDocument : configurez PrinterSettings/DefaultPageSettings, dessinez dans PrintPage, puis appelez Print(). 1

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;

public sealed class ReportPrinter
{
    // Données à imprimer. Une ligne est supposée par élément du bordereau de commande.
    private readonly IReadOnlyList<string> _lines;
    private int _lineIndex;

    public ReportPrinter(IReadOnlyList<string> lines) => _lines = lines;

    public void Print(string printerName)
    {
        using var document = new PrintDocument();
        document.DocumentName = "Détails de la commande";
        if (!string.IsNullOrEmpty(printerName))
        {
            document.PrinterSettings.PrinterName = printerName;
        }

        // Devient false ici si le nom de l'imprimante est inconnu (ne permet pas de détecter une imprimante hors ligne).
        if (!document.PrinterSettings.IsValid)
        {
            throw new InvalidOperationException(
                $"L'imprimante spécifiée est introuvable : {printerName}");
        }

        _lineIndex = 0;
        document.PrintPage += Document_PrintPage;
        document.Print();
    }

    private void Document_PrintPage(object? sender, PrintPageEventArgs e)
    {
        // MarginBounds est la zone de dessin qui reflète la marge logique (Margins).
        // PageBounds couvre toute la feuille ; tout dessin dans la marge matérielle est rogné.
        using var font = new Font("Meiryo", 10);
        float lineHeight = font.GetHeight(e.Graphics);
        float y = e.MarginBounds.Top;

        while (_lineIndex < _lines.Count)
        {
            if (y + lineHeight > e.MarginBounds.Bottom)
            {
                // Cette page se termine ici. Il reste du contenu à dessiner ; passez à la page suivante.
                e.HasMorePages = true;
                return;
            }

            e.Graphics.DrawString(_lines[_lineIndex], font, Brushes.Black,
                e.MarginBounds.Left, y);
            y += lineHeight;
            _lineIndex++;
        }

        // Toutes les lignes sont dessinées ; c'est donc la dernière page.
        e.HasMorePages = false;
    }
}

Le cas classique où « la deuxième page ne sort jamais » vient d’un mauvais HasMorePages. Vérifiez qu’il reste des lignes avant de le mettre à true, et laissez-le à false après la dernière page. QueryPageSettings permet de modifier taille/orientation par page. 12 Pour les marges, utilisez MarginBounds et tenez compte de HardMarginX/HardMarginY, puis testez plusieurs modèles d’imprimantes. PrintPreviewDialog réutilise le même PrintDocument pour l’aperçu ; les résolutions disponibles sont dans PrinterSettings.PrinterResolutions. 43

4. WPF : FlowDocument / FixedDocument et PrintDialog

  • FlowDocument est orienté lisibilité, se remet en page selon fenêtre et polices, et inclut recherche/pagination/modes d’affichage. 8
  • FixedDocument est orienté fidélité WYSIWYG et laisse à l’application le contrôle complet de la mise en page. 9

Employez FlowDocument pour un rapport interne fluide et FixedDocument pour un bordereau ou une étiquette où les traits et champs doivent rester à une position précise. L’impression de base utilise System.Windows.Controls.PrintDialog, différent de System.Windows.Forms.PrintDialog. 10

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

public static class FlowDocumentPrinter
{
    public static void Print(FlowDocument document, string jobName)
    {
        var printDialog = new PrintDialog();

        // N'imprimez que si l'utilisateur a cliqué sur OK.
        if (printDialog.ShowDialog() != true)
        {
            return;
        }

        // FlowDocument suppose des tailles de page différentes pour l'affichage et l'impression ;
        // ajustez donc la taille et les marges de la page cible à la zone imprimable de l'imprimante.
        var paginator = ((IDocumentPaginatorSource)document).DocumentPaginator;
        paginator.PageSize = new Size(
            printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);

        printDialog.PrintDocument(paginator, jobName);
    }
}

Pour contrôler format, bac ou recto-verso, employez PrintQueue, PrintTicket et PrintCapabilities, puis validez la demande par PrintQueue.MergeAndValidatePrintTicket. 711

using System.Linq;
using System.Printing;

public static class DuplexPrintTicketBuilder
{
    public static PrintTicket BuildDuplexTicket(PrintQueue printQueue)
    {
        PrintCapabilities capabilities = printQueue.GetPrintCapabilities();

        // Vérifiez au préalable si l'imprimante prend en charge l'impression recto-verso (reliure sur le bord long).
        bool supportsDuplex = capabilities.DuplexingCapability
            .Contains(Duplexing.TwoSidedLongEdge);

        var requestedTicket = new PrintTicket();
        if (supportsDuplex)
        {
            requestedTicket.Duplexing = Duplexing.TwoSidedLongEdge;
        }

        // Ne fusionnez que la demande d'impression recto-verso avec le PrintTicket par défaut de l'utilisateur.
        // Les paramètres non pris en charge sont automatiquement remplacés par des valeurs valides.
        ValidationResult result = printQueue.MergeAndValidatePrintTicket(
            printQueue.UserPrintTicket, requestedTicket);

        return result.ValidatedPrintTicket;
    }
}

Pour un rapport FixedDocument, XpsDocumentWriter.Write/WriteAsync envoie directement dans la file XPS ; la conversion GDI est automatique pour une imprimante non XPSDrv. 7

5. Options de sortie PDF

« Nous voulons du PDF » peut désigner : choisir manuellement l’enregistrement PDF lors d’une impression, produire un PDF depuis l’application en passant par la boîte de dialogue, ou produire de gros volumes sans impression ni intervention.

Microsoft Print to PDF convient aux deux premiers cas, pas au dernier. C’est une file et un pilote intégrés à Windows 10 et plus récents, visibles comme imprimante ordinaire, mais conçus pour demander un nom/emplacement de fichier à chaque impression. 12 Pour un lot non surveillé vers un chemin fixe, générez directement le PDF avec une bibliothèque dédiée.

Axe À vérifier
Licence OSS MIT/Apache ou GPL/AGPL, ou licence commerciale ; vérifiez toujours texte de licence et site vendeur pour obligations de publication, redistribution et usage commercial
Polices japonaises Incorporation partielle des polices, incorporation de la donnée de police et support de l’écriture verticale ou IVS si nécessaire
Fonctions de rapport Modèles, codes-barres/2D, signatures numériques, PDF/A et facilité de reprise d’une mise en page papier existante
Dépendances / runtime Fonctionnement serveur sans UI ou conteneur, dépendances natives, historique .NET Core/.NET
Performances Mémoire lors de gros volumes/pages et comportement en génération parallèle

Les licences dépendent du produit et de la version : vérifiez la documentation officielle avant adoption. Cet article ne recommande pas une bibliothèque nommée ; il donne les axes de décision.

6. Rapports via Excel / Word comme option

Quand un modèle Excel existe déjà, le réutiliser est parfois plus réaliste qu’une reconstruction. Deux approches existent :

Microsoft documente officiellement l’automatisation Office depuis serveur ou service Windows comme « non recommandée et non prise en charge ». Office est pensé pour un bureau interactif et un profil utilisateur ; en environnement non interactif, instabilité et deadlock sont possibles. 13 Dans un service résident ou lot, préférez la génération de fichier Open XML ; si COM est vraiment incontournable, exécutez-le dans une application résidente d’une session utilisateur interactive, pas dans un service.

7. Pièges pratiques

Impression depuis un service Windows — Session 0

Les services s’exécutent en Session 0, isolée des utilisateurs interactifs depuis Vista. Une boîte de dialogue peut être créée mais l’utilisateur ne la voit pas ; attendre sa réponse donne l’impression d’un blocage. 14 Les imprimantes réseau ou mappées par utilisateur sont également visibles différemment depuis le contexte du service. Décidez dès la conception quelles imprimantes sont visibles depuis quel compte et quelle session ; voir Understanding Windows Session Isolation et How to Build and Operate a Windows Service.

Lorsque l’imprimante est absente ou hors ligne

Un PrinterSettings.PrinterName inconnu ou l’absence d’imprimante par défaut échoue à l’impression. Validez avec PrinterSettings.IsValid, concevez la relance et la notification d’erreur, et ne considérez pas la soumission au spooler comme la fin : une imprimante réseau hors ligne peut garder le travail en attente.

Danger de dépendre de l’imprimante par défaut

Ne pas indiquer explicitement PrinterSettings.PrinterName rend le comportement risqué : changement par l’utilisateur ou par déplacement du portable et le rapport part vers une destination inattendue. Les rapports critiques doivent conserver l’imprimante cible dans un fichier de configuration ou des réglages applicatifs.

Fuites de handles GDI en fonctionnement long

Sans using ou Dispose() fiable pour Graphics, Font, Brush ou Pen, les handles GDI finissent par manquer et les appels de dessin échouent. Leur limite par processus est finie et configurable de 256 à 65 536 via GDIProcessHandleQuota. 15 Le problème apparaît souvent seulement en production dans une application résidentielle qui imprime fréquemment ; voir Investigating a Long-Running Crash in an Industrial Camera — The Handle-Leak Edition.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC assure le conseil technique sur l’impression et la sortie de rapports des applications métier Windows, la reconstruction de rapports tirant parti d’actifs Excel existants et la conception d’impression/sortie pour services résidents.

Références

  1. Microsoft Learn, PrintDocument Class. Rôle de PrintDocument et impression WPF via System.Printing 2 3

  2. Microsoft Learn, PrintPageEventArgs.HasMorePages Property. Valeur par défaut false et répétition de PrintPage jusqu’à false 2

  3. Microsoft Learn, PageSettings.PrinterResolution Property. Résolution de l’imprimante et choix dans PrinterSettings.PrinterResolutions 2 3

  4. Microsoft Learn, PageSettings.HardMarginX Property. Hard margin, marge physique de l’imprimante.  2 3

  5. Microsoft Learn, PageSettings.Margins Property. Marge par défaut et zone imprimable.  2

  6. Microsoft Learn, Windows Print Path Overview. Chemins GDI et XPS.  2 3

  7. Microsoft Learn, Printing documents overview. Impression XPS WPF, PrintDialog, PrintTicket/Capabilities/Queue/XpsDocumentWriter et conversion GDI.  2 3 4

  8. Microsoft Learn, Flow Document Overview. Document fluide adapté à la taille de fenêtre et résolution.  2

  9. Microsoft Learn, FixedDocument Class. WYSIWYG fixe pour fidélité écran/imprimante.  2

  10. Microsoft Learn, PrintDialog Class. PrintDialog WPF et réglage PrintTicket/PrintQueue.  2

  11. Microsoft Learn, How to: Validate and Merge PrintTickets. Capabilités et validation MergeAndValidatePrintTicket.  2

  12. Microsoft Learn, RemoveMPDW. Microsoft Print to PDF, file/pilote et configuration de l’image.  2

  13. Microsoft Support, Considerations for server-side Automation of Office. Office non recommandé/non pris en charge en automatisation non interactive côté serveur/service.  2

  14. Microsoft Learn, Service Changes for Windows Vista - Session 0 Isolation. Isolation de Session 0 et boîtes de dialogue non visibles des services.  2

  15. Microsoft Learn, GDI Objects. Limite de handles GDI et GDIProcessHandleQuota 2

  16. Microsoft Learn, Introduction to printing. Spooler, pilotes et EMF GDI.  2

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Dois-je créer l'impression avec PrintDocument ou avec la pile WPF ?
Le choix direct est PrintDocument pour une UI WinForms et FlowDocument/FixedDocument pour une UI WPF. Il n'est pas nécessaire de mélanger les technologies : le critère pratique est de correspondre au framework UI existant. Pour un nouveau projet WPF, décidez d'emblée si le rapport doit être fluide ou à mise en page fixe afin de centrer la conception sur FlowDocument ou FixedDocument.
Faut-il acheter une bibliothèque de rapports ?
Si réaliser vous-même, avec GDI+ ou WPF, des rapports complexes à nombreux traits, plusieurs formats de papier, polices japonaises incorporées ou écriture verticale coûte cher, une bibliothèque commerciale revient souvent moins cher. À l'inverse, un simple rapport comme une liste A4 convient très bien à PrintDocument ou FixedDocument. Évaluez d'abord la complexité des exigences.
Peut-on produire uniquement des PDF sans imprimer ?
Oui. Le PDF est un format de sortie ; le générer directement par une bibliothèque PDF, sans boîte de dialogue ni imprimante par défaut, convient souvent bien mieux au traitement par lot et à l'automatisation. Microsoft Print to PDF est un pilote conçu pour une interaction et non pour une génération PDF non surveillée.
Cela peut-il aussi fonctionner avec une imprimante d'étiquettes ou de tickets ?
C'est possible, mais étendre l'approche généraliste PrintDocument/FixedDocument peut devenir difficile. La plupart des imprimantes d'étiquettes ont leur propre langage de commande ou SDK et sont pilotées hors des chemins d'impression Windows standard. Vérifiez d'abord si le modèle cible fonctionne comme pilote Windows ordinaire ou exige un SDK dédié.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog