I am using RAD Studio XE6 and I have a simple fire monkey form that I use to print an image. I thought it would be a good idea if I managed to create a preview functionality for displaying the final image before it is printed. To do that I tried using a TImage component and instead of sending my data to the printer canvas send it to the image canvas by using something like tho code below.
ImageViewer1.Canvas.Font.Size := 15;
ImageViewer1.Canvas.Font.Family := 'Arial';
ImageViewer1.Canvas.Font.Style := [TFontStyle.fsbold];
ImageViewer1.Canvas.Fill.Color := claBlack;
ImageViewer1.Canvas.Fill.Kind := TBrushKind.Solid;
s := 'Test Print';
l := Round((ImageViewer1.Width - ImageViewer1.Canvas.TextWidth(s)) / 99);
t := Round(ImageViewer1.Canvas.TextHeight(s)*3/100);
r := l + Round(ImageViewer1.Canvas.TextWidth(s));
b := t + Round(ImageViewer1.Canvas.TextHeight(s));
ImageViewer1.Canvas.FillText(TRectF.Create(l, t, r, b), s, false, 1,
[TFillTextFlag.RightToLeft], TTextAlign.Leading, TTextAlign.Leading);
The thing is that in the end nothing gets displayed in my TImage component. Have I done something wrong?
What you are creating is not a print preview.
Print previews show orientation, margins and such. If you wish to create a print preview, you should do this:
You can create your own print-preview of anything that you send to the printer.
Just replace Printer.Canvas by the Canvas of a TImage component, that is placed on an otherwise empty form.
In other words: create a new form, place a TImage on it (set it to alClient) and just modify your printing routine to accept a TCanvas as a paramenter. That way you can use the same routine for both the printer and the TImage canvas.
Related
As a Delphi programmer I'm a bit of a hack - learned bits and pieces along the way, mostly self taught.
I'm working on a program for fun that's a database for an out of print card game. Has info of the cards and links to a JPG of the card image.
Was talking to someone about it and they said "Wouldn't it be cool if you could render multiple card images on a page at 2.5 x 3.5 inches and print them".
Legal issues aside (I won't be distributing this without express permission from the former publisher), I wanted to see if it could be done as an exercise to teach me how to use TCanvas. Unfortunately I've really got no idea how to do this.
I'm working with an 8.5 x 11 inch page, so I'd want a 2.3 x 3.5 card image which gives me about an inch to play with that's not taken up by card images, so like 0.25 inches page margins all around and .25 inches between each image. This would put 9 cards on the page.
For what I have written in my program so far, I can drop a TImage on a form and read the associated jpg file for any given card from disk and show it on the form.
Assume I have built a deck of these cards and want to print the images of my "deck". How do I then take that associated image for each card, resize it so that it would fit into a 2.5 x 3.5 space on a canvas and then continue loading images until I've got 9 on the page. Then I'd want to move to a new page and do it again until I've printed the cards in my deck (usually around 50 of them - so about 6 pages).
I've been doing some searching and I'm not sure how to deal with these aspects
Resize the JPG once read
Position it on the canvas - and get 9 images total there
The current images are (for the most part) 390 x 546 pixels. (which is the correct ratio for 2.5 x 3.5 inches).
Also, in order to try to preserve ink & paper, is there a way to "preview" the canvas before you send it to the printer (a print preview if you will), or is it just a huge trial and error thing until you get it right.
While specific code example would be preferred, I'm willing to do some legwork if I could even get a pointer to a website that showed how to work with these objects.
About 15 years ago I mucked around with putting Text onto a TCanvas and printing it, but I'm not sure how to make this work with a JPG after reading from disk and then resizing it to print to the scale I want here.
Anyone able to offer some pointers for me here ?
Oh if it matters, I'm working with Delphi XE3 Enterprise / Windows 10.
Thanks.
There is some rude way to make it work.
First of all, I see at least two ways to make it:
load all images into array or ObjectList (that is better I suppose) and then make transofrmations and draw them on canvas;
load, transform and draw image one by one;
Which of them the better one? I don't know. I'd prefer second because it will take less RAM I suppose.
uses ...,jpeg, SysUtils, Graphics;
...
const CARD_WIDTH:integer = /*your card width*/;
const CARD_HEIGHT:integer = /*your card height*/;
const MARGIN_X:integer = 0; //you can change it if you want to
const MARGIN_Y:integer = 0; //you can change it if you want to
...
//Load images. You can call it from button's OnClick event;
function LoadImages(aWidth, aHeight:integer):TBitmap;
var i:integer;
lImage:TJpegImage;
lResizedBmp:TBitmap;
lPosX,lPosY:integer; //it can be TPoint as well
begin
result := TBitmap.Create;
lPosX := MARGIN_X;
lPosY := MARGIN_Y;
try
result.Width := aWidth;
result.Height := aHeight;
//I don't know how you will get filenames, so I'll make some dummy code
for i := 0 to 10 do
begin
lImage := TJpegImage.Create;
lResizedBmp := nil;
try
lImage.LoadFromFile('C:\' + inttostr(i) + '.jpg'); //it's bad code just to demonstrate the way we load file. I don't remember if "i.toString()" is acceptable in XE3.
lResizedBmp := ResizeJpeg(lImage, CARD_WIDTH, CARD_HEIGHT);
result.Canvas.Draw(lPosX, lPosY, lResizedBmp);
//let's calculate next card position
lPosX := lPosX + CARD_WIDTH + MARGIN_X;
if (lPosX + CARD_WIDTH + MARGIN_X > aWidth) then
begin
lPosX := MARGIN_X;
lPosY := lPosY + MARGIN_Y + CARD_HEIGHT;
end;
finally
FreeAndNil(lImage);
if assigned(lResizedBmp) then
FreeAndNil(lResizedBmp)
end;
end;
except
on (e:Exception) do
begin
FreeAndNil(result);
raise e;
end;
end;
end;
//Resize image and convert it into Bitmap
function ResizeJpeg(aJpg:TJpegImage; aWidth, aHeight:integer):TBitmap;
var lProxyBmp:TBitmap;
begin
result := TBitmap.Create;
try
result.Width := aWidth;
result.Height := aHeight;
lProxyBmp := TBitmap.Create;
try
lProxyBmp.Assign(aJpg);
result.Canvas.StretchDraw(result.Canvas.ClipRect, lProxyBmp);
finally
FreeAndNil(lProxyBmp);
end;
except
on e:Exception do
begin
FreeAndNil(result);
raise e;
end;
end;
end;
So, you have all procedures to make your page. Just make Form, place TImage and two buttons on in. Set TImage's Stretched and Proportional properties to true. Set buttons' captions to Load and Print. Don't forget to add unit with procedures to uses or make them as Form's methods. Add private field _bmp:TBitmap to your Form;
For Load button:
//Page preview.
procedure TForm1.Button1Click(Sender:TObject)
begin
if assigned(_bmp) then
FreeAndnIl(_bmp);
_bmp := LoadImages(2000,3000);
Image1.Picture.Assign(_bmp);
end;
For Print button:
procedure TForm1.Button2Click(Sender:TObject)
begin
if not assigned(_bmp) then
begin
ShowMessage('Click "Load" first');
Exit;
end;
with TPrintDialog.Create(nil) do
try
if not Execute then
Exit;
finally
Free;
end;
Printer.BeginDoc;
try
Printer.Canvas.Draw(0,0,_bmp);
finally
Printer.EndDoc;
end;
end;
After I checked this code on Delphi 10.1 using PDF printer I got my pdf file.
I've tried to make it as simple as possible, but there is plenty of code. I could miss something, but I'm ready to help. All constants can be made as variables and passed as function's params, it's up to you.
My application uses a TGridPanelLayout to present a number of images to the user. When the user clicks at the correct image, all images are cleared and a new serie of images is presented. The code for adding the images to the grid is shown below:
// Clear the grid of all controls, rows and columns and reinitialize
Grid.ControlCollection.Clear;
Grid.ColumnCollection.Clear;
Grid.RowCollection.Clear;
// Next follows some code to add columns and rows
// Display the names, n_images = rows * columns
for i := 0 to n_images - 1 do
begin
Image := TImage.Create (nil);
Image.HitTest := True;
if Sel_Array [i]
then Image.OnClick := test_positive
else Image.OnClick := test_negative;
c := i mod Options_Project.Cols;
r := i div Options_Project.Cols;
Image.Name := Format ('Image_%2.2d_%2.2d', [r, c]);
Image.Bitmap.LoadFromFile (Sel_Names [i]);
Image.Align := TAlignLayout.alClient;
Grid.AddObject (Image); // Grid: TGridPanelLayout
end; // for
This all works fine but the problem is in recreating the TGridPanelLayout. When for the second time Grid.ControlCollection.Clear is executed an access violation occurs when one of the images is freed.
How can I clear a TGridPanellayout at runtime without crash? And an additional question: is AddObject the correct way to add controls to the TGridPanelLayout? I tried AddControl but then no image was shown.
This application is tested in Windows 7.
Edit
Tom noticed that I should've assigned the .Parent and that did the trick, together with the remark of Dalija that I should've used AddControl. The code below works:
for i := 0 to n_images - 1 do
begin
Image := TImage.Create (Grid);
Image.HitTest := True;
if Sel_Array [i]
then Image.OnClick := test_positive
else Image.OnClick := test_negative;
c := i mod Options_Project.Cols;
r := i div Options_Project.Cols;
Image.Name := Format ('Image_%2.2d_%2.2d', [r, c]);
Image.Bitmap.LoadFromFile (Sel_Names [i]);
Image.Align := TAlignLayout.alClient;
Image.Parent := Grid;
Grid.ControlCollection.AddControl (Image);
end; // for
Thank you all for your help!
It is correct to call Grid.ControlCollection.Clear to delete the items in the collection. From Help:
Clear empties the Items array and destroys each TCollectionItem.
Note the "destroys", which means it takes ownership and resposnibility of managing the lifetime of the image.
You say:
an access violation occurs when one of the images is freed.
Do you mean actively freeing by your code? Then that is wrong and the reason for the AV.
Is the image the same that the user clicked on and that triggered the display of a new series of images? Then you need to review the code how you manipulate the image in test_positive and maybe test_negative also.
To add controls to the TGridPanelLayout you can use either
Grid.AddObject(Image);
or
Image.Parent := Grid;
Grid.Controls.Add(Image);
Note, in this case you need to set the parent in order for the image to show (and to be managed by the grid).
The above is tested with XE7.
I apologize in advance, This is very confusing to explain. Please assist in making it clearer if need be.
I am working with a MS Word document that i generate from code using the following code. The document has 1 table with a bunch of rows and columns that i intend to populate.
wrdDoc.Tables.Add(wrdSelection.Range,9,2);
wrdDoc.tables.Item(1).Rows.Alignment := wdAlignRowLeft;
wrdDoc.Tables.Item(1).Columns.Item(1).SetWidth(155,wdAdjustNone);
wrdDoc.Tables.Item(1).Columns.Item(2).SetWidth(299,wdAdjustNone);
wrdDoc.tables.Item(1).Borders.Item(wdBorderLeft).LineStyle := wdLineStyleSingle;
wrdDoc.tables.Item(1).Borders.Item(wdBorderRight).LineStyle := wdLineStyleSingle;
wrdDoc.tables.Item(1).Borders.Item(wdBorderVertical).LineStyle := wdLineStyleSingle;
wrdDoc.tables.Item(1).Borders.Item(wdBorderTop).LineStyle := wdLineStyleSingle;
wrdDoc.tables.Item(1).Borders.Item(wdBorderBottom).LineStyle := wdLineStyleSingle;
wrdDoc.tables.Item(1).Borders.Item(wdBorderHorizontal).LineStyle := wdLineStyleSingle;
Basically what i am trying to do is change the following values:
Right Click on the table->Table Properties->Table Tab
Text Wrapping = Around
->Positioning->Horizontal:
Position = -0.18"
Relative To = Margin
->Positioning->Vertical:
Position = -0.63"
Relative To = Paragraph
->Positioning->Options:
Move With Text = True
Allow Overlap = True
I have not been able to find any code to assist me. Or even any Code samples that handle changing the text wrapping to around using code in Delphi. So any assistance would be great.
Thank You
The following code does what you asked using D7 and Word2007.
You don't say whether your unit already uses one of the Delphi import units for the MS Word type libraries. You'll need to use one, because that's where the constants like wdTableLeft are defined. I'm using D7 (+Word 2007), so I used the Word2000 import unit that came with D7.
Also my Table and TablesRows are OleVariants which you'll need to add to your code if you don't declare them already.
First thing is that you'll need to add some code above your procedure which creates the table. The reason for this is explained below.
const
CmToPostScriptPoints : Single = 28.3464567;
InchesToCm : Single = 2.54;
function CentimetersToPoints(Centimeters : Single) : Single;
begin
Result := CmToPostScriptPoints * Centimeters;
end;
Then replace the code in your q by the following. Please read the embedded comments carefully because they explain a couple of problems I ran into which took a long time to figure out.
Table := wrdDoc.Tables.Add(wrdSelection.Range, 9, 2);
TableRows := Table.Rows;
TableRows.WrapAroundText := True;
// TableRows.MoveWithText := True;
// Note: If you un-comment the line above it will cause an exception
// Method "MoveWithText" not supported by automation object
// However, even with MoveWithText commented out, the corresponding property
// in Word's table properties will still be ticked by the time the code is finished
TableRows.AllowOverlap := True;
Table.Rows.Alignment := wdAlignRowLeft;
Table.Columns.Item(1).SetWidth(155,wdAdjustNone);
Table.Columns.Item(2).SetWidth(299,wdAdjustNone);
Table.Borders.Item(wdBorderLeft).LineStyle := wdLineStyleSingle;
Table.Borders.Item(wdBorderRight).LineStyle := wdLineStyleSingle;
Table.Borders.Item(wdBorderVertical).LineStyle := wdLineStyleSingle;
Table.Borders.Item(wdBorderTop).LineStyle := wdLineStyleSingle;
Table.Borders.Item(wdBorderBottom).LineStyle := wdLineStyleSingle;
Table.Borders.Item(wdBorderHorizontal).LineStyle := wdLineStyleSingle;
TableRows.RelativeHorizontalPosition := wdRelativeHorizontalPositionMargin;
TableRows.RelativeVerticalPosition := wdRelativeVerticalPositionParagraph;
TableRows.HorizontalPosition := CentimetersToPoints(-0.18 * InchesToCm) ;
// Note : At first, I thought the line above didn't do anything because
// after this routine finishes, the HorizontalPosition in Word
// under TableProperties | Positioning is shown as Left.
//
// However, if you put a breakpoint on the line above,
// and single-step past it, you should see the table shift leftwards
// when the line is executed. The ShowMessage should display - 0.179[...]
ShowMessage(FloatToStr(TableRows.HorizontalPosition / 72)); // 72 PostScript points to an inch
TableRows.VerticalPosition := CentimetersToPoints(-0.63 * InchesToCm);
The reason I've defined a CentimetersToPoints function rather than the Word Application's CentimetersToPoints is that there seems there is a long-standing problem with trying to call CentimetersToPoints from Delphi code - if you're interested, see this SO q and its comments to the answer:
Unspecified error when calling Word CentimetersToPoints via OLE
I am developing an application in Firemonkey (Delphi XE5) where I am using Fast report 4 for printing data. I am using TFrxUserDataSet to keep data and to print this, I am using MasterData band in fast report.
Now, I also need to print TBitamp with each row, so here the bitmap for each record will be different.
Does any body has any idea how can I do this?
Нou can load an external image file into a picture control in your report. I'm doing this with a script that is part of the report itself using the OnBeforePrint event as follows:
PROCEDURE Data2OnBeforePrint(Sender: TfrxComponent);
VAR
lFN : STRING;
lFP : STRING;
BEGIN
// Use the filename as found in the Media dataset fields
lFP := Trim(< Media."ImagePath">); // Images folder below Image Root Path
lFN := Trim(< Media."FileName1">); // Actual Image File Name
WITH Picture2 DO BEGIN
// NB: There is no checking in this example, it may be useful to do a
// couple of checks before trying to load the image, especially if
// the data is user entered
LoadFromFile(ImageRootPath + IncludeTrailingSlash(lFP) + lFN);
// Do whatever manipulations you want to with the loaded image...
AutoSize := False;
Width := 1620;
Height := 1080;
Top := 0;
Left := (1920 - Width) / 2;
HightQuality := True; // Note the typo in the property name... HighQuality?
KeepAspectRatio := True;
Transparent := True;
Stretched := NOT Picture3.AutoSize;
END;
END;
Note that I have added a few user functions like ImageRootPath IncludeTrailingSlash() to make the script easier. You could do similar to check for a valid file prior to attempting to load to avoid exceptions.
My devt environment is Delphi XE5 with FastReport FMX and it works just fine. I am in the midst of moving to XE6 and FR FMX 2, but am pretty sure this will work fine.
Is there a way I can send data to my PDF file, (fill in the fields/blanks), either manually or by a third party component, the PDF files have certain fields that can be modified by user, entering numbers.. checkboxes etc etc
how can I achieve this goal, an if it will require some third party component, which is the best, and what are the prices?
our Development IDE is delphi 2010 / Delphi 2011 XE
thanks :)
I guess you want your application to create some PDF content from User Interface field.
You can do this easily from code, using a report generator from code, then a PDF engine.
We propose an Open Source solution just for doing this, from Delphi 6 up to XE.
Here is a code extract from one demo, which create a reports, using some User Interface fields as source (e.g. edt1.Text or mmo1.Text):
procedure TForm1.btn1Click(Sender: TObject);
(...)
with TGDIPages.Create(self) do
try
// the name of the report is taken from main Window's caption
Caption := self.Caption;
// now we add some content to the report
BeginDoc;
(...)
// main content (automaticaly split on next pages)
NewHalfLine;
TextAlign := taJustified;
s := 'This is some big text which must be justified on multiple lines. ';
DrawText(s+s+s+s);
NewLine;
TextAlign := taLeft;
DrawTitle(edt1.Text,true);
for i := 1 to 10 do
DrawText('This is some text '+IntToStr(i));
NewLine;
DrawBMP(Bmp,maxInt,50,'Some bitmap in the report');
AddBookMark('bookmarkname');
WordWrapLeftCols := true;
AddColumns([10,20,50]);
AddColumnHeaders(['#','Two','Three'],true,true);
for i := 1 to 100 do
DrawTextAcrossCols([IntToStr(i),'Column '+IntToStr(i),'Some text here. '+s]);
NewLine;
DrawBMP(Bmp,maxInt,50,'Some bitmap in the report (twice)');
DrawTitle('This is your text',false,0,'','bookmarkname');
DrawText(mmo1.Text);
EndDoc;
// set optional PDF export options
// ExportPDFForceJPEGCompression := 80;
// ExportPDFEmbeddedTTF := true;
// ExportPDFUseUniscribe := true;
// ExportPDFA1 := true;
// show a preview form, and allow basic actions via the right click menu
// ShowPreviewForm;
// export as PDF
ExportPDF('test.pdf',false);
finally
Free;
end;
There are other solutions around, but this one is Open Source, and you can even draw whatever you want to the report (using a "standard" TCanvas property - you can even directly any graphical component using the PaintTo method), not only dedicated report generated methods like DrawTitle() or DrawText().
EDIT:
If your question was about creating PDF files with forms, this library won't work.
You should use some closed-source libraries like VeryPdf or QuickPdf. Google is your friend.