Access Violation while loading multiple Timages in delphi - 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);

Related

Setting COM (RDPEncomAPI) property in Delphi

I've used Delphi for some time, but I am trying some COM programming and having trouble. I apologize if this is a NewBie issue, but after searching an trying lots of things I have not been able to get or set the properties of an RDPEncom RDPSession object. The code (including several naive attemps) is below. If I remove the line attempting to read properties, remaining code works fine.
How can I get and Set the PortID property of RDPSession.Properties ?
uses rdpencomapi_TLB; // from JWAPI
...
myRDPSession := CoRDPSession.Create();
if VarIsNull(myRDPSession) then
begin
application.MessageBox('MsRdpSession creation failed.', 'Error');
Result := False;
Exit;
end;
try
didShare := myRDPSession.Open;
except
ShowMessage('Unable to share desktop !');
Exit;
end;
theProperty := 'PortID';
ActiveXProp := myRDPSession.Properties;
//lValues := ActiveXProp.Property_(theProperty); // method not supported
//lValues := ActiveXProp.Property(theProperty); // member not found
myRDPSession.Properties.GetProperty(lValues, myRDPSession.Properties.Property, theProperty);
{
ALL RETURN INVALID NUMBER OF PARAMETERS..
ActiveXProp.GetProperty(lValues, ActiveXProp.Property, 'PortID');
ActiveXProp.Property.GetProperty(ActiveXProp.Property, lValues, 'PortID');
ActiveXProp.Property.GetProperty(lValues, ActiveXProp, 'PortID');
ActiveXProp.Property.Get_Prop_('PortID', ActiveXProp);
ActiveXProp.Property.SetProperty('PortID', ActiveXProp);
ActiveXProp.Property.Set_Prop_('PortID', ActiveXProp);
}
ActiveXInvite := myRDPSession.Invitations.CreateInvitation('RemoteSupport', 'WePresent', '12345', 75);
...
Ken,
Your comment put me onto something.. I regenerated the TLB file from my own machine and found it did have a property that was not in the TLB I used originally (from Jedi Project). This one has a single Property called 'Property' that allowed me to do what I needed. Basically I was missing the COM interface point. I got it to work after updating the TLB this way (with no error checking yet):
// get properties interface
myRDPSessionProp := myRDPSession.Properties;
// set listening port
myRDPSessionProp.Property['PortID'] := 59000;
// set color depth
myRDPSession.colorDepth := 8;
didShare := myRDPSession.Open;

How to clear a TGridPanelLayout at runtime

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.

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.

Delphi dll image stretchdraw errors

I am trying to resize (scale) a bitmap image using a dll function which is below mentioned
{ to resize the image }
function ResizeImg(maxWidth,maxHeight: integer;thumbnail : TBitmap): TBitmap;
var
thumbRect : TRect;
begin
thumbRect.Left := 0;
thumbRect.Top := 0;
if thumbnail.Width > maxWidth then
begin
thumbRect.Right := maxWidth;
end
else
begin
thumbRect.Right := thumbnail.Width;;
end;
if thumbnail.Height > maxHeight then
begin
thumbRect.Bottom := maxHeight;
end
else
begin
thumbRect.Bottom := thumbnail.Height;
end;
thumbnail.Canvas.StretchDraw(thumbRect, thumbnail) ;
//resize image
thumbnail.Width := thumbRect.Right;
thumbnail.Height := thumbRect.Bottom;
//display in a TImage control
Result:= thumbnail;
end;
It works fine when I use this application call (to feed all the images in my listview):
//bs:TStream; btmap:TBitmap;
bs := CreateBlobstream(fieldbyname('Picture'),bmRead);
bs.postion := 0;
btmap.Loadfromstream(bs);
ListView1.Items[i].ImageIndex := ImageList1.Add(ResizeImg(60,55,btmap), nil);
But when I try this application call (to get individual image into my TImage component):
bs := CreateBlobstream(fieldbyname('Picture'),bmRead);
bs.postion := 0;
btmap.Loadfromstream(bs);
Image1.Picture.Bitmap := ResizeImg(250,190,btmap);
It gives me an error on:
thumbnail.Canvas.StretchDraw(thumbRect, thumbnail) ;
saying:
AV at address 00350422 in module 'mydll.dll' Read of Address 20000027
And when I close my executable I get this:
runtime error 216 at 0101C4BA
If I define and use the same function (ResizeImg) inside my exe pas file it works completely fine without any errors.
You can't pass Delphi objects between modules unless you take steps to ensure that those modules share the same runtime and memory allocator. Ir appears you have not taken such steps.
The basic problem is that a Delphi object is both data and code. If you naively call a method on an object that was created in a different module then you execute code from this module on data from that module. That typically ends in runtime errors.
You have at least the following options:
Use runtime packages. This will enforce a shared runtime.
Use COM interop. COM was designed for sharing components across module boundaries.
Link all code into a single executable.
Pass HBITMAPs between the modules since they can be shared in such a manner.

Resources