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

Impression Avec C++ Builder


précédentsommairesuivant

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

Ici, nous allons parler des API que nous allons pouvoir utiliser avec C++ Builder pour compléter notre objet Printer. Intéressons-nous à GetDc, GetDeviceCaps, DrawText, CreateFont et TabbedTextOut.

II-A. GetDc

On retrouve parfois sur les forums C++ Builder les questions suivantes :

« Comment imprimer l'écran sur une feuille A4 ? » ;
« Comment imprimer ma grille de données ? ».

L'impression d'un composant visuel (TForm, TDBGrid, TImage, etc.) passe par l'obtention d'un Hdc sur ce composant avec l'API GetDC.

Le code suivant est un bon exemple qui illustre l'effet sans imprimer (placez un bouton sur une Form et mettre le code suivant sur son onclick) :

 
Sélectionnez
void __fastcall TForm1::Button4Click(TObject *Sender)
 {
 HWND hdc;
 
 // Obtention d'un handle de device context sur l'écran
 hdc = GetDC( NULL );
 
 Printer()->BeginDoc();
 // Impression sur le canvas imprimante de la représentation graphique du hdc de l'écran
 // On utilise StretchBlt pour redimensionner la source à la taille de la destination 
 StretchBlt(Printer()->Canvas->Handle, 0, 0, Printer()->PageWidth, Printer()->PageHeight,
              hdc, 0, 0, Screen->Width, Screen->Height,  SRCCOPY);
 
 Printer()->EndDoc();
 ReleaseDC(Handle, hdc); //ne pas oublier de relacher le hdc
 }

Il faut se souvenir que ceci n'est qu'une copie graphique de la partie visible de l'élément à imprimer. Ainsi, si une fenêtre (par exemple) recouvre une partie de l'élément à imprimer, vous imprimerez également la partie de l'autre élément (indésirable dans ce cas). Vous remarquerez que vous pouvez utiliser cette technique pour créer une image de votre écran à un instant T pour la sauvegarder au format bmp par exemple.

S'il s'agit d'imprimer le contenu d'une TForm, il faut rappeler que cet objet possède une fonction GetFormImage qui renvoie un TBitmap, ce dernier pouvant être sauvegardé sous forme de fichier ou imprimé avec les techniques indiquées dans ce tutoriel. À ce propos, vous pouvez consulter l'aide C++ Builder sur TCustomForm.GetFormImage.

II-B. GetDeviceCaps

Dans l'épisode 1, nous avons vu que l'objet Printer utilise GetDeviceCaps de façon transparente pour l'utilisateur afin de lui fournir divers renseignements sur l'imprimante. On peut ainsi connaitre la hauteur et la largeur de la page en pixels. Mais GetDeviceCaps permet aussi d'obtenir d'autres renseignements intéressants. Rappelons que GetDeviceCaps nécessite un handle et une constante pour lui indiquer la valeur que nous voulons obtenir.

Pour illustrer notre propos, reprenons le projet exemple de l'épisode 1. Commencez par ajouter un bouton de commande que nous appellerons (évidemment) Episode2. Placez sur la feuille un composant TMemo que nous appellerons Details. L'écran devrait ressembler à l'image suivante :

Image non disponible

Dans l'événement OnClick du bouton Episode 2, nous allons écrire le code suivant :

 
Sélectionnez
HWND hdc;
 
 // On place dans une variable le handle de l'imprimante
 hdc = Printer()->Handle;
 
 //La constante HORZSIZE nous fournit la largeur en millimètres
 Details->Lines->Add("Largeur zone imprimable en mm: " + IntToStr(GetDeviceCaps(hdc, HORZSIZE)));
 //La constante VERTSIZE nous fournit la hauteur en millimètres
 Details->Lines->Add("Hauteur zone imprimable en mm: " + IntToStr(GetDeviceCaps(hdc, VERTSIZE)));
 //La constante HORZRES nous fournit la largeur en pixels
 Details->Lines->Add("Largeur zone imprimable en pixels: " + IntToStr(GetDeviceCaps(hdc, HORZRES)));
 //La constante VERTRES nous fournit la hauteur en pixels
 Details->Lines->Add("Hauteur zone imprimable en pixels: " + IntToStr(GetDeviceCaps(hdc, VERTRES)));
 
 // Vous pouvez voir ici qu'il est maintenant facile de calculer le rapport entre  les pixels 
 // et les millimètres sur votre page imprimée 1 pixel horizontal imprimante = 
 // GetDeviceCaps(hdc, HORZSIZE) / GetDeviceCaps(hdc, HORZRES)
 
 // La constante PHYSICALOFFSETX nous fournit la taille en pixels de la zone 
 // non imprimable à partir du bord gauche de la feuille
 Details->Lines->Add("pixels non imprimables à gauche: " + IntToStr(GetDeviceCaps(hdc, PHYSICALOFFSETX)));
 
 // La constante PHYSICALOFFSETY nous fournit la taille en pixels de la zone 
 // non imprimable à partir du bord haut de la feuille
 Details->Lines->Add("pixels non imprimables en haut: " + IntToStr(GetDeviceCaps(hdc, PHYSICALOFFSETY)));

Je vous laisse vous référer à l'aide C++Builder pour toutes les constantes utilisables.

II-C. DrawText

Cette API ressemble énormément à la procédure TextRect de l'objet TCanvas. Elle permet d'écrire un texte à l'intérieur du rectangle (TRect) passé en paramètre. Mais DrawText est bien plus puissante. Comme pour GetDeviceCaps, on peut utiliser certaines constantes pour obtenir différents résultats

Ces constantes sont nombreuses et comme précédemment, je vous invite à voir l'aide sur cette API pour en avoir tous les détails. Certaines méritent d'être détaillées.

DT_CALCRECT: Elle permet de déterminer la largeur et la hauteur du rectangle nécessaire pour contenir le texte passé en paramètre. Cela veut dire qu'avec cette constante, DrawText n'écrit pas le texte, mais effectue simplement un calcul. Selon le texte, elle va agir différemment. Si, par exemple, le texte contient des retours à la ligne, DrawText calculera la hauteur du rectangle en fonction du nombre de lignes. Si le texte ne contient qu'une seule ligne, DrawText va augmenter la largeur du paramètre TRect. Vous pouvez ainsi connaitre la taille exacte, au pixel près, du rectangle nécessaire pour contenir votre texte. Continuons pour cela notre code écrit précédemment! Nous devons rajouter deux variables S et R.

 
Sélectionnez
// ajouter dans la déclaration des variables de la fonction
 String S;
 TRect R;
 // notre précédent code ...............
 R.left = 10;
 R.top = 10;
 S = "Calcul rectangle avec DrawText";
 DrawText(Printer()->Handle, S.c_str(), S.Length(), &R, DT_CALCRECT);
 Details->Lines->Add("Largeur / Hauteur ; " + IntToStr(R.right) + " / " + IntToStr(R.Bottom));
 // ajoutons un saut de ligne pour voir la différence
 S = "Calcul rectangle \r\n avec DrawText";
 DrawText(Printer()->Handle, S.c_str(), S.Length(), &R, DT_CALCRECT);
 Details->Lines->Add("Largeur / Hauteur ; " + IntToStr(R.right) + " / " + IntToStr(R.Bottom));

Pour placer votre texte par rapport à un point défini, vous pouvez utiliser les constantes

  • DT_TOP, DT_VCENTER, DT_BOTTOM : pour l'alignement vertical ;
  • DT_LEFT, DT_CENTER, DT_RIGHT : pour l'alignement horizontal ;
  • DT_END_ELLIPSIS : permet d'indiquer à l'impression que le texte n'est pas affiché complètement en tronquant la chaine de caractères et en y ajoutant des points de suspension.

Voici un exemple d'utilisation de ces constantes de positionnement. Cet exemple utilise un TImage comme sortie, mais comme vous le savez maintenant, le canvas d'une image est identique à un canvas d'imprimante

 
Sélectionnez
HWND hdc;
 TRect R;
 
 hdc = Image1->Canvas->Handle;
 R = Image1->ClientRect;
 
 String chaine0 = "NORD";
 String chaine1 = "CENTRE";
 String chaine2 = "SUD";
 String chaine3 = "NORD_OUEST";
 String chaine4 = "OUEST";
 String chaine5 = "SUD_OUEST";
 String chaine6 = "NORD_EST";
 String chaine7 = "EST";
 String chaine8 = "SUD_EST";
 
 DrawText(hdc, chaine0.c_str(), chaine0.Length(), &R, DT_TOP | DT_CENTER | DT_SINGLELINE);
 DrawText(hdc, chaine1.c_str(), chaine1.Length(), &R, DT_VCENTER | DT_CENTER | DT_SINGLELINE);
 DrawText(hdc, chaine2.c_str(), chaine2.Length(), &R, DT_BOTTOM | DT_CENTER | DT_SINGLELINE);
 DrawText(hdc, chaine3.c_str(), chaine3.Length(), &R, DT_TOP | DT_LEFT | DT_SINGLELINE);
 DrawText(hdc, chaine4.c_str(), chaine4.Length(), &R, DT_VCENTER | DT_LEFT | DT_SINGLELINE);
 DrawText(hdc, chaine5.c_str(), chaine5.Length(), &R, DT_BOTTOM | DT_LEFT | DT_SINGLELINE);
 DrawText(hdc, chaine6.c_str(), chaine6.Length(), &R, DT_TOP | DT_RIGHT | DT_SINGLELINE);
 DrawText(hdc, chaine7.c_str(), chaine7.Length(), &R, DT_VCENTER | DT_RIGHT | DT_SINGLELINE);
 DrawText(hdc, chaine8.c_str(), chaine8.Length(), &R, DT_BOTTOM | DT_RIGHT | DT_SINGLELINE);

Voici ce que cela donne :

Image non disponible

Vous commencez maintenant à entrevoir toutes les possibilités d'impression avec cette API ?

II-D. CreateFont (ou CreateFontIndirect)

Pourquoi utiliser CreateFont alors que le canvas de l'objet Printer possède déjà une propriété Font ? Et bien tout simplement pour modifier l'orientation de votre texte. En effet, cette API permet de créer une fonte avec un certain angle. CreateFont nous oblige à redéfinir une fonte de toutes pièces. Plutôt que de tout refaire, nous allons utiliser les éléments de la fonte en cours du Canvas. Je vais tout simplement reprendre une procédure trouvée sur le net en y ajoutant quelques commentaires. Elle permet d'écrire sur n'importe quel canvas avec l'orientation désirée :

 
Sélectionnez
void __fastcall TForm1::AngleTextOut(TCanvas *CV, String sText, int x, int y, int angle)
 {
 LOGFONT lgfont;
 int TailleFont;
 String TypeFont;
 AnsiString text = "C++ Builder c'est Super";
 
 // Sauvegarde de la fonte en cours du canvas
 TailleFont = Image1->Canvas->Font->Size;
 TypeFont = Image1->Canvas->Font->Name;
 
 // Récupération des détails de la fonte dans la structure LogFont
 GetObject(Image1->Canvas->Font->Handle, sizeof(LOGFONT), &lgfont);
 
 // Modification à notre guise de l'orientation de la fonte
 lgfont.lfEscapement = 450;   //Angle *10
 lgfont.lfOrientation = 450;  //Angle *10
 lgfont.lfOutPrecision = OUT_TT_ONLY_PRECIS;
 lgfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
 
 // Création de la fonte par CreateFontIndirect en se basant sur la structure
 // LogFont et assignation à la fonte du canvas
 Image1->Canvas->Font->Handle = CreateFontIndirect(&lgfont);
 SetBkMode(Image1->Canvas->Font->Handle, TRANSPARENT);
 
 // Ecriture du texte avec l'inclinaison voulue
 Image1->Canvas->TextOut(50, 210, text); // TextOut(X, Y, text)
 
 // On redonne au canvas sa fonte sauvegardée pour lui redonner son état initial
 Image1->Canvas->Font->Size = TailleFont;
 Image1->Canvas->Font->Name = TypeFont;
 }

ATTENTION : cette possibilité de modification de l'inclinaison ne fonctionne qu'avec les fontes True Type

Pour tester, créez un nouveau projet, posez un composant TImage sur la form, et ajoutez la procédure ci-dessus dans le code de la feuille. Ensuite ajoutez dans l'événement OnClick de l'image le code exemple pour DrawText et ajouter les lignes suivantes:

 
Sélectionnez
Image1->Canvas->Font->Name = "Times New Roman";
 AngleTextOut(Image1->canvas, "INCLINE", int(Image1->width / 4), Image1->Height, 90);

Testez…

II-E. TabbedTextOut

À quoi va bien pouvoir nous servir cette API ? Tout simplement à écrire nos chaines de caractères sous forme de colonnes (Tabbed pour tabulations et textout pour écriture du texte). Imaginons que vous vouliez imprimer des chaines de caractères sur quatre colonnes. Vous pourriez calculer les positions X de début de chaque colonne, et écrire quatre fois l'instruction TextOut. Mais TabbedTextOut vous permet de le faire en « une seule passe ». Un petit exemple valant mieux qu'un long discours… :-)

Nous connaissons la largeur de notre page imprimée avec PageWidth. Si nous désirons 4 colonnes, nous allons créer un tableau de quatre valeurs entières qui contiendra les positions gauches de nos colonnes. À nouveau, j'utilise ici une sortie sur un TImage et vous transposerez vous-même sur le canvas de l'imprimante.

 
Sélectionnez
int XCols[4];
 String S;
 
 int I, Y, H;
 XCols[1] = 0;
 XCols[2] = int(Image1->Width / 4);
 XCols[3] = int(Image1->Width / 2);
 XCols[4] = Image1->Width - XCols[2];
 
 Y = 0;
 H = Image1->Canvas->TextHeight("M"); // la plus haute
 
 for (I = 0; I <+5; I++)
 {
  S = ("L" + IntToStr(I) + "C0 \t L" + IntToStr(I) + "C1 \t L" + IntToStr(I) +
           "C2 \t L" + IntToStr(I) + "C3");
  TabbedTextOut(Image1->Canvas->Handle, 0, Y, S.c_str(), S.Length(), 4, &XCols[1], 0);
  Y = Y + H + 2;
 }

Voici la sortie sur l'image :

Image non disponible

Vous voyez tous les bénéfices que vous pouvez tirer de cette API ?

Et bien voilà ! Cet épisode 2 est terminé. Dans le 3e épisode nous réunirons les deux premiers pour faire une petite impression d'une grille de données.


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.