II. L'impression avec Delphi (épisode 2)▲
Ici, nous allons parler des API que nous allons pouvoir utiliser avec Delphi pour compléter notre objet Printer. Intéressons-nous à GetDc, GetDeviceCaps, DrawText, CreateFont et TabbedTextOut.
II-A. GetDc▲
On retrouve parfois sur les forums delphi 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 (placer un bouton sur une form et mettre çà sur son onclick)
procedure
TForm1.Button4Click(Sender: TObject);
var
H: integer
;
begin
H := GetDc(GetDeskTopWindow); {Obtention d'un handle de device context sur l'écran}
{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(Self
.Canvas.Handle,
0
,0
,
self
.Width, self
.Height,
H,
0
,0
,
Screen.Width, Screen.Height,
SRCCOPY);
releaseDc(handle, H); {ne pas oublier de relacher le hdc}
end
;
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. A ce propos, vous pouvez consulter l'aide Delphi 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 connaître 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 appelerons (évidemment) Episode2. Placez sur la feuille un composant TMemo que nous appelerons Details. L'écran devrait ressembler à l'image suivante:
Dans l'événement OnClick du bouton Episode 2 nous allons écrire le code suivant:
var
H: integer
;
begin
// On place dans une variable le handle de l'imprimante
H := Printer.handle;
//La constante HORZSIZE nous fournit la largeur en millimètres
Details.Lines.Add(format('Largeur zone imprimable en mm: %d'
, [GetDeviceCaps(H, HORZSIZE)]));
//La constante VERTSIZE nous fournit la hauteur en millimètres
Details.Lines.Add(format('Hauteur zone imprimable en mm: %d'
, [GetDeviceCaps(H, VERTSIZE)]));
//La constante HORZRES nous fournit la largeur en pixels
Details.Lines.Add(format('Largeur zone imprimable en mm: %d'
, [GetDeviceCaps(H, HORZRES)]));
//La constante VERTRES nous fournit la hauteur en pixels
Details.Lines.Add(format('Hauteur zone imprimable en mm: %d'
, [GetDeviceCaps(H, 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(H, HORZSIZE) / GetDeviceCaps(H, 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(format('pixels non imprimables à gauche: %d'
, [GetDeviceCaps(H, PHYSICALOFFSETX)]));
{La constante PHYSICALOFFSETY nous fournit la taille en pixels de la zone }
{ imprimable à partir du bord gauche de la feuille}
Details.Lines.Add(format('pixels non imprimables en haut: %d'
, [GetDeviceCaps(H, PHYSICALOFFSETY)]));
Je vous laisse vous référer à l'aide Delphi 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 retour à 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écedemment! Nous devons rajouter 2 variables S et R.
var
{rajouter dans la déclaration des variables de la fonction}
S: string
;
R: TRect;
begin
{notre précédent code ...............}
R.Left := 10
;
R.Top := 10
;
S := 'Calcul rectangle avec DrawText'
;
DrawText(Printer.handle, PChar(S), length(S), R, DT_CALCRECT);
Details.Lines.add(format('Largeur %d / Hauteur %D'
, [R.Right, R.Bottom]));
//Rajoutons un saut de ligne pour voir la différence
S := 'Calcul rectangle'#13'avec DrawText'
;
DrawText(Printer.handle, PChar(S), length(S), R, DT_CALCRECT);
Details.Lines.Add(format('Largeur %d / Hauteur %D'
, [R.Right, 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 chaîne 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.
var
H: integer
;
R: TRect;
begin
H := image1.Canvas.handle;
R := image1.ClientRect;
DrawText(H, PChar('NORD'
), length('NORD'
), R, DT_TOP or
DT_CENTER or
DT_SINGLELINE);
DrawText(H, PChar('CENTRE'
), length('CENTRE'
), R, DT_VCENTER or
DT_CENTER or
DT_SINGLELINE);
DrawText(H, PChar('SUD'
), length('SUD'
), R, DT_BOTTOM or
DT_CENTER or
DT_SINGLELINE);
DrawText(H, PChar('NORD_OUEST'
), length('NORD_OUEST'
), R, DT_TOP or
DT_LEFT or
DT_SINGLELINE);
DrawText(H, PChar('OUEST'
), length('OUEST'
), R, DT_VCENTER or
DT_LEFT or
DT_SINGLELINE);
DrawText(H, PChar('SUD_OUEST'
), length('SUD_OUEST'
), R, DT_BOTTOM or
DT_LEFT or
DT_SINGLELINE);
DrawText(H, PChar('NORD_EST'
), length('NORD_EST'
), R, DT_TOP or
DT_RIGHT or
DT_SINGLELINE);
DrawText(H, PChar('EST'
), length('EST'
), R, DT_VCENTER or
DT_RIGHT or
DT_SINGLELINE);
DrawText(H, PChar('SUD_EST'
), length('SUD_EST'
), R, DT_BOTTOM or
DT_RIGHT or
DT_SINGLELINE);
end
;
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à un 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
procedure
AngleTextOut(CV: TCanvas; const
sText: String
; x, y, angle:integer
);
var
LogFont: TLogFont;
SaveFont: TFont;
begin
{Sauvegarde de la fonte en cours du canvas}
SaveFont := TFont.Create;
SaveFont.Assign(CV.Font);
{Récupération des détails de la fonte dans la structure LogFont}
GetObject(SaveFont.Handle, sizeof(TLogFont), @LogFont);
{Modification à notre guise de l'orientation de la la fonte}
with
LogFont do
begin
lfEscapement := angle * 10
;
lfPitchAndFamily := FIXED_PITCH or
FF_DONTCARE;
end
; {with}
{ Création de la fonte par CreateFontIndirect en se basant sur la structure }
{ LogFont et assignation à la fonte du canvas}
CV.Font.Handle := CreateFontIndirect(LogFont);
SetBkMode(CV.Handle, TRANSPARENT);
{Ecriture du texte avec l'inclinaison voulue}
CV.TextOut(x, y, sText);
{On redonne au canvas sa fonte sauvegardée pour lui redonner son état initial}
CV.Font.Assign(SaveFont);
SaveFont.Free;
end
;
ATTENTION: Cette possibilité de modification de l'inclinaison ne fonctionne qu'avec les fontes True Type
Pour tester, créer un nouveau projet, poser un composant TImage sur la form, et ajouter la procédure ci-dessus dans le code de la feuille. Ensuite ajouter 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'
, round(image1.width / 4
), image1.Height, 90
);
Tester …
II-E. TabbedTextOut▲
A quoi va bien pouvoir nous servir cette API ? Tout simplement à écrire nos chaînes 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 4 colonnes. Vous pourrier calculer les positions X de début de chaque colonne, et écrire 4 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 4 valeurs entières qui contiendra les positions gauches de nos colonnes. A nouveau, j'utilise ici une sortie sur un TImage et vous transposerez vous-même sur le canvas de l'imprimante.
var
XCols: array
[1
..4
] of
integer
;
S: string
;
I, y, H: integer
;
begin
XCols[1
] := 0
;
XCols[2
] := round(image1.Width / 4
);
XCols[3
] := round(image1.Width / 2
);
XCols[4
] := image1.Width - XCols[2
];
y := 0
;
H := Image1.Canvas.TextHeight('M'
); {la plus haute}
for
i := 0
to
5
do
begin
S := format('L%dC0'#9'L%dC1'#9'L%dC2'#9'L%dC3'
, [I, I, I, I]);
TabbedTextOut(image1.Canvas.handle, 0
, y, PChar(S), length(S), 4
, XCols[1
], 0
);
Y := Y + H + 2
;
end
;
Voici la sortie sur l'image.
Vous voyez tout le parti que vous pouvez tirer de cette API?
Et bien voilà! Cet épisode 2 est terminé. A la prochaine fois pour le 3eme épisode dans lequel nous réunirons les 2 premiers pour faire une petite impression d'une grille de données.