Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Eigene Druckvorschau mit TMetafile und TImage (https://www.delphipraxis.net/206545-eigene-druckvorschau-mit-tmetafile-und-timage.html)

runningsoft 4. Jan 2021 16:18

Eigene Druckvorschau mit TMetafile und TImage
 
Hallo zusammen,

ich bin gerade dabei eine eigene Druckvorschau zu erstellen (ich weiß, dafür gibts fertige Komponenten, die möchte ich aber nach Möglichkeit nicht nutzen).

Ich gehe dabei für jede einzelne Druckseite folgendermaßen vor:

-TMetafile erstellen
-auf TMetafileCanvas schreiben / Zeichnen, was auch immer

wenn Seitenende erreicht:
-unsichtbares TImage für die spätere Anzeige auf einem Panel erzeugen
-Inhalt des MetaFiles per StretchDraw in Image kopieren
-TMetafile wieder freigeben
-für neue Seite neues TMetaFile erstellen usw.

Das funktioniert soweit wunderbar, ich hab testweise 200 Druckseiten erzeugt (reiner Text), durch die ich dann ohne sichtbare Zeitverzögerung blättern konnte, indem ich einfach die betreffenden TImages sichtbar gemacht habe.

Was nicht funktioniert ist ein nachträglices Zoomen. Die ursprünglichen Metafiles, die man beliebig Zoomen könnte, hab ich nicht mehr. Wenn man die Images zoomt, wirds natürlich pixelig.

Deshalb meine Frage: Kann man die MetaFiles jeder einzelnen Seite irgendwie im Arbeitsspeicher halten, so dass ich jede einzelne TImage nach dem zoomen mit dem neuen Zoomfaktor neu schreiben kann?
Meine Idee war, beliebig viele Metafiles zur Laufzeit zu erzeugen (für jede Druckseite eines). Leider gibt es jedoch keine Eigenschaft TMetafile.Name, so dass ich daran bisher gescheitert bin.

Was könnte ich noch machen?
Könnte man eine Art "MetaFileListe" (TList ???) erstellen, der ich nach dem Erzeugen einer Seite die Daten des MetaFiles hinzufügen kann, so dass man beim Zoomen auf die Einträge in der MetafileListe zugreifen kann?

BerndS 4. Jan 2021 16:48

AW: Eigene Druckvorschau mit TMetafile und TImage
 
Hallo,
solltes du Zugriff auf die Quickreport Quellen haben, kannst du dir da sicher was abschauen.
Da gibt es z.B. in der Unit QRPrntr die TQRPageList und dazu passend Units zum Anzeigen und Drucken.

Gruß Bernd

DeddyH 4. Jan 2021 16:49

AW: Eigene Druckvorschau mit TMetafile und TImage
 
Oder vielleicht auch mal hier schauen: http://www.delphiarea.com/products/d...nents/preview/

TurboMagic 4. Jan 2021 18:48

AW: Eigene Druckvorschau mit TMetafile und TImage
 
Mit Sydney hast du doch Generics. Warum nicht eine
TObjectList<TMetaFile>?

Die kann dann auch die Referenz freigeben, da OwnsObject Semantik
eingebaut ist.

runningsoft 4. Jan 2021 22:27

AW: Eigene Druckvorschau mit TMetafile und TImage
 
Der Ansatz von TurboMagic ist glaub ich das, was ich möchte, allein: es funktioniert leider noch nicht :evil:

Ich habe bisher folgendes:
Delphi-Quellcode:
var
  fmVorschau: TfmVorschau;
  AnzPages, AktPage : integer;
  PrevMeta : TMetaFile;
  PrevMetaCanvas : TMetaFileCanvas;
  MFList : TObjectList<TMetaFile>;

procedure TfmVorschau.FormCreate(Sender: TObject);
var I : integer;
begin
AnzPages := 1;
AktPage := 1;
MFList := TObjectList<TMetafile>.Create;
end;

procedure TfmVorschau.CreateMetaFile;
begin
PrevMeta := TMetafile.Create;
PrevMeta.Enhanced := true;
PrevMeta.SetSize(PWidth,PHeight);       //Größe des Metafiles, je nach Papierformat
PrevMetaCanvas := TMetafileCanvas.Create(PrevMeta,0);       //Canvas erzeugen
PrintUtils.AktCanvas := PrevMetaCanvas; //aktuellen Canvas für Unit PrintUtils festlegen, diese enthält die Basisdruckroutinen
SetMapMode(AktCanvas.Handle,MM_TEXT);
SetTextAlign(AktCanvas.Handle,TA_BaseLine);
end;

...

procedure TfmVorschau.NewPage;
begin
//Freigabe des MetafileCanvas der VORHERIGEN Druckseite
PrevMetaCanvas.Free;
//PrevMeta.SaveToFile('D:\Test_Firebird_Server\' + IntToStr(AnzPages) + '.emf'); //diese Variante funktioniert

//Metafile der VORHERIGEN Druckseite in Objektliste ablegen und freigeben
MFList.Add(PrevMeta);
PrevMeta.Free;

//neue Seite erzeugen
Inc(AnzPages);
CreateMetafile;     //neues Metafile für nächste Seite erzeugen
end;

//Abschlussprozedur nach dem Erzeugen der letzten Druckseite
procedure TfmVorschau.FinishPreview;
begin
PrevMetaCanvas.Free;
//PrevMeta.SaveToFile('D:\Test_Firebird_Server\' + IntToStr(AnzPages) + '.emf'); //siehe oben
MFList.Add(PrevMeta);
PrevMeta.Free;
end;

...

//die Anzeigeprozedur produziert nur leere Seiten
//die Liste weist genau so viele Einträge auf, wie Seiten vorher gedruckt und in der Liste abgelegt wurden
procedure TfmVorschau.ShowAktPage;
var CurrMetaFile : TMetafile;
begin
CurrMetaFile := TMetafile.Create;
//CurrMetaFile.LoadFromFile('D:\Test_Firebird_Server\' + IntToStr(AktPage) + '.emf'); //Anzeige funktioniert auf diese Art und Weise

//Eintrag der gewünschten Seite auf Metafile-Liste holen --> ist das richtig so ???
CurrMetafile := MFList.Items[AktPage-1];
   
//Seitengröße entsprechend der gewählten Zoomstufe einstellen
CurrMetaFile.SetSize(PageP1.Width,PageP1.Height);

//mit dem Vorschauimage verbinden  
PrevIMG.Picture.Assign(CurrMetaFile);

//Freigabe              
CurrMetafile.Free;
Label1.Caption := 'Seite ' + IntToStr(AktPage);
end;
Wie schon im Quelltext angemerkt, funktioniert das Ganze, wenn ich jede einzelne Druckseite als Datei auf Festplatte abspeichere und für die Anzeige wieder einlese. Das will ich natürlich nicht.
Hab ich einen Fehler in der Prozedur des Ablegens der Metafiles in der Objektliste oder in der Prozedur für das Auslesen aus dieser?

DeddyH 5. Jan 2021 06:26

AW: Eigene Druckvorschau mit TMetafile und TImage
 
In CreateMetaFile vermisse ich das Hinzufügen der angelegten Instanz zur Liste.

haentschman 5. Jan 2021 06:35

AW: Eigene Druckvorschau mit TMetafile und TImage
 
Moin...:P
Delphi-Quellcode:

procedure TfmVorschau.CreateMetaFile;
begin
PrevMeta := TMetafile.Create;
PrevMeta.Enhanced := true;
PrevMeta.SetSize(PWidth,PHeight); //Größe des Metafiles, je nach Papierformat
PrevMetaCanvas := TMetafileCanvas.Create(PrevMeta,0); //Canvas erzeugen
PrintUtils.AktCanvas := PrevMetaCanvas; //aktuellen Canvas für Unit PrintUtils festlegen, diese enthält die Basisdruckroutinen
SetMapMode(AktCanvas.Handle,MM_TEXT);
SetTextAlign(AktCanvas.Handle,TA_BaseLine);
end;

...

procedure TfmVorschau.NewPage;
begin
//Freigabe des MetafileCanvas der VORHERIGEN Druckseite
PrevMetaCanvas.Free;
//PrevMeta.SaveToFile('D:\Test_Firebird_Server\' + IntToStr(AnzPages) + '.emf'); //diese Variante funktioniert

//Metafile der VORHERIGEN Druckseite in Objektliste ablegen und freigeben
MFList.Add(PrevMeta);
PrevMeta.Free;

//neue Seite erzeugen
Inc(AnzPages);
CreateMetafile; //neues Metafile für nächste Seite erzeugen
end;

//Abschlussprozedur nach dem Erzeugen der letzten Druckseite
procedure TfmVorschau.FinishPreview;
begin
PrevMetaCanvas.Free;
//PrevMeta.SaveToFile('D:\Test_Firebird_Server\' + IntToStr(AnzPages) + '.emf'); //siehe oben
MFList.Add(PrevMeta);
PrevMeta.Free;
end;
Unklarheiten:
Ich verstehe das so...Jede Seite besteht aus einem PrevMeta und immer einem PrevMetaCanvas?
1. Der MetaCanvas ist imho ein Teil des PrevMeta. Warum ist PrevMetaCanvas dann keine Property von PrevMeta? Das macht die Freigabe einfacher...:zwinker:
2. Warum nicht alle Druckseiten erzeugen und in die Liste legen? Und dann die Liste durchiterieren und drucken bzw. anzeigen?
3.
Delphi-Quellcode:
procedure TfmVorschau.FinishPreview;
begin
PrevMetaCanvas.Free; // Freigabe des Canvas
MFList.Add(PrevMeta); // MetaData in die Liste packen
PrevMeta.Free; // MetaData freigeben
end;
Warum erst in die Liste aufnehmen und gleich wieder freigeben? :gruebel: Erst am Ende die Liste komplett freigeben!
4. Bitte keine globalen Variablen. :?

Vorschlag (nur das Wichtigste):
Delphi-Quellcode:
interface
   // Muster
   TMetaFileOwn = class(TMetaFile) // oder so
   strict private
     FMetaFileCanvas: TMetaFileCanvas;
   public
     constructor Create;
     destructor Destroy; override;
     property MetaFileCanvas: TMetaFileCanvas read FMetaFileCanvas write FMetaFileCanvas
   end;
   // Muster

   TfmVorschau = class(TForm)
   strict private
     FAnzahlPages: Integer;
     FAktPage: Integer;
     FCurrentMetaData: TMetaData; // die aktuell gewählten Daten aus der Liste (Previous / Prior über Buttons etc.)
     FMFList: TObjectList<TMetaFileOwn>;
   public
   end;

var
  fmVorschau: TfmVorschau;

implementation

constructor TMetaFileOwn.Create;
begin
  inherited;
  FMetaFileCanvas := TMetaFileCanvas.Create(Self, 0); //Canvas erzeugen, Self = TMetaFile;
end;

destructor TMetaFileOwn.Destroy; override;
begin
  FMetaFileCanvas.Free;
end;

procedure TfmVorschau.FormCreate(Sender: TObject);
begin
  AktPage := 1;
  FMFList := TObjectList<TMetaFileOwn>.Create;
end;

procedure TfmVorschau.FormDestroy(Sender: TObject);
begin
  FinishPreview; // Liste leeren nicht nötig
  FMFList.Free; // Liste leeren macht Free sowieso...nur als Erklärung
end;

procedure TfmVorschau.CreateMetaFile;
begin
  FCurrentMetaData := TMetaFileOwn.Create;
  FCurrentMetaData.Enhanced := true;
  FCurrentMetaDataSetSize(PWidth, PHeight); //Größe des Metafiles, je nach Papierformat
  PrintUtils.AktCanvas := FCurrentMetaData.MetaFileCanvas; //aktuellen Canvas für Unit PrintUtils festlegen, diese enthält die Basisdruckroutinen
  SetMapMode(AktCanvas.Handle, MM_TEXT);
  SetTextAlign(AktCanvas.Handle, TA_BaseLine);
  FMFList.Add(FCurrentMetaData); // eine nach der Anderen in die Liste
end;

procedure TfmVorschau.NewPage;
begin
  CreateMetafile; //neues Metafile für nächste Seite erzeugen
  FAnzahlPages := FMFList.Count;
end;

procedure TfmVorschau.ShowPreview; // irgendwo müssen die Seiten auch angezeigt werden
begin
  Show(FMFList); // der Anzeigeprocedure die Liste übergeben
end;

procedure TfmVorschau.FinishPreview;
begin
  FMFList.Clear; // alle Objekte freigegeben
end;

runningsoft 5. Jan 2021 10:15

AW: Eigene Druckvorschau mit TMetafile und TImage
 
Vielen Dank für die Hinweise und Denkanstöße. es hat mich noch mal 2 Stunden gekostet, aber jetzt funktioniert es.

@DeddyH: Ich war der Meinung, dass man erst das "gefüllte" Metafile, also nachdem die Seite geschrieben wurde, der Objektliste anhängen kann, deshalb hatte ich das nicht sofort in der CreateMetaFile - Prozedur gemacht.

Der entscheidende Hinweis war aber dann der von haentschman
Zitat:

Warum erst in die Liste aufnehmen und gleich wieder freigeben? Erst am Ende die Liste komplett freigeben!
Genau da lag mein Fehler. Man kann letztlich beliebig viele Objekte vom gleichen Typ (hier TMetafile) erzeugen und der Liste hinzufügen. Freigegeben werden Sie erst wieder mit der Freigabe der kompletten Liste. Dies passiert, wenn ich das richtig verstanden habe, automatisch, wenn die generische Objektliste Owner der Objekte ist

Delphi-Quellcode:
MFList.OwnsObjects := true;
oder gleich
Delphi-Quellcode:
MFList := TObjectList<TMetafile>.Create(true);

Falls mal jemand ein ähnliches Problem hat, hier noch die entscheidenden Punkte des Quelltextes mit ein paar Kommentaren:
Delphi-Quellcode:
private
    { Private-Deklarationen }
    var AktPage, PWidth, PHeight : integer;
        FScale : real;
        MFList : TObjectList<TMetaFile>;     //generische Objektliste zur Speicherung der Metafiles
        PrevListMeta : TMetaFile;            //Metafile, das in die Objektliste eingefügt wird
        PrevMetaCanvas : TMetaFileCanvas;    //MetaCanvas, auf dem gezeichnet wird
        ZoomManuell: Boolean;

procedure TfmVorschau.FormCreate(Sender: TObject);
var I : integer;
begin
AktPage := 1;
MFList := TObjectList<TMetafile>.Create(true);
end;

procedure TfmVorschau.FormShow(Sender: TObject);
begin
//Ausgehend von der Pixeldichte des Monitors die Größe des Panels bei 100% Darstellung berechnen
// z.B. 96ppi / 25.4 (=ppm) * 210 mm
PWidth := trunc(Screen.PixelsPerInch / 25.4 * AktFormatArr[1]);
PHeight := trunc(Screen.PixelsPerInch / 25.4 * AktFormatArr[2]);
InitPreview(AktFormatArr[1],AktFormatArr[2]);
CreateMetafile;
end;

procedure TfmVorschau.CreateMetaFile;
begin
PrevListMeta := TMetafile.Create;
PrevListMeta.Enhanced := true;
PrevListMeta.SetSize(PWidth,PHeight);       //Größe des Metafiles, je nach Papierformat
MFList.Add(PrevListMeta);                   //uch wenn das Metafile noch leer ist, kann es schon der Liste hinzugefügt werdena

//Canvas erzeugen, auf dem gezeichnet wird
PrevMetaCanvas := TMetafileCanvas.Create(PrevListMeta,0);

//als aktuellen Canvas an unit PrintUtils geben
//für realen Druck wird in anderer Prozedur Printer.Canvas als AktCanvas festgelegt
PrintUtils.AktCanvas := PrevMetaCanvas;
SetMapMode(AktCanvas.Handle,MM_TEXT);
SetTextAlign(AktCanvas.Handle,TA_BaseLine);
end;

procedure TfmVorschau.NewPage;
begin
//MetaCanvas der vorherigen Seite freigeben
//erst nach Freigabe des MetaCanvas werden die darauf enthaltenen Daten an das
//Metafile übergeben
PrevMetaCanvas.Free;

//PrevMeta.Free; //--> das war der Fehler, weshalb die Objekte in der Objektliste leer waren
                  // die erzeugten Metafiles dürfen nicht sofort wieder freigegeben werden
                  // sondern erst mit der Freigabe der Objektliste beim dem Schließen der Form

//neues Metafile für nächste Seite erzeugen
//dort wird dann auch wieder ein neuer MetaCanvas erzeugt, auf dem gezeichnet wird
CreateMetafile;
end;

procedure TfmVorschau.LastPage;
var i: integer;
    Temp: TComponent;
begin
//siehe Kommentar NewPage
PrevMetaCanvas.Free;
end;

...

procedure TfmVorschau.ShowAktPage;
var PrevIMGMeta : TMetaFile;
begin
//Eintrag aus Objektliste in Metafile übernehmen
PrevIMGMeta := MFList.Items[AktPage-1];
//Seitengröße auf aktuelle Zoomstufe anpassen
PrevIMGMeta.SetSize(PageP1.Width,PageP1.Height);
//TImage.Picture mit Metafile verbinden
PrevIMG.Picture.Assign(PrevIMGMeta);

Label1.Caption := 'Seite ' + IntToStr(AktPage);
end;
Vielen Dank für eure Hilfe :-D


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:57 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz