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) :
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 :
Dans l'événement OnClick du bouton Episode 2, nous allons écrire le code suivant :
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.
// 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
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 :
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 :
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:
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.
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 :
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.