I created an array of TImage but they aren't visible [closed] - delphi

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I'm using Delphi and I created an array of ten elements of type TImage whit this name and structure:
Form3.images[1..max] of TImage.
I tried to initialize it in this way:
for x := 1 to max do
begin
images[x] := TImage.Create(Form3);
images[x].AutoSize := True;
images[x].Name := 'image' + IntToStr(x);
images[x].Visible := true;
images[x].Parent := Form3;
end;
After that I tried to put the content of another variable (called Form3.a1:TImage) to every element of the array.
I tried to do this with these instructions:
for i := 1 to max do
begin
Form3.Images[i]:=Form3.a1; // ( Form3.a1: TImage) <- this is visible
end;
(I don't know if using the instructions before, is the right thing to do)
After that I changed positions of array's images:
//Form3.square:TShape
x := Form3.square.Left;
y := Form3.square.Top;
Form3.Images[1].Top := y + 70;
Form3.Images[1].Left := x + 60;
...
Form3.Images[1].Top := y + 10;
Form3.Images[1].Left := x + 50;
I set different positions for each image of the array but when I run the program, images of the array aren't visible. I also tried to set Form3.square.visible=false but nothing changes.
This is what I want:
have the same contents between variable a1 and variables of the array images, changing only positions
make array's images visible (I tried images[x].Visible := true; but it doesn't work).
please i need help, I can give other details.
Thank you.

You forget to set the control's position; Left and Top. This is preferrably done by calling SetBounds. Also the dimensions are not specified , but that is taken care of by the AutoSize property (it wouldn't need it for becoming visible though).

Provided you have set the position of the images in the images array (as you state in the comment on te other answer), then the first code should work normally.
Check the following though:
Are the Width and Height properties of the ImageList Form3.Lista set? Note that when you change them, the ImageList is cleared.
Ensure that you are looking at the same TForm3 instance as where the Form3 variable points to. (You should not use that global form variable anyway!)
Now, about the second piece of code wherein you copy images from one to another:
Is a1 a (local) declared variable as you wrote halfway the question? Then Form3.a1 (which is a private field of TForm3) and a1 (the variable) are not the same!
Note that you are copying from Form3.a1 to Images[i]. Shouldn't that maybe the other way around?
If this indeed is what you want: Is Form3.a1 by any chance empty?
There are 2 more possibilities I can think of, but both would result in an exception, so I imagine these are not the case:
ImageList Form3.Lista holds no or not enough images,
Image Form3.a1 holds no bitmap, but a gif, jpeg or other graphic type.

Related

Delphi graphics32 saving layers as transparent PNG goes wrong

I am having a strange problem, and I do not think I can solve it.
I have an ImgView containing layers (transparent png images) and I intend to save all the layers as png files (like a "Save Project" thing), so that later on I can re-open them and place them where I left them. (like an "Open Project" thing)
This is my problem, following steps work just fine:
I add layers (transparent PNG files)
I move them around and place them where I want them
I press save project (so here I save all layers as png image files)
It works
If I do the next following steps, something goes wrong:
I add layers (transparent PNG files)
I move them around and place them where I want them
I change the location of the layers (as in: send to back one layer for example) (so this step is different)
I press save project (so here I save all layers as png image files)
It crashes with "Access violation at address 005380FB in module 'MyApp.exe'. Read of address 000000C0"
Right now it only gives me the above error, but a few runs ago, it pointed me to this line:
procedure TCustomBitmap32.ResetAlpha(const AlphaValue: Byte);
var
I: Integer;
P: PByteArray;
begin
if not FMeasuringMode then <<<<<------ this line
So if I change the index of layers... I cannot save them anymore as PNG ?!?!?!
Here is my save procedure:
for i:=0 to mainForm.ImgView.Layers.Count-2 do
begin
mylay := TBitmapLayer(mainForm.ImgView.Layers.Items[i]);
SaveBMPAsPng(mylay.Bitmap,'C:\MyApp\tmp\'+getLayerPan(i)+'.png');
end;
// where getLayerPan is a function that retrieves a name that I gave to the layer
... and
procedure SaveBmpAsPng(bmp:TBitmap32;dest:string);
var
Y: Integer;
X: Integer;
Png: TPortableNetworkGraphic32;
function IsWhite(Color32: TColor32): Boolean;
begin
Result:= (TColor32Entry(Color32).B = 255) and
(TColor32Entry(Color32).G = 255) and
(TColor32Entry(Color32).R = 255);
end;
begin
bmp.ResetAlpha;
for Y := 0 to bmp.Height-1 do
for X := 0 to bmp.Width-1 do
begin
if IsWhite(bmp.Pixel[X, Y]) then
bmp.Pixel[X,Y]:=Color32(255,255,255,0);
end;
Png:= TPortableNetworkGraphic32.Create;
Png.Assign(bmp);
Png.SaveToFile(dest);
Png.Free;
end;
What could be wrong?
Please help...
EDIT
I think I discovered my problem...
When I move the layers around, the only way (that I know of) to do it clean, is to load all layers into an imagelist (TBitmap32List was my choice at that moment) and after that clean the layers and re-add them from the imagelist to my ImageView in the desired order.
I can only assume that this is where something goes wrong.
It must be because in the layers I have transparent PNGs, and when I load them into the Bitmap32List, I load them as BMPs.
I must look for another way of reorganizing my layers before going any further. I will update you with my solution. If any of you know of a better way of reordering layers in ImageView32, please let me know.
EDIT
So, please observe in the image bellow that the GUI is done, and working. I have the panels representing the layers, I can move them around (as you may see in the image I am dragging layer 'Elementul 0' and movin it up in the chain).
And I repeat, my logic also works when I use temporary files for moving layers up or down in the order. One of the answers suggested that I should just use the Index property to change a layers position in the layers hierarchy, and I am saying that it cannot be done without at least adding new layers to the image. So this is not a double question. It is just a response to one of the answers I received.
Thank you
Your problem is a lot simpler than you might think. Working with layers comes natural:
Send to back
Set the layer's index to 0 or simply call SendToBack. All layers previously before it will have their index increased by 1. All layers previously after it remain at the same position.
Send backward
Decrease the layer's index by 1. The layer previously before it will now come after it, thus has its index increased by one.
Send forward
Increase the layer's index by 1. The layer previously after it will now come before it, thus has its index decreased by one.
Send to front
Set the layer's index to the number of layers minus 1. The layers previously after it have their increased decreased by one.
Hence there is absolutely no need to touch the bitmap, save to disk it to disk, or use any kind of temporary layers to change the order. In virtually every case, the right thing happens when you just set the index of the layer to the position (counting from 0, back to front) you want it to appear at. After moving a panel in your list, you could set the corresponding layer's index to the new index of the panel in the list. However, because the panel is ordered front to back and GR32 orders back to front, you need to translate the index of the panel to the desired index of the layer.
Here's an example how to do that with a TListBox and a TButton:
procedure TForm1.SendBackwardButtonClick(Sender: TObject);
var
LNewListBoxItemIndex: Integer;
begin
// Calculate the new list index and make sure it's valid
LNewListBoxItemIndex := Max(0, Min(ListBox1.ItemIndex + 1, ListBox1.Items.Count - 1));
// Transform the current and new list indices and use them to move the layer
ImgView321.Layers[ListBox1.Items.Count - 1 - ListBox1.ItemIndex].Index :=
ListBox1.Items.Count - 1 - LNewListBoxItemIndex;
// Move the list item
ListBox1.Items.Move(ListBox1.ItemIndex, LNewListBoxItemIndex);
// Preserve the selection (if applicable)
ListBox1.ItemIndex := LNewListBoxItemIndex;
end;
You may also decide to fully synchronize the list with the layers. In that case you should associate each item (possibly TPanel) with a layer.
// Create layers from front to back
LLayer := TBitmapLayer.Create(ImgView321.Layers);
ListBox1.Items.AddObject('First layer', LLayer);
// Could use LPanel := TPanel.Create(...); LPanel.Tag := Integer(Pointer(LLayer)) instead
LLayer := TBitmapLayer.Create(ImgView321.Layers);
ListBox1.Items.AddObject('Second layer', LLayer);
// Now the list is correct but the layers are not in the right order.
// Use the code listed below whenever you need to synchronize the layers
// with the list. In theory it may be slow (O(n^2)) but practically it
// won't matter much assuming you won't have hundreds of layers.
// Don't update the screen every time we move a layer to get closer to the final result
ImgView321.BeginUpdate;
try
for LIndex := 0 to ListBox1.Items.Count - 1 do
// Get the associated layer and make it the least visible of all processed so far
TCustomLayer(ListBox1.Items.Objects[LIndex]).SendToBack;
// Could use TCustomLayer(Pointer(SomePanel.Tag)).SendToBack instead
finally
// Always do this not to have strange behavior after an error
ImgView321.EndUpdate;
end;
// When it's done, update the screen
ImgView321.Changed;
By your description of how you changed the order of the layers, it is most likely the reason for your problem. Since you did not post that part of the code it can not be assessed with certainty.
Anyway, to rearrange the layers, you can use the Index property of TCustomLayer (of which TBitmapLayer is a descendant)
So the solution to the problem is to NOT use a Bitmap32List as a temporary container for png layers when reordering layers, because something gets lost in the process.
So try other solution for reordering. My curent solution is to drop the layers as PNG files to the disk, then reload them from the disk in the desired order.
Another solution (untested yet) would be to create a number of new layers, equal to the number of existing layers, move actual layers there, then get them back one by one in the desired order, and then remove the extra layers.
Anyway. That was the question, and this is the answer so far

How to add a Timage to a TScrollBox in Firemonkey XE6?

Firstly sorry if this has come up before but I am struggling to find anything on the matter.
I'm trying to add a number of TImage's to a scrollbox which is meant to hold the images and allow the user to scroll across them. This creation is done in run time.
The images are stored in an array of TImage.
Below is the code I have to create the images.
procedure TfrmMain.CreateSolutionImages(ImageCount: Integer);
var
I: Integer;
ImageScale: double;
begin
if sbSolutionImages.ComponentCount > 0 then //destroy the images already in the scrollbox
sbSolutionImages.DestroyComponents;
SetLength(SolutionImages,0); //clear the array of images
SetLength(SolutionImages,ImageCount); //SolutionImages is an array of timage
ImageScale:= ((sbSolutionImages.Width - 20)/Guillotine.StockWidth);
for I := 0 to ImageCount - 1 do
begin
if not Assigned(SolutionImages[I]) then //if not assigned then create and set the parent to the scrollbox
begin
SolutionImages[I]:= TImage.Create(sbSolutionImages);
SolutionImages[I].Parent:= sbSolutionImages;
SolutionImages[I].Width:= trunc(Guillotine.StockWidth * ImageScale); //set image dimentions and positions
SolutionImages[I].Height:= trunc(Guillotine.StockHeight * ImageScale);
SolutionImages[I].Position.X:= 10;
if I = 0 then
begin
SolutionImages[I].Position.Y:= 10;
end
else
begin
SolutionImages[I].Position.Y:= SolutionImages[I-1].Position.Y + SolutionImages[I-1].Height + 20;
end;
end;
//forgot to include these lines
SolutionImages[I].Bitmap.SetSize(Round(SolutionImages[I].Width),Round(SolutionImages[I].Height));
SolutionImages[I].Bitmap.Clear(TAlphaColors.White);
end;
end;
What is happening is that the scrollbox (sbSolutionImages) is reporting that it contains the images, i.e. componentcount increases, however it is not drawing the images and no scrollbars appear, which should logically happen as some of the images won't be in the viewable region.
Any help would be greatly appreciated.
Add a TLayout as a child of the TScrollBox.
Set the Width and Height as appropriate (and set Position=(0,0)).
Add your images as children on the TLayout.
The TScrollBox will then know the bounds of the TLayout and will set it's scroll bars based on this.
Ok sorry. It was a simple stupid issue.
I forgot to set the sizes on the bitmaps of all the images.
Still within the for loop I needed to add.
SolutionImages[I].Bitmap.SetSize(Round(SolutionImages[I].Width),Round(SolutionImages[I].Height));
SolutionImages[I].Clear(TAlphaColors.White);
Ok so it appears that I am still having a problem. The scrollbars are not coming up and trying to the resize the scrollbox (I have a slider between two panels, one is the parent of the scrollbox and the other holds other components) either does nothing (nothing moves) or causes the slider to shoot off the screen to the left, thus hiding everything "off" the application window.
As I am not familiar with firemonkey, this is boggling. I could've done this easily in VCL however we are trying to explore the "acclaimed power" of firemonkey.

Resizing main menu for high DPI/font size

I have an issue with font height in standard main menu/popup menu when it contains images. Looks like this.
When there are no images, there are no problems as displayed above. Main menu uses TImageList with image width/height set to 16.
So I want to preserve image size at 16x16 and center it, to get something like this:
How can I read the font height of the main menu and adjust images in TImageList accordingly? One idea I have is to copy images from one TImageList to another with larger image width/height but I still need to determine proper size from the font size. How do I do that?
UPDATE
I solved this by examining SystemParametersInfo - SPI_GETNONCLIENTMETRICS value and using the iMenuHeight value for TImageList Width/Height. As images are deleted after changing Width/Height, I copied another to another TImageList. Works exactly as it should. Thank you everyone for your most helpful answers.
UPDATE 2
After examining the problem futher the solution which I marked as correct down there is giving better result so I switched to that one instead. Tested on Win7 and XP, appears to be working properly.
You can get the height of Screen.MenuFont by selecting it to a temporary DC:
function GetMenuFontHeight: Integer;
var
DC: HDC;
SaveObj: HGDIOBJ;
Size: TSize;
begin
DC := GetDC(HWND_DESKTOP);
try
SaveObj := SelectObject(DC, Screen.MenuFont.Handle);
GetTextExtentPoint32(DC, '|', 1, Size); // the character doesn't really matter
Result := Size.cy;
SelectObject(DC, SaveObj);
finally
ReleaseDC(HWND_DESKTOP, DC);
end;
end;
Well, Canvas.GetTextHeight('gh') usually helps to get height of text. But in case of different DPI, you can simply scale by Screen.PixelsPerInch / 96.0.
The text height is probably not what you need to use. I suggest that you use icons whose square dimension is equal to the prevailing small icon size. That's the system metric whose ID is SM_CXSMICON. Retrieve the value by calling GetSystemMetrics passing that ID.
You can use Power Menu Component with many advanced features
Download from here : http://elvand.com/downloads/DELPHI/PowerMenu.zip
Delphi7-XE2
size=193 KB
#include <windows.h>
int GetMainMenuHeight(void)
{
NONCLIENTMETRICS Rec;
Rec.cbSize = sizeof(Rec);
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, Rec.cbSize, &Rec.cbSize, 0))
return Rec.iMenuHeight;
else return -1;
}

How do I determine the height of a line of text in a TMemo programmatically?

I've got a TMemo, and I want to always make it exactly high enough to display the number of lines it contains. Unfortunately, I don't quite know how to calculate that. I can't base it off the .Font.Size property, because that will vary based on DPI. And I can't use TCanvas.TextHeight because TMemo doesn't seem to have a canvas.
Anyone know how to do this right?
I see a problem, i think all lines on a TMemo are equal on height, but some can be empty...
So getting Height of empty ones will give zero while they are not zero height on the TMemo.
So the solution maybe doing something like Memo.Lines.Count*LineHeight
Beware that the Lineheight may not be getted by Canvas.TextHeight since Canvas.TextHeight will give more or less exact height of minimal height for a text... i mean it will not give same height for text 'ABC' than for 'ABCp', etc...
I would recomend (if not want to call Windows API) to use Font.Height, but if it is negative the internal leading of each line is not measured...
So i would recomend to do the next steps (tested):
Asign a positive value for Memo.Font.Height on the OnCreate event or anywhere you want, with this the lineheight ot the TMemo will be such value you asigned
Total height now can be obtained directly by Memo.Lines.Count*LineHeight, since you have asigned a positive value to Memo.Font.Height (remember that would make Memo.Font.Size to be negative)
Personally i do this on the TForm OnCreate event (to ensure it is done only once), just to ensure visual font size is not changed and MyMemo.Font.Height includes internal leading of each line:
MyMemo.Font.Height:=Abs(MyMemo.Font.Size*MyMemo.Font.PixelsPerInch div Screen.PixelsPerInch);
Ensure the previous to be done only once, otherwise the text size will be visaully bigger and bigger, as much as times you run it... it is caused because not allways MyMemo.Font.PixelsPerInch is equal to Screen.PixelsPerInch... in my case they are 80 and 96 respectively.
Then, when i need to know line height i just use:
Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72)
That gives exact height of one TMemo line, since all lines have the same height, the total height would be:
MyMemo.Lines.Count*Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72)
So, to make TMemo height as big as its contained text i do this (read the comment of each line, they are very important):
MyMemo.Font.Height:=Abs(MyMemo.Font.Size*MyMemo.Font.PixelsPerInch div Screen.PixelsPerInch); // I do this on the Tform OnCreate event, to ensure only done once
MyMemo.Height:=1+MyMemo.Lines.Count*Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72); // I do this anywhere after adding the text and/or after editing it
I you do not want to play with Screen.PixelsPerInch you can just do this (read the comment of each line, they are very important):
MyMemo.Font.Height:=Abs(MyMemo.Font.Height); // This may make text size to visually change, that was why i use the corrector by using MyMemo.Font.PixelsPerInch and Screen.PixelsPerInch
MyMemo.Height:=1+MyMemo.Lines.Count*Abs(MyMemo.Font.Height*MyMemo.Font.PixelsPerInch div 72);
Hope this can help anyone.
You can write your own implementation of TCanvas.TextHeight for TMemo:
function CountMemoLineHeights(Memo: TMemo): Integer;
var
DC: HDC;
SaveFont: HFont;
Size: TSize;
I: Integer;
begin
DC:= GetDC(Memo.Handle);
SaveFont:= SelectObject(DC, Memo.Font.Handle);
Size.cX := 0;
Size.cY := 0;
// I have not noticed difference in actual line heights for TMemo,
// so the next line should work OK
Windows.GetTextExtentPoint32(DC, 'W', 1, Size);
// BTW next (commented) line returns Size.cY = 0 for empty line (Memo.Lines[I] = '')
// Windows.GetTextExtentPoint32(DC, Memo.Lines[I], Length(Memo.Lines[I]), Size);
Result:= Memo.Lines.Count * Size.cY;
SelectObject(DC, SaveFont);
ReleaseDC(Memo.Handle, DC);
end;
You need to use a TCanvas for this. You can either create a TBitMap in the background and use its TCanvas (after assigning the Memo's Font property to the Bitmap.Canvas' one), or use a TCanvas from another component. Somthing like this:
BMP:=TBitMap.Create;
TRY
BMP.Canvas.Font.Assign(Memo.Font);
TotalHeight:=0;
FOR LineNo:=1 TO Memo.Lines.Count DO INC(TotalHeight,BMP.Canvas.TextHeight(Memo.Lines[PRED(I)]))
FINALLY
FreeAndNIL(BMP)
END;
Edit:
Or perhaps this one:
BMP:=TBitMap.Create;
TRY
BMP.Canvas.Font.Assign(Memo.Font);
LineHeight:=BMP.Canvas.TextHeight('Wq');
TotalHeight:=Memo.Lines.Count*LineHeight
FINALLY
FreeAndNIL(BMP)
END;
I originally suggested looing at the "Lines" TStrings list member in TMemo.
Instead, please look at the "Font" member in the parent class, TCustomEdit.
'Hope that helps .. PS

Start program on a second monitor?

Is there a way to specify which monitor a application appears on in Delphi or C++Builder?
I am developing a simple program for a customer, which displays kitchen orders on a secondary monitor, generated by a hospitality system. Currently they need to manually drag the window onto the second monitor after it starts.
The global Screen object (part of Forms) has the concept of Monitors. I think this was added circa Delphi 6 or 7. The following code will work:
// Put the form in the upper left corner of the 2nd monitor
// if more then one monitor is present.
if Screen.MonitorCount > 1 then
begin
Left := Screen.Monitors[1].Left;
Top := Screen.Monitors[1].Top;
end;
You could use any positive offset from that position to put it anywhere in that monitor. You can get the width and height from there too to know the dimensions.
Save the window position before program shutdown and restore them on startup. Multimonitor displays just increase the size of the desktop; other monitor surfaces just have a different section of the same X/Y plane with its origin at the top-left of the primary monitor.
This can be done automatically for you by any of several components.
BTW, the Screen variable in the Forms unit has a property called MonitorCount and another indexable property, Monitors[Index: Integer]: TMonitor. TMonitor has properties indicating the left, top, width, height etc., so all the information you need is there.
procedure TMDIChild.btnShowMonClick(Sender: TObject);
begin
if Screen.MonitorCount > 1 then
begin
FormShow.Left:=Screen.Monitors[1].Left;
FormShow.Top:=Screen.Monitors[1].Top;
FormShow.Width:=Screen.Monitors[1].Width;
FormShow.Height:=Screen.Monitors[1].Height;
end
else
begin
FormShow.Show;
end;
end;
Not really the answer your question implies, but couldn't you store the window settings (size, position, Maximized State) when ever the application is closed, and then apply them at startup?
I have done a similar thing a while ago in Delphi 5:
procedure TForm18.FormCreate(Sender: TObject);
var
Mon: TMonitor;
MonitorIdx: Integer;
begin
MonitorIdx := 1; // better read from configuration
if (MonitorIdx <> Monitor.MonitorNum) and (MonitorIdx < Screen.MonitorCount) then begin
Mon := Screen.Monitors[MonitorIdx];
Left := Left + Mon.Left - Monitor.Left;
Top := Top + Mon.Top - Monitor.Top;
end;
end;
Windows will let you specify the coordinates of a window in the CreateWindow API call. I don't know enough about Delphi or C++Builder to know if you have access to that part of the process.
You might also be able to move the window in a WM_CREATE handler.
EnumDisplayMonitors will give you the coordinates of each monitor in the system.
Evidently Delphi and C++ Builder have facilities that make this answer somewhat irrelevant. I'll leave it here in case someone comes across this question but needs it answered for a different environment.
I don't do much with windows systems, so I would suggest a hack like this.
Grab the width of the viewable desktop(both monitors combined), divide it by half and make that your starting position.
You may also look into what api tells you monitor2's dimensions.

Resources