IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Impression Avec C++ Builder

Impression Avec C++ Builder


précédentsommairesuivant

3. L'impression avec C++ Builder (épisode 3)

Nous allons poursuivre notre étude sur l'impression avec C++ Builder en utilisant les divers éléments vus dans les précédents épisodes pour imprimer une grille de données. Pour cela, nous allons réaliser ensemble ce projet exemple. Nous utiliserons les données de la table employee.db de la base DBDemos fournie comme exemple avec C++ Builder " COMMON\Borland Shared\Data ". Nous allons imprimer les données de la table en ajoutant à notre état de sortie un en-tête et un bas de page.

1) Créez un projet et sauvegardez-le à l'endroit qui vous plaira et ajoutez Printers.hpp dans l'entête.

2) Sur la "Tform", disposez à votre guise un TDatasource (Datasource1), un TTable (Datas), un TDBGrid (DBGrid1) et un TButton (Imprimer). Dans l'inspecteur d'objet, définissez les propriétés :

  • Datasource1.Dataset = Datas
  • DBGrid1.DataSource = Datasource1
  • Datas.DatabaseName = DBDemos
  • Datas.Active = true

Les données doivent maintenant être affichées dans la grille.

Bien! En ce qui concerne le coté visuel, c'est terminé. Vous pouvez fermer la feuille pour vous concentrer sur le code lui-même. Pour cette fois, j'ai préféré écrire le code et vous le copierez en entier.

Nous avons d'abord besoin de définir quelques variables globales

 
Sélectionnez
 TRect Entete ;
 TRect Details ;
 TRect PiedPage;        // pour connaître les limites des zones respectives
 int YCurr;             // La position en cours pour l'écriture d'une ligne
 int Li;                // nombre de lignes imprimées
 int Dx;
 int Dy;                // Décalage par rapport aux marges
 int unsigned short NPage = 0;          // Le numéro de page à incrémenter à chaque nouvelle page
 int unsigned short TotalPages = 0;     // Le nombre de pages à imprimer. Pour afficher Page x / TotalPages
 int resultX;
 int resultY;
 AnsiString S;
 vector<int> T;         // Déclaration du tableau dynamique

Ajoutons 2 fonctions de conversion des millimètres en pixels (voir "Travaillez en mm sur les imprimantes")

 
Sélectionnez
 void __fastcall TForm1::Millimetres2PixelsX(int Millims)
 {
  resultX = MulDiv(GetDeviceCaps(Printer()->Handle, LOGPIXELSX), 10 * Millims, 254);
 }
 //---------------------------------------------------------------------------
 void __fastcall TForm1::Millimetres2PixelsY(int Millims)
 {
  resultY = MulDiv(GetDeviceCaps(Printer()->Handle, LOGPIXELSY), 10 * Millims, 254);
 }

Une procédure pour modifier plus facilement la fonte de notre canvas d'imprimante

 
Sélectionnez
 void __fastcall TForm1::DefFonte(TFont *F, String Nom, int Taille, TFontStyles Styl)
 {
  F->Name = Nom;
  F->Size = Taille;
  F->Style = Styl;
 }

Dans l'entête, nous allons imprimer en haut à gauche: La date Centrée (horizontal et vertical), le nom de la table en haut à droite, le numéro de la page

 
Sélectionnez
 void __fastcall TForm1::ImprimeEnTete()
 {
 String S;
 // On dessine l'entourage (si on le désire)

 Printer()->Canvas->Rectangle(Entete);

 DefFonte(Printer()->Canvas->Font, "Arial", 10, TFontStyles());
 // Ecriture de la date en haut à gauche en n'oubliant pas les
 // décalages par rapport aux marges
 // Vous remarquerez que l'on aurait aussi pu utiliser DrawText avec DT_LEFT or DT_VTOP
 Printer()->Canvas->TextOut(Entete.Left + Dx, Entete.Top + Dy, DateToStr(Date()));
 // Notre page en cours
 S = ("Page" + IntToStr(NPage) + " / " + IntToStr(TotalPages));
 Printer()->Canvas->TextOut(Entete.Right - Printer()->Canvas->TextWidth(S) - Dx, Entete.top + Dy, S);

 // Enfin, centré au milieu de notre rectangle d'en-tète le nom de la table
 DefFonte(Printer()->Canvas->Font, "Times New Roman", 24, TFontStyles());
 DrawText(Printer()->Canvas->Handle, (Datas->TableName).c_str(), (Datas->TableName).Length(), &Entete,
     			DT_VCENTER | DT_SINGLELINE | DT_CENTER);
 // On dessine si on le désire un entourage autour de la zone Détails
 Printer()->Canvas->Rectangle(Details);
 }

Dans le pied de page, nous allons centrer (horizontal et vertical) le nombre d'enregistrements déjà imprimés par rapport au nombre total d'enregistrements.

 
Sélectionnez
 void __fastcall TForm1::ImprimePiedPage()
 {
 String S;
 Printer()->Canvas->Rectangle(PiedPage);
 DefFonte(Printer()->Canvas->Font, "Arial", 10, TFontStyles());
 S = (IntToStr(Li) + "lignes imprimées sur un total de "  + IntToStr(Datas->RecordCount));
 DrawText(Printer()->Canvas->Handle, S.c_str(), S.Length(), &PiedPage,
 				 DT_VCENTER | DT_SINGLELINE | DT_CENTER)
 }

J'ai découpé le code en procédures distinctes pour mieux structurer le déroulement du programme.

J'utilise ici une variable RealNewPage qui va nous indiquer si on doit demander une nouvelle page à Printer. Pourquoi ? Tout simplement parce que cette procédure NouvellePage va être utilisée en tout premier avant même de commencer l'impression des lignes. Donc, au premier passage, la nouvelle page existe déjà (créée par Printer->BeginDoc()). Si on ne prenait pas cette précaution, nous aurions une page blanche à chaque impression lancée.

 
Sélectionnez
 void __fastcall TForm1::NouvellePage(bool RealNewPage)
 {
 if(RealNewPage)
  {
   Printer()->NewPage();
  }
 NPage++;
 ImprimeEnTete();
 YCurr = Details.Top + Dy;
 }

 //---------------------------------------------------------------------------
 void __fastcall TForm1:: ImprimerClick(TObject *Sender)
 {
 int HM;
 TBookmark Bm;
 bool Bl;
 Word LignesParPage;

 Li = 0;                 // initialisation du nombre de lignes imprimée
 Millimetres2PixelsX(2); // Calcul en pixels d'un décalage haut et bas de 2 mm
 Dx = resultX;           // en cas d'impression texte près des marges
 Millimetres2PixelsY(2);
 Dy = resultY;

 Printer()->Orientation = poLandscape;
 Printer()->BeginDoc();

 // Hauteur de l'entète = 10% de la hauteur totale
 Entete = Rect(0, 0, Printer()->PageWidth, MulDiv(Printer()->PageHeight, 10, 100));

 // Hauteur du pied de page = 5% de la hauteur totale
 PiedPage = Rect(0, Printer()->PageHeight - MulDiv (Printer()->PageHeight, 5, 100),
 				 Printer()->PageWidth, Printer()->PageHeight);

 Details.Left = 0;
 Details.Right = Printer()->PageWidth;

 // Haut de la zone détails à 10 mm sous le bas de l'en-tète
 Millimetres2PixelsY(10);
 Details.Top = Entete.bottom + resultY;

 // Bas de la zone détails à 5 mm au-dessus du haut du pied de page
 Millimetres2PixelsY(5);
 Details.Bottom = PiedPage.Top - resultY;

Voici le schéma de notre "découpage"

Image non disponible
 
Sélectionnez
 // Calcul du nombre de ligne par page, et ainsi du nombre de page
 DefFonte(Printer()->Canvas->Font, "Arial", 11, TFontStyles());
 HM = Printer()->Canvas->TextHeight("M");     // On calcule la hauteur d une ligne en pixels

 // On peut ainsi calculer le nombre de lignes que peut contenir la zone Details
 LignesParPage = (Details.Bottom - Details.Top - Dy) / HM;

 // Le nombre de pages sera calculé en divisant le nombre total de lignes par LignesParPage
 TotalPages = Datas->RecordCount / LignesParPage;

 // ATTENTION: on doit également vérifier si les pages seront complètes.
 // Avec mod on peut voir s'il nous restera des lignes à imprimer en fin de document
 Bl = ((Datas->RecordCount) % (LignesParPage)) != 0;

 // Si on obtient un reste, il nout faut ajouter une page
 if(Bl)
  {
   TotalPages++;
  }

 // Prenons un exemple! Si la zone Details peut contenir 35 lignes et si nous avons
 // 42 lignes à imprimer, nous aurons les valeurs suivantes:
 // TotalPages := 42 div 35; Donc  TotalPages  =  1 (1 page de 35 lignes)
 // Bl = (42 mod 35) <> 0 ;  Donc  true puisque 42 mod 35 = 7.
 // Ainsi, nous avons défini que nous aurons 1 page complète de 35 lignes et une page
 // incomplète de 7 lignes
 // Utilisons le nombre de colonnes de la DBGrid pour définir nos colonnes sur la sortie imprimée
 // Bien sur, cela n'est valable que si vous avez un nombre raisonnable de colonnes.
 // La table utilisée dans l'exemple s'y prète bien.
 // Dans un autre cas, vous serez obligé de définir quelles colonnes vous voulez imprimer

 // Définition de sa taille
 int t = DBGrid1->Columns->Count;

 DefiniColonnes(t);

 NPage = 0;

 // Le paramètre de NouvellePage est false, car la page est déjà créée par BeginDoc
 NouvellePage(false);

 Bm = Datas->GetBookmark();
 Datas->DisableControls();
 Datas->First();
 DefFonte(Printer()->Canvas->Font, "Arial", 11, TFontStyles());
 while(!Datas->Eof)
 {
 Datas->Next();
 // Utilisation d'une procédure pour construire la chaîne à utiliser avec TabbedTextOut
 DefLigne();

 // à chaque ligne on incrémente le nombre de lignes imprimées (pour le bas de page)
 Li++;
 TabbedTextOut(Printer()->Canvas->Handle, Dx, YCurr, S.c_str(), S.Length(), T.back(), &T[1], 0);

 // après impression de la ligne en cours, on passe à la ligne suivante next;
 // Ici, nous allons gérer la nécessité de changer de page. yCurr est notre position
 // d'impression courante sur notre page.
 // Puisque nous connaissons la hauteur d'une ligne, nous allons incrémenter yCurr
 // de cette hauteur pour définir la valeur suivante en y
 YCurr = YCurr + HM;

 // Ici, nous devons vérifier si yCurr est toujours à l'intérieur de la zone Details,
 // et surtout si la place est suffisante pour imprimer une ligne
 if(YCurr >= (Details.bottom - HM))
  {
   ImprimePiedPage();
   NouvellePage(true);
  }
 }
 // Enfin, si le nombre de lignes ne correspondait pas à des pages complètes,
 // il faudrait imprimer le bas de page. On est obligé d'imprimer le pied de
 // page seulement quand une page de détails a été créée pour connaitre le nombre
 // de lignes déjà imprimées. Dans un autre cas, ImprimerPiedPage pourrait être
 // ajouté à la procédure NouvellePage
 if(Bl)
  {
   ImprimePiedPage();
  }

 Datas->GotoBookmark(Bm);
 Datas->FreeBookmark(Bm);
 Datas->EnableControls();

 Printer()->EndDoc();
 }

Pour définir mes colonnes ainsi que leur largeur respective, j'utilise la largeur des colonnes de la DBGrid. Connaissant la largeur de la DBGrid elle-même, je peux calculer le pourcentage de la largeur grille utilisée par chaque colonne. Je reporte ce pourcentage sur la largeur de la zone Details. Je n'ai pas cherché ici à savoir si mes colonnes remplissent toutes la grilles. Si vous désirez étaler au maximum vos colonnes, utilisez la somme des largeurs des colonnes utilisées plutôt que la largeur de la grille.

 
Sélectionnez
 void __fastcall TForm1::DefiniColonnes(int Tb)
 {
 int I;
 int J;
 int L;
 long double tabu = 0;
 long double lrg_cols = 0;
 long double lrg;

 L = Details.Right - Details.Left;
 lrg = Width;
 T.resize(Tb);
 T[0] = 0;
 for(I = 0; I < Tb; I++)
  {
   long double lrg_col = DBGrid1->Columns->Items[I]->Width;
   lrg_cols = lrg_cols + lrg_col;
   long double rapport_lrg = lrg_col / lrg;
   tabu =  tabu + (L * rapport_lrg);
   T[I + 1] = tabu;
  }

 long double coef = lrg_cols / lrg;
 for(I = 1; I < Tb; I++)
  {
   T[I] = T[I] / coef;
  }
 }
 
 //--------------------------------------------------------------------------- 
 void __fastcall TForm1::DefLigne()
 {
 int I;
 String s = "";
 for(I = 0; I <= Datas->Fields->Count - 1;I++)
  {
   s = s + Datas->Fields->Fields[I]->AsString + "\t";
  }
 S = s.SetLength(s.Length() - 1);// Pour supprimer le dernier caractère de tabulation
 }

Voilà! Votre impression de grille est terminée. J'ai utilisé TabbedTextOut pour l'exemple. Mais, pour notre grille de données, il aurait été plus présentable d'imprimer chaque valeur séparément. En effet, une présentation correcte de nos données aurait nécessité un formatage à 2 décimales des valeurs numériques ainsi qu'un alignement à droite. Dans ce cas, pour imprimer une copie plus poussée de notre grille, vous pourriez utiliser les propriétés de chaque objet TColumn de DBGrid1.Columns. Mais avec ce que vous avez appris jusqu'à maintenant, ce sera pour vous un jeu d'enfant.

Dans le prochain et dernier chapitre, nous réaliserons pour notre grille un module d'aperçu avant impression.


précédentsommairesuivant

Ce document est issu de http://www.développez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.