How to clear a TGridPanelLayout at runtime - delphi

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.

Related

Access Violation while loading multiple Timages in delphi

I am getting an access violation when I try to load the images. The error occurs when I try to load from file. Is there any way around this?
Here is the code:
Public
img: array of TImage;
...
SetLength(img, 50);
for I := 0 to 50 - 1 do
begin
img[I] := TImage.Create(panels[I]);
img[I].parent := panels[I];
img[I].Width := 80;
img[I].Height := 80;
img[I].left := 0;
img[I].top := 0;
img[I].Stretch := true;
img[I].Free;
img[I].Picture.LoadFromFile('./img1.bmp');
end;
I want to create multiple images with different picture that I get from a URL. But I need to get this working with one image first.
You are constructing image objects and adding them in a list, but then you call img[I].Free which destroys that image object. When you access it in the next line trying to load it, you will be using invalid object and this is why your application crashes.
Just remove that line.
You are constructing TImage controls with owner control - panel - that panel as their owner will automatically cleanup those image controls when the panel itself is destroyed, so you don't have to worry about memory leaks.
However, if for some reason you want to clear and remove images you store in the array, you will have to loop through the array and then call img[I].Free. Of course, after you do that you shouldn't access those images. Commonly in such scenarios you should set image reference to nil, to signal your other code there is no valid image in the array. Another way would be setting length of array to 0.
for I := 0 to 50 - 1 do
begin
img[I].Free;
img[I] := nil;
end;
or
for I := 0 to 50 - 1 do
begin
img[I].Free;
end;
SetLength(img, 0);

Delphi - How to add multiple JPG images to a TCanvas and print them

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.

How to Change "Table Positioning" in the table properties Inside a MS Word Document Using Delphi XE5

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

Insert different Picture object after each row print in Fast Report

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.

How to handle subcomponents and properties created in runtime vs designtime?

I can' t realize this thing. I have a component in DELPHI that includes 2 other components: a Firemonkey Layout and inside of that an dynamic array of TLayout which includes a TRectangle.
This is achieved through the property BarNumber.
I have lots of problems about Design Time vs. Runtime behaviour, this is due to the DFM (FMX in Firemonkey) that stores the subcomponents as part of the Object.
Now. This is the code of the On Create part.
constructor TFluffyTable.Create(Owner: TComponent);
var
i: integer;
begin
inherited Create(Owner);
Width:=300;
Height:= 160;
BarNumber:=100;
SetLength(Column, FBarNumber);
for i := 0 to (FBarNumber-1) do
begin
Column[i]:= TColumn.Create(Self);
Column[i].Name:= 'Column_' + IntToStr(i);
Column[i].Parent:= Self;
Column[i].Height:=Height;
Column[i].Width:=Width/FBarNumber;
Column[i].Align:= TAlignLayout.alMostLeft;
end;
end;
If I register the component and I use it in design time I get the correct number of bars displayed. But if I run the program with the component, I get twice the number of bars, since the EXE loads the values. I managed to solve this with
if not (csDesigning in ComponentState) then
just before the for loop.
But I can't see, obviously, the BARS in design mode. Well I can stand that if this is the only solution.
That's not over..!
For a strange reason, The only one place I can set my values for Width, Height and BarNumber is that part of code. If I set them in the object inspector they won't be considered and reset to default when I run the program.
(BarNumber is a variable which reads and writes on FBarNumber)
In short: I don't know how to handle and manage my component to make BarNumber and other properties to be set in design time, and to see the correct number of bars in Runtime.
Thank you so much.
I had the similar problem. I used stored property to avoid this problem.
Example:
constructor TMachine.Create(AOwner: TComponent);
begin
inherited;
self.Width := 50;
self.Height := 90;
// create machine rectangle and set default properties
FMachine := TRectangle.Create(self);
FMachine.Parent := self;
FMachine.Height := 50;
FMachine.Align := TAlignLayout.alBottom;
FMachine.Fill.Color := TAlphaColorRec.red;
FMachine.Stroke.Color := TAlphaColorRec.Black;
FMachine.Stroke.Thickness := 3;
FMachine.Stored := false;
end;
The problem is that the component you create at design time will be stored in the fmx files. When you run the application you have twice controls, to resolve the problem you need to set the stored property to false to the sub objects of your component like this:
Column[i].Stored := False;
You have to make sure that you are starting with 0 columns at runtime.
Just add something like:
for [i] = pred(length(column)) downto 0 do
begin
column[i].free
end;
before you start making the columns.

Resources