III. Impression avec Delphi (épisode 3)▲
Nous allons poursuivre notre étude sur l'impression avec Delphi 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 Delphi. 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 ajouter l'unité Printers à la clause uses.
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 copirez en entier.
implementation
{$R *.DFM}
var
Nous avons d'abord besoin de définir quelques variables globales
Entete, Details, PiedPage: TRect; {pour connaître les limites des zones respectives}
YCurr: integer
; {La position en cours pour l'écriture d'une ligne}
C: TCanvas; {Notre canvas pour imprimer}
Li: integer
; {nombre de lignes imprimées }
Dx, Dy: integer
; {Décalage par rapport aux marges}
NPage: word
= 0
; {Le numéro de page à incrémenter à chaque nouvelle page}
TotalPages: word
= 0
; {Le nombre de pages à imprimer. Pour afficher Page x / TotalPages }
{ TForm1 }
{Ajoutons 2 fonctions de conversion des millimètres en pixels (voir « Travaillez en mm sur les imprimantes ») }
function
Millimetres2PixelsX(Millims: integer
): integer
;
begin
result := MulDiv(GetDeviceCaps(Printer.Handle, LOGPIXELSX), 10
* Millims, 254
);
end
;
function
Millimetres2PixelsY(Millims: integer
): integer
;
begin
result := MulDiv(GetDeviceCaps(Printer.Handle, LOGPIXELSY), 10
* Millims, 254
);
end
;
Une procédure pour modifier plus facilement la fonte de notre canvas d'imprimante
procedure
DefFonte(F: TFont; Nom: string
; Taille: integer
; Styl: TFontStyles);
begin
with
F do
begin
Name := Nom;
Size := Taille;
Style := Styl;
end
;
end
;
Dans l'en-tète, nous allons imprimer en haut à gauche: La date Centré (horizontal et vertical), le nom de la table en haut à droite, le numéro de la page
procedure
TForm1.ImprimeEnTete;
var
S: string
;
begin
{On dessine l'entourage (si on le désire)}
C.Rectangle(Entete);
DefFonte(C.Font, 'Arial'
, 10
, []);
{ 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}
C.TextOut(Entete.Left + Dx, Entete.top + Dy, DateToStr(now));
{Notre page en cours}
S := format('Page %d / %d'
, [NPage, TotalPages]);
C.TextOut(Entete.Right - C.TextWidth(S) - Dx, Entete.top + Dy, S);
{Enfin, centré au milieu de notre rectangle d'en-tète le nom de la table}
DefFonte(C.Font, 'Times New Roman'
, 24
, []);
DrawText(C.handle, PChar(Datas.TableName), length(Datas.tablename),
Entete, DT_VCENTER or
DT_SINGLELINE or
DT_CENTER);
{On dessine si on le désire un entourage autour de la zone Détails}
C.Rectangle(Details);
end
;
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
procedure
TForm1.ImprimePiedPage;
var
S: string
;
begin
C.Rectangle(PiedPage);
DefFonte(C.Font, 'Arial'
, 10
, []);
S := format('%d lignes imprimées sur un total de %d'
, [Li, Datas.RecordCount]);
DrawText(C.handle, PChar(S), length(S), PiedPage, DT_VCENTER or
DT_SINGLELINE or
DT_CENTER);
end
;
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.
procedure
TForm1.NouvellePage(RealNewPage: boolean
);
begin
if
RealNewPage then
Printer.NewPage;
Inc(NPage);
ImprimeEnTete;
YCurr := Details.Top + Dy;
end
;
procedure
TForm1.ImprimerClick(Sender: TObject);
var
HM: integer
;
T: array
of
integer
;
S: string
;
Bm: TBookmark;
Bl: boolean
;
LignesParPage: word
;
begin
Li := 0
; {initialisation du nombre de lignes imprimée}
Dx := Millimetres2PixelsX(2
); {Calcul en pixels d'un décalage haut et bas de 2 mm }
{en cas d'impression texte près des marges}
Dy := Millimetres2PixelsY(2
);
with
Printer do
begin
Orientation := poLandScape;
BeginDoc;
{Hauteur de l'en-tète = 10% de la hauteur totale}
Entete := Rect(0
, 0
, PageWidth, muldiv(PageHeight, 10
, 100
));
{Hauteur du pied de page = 5% de la hauteur totale}
PiedPage := Rect(0
, PageHeight - muldiv(PageHeight, 5
, 100
), PageWidth, PageHeight);
end
;
with
Details do
begin
Left := 0
;
Right := Printer.PageWidth;
{Haut de la zone détails à 10 mm sous le bas de l'en-tète}
Top := EnTete.bottom + Millimetres2PixelsY(10
);
{Bas de la zone détails à 5 mm au-dessus du haut du pied de page}
Bottom := PiedPage.Top - Millimetres2PixelsY(5
);
end
;
Voici le schéma de notre « découpage »
C := Printer.Canvas; {Pour un code plus concis}
//Calcul du nombre de ligne par page, et ainsi du nombre de page
DefFonte(C.Font, 'Arial'
, 11
, []);
Hm := C.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) div
HM;
{Le nombre de pages sera calculé en divisant le nombre total de lignes par LignesParPage}
TotalPages := Datas.RecordCount div
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 mod
LignesParPage) <> 0
;
{Si on obtient un reste, il nout faut ajouter une page}
if
BL then
inc(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}
SetLength(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
);
with
Datas do
begin
Bm := GetBookmark;
DisableControls;
first;
while
not
eof do
begin
{Utilisation d'une procédure pour construire la chaîne à utiliser avec TabbedTextOut}
DefLigne(S);
{à chaque ligne on incrémente le nombre de lignes imprimées (pour le bas de page)}
Inc(Li);
TabbedTextOut(C.handle, Dx, yCurr, PChar(S), length(S), high(T), T[0
], 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) then
begin
ImprimePiedPage;
NouvellePage(true
);
end
;
end
;
{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 then
ImprimePiedPage;
GotoBookmark(Bm);
FreeBookmark(Bm);
EnableControls;
end
;
Printer.EndDoc;
end
;
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
procedure
TForm1.DefiniColonnes(var
Tb: array
of
integer
);
var
I, J, L: integer
;
T: array
of
single
;
begin
L := Details.Right - Details.Left;
SetLength(T, high(Tb));
with
DBGrid1 do
for
I := low(T) to
high(T) do
T[I] := L * (Columns[I].Width / Width);
Tb[0
] := Details.Left;
for
I := low(Tb) + 1
to
high(Tb) do
Tb[I] := Tb[I - 1
] + round(T[I - 1
]);
end
;
procedure
TForm1.DefLigne(var
S: string
);
var
I: integer
;
begin
S := ''
;
with
Datas do
for
I := 0
to
Fields.Count - 1
do
S := S + Fields[I].AsString + #9
;
SetLength(S, Length(S) - 1
); {Pour supprimer le dernier caractère de tabulation}
end
;
end
.
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 épisode (bientôt), nous réaliserons pour notre grille un module d'aperçu avant impression.