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

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.

Related

Get Top and Left position of an FMX Control, based on screen bounds

I'm trying to place an outside program right into an FMX TPanel using SetWindowPOS from WinAPI.
In order to do that I need the exact Top and Left values of the TPanel to pass it to SetWindowPOS. However, this value is always according to its parent, so i.e. if the Tpanel's parent is a TLayout and no margins are set, then the value of Top will be 0.
I need the Top value according to the screen.
I tried searching for that quite a bit but I can't figure it out.
Any help with that would be greatly appreciated.
After just posting the question I got the simple solution
var
pnt: TPointF;
begin
pnt := myRctngl.LocalRect.TopLeft;
pnt := myRctngl.LocalToAbsolute(pnt);
pnt := ClientToScreen(pnt);
End;
pnt should now have the value of the Point according to the screen.
You can now use pnt.x and pnt.y to get the top/left values.

Delphi XE2: Can a form cause all of its child controls to paint themselves offscreen?

I am working with this legacy app that has forms having a TScrollBox containing deeply nested TPanel descendents (up to 8 or so levels of nesting), each hosting controls that can cause the panel to be resized to make room for new child panels with their own controls. e.g. the user clicks a radio button, which resizes the panel hosting it to accommodate a new child panel with its own controls, including radio buttons that can cause the child to be resized and repopulated in the same manner.
The performance of this app was good until it was modified to use VCL styles. The group doing that work had to abandon the effort when it was found that the themed version might take over a minute to redraw itself after a single click.
It turned out that enabling double-buffering at the form level worked to make the repainting performance acceptable when the user clicks on a control. Scrolling is still a problem however. The controls on the form are not able to paint themselves fast enough to keep up with a user scrolling by rolling the mouse wheel. The scrollbar gets moved, a part of the client area gets redrawn, then the next mouse wheel message gets processed, a little bit of the client gets redrawn, and so on until the user quits rolling the mouse and there's time to redraw the whole thing. The code that does the scrolling looks like this:
procedure ScrollControl(Window: TScrollingWinControl; ScrollingUp: boolean; Amount: integer = 40);
var
Delta: integer;
// This is needed to tell the child components that they are moving,
// The TORCombo box, for example, needs to close a dropped down window when it moves.
// If Delphi had used standard scroll bars, instead of the customized flat ones, this
// code wouldn't be needed
procedure SendMoveMessage(Ctrl: TWinControl);
var
i: integer;
begin
for i := 0 to Ctrl.ControlCount - 1 do
begin
if Ctrl.Controls[i] is TWinControl then with TWinControl(Ctrl.Controls[i]) do
begin
SendMessage(Handle, WM_MOVE, 0, (Top * 65536) + Left);
SendMoveMessage(TWinControl(Ctrl.Controls[i]));
end;
end;
end;
begin
Delta := Amount;
if ScrollingUp then
begin
if Window.VertScrollBar.Position < Delta then
Delta := Window.VertScrollBar.Position;
Delta := - Delta;
end
else
begin
if (Window.VertScrollBar.Range - Window.VertScrollBar.Position) < Delta then
Delta := Window.VertScrollBar.Range - Window.VertScrollBar.Position;
end;
if Delta <> 0 then
begin
Window.VertScrollBar.Position := Window.VertScrollBar.Position + Delta;
SendMoveMessage(Window);
end;
end;
What I think I want to do is to (right before the outermost call to SendMoveMessage)
make a DC for Window
allocate an offscreen bitmap the size of Window's client area
arrange for all drawing operations on Window and its children to be drawn to the offscreen bitmap
Call SendMoveMessage and
bitblt the offscreen bitmap into Window's client area and free the bitmap and DC.
But I cannot see how to do step 3. Is it even possible?

How to find the real size ("logical area") of a TScrollBox

I need to find the entire size (also called "logical area") of a TScrollBox - as opposite to visible area that you get via Width and Height property (or ClientWidth ClientHeight).
I want to create some controls inside that TScrollBox. One of them (called TViewer) needs to be as high as the TScrollBox itself. The thing is that during creation, the TScrollBox is scrolled down to show last created control. So, using Top=1 will not work because my control will have top=1 which is not the top of the logical area.
Delphi 7
Drop a component, like a TLabel, on the TScrollBox.
Set the component's Left and Top properties to 0.
Set the component's Visible property to False.
Now you always have the origin. The "logical height" is now:
myScrollBox.Height + (myOriginControl.Top * -1);
Maybe ScrollBox.HorzScrollBar.Range and ScrollBox.VertScrollBar.Range + the corresponding .Positions are what you need.
Look at Scrollbars:
ScrollBox1.VertScrollBar.Range
ScrollBox1.HorzScrollBar.Range
It can be less than height and width if the scrollbox logical area is not bigger than phisical area (scrollbars not visible in that case)
Or use this to get the max of both:
var
AHeight, AWidth: Integer;
begin
AHeight := Max(ScrollBox1.VertScrollBar.Range, ScrollBox1.Height);
AWidth := Max(ScrollBox1.HorzScrollBar.Range, ScrollBox1.Width);
ShowMessageFmt('%d,%d', [AHeight, AWidth]);
end;
Edit
From #Altar comments, I can add the logical height and/or width is not the problem. If you want to add any control which occupies all the height of the scrollbar, use the AHeight from the above calculation, but set the Top to the negative of VertScrollBar.Position, something like this:
procedure TForm2.Button3Click(Sender: TObject);
var
AHeight, AWidth: Integer;
Btn: TButton;
begin
AHeight := Max(ScrollBox1.VertScrollBar.Range, Height);
AWidth := Max(ScrollBox1.HorzScrollBar.Range, Width);
Btn := TButton.Create(Self);
Btn.Parent := ScrollBox1;
Btn.Left := 15;
Btn.Top := -ScrollBox1.VertScrollBar.Position;
Btn.Height := AHeight;
end;
Im not sure i understand exactly what you want to do, but to get the complete area defined as "scrollable" you would have to write ScrollBox.HorScrollBar.Range + ScrollBox.Clientwidth (and the same thing for the vertical section). The scrollbox always deducts the visible "page" size from the total. So if you define a height of 1000 pixels, with 100 pixels showing - it will have a scrolling range of 900. You have to add the clientheight to get the rest.
Also, to get the correct "top" position you will have to read Canvas.Cliprect.Top, since a scrolling window does not change the top position of sub-controls. Windows handles this for you and only tells you what to re-draw once the scrollbars are initialized.
Since you want to create a control that is just as high as the complete scrollable area, i presume you are creating an editor of sorts?
If so you would probably get better results taking a look at SynEdit and extract the base-class that adds scrollbars to a normal TCustomControl (it's quite easy). That way you can control both the painting and the layout of your controls.
Here is one I wrote for Lazarus and FreePascal a while back. If you add messages to the uses clause and prefix the message handlers with WM rather than TLM it will compile under Delphi.
(code to long, had to use external link): http://delphimax.wordpress.com/2010/09/20/platform-independent-image-component-for-lazarus/
I have tried to do that, and believe me, I was not able to.
If you have the instances of the controls that are inside TScrollBox, you can use them to calculate (not precisely) the area.
Here is a complicated (but complete) solution:
I create my first child control during TScrollBox.Create (when TScrollBox does not have yet a scrollbar)
Set Child.Top:= 1
Create the rest of the child controls (this may force the TScrollBox to show the scrollbar)
Scroll the TScrollBox up
Use one of the above solutions to calculate the height of the TScrollBox

Problem with TImage and TScrollBox

I am working with delphi.
I have one scroll box in which I am putting TImage control. Now I wanted to zoom the image rendered into TImage control. So, I am using stretchDraw method of TCanvas. My code is -
if sbZoom.Down then begin
rct := imgmain.Picture.Bitmap.Canvas.ClipRect;
rct := Rect(rct.Left * 2,rct.Top * 2,rct.Right * 2,rct.Bottom * 2);
imgmain.Picture.Bitmap.Canvas.StretchDraw(rct,imgmain.Picture.Bitmap);
imgmain.Repaint;
end;
It is correctly zooming the image, my problem is I want the size of scroll box also should be changed with zooming of image.
Also explain me parameters of Canvas.StretchDraw method. I am little confused with it.
Thank You.
You can do this quite easily without calling StretchDraw:
if Zoomed then begin
Image1.AutoSize := false;
Image1.Stretch := true;
Image1.Width := 2*Image1.Width;
Image1.Height := 2*Image1.Height;
end
else begin
Image1.Stretch := false;
Image1.AutoSize := true;
end;
AutoSize := true assures that the TImage is the same size as the picture inside. During zoom we switch AutoSize off and Stretch on, so the picture is resized to the TImage size (which is still the same here). Then we double the size of the TImage to make the zoom effect. As the TImage is now larger, the scrollbox can work properly.
Uwe Raabe is giving you the right way to do it. Here's why your way doesn't work: A scroll box will show scrollbars and help you see whole controls. In your case, it will only show scrollbars when the TImage object grows larger then the Scrollbox. The Scrollbox can't possibly know the internals of TImage so it doesn't care about TImage.Picture, it only cares about the control. And a TImage that has AutoSize = False doesn't care about it's Picture, it's size stays the same at all times.
Your code repaints the base bitmap onto itself. The problem is, the bitmap has fixed Width and Height: if you paint outside the bitmap's area you're basically silently ignored. When you're "zooming" by StretchDrawing the bitmap onto itself (and I'm surprised it worked to start with!) you're not making the bitmap larger and the stuff that doesn't fit gets silently clipped away. If you do want the internal bitmap to change size then you'll first need to create a new, larger bitmap, draw your enlarged image to the new bitmap and then assign the bitmap to your TImage. If you do this, make sure TImage.AutoSize = True.
You should set the size of the image control to the size of the bitmap.

How to draw on the entire area of a resized TImage in Delphi?

I've narrowed a problem I have drawing on TImage.Canvas in Delphi 2009 down to the following reproducible case:
Given: a form, a TImage, TLabel and TButton on it. The TImage is anchored to all four edges so that resizing the form will resize the TImage. What I want to be able to do is draw on the maximal area of Image1 available to me after resizing. So in my test case I have the following code in my the Button's OnClick handler:
procedure TForm1.Button1Click(Sender: TObject);
begin
Label1.Caption:= IntToStr (Image1.Width)+' x '+IntToStr(Image1.Height);
Image1.Canvas.Pen.Color:= 0;
Image1.Canvas.Rectangle(0,0,Image1.Width, Image1.Height);
end;
You'll see that if the form is resized, Image1.Width and .Height change as expected, however the rectangle that is drawn if the resized form is larger than the original one, will be incomplete, only drawing on the same area that was there previously.
How do I get it do use the entire resized area?
For what it's worth, in my original problem I had played with Image1.Stretch, which allows me to use more of the area upon resizing but will result in my drawings being distorted (not desired). If I also use Image1.Proportional, then it's better but I still can't use the full area available. Image1.AutoSize doesn't seem to be doing anything useful to me either.
Any help appreciated.
Add an OnResize-event to your form:
procedure TForm1.FormResize(Sender: TObject);
begin
Image1.Picture.Bitmap.Width := Image1.Width;
Image1.Picture.Bitmap.Height := Image1.Height;
end;
Also, if you are using the component to draw on, rather than displaying images from file etc, consider using the TPaintBox rather than TImage.
Maybe you have to also adjust Image1.Picture.Width/Height or Image1.Picture.Bitmap.Width/Height.

Resources