Impression avec Delphi (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 commenter.

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

F.Name := Nom;

F.Size := Taille;

F.Style := Styl;

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, []);


{Écriture 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) lenombre 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

procedure TForm1.NouvellePage(RealNewPage: boolean);

begin

{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}

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ées


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);

// Impression de la ligne

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.

La suite ...

Alphomega

Accueil