------------------------- ORIGINAL QUESTION -------------------------
Greetings to all Delphi developers! In a Delphi 2006 non MDI application, I create a non-sizeable, autoscrollable, autosizeable form. This is an excerpt from the form's unit:
uses Grid;
TGridFrm = class(TForm)
public
Grid : TGrid;
constructor Create(AOwner : TComponent; Asize : TPoint);
end;
implementation
constructor TGridFrm.Create(AOwner: TComponent; Asize : TPoint);
begin
inherited Create(aowner);
borderstyle := bsSingle; // users are not allowed to resize the form
windowstate := wsNormal;
borderwidth := 0;
autosize := True;
autoscroll := True;
constraints.maxwidth := screen.width - 1;
constraints.maxheight := screen.height - 1;
grid := TGrid.Create(asize.x, asize.y, self);
end;
Now, TGrid is a custom control with its own canvas of course. This is an excerpt from its unit:
TGrid = class (TCustomControl)
public
NoOfCellsX,
NoOfCellsY,
CellSize : integer;
procedure SetZoom(z : integer);
constructor Create(AWidth, AHeight : Integer; AParent : TForm = nil);
end;
implementation
constructor TGrid.Create(AWidth, AHeight : Integer; AParent : TForm = nil);
begin
inherited Create(AParent);
Parent := AParent;
align := alCustom;
left := 0;
top := 0;
end;
procedure TGrid.SetZoom(zoom : integer);
begin
cellsize := zoom * 10 div 100;
width := noofcellsx * cellsize;
height := noofcellsy * cellsize;
end;
In the form's unit I have arranged things up (through an ApplicationEvents object) so that SetZoom is called with some zoom value, whenever the numeric +/- keys are pressed. The idea behind all this was to have my custom control snap to the upper left corner of the form (with some predefined margin/borderwidth), and have the entire form automatically adjust its size whenever I zoom in or out of the custom control, but never extending beyond the screen limits. It's working, but only up to the point where the scrollbars must become visible: they never show up. Since this is an autoscrollable form, aren't they supposed to show up whenever a control inside the form (Grid in this case) gets larger than the constrained form and get out of the way when it gets smaller? I even tried some refactoring by moving SetZoom to the form's class, but to no avail. What am i missing here?
----------------- COMPILABLE CODE ADDED AFTERWARDS ------------------
The project file:
program MyApp;
uses
Forms,
Grid in 'Source\Grid.pas',
GridForm in 'Source\GridForm.pas' {GridFrm},
Main in 'Source\Main.pas' {MainFrm};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TMainFrm, MainFrm);
Application.Run;
end.
The Main.pas:
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TMainFrm = class(TForm)
CreateNewFormButton: TButton;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure CreateNewFormButtonClick(Sender: TObject);
end;
var
MainFrm: TMainFrm;
implementation
{$R *.dfm}
uses
GridForm;
procedure TMainFrm.CreateNewFormButtonClick(Sender: TObject);
var aform : TForm;
begin
aform := TGridFrm.Create(self, point(15, 15));
aform.show;
tgridfrm(aform).grid.SetZoom(100);
end;
procedure TMainFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
The GridForm.pas:
unit GridForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Grid, AppEvnts;
type
TGridFrm = class(TForm)
ApplicationEvents1: TApplicationEvents;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
private
TheGrid : TGrid;
public
property Grid : TGrid READ TheGrid WRITE TheGrid;
constructor Create(AOwner : TComponent; ASize : TPoint);
end;
var
GridFrm: TGridFrm;
implementation
{$R *.dfm}
procedure TGridFrm.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
var keystate : TKeyboardState;
begin
if not Active then begin exit; end;
if msg.message = WM_KEYDOWN then
begin
getkeyboardstate(keystate);
case msg.wparam of
vk_Add : begin // zoom in
grid.setzoom(grid.zoom + 10);
handled := True;
end;
vk_Subtract : begin // zoom out
grid.setzoom(grid.zoom - 10);
handled := True;
end;
// other keys down here...
end;
end;
end;
constructor TGridFrm.Create(AOwner : TComponent; ASize : TPoint);
begin
inherited Create(AOwner);
borderstyle := bsSingle;
borderwidth := 2;
autosize := True;
autoscroll := True;
constraints.maxwidth := screen.width - 1;
constraints.maxheight := screen.height - 1;
visible := False;
grid := TGrid.Create(asize.x, asize.y, random(800) + 500, self);
end;
procedure TGridFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
And the Grid.pas:
unit Grid;
interface
uses
StdCtrls, SysUtils, Controls, Forms, Graphics, Dialogs;
type
TGrid = class (TCustomControl)
Lbl1, Lbl2,
GridSizeInfoLbl,
FormSizeInfoLbl,
WarningLbl : TLabel;
public
NoOfCellsX,
NoOfCellsY,
SquareSize, // in 1/1000ths of centimeter
CellSize, // in pixels
Zoom : integer;
procedure SetZoom(z : integer);
constructor Create(x, y, asquaresize : integer; AParent : TForm = nil);
end;
implementation
uses
GridForm;
constructor TGrid.Create(x, y, asquaresize : integer; AParent : TForm = nil);
begin
inherited Create(AParent);
parent := AParent;
color := clTeal;
align := alCustom;
left := 0;
top := 0;
noofcellsx := x;
noofcellsy := y;
squaresize := asquaresize;
Lbl1 := TLabel.Create(self);
Lbl2 := TLabel.Create(self);
GridSizeInfoLbl := TLabel.Create(self);
FormSizeInfoLbl := TLabel.Create(self);
WarningLbl := TLabel.Create(self);
with Lbl1 do
begin
parent := self;
caption := 'Size of grid: ';
width := 55;
height := 18;
left := 2;
top := 1;
end;
with Lbl2 do
begin
parent := self;
caption := 'Size of form: ';
width := 75;
height := 18;
left := 2;
top := 19;
end;
with GridSizeInfoLbl do
begin
parent := self;
width := 100;
height := 18;
left := 65;
top := 1;
end;
with FormSizeInfoLbl do
begin
parent := self;
width := 100;
height := 18;
left := 65;
top := 19;
end;
with WarningLbl do
begin
parent := self;
width := 150;
height := 18;
left := 2;
top := 39;
end;
end;
procedure TGrid.SetZoom(z : integer);
begin
zoom := z;
cellsize := (screen.pixelsperinch * squaresize * zoom) div (1000 * 254);
width := noofcellsx * cellsize;
height := noofcellsy * cellsize;
GridSizeInfoLbl.caption := inttostr(Width) +
'x' + inttostr(Height) +
' (zoom: ' + inttostr(zoom) +
', cellsize zoomed: ' + inttostr(cellsize) +
', squaresize: ' + inttostr(squaresize) +
'mm, squares: ' + inttostr(noofcellsx) + 'x' + inttostr(noofcellsy) + ')';
with tgridfrm(parent) do
begin
left := (screen.Width - width) div 2;
top := (screen.Height - height) div 2;
FormSizeInfoLbl.caption := inttostr(Width) + 'x' + inttostr(Height) +
' (clientarea: ' + inttostr(clientwidth) + 'x' + inttostr(clientheight) + ')';
if self.width > clientwidth then
if self.Height > clientheight then
warninglbl.caption := 'Both scrollbars should appear!'
else
warninglbl.caption := 'Horizontal scrollbar should appear!'
else if self.Height > clientheight then
warninglbl.caption := 'Vertical scrollbar should appear!'
else
warninglbl.caption := 'No scrollbars needed';
end;
end;
end.
Code synopsis: A click on the main form' s button creates an autosizeable form, which in turn creates a child grid of random initial size. Numeric +/- keys make the grid larger or smaller and the form is autosized accordingly, but no scrollbars ever show up, no matter how large the grid becomes (the labels I added provide visual feedback).
Your problem is twofold.
The first is, as Jerry commented to the question, AutoSize. The purpose of autosize is to resize the form such that content is visible. There can be no scrollbars when all content is visible, so clearly the two properties are contradictory.
As such VCL developers have took their precaution. Below is from D2007 source:
function TScrollingWinControl.AutoScrollEnabled: Boolean;
begin
Result := not AutoSize and not (DockSite and UseDockManager);
end;
As you can see setting AutoScroll has no effect when AutoSize is set.
You could override this behavior, this is a virtual method, if it wouldn't interfere with the second fold.
Now that you've decided to leave autosize off and calculate and set the required size of your form yourself depending on the workarea size, meet your second problem: alignment of your grid control.
The below is the D2007 code when a vertical scroll bar wants to see if it needs to adjust:
procedure ProcessVert(Control: TControl);
begin
if Control.Visible then
case Control.Align of
alTop, alNone:
if (Control.Align = alTop) or (Control.Anchors * [akTop, akBottom] = [akTop]) then
NewRange := Max(NewRange, Position + Control.Top + Control.Height);
alBottom: Inc(AlignMargin, Control.Height);
end;
end;
As you can see a control will not have an effect on an automatic vertical scroll bar if it doesn't have either alTop, alBottom or alNone alignment. Yours have alCustom.
This is also why overriding autosizing behavior won't help, AutoSize depends on controls having "left", "right", "top", "bottom" or "none" aligned controls.
You have to redesign your control taking into consideration how VCL internally works. Not all of the internal dependency aspects can be documented, so you have to use the source for this kind of enhanced development.
Related
Some time ago I have decided to create my own ListControl. What is mean under ListControl - is a control that similar to standard TListBox in Delphi.
I know, it is 'reinventing a wheel', but I want to finish my control.
So, I implemented not so much features in that control like TListBox has, but my control allows:
Add items;
Select item;
Navigate through items via keyboard (arrow keys Up an Down).
I plan to implement my ScrollBar, but this is another topic.
But I have a problem: when summary height of items is more than control's height and last item selected and I try to increase control's height I got a 'blank space', but I want to 'scroll' items down to fill blank space.
At the picture above you can see that control has lack of items to draw them onto 'blank space'.
May be I explain my problem not so clear, but do next:
Put standard TListBox on form and set its height equal 100 px;
Put standard TrackBar on form, set Max value to 100 and in event OnChange write this:
ListBox1.Height := ListBox1.Height + TrackBar1.Position;
Add 12 items at this Listbox;
Compile project and select last item in Listbox, then begin to change its height via TrackBar. You will see, that 'invisible top items' are come from top to down one by one.
That effect I want to add in my control, but I have no idea why.
Control's code
unit aListBox;
interface
uses
Windows,
Messages,
SysUtils,
Classes,
Graphics,
Controls,
Forms,
StdCtrls,
ExtCtrls,
StrUtils,
Dialogs,
Math;
type
{ main class }
TaListBox = class;
{>>>>>>>>>>>>>>>>>>>>>>>>>}
TaListBox = class(TCustomControl)
private
{ Private declarations }
protected
{ Protected declarations }
FItemBmp: TBitmap;
FEnabled: Boolean;
FSelected: Boolean;
FItems: TStringList;
FItemHeight: Integer;
FCurrentItemIndex: Integer;
FMode: Integer;
FGlobalY: Integer;
FScrollOffset: Integer;
FDownScroll: Integer;
procedure SetItems(value: TStringList);
procedure WMSIZE(var Message: TWMSize); message WM_SIZE;
procedure WMGETDLGCODE(var Message: TWMGETDLGCODE); message WM_GETDLGCODE;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
function GetItemIndex: Integer;
function GetVisibleItemsCount: Integer;
function GetScrollItemIndex: Integer;
procedure PaintItemStandard(BmpInOut: TBitmap; AMode, AIndex: Integer);
procedure PaintControlStandard(ACanvas: TCanvas; AMode: Integer);
procedure Paint; override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Click; override;
property ItemIndex : Integer read FCurrentItemIndex;
published
{ Published declarations }
property Items : TStringList read FItems write FItems;
property OnClick;
end;
{<<<<<<<<<<<<<<<<<<<<<<<<<}
implementation
{ TaListBox }
procedure Register;
begin
RegisterComponents('MyControl', [TaListBox]);
end;
constructor TaListBox.Create(AOwner: TComponent);
begin
Inherited Create(AOwner);
{ standard declarations }
ControlStyle := ControlStyle + [csOpaque, csCaptureMouse, csDoubleClicks];
Width := 100;
Height := 120;
DoubleBuffered := true;
{ control's declarations }
FItemBmp := TBitmap.Create;
FEnabled := true;
FSelected := false;
FItems := TStringList.Create;
FItemHeight := 20;
FCurrentItemIndex := -1;
FScrollOffset := 0;
FDownScroll := 0;
FMode := 1;
end;
destructor TaListBox.Destroy;
begin
FreeAndNil(FItemBmp);
FreeAndNil(FItems);
Inherited Destroy;
end;
procedure TaListBox.Click;
begin
if FEnabled then
Inherited Click
else
Exit;
end;
procedure TaListBox.SetItems(value: TStringList);
begin
Invalidate;
end;
procedure TaListBox.WMSize(var Message: TWMSize);
var
LScrollIndex, LVisibleCount: Integer;
begin
inherited;
LScrollIndex := FScrollOffset div FItemHeight;
LVisibleCount := GetVisibleItemsCount;
if (FItems.Count - LScrollIndex) < LVisibleCount then
FScrollOffset := FItemHeight * max(0, FItems.Count - GetVisibleItemsCount);
end;
procedure TaListBox.WMGETDLGCODE(var Message: TWMGETDLGCODE);
begin
Inherited;
Message.Result := DLGC_WANTARROWS;
end;
procedure TaListBox.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
begin
Windows.SetFocus(Handle);
if PtInRect(Rect(1, 1, Width - 1, Height - 1), Point(X, Y)) then
FGlobalY := Y - 2;
if GetItemIndex > FItems.Count - 1 then
Exit
else
begin
FSelected := true;
FCurrentItemIndex := GetItemIndex;
// prevent selecting next item if height too low
if Height >= FItemHeight then
if PtInRect(Rect(1, Height - FDownScroll - 1, Width - 1, Height - 1), Point(X, Y)) then
FScrollOffset := FScrollOffset + FItemHeight;
Invalidate;
end;
end;
Inherited MouseDown(Button, Shift, X, Y);
end;
procedure TaListBox.KeyDown(var Key: Word; Shift: TShiftState);
var
ScrollIndex: Integer;
begin
Inherited KeyDown(Key, Shift);
if FEnabled then
begin
case Key of
VK_UP:
begin
if FCurrentItemIndex = 0 then
Exit
else
begin
if (FCurrentItemIndex + 1) > 0 then
begin
Dec(FCurrentItemIndex);
ScrollIndex := FScrollOffset div FItemHeight;
if FCurrentItemIndex < ScrollIndex then
FScrollOffset := FScrollOffset - FItemHeight;
end;
end;
end;
VK_DOWN:
begin
if FCurrentItemIndex = FItems.Count - 1 then
Exit
else
begin
if (FCurrentItemIndex + 1) < FItems.Count then
begin
Inc(FCurrentItemIndex);
ScrollIndex := FScrollOffset div FItemHeight;
if (FCurrentItemIndex - GetVisibleItemsCount + 1) > ScrollIndex then
FScrollOffset := FScrollOffset + FItemHeight;
end;
end;
end;
end;
Invalidate;
end
else
Exit;
end;
function TaListBox.GetItemIndex: Integer;
begin
Result := (FGlobalY + FScrollOffset) div FItemHeight;
end;
function TaListBox.GetVisibleItemsCount: Integer;
begin
Result := Height div FItemHeight;
end;
function TaListBox.GetScrollItemIndex: Integer;
begin
Result := FScrollOffset div FItemHeight;
end;
procedure TaListBox.PaintItemStandard(BmpInOut: TBitmap; AMode, AIndex: Integer);
var
Text: String;
R: TRect;
begin
BmpInOut.Width := Width - 2;
BmpInOut.Height := FItemHeight;
case AMode of
1:
begin
if FSelected then
begin
BmpInOut.Canvas.Brush.Color := clWebCrimson;
BmpInOut.Canvas.Font.Color := clWhite;
end
else
begin
BmpInOut.Canvas.Brush.Color := clWhite;
BmpInOut.Canvas.Font.Color := clBlack;
end;
BmpInOut.Canvas.Pen.Color := clGray;
end;
4:
begin
BmpInOut.Canvas.Brush.Color := clSilver;
BmpInOut.Canvas.Pen.Color := clGray;
BmpInOut.Canvas.Font.Color := clBlack;
end;
end;
BmpInOut.Canvas.FillRect(BmpInOut.Canvas.ClipRect);
// paint item's text
if AIndex = - 1 then
Exit
else
BmpInOut.Canvas.TextOut(18, 2, FItems.Strings[AIndex]);
end;
procedure TaListBox.PaintControlStandard(ACanvas: TCanvas; AMode: Integer);
var
i: Integer;
OldSelected: Boolean;
TempBmp: TBitmap;
begin
case AMode of
1:
begin
ACanvas.Brush.Color := clWhite;
ACanvas.Pen.Color := clBlack;
end;
4:
begin
ACanvas.Brush.Color := clSilver;
ACanvas.Pen.Color := clBlack;
end;
end;
ACanvas.Rectangle(Rect(0, 0, Width, Height));
// calculate DownButton size
FDownScroll := Height - GetVisibleItemsCount * FItemHeight - 1 {top border pixel} - 1 {bottom border pixel};
// create output bitmap
TempBmp := TBitmap.Create;
TempBmp.Width := Width - 2;
TempBmp.Height := Height - 2;
// turn off selected flag
OldSelected := FSelected;
FSelected := false;
for i:=0 to FItems.Count - 1 do
begin
PaintItemStandard(FItemBmp, FMode, i);
TempBmp.Canvas.Draw(0, 0 + (FItemHeight * i) - FScrollOffset, FItemBmp);
end;
// output result
ACanvas.Draw(1, 1, TempBmp);
// restore selected flag
FSelected := OldSelected;
if FSelected then
begin
// paint selected item
PaintItemStandard(FItemBmp, FMode, FCurrentItemIndex);
ACanvas.Draw(1, 1 + (FItemHeight * FCurrentItemIndex) - FScrollOffset, FItemBmp);
end;
// free resources
FreeAndNil(TempBmp);
end;
procedure TaListBox.Paint;
begin
if FEnabled then
PaintControlStandard(Canvas, 1)
else
PaintControlStandard(Canvas, 4);
end;
end.
I hope I can find some help here.
Thank you for your attention!
P.S.
In the source code was added implementation of scrolling items by changing control's size, written by Tom Brunberg.
P.S.S.
Thanks to user fantaghirocco for formatting my question ;)
Following your directions to create a standard TListBox I noted, as you said, that the number of visible items increased when increasing the list box (regardless of any item being selected).
But, decreasing the size did not scroll items up again, regardless of any item being selected. I understand that you ask about the same functionality, since you refer to the standard TListBox.
Add to the uses clause and to the TaListBox class declaration:
uses ... Math;
...
TaListBox = class(TCustomControl)
private
procedure WMSize(var Message: TWMSize); message WM_SIZE;
and to the implementation
procedure TaListBox.WMSize(var Message: TWMSize);
var
LScrollIndex, LVisibleCount: Integer;
begin
inherited;
LScrollIndex := FScrollOffset div FItemHeight;
LVisibleCount := GetVisibleItemsCount;
if (FItems.Count - LScrollIndex) < LVisibleCount then
FScrollOffset := FItemHeight * max(0, FItems.Count - GetVisibleItemsCount);
end;
A side note: you use the following kind of expressions in many places, e.g.
Round(FScrollOffset div FItemHeight);
The div operator means integer division. It always returns an integer, thus the call to Round is meaningless. Read about div and also mod in the documentation.
The idea is simple:
Always know how many items can be displayed when your control is a certain height. That means if your clientheight is 100px and an item's height is 10px then you obviously will be able to display 10 items completely whitout anyone being clipped. Save that amount in a variable. Keep as a float because sometimes an item will be clipped. (Visible Count)
Keep a variable of which direction you scrolled last. This is important as that will help you decide whether to bring items into view from the bottom or from the top or whether to hide items at the top or bottom when the control's height decreases/increases.
Keep an index of the item that is at the top or bottom the last time you scrolled. Whether to keep the top one or the bottom one will depend on which direction you last scrolled (point 2). It will obviously change as you add items, etc.
So let's say the situation is that you have more items than can be displayed and the last time you scrolled was up, so you will keep the item index of the top most visible item. If that index is 0 (zero) then obviously you just need to bring items into view from the bottom. But if that index is for example; 5, then you will keep bringing items into view also from the bottom but only until the Visible Count grows as large or larger than the Item Count in which case you wil start to bring as many items into view from the top as is needed to fill the client area.
You just have to adapt according to last scroll direction and whether the height increases or decreases
It is a Firemonkey component, however I could see that most of the component base is the same for VCL and FMX, so please if you know how to do that in VCL share your knowledge, it can be eventually the solution for my case.
I am using a TPopup as the ancestor. It is convenient for me since it remains on the form/frame and I can wire it with LiveBindings using the same context/structure of the parent, this is very convenient for me.
I need it behave exactly it is the TPopup, as a container. But I need it looks better and have my specific buttons (I have created some properties and automations for my software inside it)
The problem is that I create some internal controls, like TLayouts, Tpanels and Tbuttons to make looks like this: (empty)
That black area inside it is where I want to drop controls like TEdit and others.
I have set all the internal created controls to Store = false, so it is not getting stored on the streaming system. Doing that when I drop a TEdit for example, what I get is this (Tedit with aligned=top I need this):
However I was expecting this:
If I change the Store = true I can get the right effect, but all the inside controls are exposed on the Structure panel and every time I save the form and reopen everything gets duplicated. The inside components exposed is not a problem for me, but the duplication is, if I close and open the component 10 times I will get the entire inside structure replicated 10 time.
I will try to show some code that is related to the design of the component:
Class declaration:
[ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 or pidiOSSimulator or pidiOSDevice or pidAndroid)]
TNaharFMXPopup = class(TPopup, INaharControlAdapter, INaharControl)
private
protected
FpnlMain : TPanel;
FlytToolBar : TLayout;
FbtnClose : TButton;
FbtnSave : TButton;
FbtnEdit : TButton;
FpnlClientArea : TPanel;
FlblTitle : TLabel;
procedure Loaded; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
constructor Create:
constructor TNaharFMXPopup.Create(AOwner: TComponent);
begin
inherited;
FpnlMain := TPanel.Create(Self);
FlblTitle := TLabel.Create(Self);
FlytToolBar := TLayout.Create(Self);
FbtnEdit := TButton.Create(Self);
FpnlClientArea := TPanel.Create(Self);
FbtnClose := TButton.Create(FlytToolBar);
FbtnSave := TButton.Create(FlytToolBar);
Height := 382;
Placement := TPlacement.Center;
StyleLookup := 'combopopupstyle';
Width := 300;
ApplyControlsProp;
end;
Setting properties of the internal controls:
procedure TNaharFMXPopup.ApplyControlsProp;
begin
with FpnlMain do
begin
Parent := Self;
Align := TAlignLayout.Client;
StyleLookup := 'grouppanel';
TabOrder := 0;
Margins.Bottom := 10;
Margins.Left := 10;
Margins.Right := 10;
Margins.Top := 10;
Stored := false;
end;
with FlblTitle do
begin
Parent := FpnlMain;
Text := 'TÃtulo';
Align := TAlignLayout.Top;
Height := 36;
StyleLookup := 'flyouttitlelabel';
Stored := false;
end;
with FpnlClientArea do
begin
Parent := FpnlMain;
Align := TAlignLayout.Client;
StyleLookup := 'gridpanel';
TabOrder := 0;
Margins.Bottom := 5;
Margins.Left := 5;
Margins.Right := 5;
Margins.Top := 5;
Stored := false;
end;
with FlytToolBar do
begin
Parent := FpnlMain;
Align := TAlignLayout.Bottom;
Height := 50;
Stored := false;
end;
with FbtnClose do
begin
Parent := FlytToolBar;
Text := 'Fecha';
Align := TAlignLayout.Left;
Height := 50;
StyleLookup := 'tilebutton';
TabOrder := 0;
Width := 70;
ModalResult := mrClose;
Stored := false;
end;
with FbtnEdit do
begin
Parent := FlytToolBar;
Text := '';//'Edita';
Align := TAlignLayout.Left;
Height := 50;
StyleLookup := 'tilebutton';
TabOrder := 1;
Width := 70;
ModalResult := mrContinue;
Stored := false;
Enabled := false;
end;
with FbtnSave do
begin
Parent := FlytToolBar;
Text := 'Salva';
Align := TAlignLayout.Left;
Height := 50;
StyleLookup := 'tilebutton';
TabOrder := 2;
Width := 70;
ModalResult := mrOk;
Stored := false;
end;
end;
Loaded:
procedure TNaharFMXPopup.Loaded;
begin
inherited;
ApplyControlsProp;
SetEvents;
end;
I have tried the following with notification, trying to make the inserted control a parent for my intenal "clientarea"
procedure TNaharFMXPopup.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opInsert) and (csDesigning in ComponentState) then
begin
if AComponent.Owner = self then
if AComponent is TFmxObject then
begin
(AComponent as TFmxObject).Parent := FpnlClientArea;
end;
end;
end;
But that made nothing change.
I have asked similar question before, but I was not aware of many things on creating such a component and the answer I got gave little help, I was missing the Parent of each internal component.
Now I am trying to really show where is my need: I need to drop controls on my TPopup dialog that will be parented of the ClientArea inside it.
Take a closer look at TTabControl / TTabItem in the unit FMX.TabControl. This is your perfect example because it basically needs to solve the same problem.
The following function is what you need to override:
procedure DoAddObject(const AObject: TFmxObject); override;
This is called when a control is added to your control. Override this function so that your control is added to the FpnlClientArea control instead. You'd get something similar to this:
procedure TNaharFMXPopup.DoAddObject(const AObject: TFmxObject);
// ...
begin
if (FpnlClientArea <> nil) and not AObject.Equals(FpnlClientArea) and not AObject.Equals(ResourceLink) then
begin
FpnlClientArea.AddObject(AObject);
end
else
inherited;
end;
Make sure that AObject.Equals also excludes your other "not stored" controls.
Without the DoAddObject override, the FMX TabControl would show the same problem as your component currently has.
The TPopup is not intended to accept controls. So that needs a few more tricks.
Here's a modified version of your unit that works for me. I've added a few comments:
unit NaharFMXPopup;
interface
uses
System.UITypes,
System.Variants,
System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Layouts, FMX.StdCtrls;
type
[ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 or pidiOSSimulator or pidiOSDevice or pidAndroid)]
TNaharFMXPopup = class(TPopup)
private
procedure ApplyControlsProp;
protected
FpnlMain : TPanel;
FlytToolBar : TLayout;
FbtnClose : TButton;
FbtnSave : TButton;
FbtnEdit : TButton;
FpnlClientArea : TContent; // change to TContent.
// For TPanel we'd have to call SetAcceptControls(False),
// but that is not easily possible because that is protected
FlblTitle : TLabel;
procedure Loaded; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure DoAddObject(const AObject: TFmxObject); override;
public
procedure InternalOnClose(Sender: TObject);
procedure InternalOnSave(Sender: TObject);
procedure InternalOnEdit(Sender: TObject);
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetEvents;
published
end;
implementation
{ TNaharFMXPopup }
constructor TNaharFMXPopup.Create(AOwner: TComponent);
begin
inherited;
FpnlMain := TPanel.Create(Self);
FlblTitle := TLabel.Create(Self);
FlytToolBar := TLayout.Create(Self);
FbtnEdit := TButton.Create(Self);
FpnlClientArea := TContent.Create(Self); // change to TContent
FbtnClose := TButton.Create(FlytToolBar);
FbtnSave := TButton.Create(FlytToolBar);
Height := 382;
Placement := TPlacement.Center;
StyleLookup := 'combopopupstyle';
Width := 300;
// A TPopup is not intended to accept controls
// so we have to undo those restrictions:
Visible := True;
SetAcceptsControls(True);
ApplyControlsProp;
end;
destructor TNaharFMXPopup.Destroy;
begin
inherited;
end;
procedure TNaharFMXPopup.ApplyControlsProp;
begin
with FpnlMain do
begin
Parent := Self;
Align := TAlignLayout.Bottom;
StyleLookup := 'grouppanel';
TabOrder := 0;
Height := 50;
Margins.Bottom := 10;
Margins.Left := 10;
Margins.Right := 10;
Margins.Top := 10;
Stored := false;
end;
with FpnlClientArea do
begin
Parent := Self; // we have to change this to Self (it refuses working if the parent is FPnlMain)
Align := TAlignLayout.Client;
Margins.Left := 3;
Margins.Right := 3;
Margins.Top := 3;
Margins.Bottom := 3;
Stored := false;
end;
with FlytToolBar do
begin
Parent := FpnlMain;
Align := TAlignLayout.Bottom;
Height := 50;
Stored := false;
end;
with FbtnClose do
begin
Parent := FlytToolBar;
Text := 'Close';
Align := TAlignLayout.Left;
Height := 50;
StyleLookup := 'tilebutton';
TabOrder := 0;
Width := 70;
ModalResult := mrClose;
Stored := false;
end;
with FbtnEdit do
begin
Parent := FlytToolBar;
Text := '';//'Edita';
Align := TAlignLayout.Left;
Height := 50;
StyleLookup := 'tilebutton';
TabOrder := 1;
Width := 70;
ModalResult := mrContinue;
Stored := false;
Enabled := false;
end;
with FbtnSave do
begin
Parent := FlytToolBar;
Text := 'Save';
Align := TAlignLayout.Left;
Height := 50;
StyleLookup := 'tilebutton';
TabOrder := 2;
Width := 70;
ModalResult := mrOk;
Stored := false;
end;
end;
procedure TNaharFMXPopup.Loaded;
begin
inherited;
ApplyControlsProp;
// SetEvents;
end;
procedure TNaharFMXPopup.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
end;
procedure TNaharFMXPopup.InternalOnClose(Sender: TObject);
begin
end;
procedure TNaharFMXPopup.InternalOnEdit(Sender: TObject);
begin
end;
procedure TNaharFMXPopup.InternalOnSave(Sender: TObject);
begin
end;
procedure TNaharFMXPopup.SetEvents;
begin
FbtnClose.OnClick := InternalOnClose;
FbtnSave.OnClick := InternalOnSave;
FbtnEdit.OnClick := InternalOnEdit;
end;
procedure TNaharFMXPopup.DoAddObject(const AObject: TFmxObject);
begin
//inherited; try commenting the block bellow and uncommenting this one
//Exit;
if (FpnlClientArea <> nil)
and not AObject.Equals(FpnlClientArea)
and not AObject.Equals(ResourceLink)
and not AObject.Equals(FpnlMain)
and not AObject.Equals(FlblTitle)
and not AObject.Equals(FlytToolBar)
and not AObject.Equals(FbtnEdit)
and not AObject.Equals(FpnlClientArea)
and not AObject.Equals(FbtnClose)
and not AObject.Equals(FbtnSave) then
begin
FpnlClientArea.AddObject(AObject);
end
else
inherited;
end;
end.
I have a problem according to run-time creation of edit components in Delphi 7.
So when I create TEdit components after the program ran for "some" time it perfectly works.
However, when I create TEdit elements at the Forms OnCreate event, they have a wrong height.
Furthermore the (almost) simultaneously created Shapes have the right height.
Edit:
procedure TTPLVisorForm.CreateZeichen(ZShape : TShape; ZEdit : TEdit; VLeft : integer);
begin
with ZShape do
begin
Width := 50;
Height := 50;
Left := VLeft;
Top := 25;
Shape := stRectangle;
Parent := self.Band;
SendToBack();
end;
with ZEdit do
begin
Text := '#';
Left := VLeft+1;
Top := 26;
Parent := self.Band;
Font.Height := 48;
Width := 48;
Height := 48;
SendToBack;
end;
end;
Getting called by:
procedure TZeichen.Anzeigen(Form : TObject; Left : integer);
begin
self.Form := Form;
self.ZShape := TShape.Create(TTPLVisorForm(self.Form).Band);
self.ZEdit := TEdit.Create(TTPLVisorForm(self.Form).Band);
TTPLVisorForm(Form).CreateZeichen(self.ZShape, self.ZEdit, Left);
end;
Getting called by:
procedure TMagnetband.ErweitereRechts;
var
Zeichen : TZeichenKette;
begin
Zeichen := TZeichenKette.Create;
self.LetztesZeichen.Naechstes := TZeichenKette(Zeichen);
Zeichen.Vorheriges := self.LetztesZeichen;
Zeichen.Zeichen.Anzeigen(self.Form,
self.LetztesZeichen.Zeichen.ZShape.Left +
self.LetztesZeichen.Zeichen.ZShape.Width +
self.Padding);
self.LetztesZeichen := Zeichen;
self.Laenge := self.Laenge+1;
end;
Getting again called by:
procedure TTuringmaschine.ZeichenAnfuegen;
begin
self.Magnetband.ErweitereRechts;
end;
Getting called by:
procedure TTuringmaschine.PanelResize(Sender: TObject);
begin
while self.Magnetband.GetRechtsMax < self.Panel.Width do
self.ZeichenAnfuegen;
end;
Finally gets called by:
Constructor TTuringmaschine.Create(Form : TObject);
var
Breite : integer;
begin
self.Zustand := 0;
self.Form := TTPLVisorForm(Form);
self.Panel := TTPLVisorForm(self.Form).Band;
self.Magnetband := TMagnetband.Create(self.Form);
TTPLVisorForm(Form).Band.OnResize := self.PanelResize;
self.PanelResize(Nil);
//self.CreateMagnetkopf;
end;
And the Constructor is either called at the OnCreate event or on another event.
There's a margin around the text in TEdit control, so if you set the Font.Height to 48, the height of the control won't be exactly 48 if the control has the AutoSize property set to True. I would personally decrease height of the font, and for being sure turn the AutoSize off. Your CreateZeichen method would then look like this:
procedure TTPLVisorForm.CreateZeichen(ZShape: TShape; ZEdit: TEdit;
VLeft: Integer);
begin
with ZShape do
begin
Width := 50;
Height := 50;
Left := VLeft;
Top := 25;
Shape := stRectangle;
Parent := Self.Band;
SendToBack;
end;
with ZEdit do
begin
AutoSize := False;
Text := '#';
Left := VLeft + 1;
Top := 26;
Parent := Self.Band;
Font.Height := 40;
Width := 48;
Height := 48;
SendToBack;
end;
end;
I have a TFrame with a TImage as background.
This frame serves as ancestor for other frames that I put on a limited space in the main TForm.
So it is just a user interface base for the other frames.
I need to put many controls inside these frames, because they will handle large database forms.
As the main form has limited space, I need to put a TScrollBox in all the TFrame space except for the title bar. But this covers the backgroud image.
How do I make this ScrollBar to be background transparent?
Or is it better to make a new component with that functionality, and how to do it?
I saw some examples in other sites, but they are buggy at the run-time
Thank You!
Edit2:
I found the TElScrollBox from ElPack from LMD Inovative.
This is background transparent and allow us to put an image as background.
But the same problem occurs: When we scroll it at run-time, it moves the ancestor's background in it's area of effect.
Edit1:
I've tried to make a descendant but the scrollbar only shows when we pass hover the mouse where it should be, and the form's background move inside the scrollbox when we scroll it.
And also, the controls inside of it get some paint errors...
interface
uses
Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms,
Dialogs, Menus, StdCtrls, ExtCtrls;
type
TTransScrollBox = class(TScrollBox)
private
{ Private declarations }
protected
procedure CreateParams(var params: TCreateParams); override;
procedure WMEraseBkGnd(var msg: TWMEraseBkGnd); message WM_ERASEBKGND;
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Eduardo', [TTransScrollBox]);
end;
procedure TTransScrollBox.CreateParams(var params: TCreateParams);
begin
inherited CreateParams(params);
params.ExStyle := params.ExStyle or WS_EX_TRANSPARENT;
end;
procedure TTransScrollBox.WMEraseBkGnd(var Msg: TWMEraseBkGnd);
begin
SetBkMode (Msg.DC, TRANSPARENT);
Msg.Result := 1;
end;
f you don't want the image to scroll you will have to roll your own scroller, which is not too difficult (It still raining here in England so I'm bored!)
To test, Create the frame put the image on and alighn to client.
Put a scrollbar on the frame set to vertical and align right.
enlarge the frame at design time.
Put controls on anywhere and then shrink it so some are not visible (below the bottom).
On the main form in form show (for testing), or when you create a new frame call Frame.BeforeShow to do the setup.
[LATER] EDIT It's raining & Still Bored So I finished it for ya!
unit ScrollingBaseFrameU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, jpeg, ExtCtrls, StdCtrls, contnrs, Generics.Collections, Grids,
DBGrids;
const
MaxLargeSteps = 5; // maximum large scroll clicks to scroll to max
IgnoreTag = 99; // Controls with this tag value are igored for scrolling
TitleHeight = 23; // Height of title bar as we are dealing with the image mouse co-ordinates
RightMargin = 25; // space after right-most control
BottomMargin = 25; // space after bottom-most control
StrControl = 'ControlName'; // prefix for controls with no name
type
TControlPos = class(Tobject) // Little object to save initial control positions
public
Name: string;
X,
Y: Integer;
end;
TScrollingBaseFrame = class(TFrame)
BackGroundImage: TImage;
HorzScrollBar: TScrollBar;
VertScrollBar: TScrollBar;
pnlTitle: TPanel;
procedure VertScrollBarChange(Sender: TObject);
procedure BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FrameResize(Sender: TObject);
procedure HorzScrollBarChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
ShowHScroller,
ShowVScroller : Boolean; // scroller needed at all?
PosList: TList<TControlPos>; // list of initial positions
procedure BeforeShow; virtual; // override in descendants for specific behaviour
procedure BeforeClose; virtual; // override in descendants for specific behaviour
function IndexOfPos(AName:string): Integer;
end;
implementation
{$R *.dfm}
procedure TScrollingBaseFrame.BeforeClose;
// Clean up
var
p: TControlPos;
begin
for p in PosList do
p.free;
PosList.Free;
end;
procedure TScrollingBaseFrame.BeforeShow;
//Setup scroller and save initial control positions
var
i,XMax,YMax,Idx: Integer;
AControl: TControl;
begin
pnlTitle.Height := TitleHeight;
PosList := TList<TControlpos>.Create;
XMax := 0;
YMax := 0;
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
if Acontrol.Tag <> IgnoreTag then
begin
Idx := PosList.Add(TcontrolPos.Create);
if AControl.Name = '' then // deal with empty names
AControl.Name := StrControl + IntToStr(i);
PosList[Idx].Name := AControl.Name;
PosList[Idx].X := AControl.Left;
PosList[Idx].Y := AControl.Top;
if YMax < AControl.Top + AControl.Height then
YMax := AControl.Top + AControl.Height;
if XMax < AControl.Left + AControl.Width then
XMax := AControl.Left + AControl.Width;
end; // Ignored
end; // is control
end; // count
VertScrollBar.Max := (YMax + BottomMargin) - Height; // bit of a bottom margin :)
VertScrollBar.LargeChange := VertScrollBar.Max div MaxLargeSteps;
ShowVScroller := VertScrollBar.Max > BottomMargin;
VertScrollBar.Visible := ShowVScroller;
HorzScrollBar.Max := (XMax + RightMargin) - Width; // bit of a Right margin :)
HorzScrollBar.LargeChange := HorzScrollBar.Max div MaxLargeSteps;
ShowHScroller := HorzScrollBar.Max > RightMargin;
HorzScrollBar.Visible := ShowHScroller;
end;
procedure TScrollingBaseFrame.FrameResize(Sender: TObject);
begin
BackGroundImage.Width := Width;
BackGroundImage.Height := Height;
end;
procedure TScrollingBaseFrame.HorzScrollBarChange(Sender: TObject);
// Move the controls left and right relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored or the background image
Acontrol.Left := PosList[j].X - HorzScrollBar.Position;
end;
end;
end;
procedure TScrollingBaseFrame.BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
// Show/Hide the scrollbars using mouse position
var
ScrollBarWidth: Integer;
begin
ScrollBarWidth := GetSystemmetrics(SM_CXVSCROLL); // assume the same for horizontal
VertScrollBar.Visible := (X > Width - ScrollBarWidth) and ShowVScroller;
HorzScrollBar.Visible := (Y > (Height - TitleHeight) - ScrollBarWidth) and ShowHScroller;
end;
function TScrollingBaseFrame.IndexOfPos(AName:string): Integer;
// Find a control position in the list by name
var
Idx: Integer;
begin
Result := -1;
Idx := 0;
while (Result < 0) and (Idx < PosList.Count) do
begin
if PosList[idx].Name = AName then
Result := idx;
inc(idx);
end;
end;
procedure TScrollingBaseFrame.VertScrollBarChange(Sender: TObject);
// Move the controls Up and down relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored
Acontrol.Top := PosList[j].Y - VertScrollBar.Position;
end;
end;
end;
end.
and the DFM for completeness:
object ScrollingBaseFrame: TScrollingBaseFrame
Left = 0
Top = 0
Width = 830
Height = 634
DoubleBuffered = True
ParentDoubleBuffered = False
TabOrder = 0
OnResize = FrameResize
object BackGroundImage: TImage
Tag = 99
Left = 0
Top = 23
Width = 813
Height = 594
Align = alClient
Picture.Data = { **Removed as it was so big!**}
Transparent = True
OnMouseMove = BackGroundImageMouseMove
ExplicitTop = 0
ExplicitWidth = 1600
ExplicitHeight = 1200
end
object HorzScrollBar: TScrollBar
Tag = 99
Left = 0
Top = 617
Width = 830
Height = 17
Align = alBottom
PageSize = 0
TabOrder = 0
OnChange = HorzScrollBarChange
ExplicitLeft = 231
ExplicitTop = 293
ExplicitWidth = 121
end
object VertScrollBar: TScrollBar
Tag = 99
Left = 813
Top = 23
Width = 17
Height = 594
Align = alRight
Kind = sbVertical
PageSize = 0
TabOrder = 1
OnChange = VertScrollBarChange
ExplicitTop = 29
end
object pnlTitle: TPanel
Tag = 99
Left = 0
Top = 0
Width = 830
Height = 23
Align = alTop
Caption = 'pnlTitle'
TabOrder = 2
ExplicitLeft = 184
ExplicitTop = 3
ExplicitWidth = 185
end
end
[2ND EDIT] Well, Not wanting my spare time to go to waste, the below should work with Delphi 6 onwards.
unit ScrollingBaseFrameU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, jpeg, ExtCtrls, StdCtrls, contnrs, Grids,
DBGrids;
const
MaxLargeSteps = 5; // maximum large scroll clicks to scroll to max
IgnoreTag = 99; // Controls with this tag value are igored for scrolling
TitleHeight = 23; // Height of title bar as we are dealing with the image mouse co-ordinates
RightMargin = 25; // space after right-most control
BottomMargin = 25; // space after bottom-most control
StrControl = 'ControlName'; // prefix for controls with no name
type
TControlPos = class(Tobject) // Little object to save initial control positions
public
Name: string;
X,
Y: Integer;
end;
TControlPosList = class(TObject)
private
function GetCount: Integer;
function GetItems(Index: Integer): TControlPos;
procedure SetItems(Index: Integer; const Value: TControlPos);
public
TheList: TObjectList;
Constructor Create; virtual;
Destructor Destroy; override;
function Add(APos: TControlPos): Integer;
function IndexOfPos(AName: string): Integer;
property Count: Integer read GetCount;
property Items[Index: Integer]: TControlPos read GetItems write SetItems; default;
end;
TScrollingBaseFrame = class(TFrame)
BackGroundImage: TImage;
HorzScrollBar: TScrollBar;
VertScrollBar: TScrollBar;
pnlTitle: TPanel;
procedure VertScrollBarChange(Sender: TObject);
procedure BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FrameResize(Sender: TObject);
procedure HorzScrollBarChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
ShowHScroller,
ShowVScroller : Boolean; // scroller needed at all?
PosList: TControlPosList; // list of initial positions
procedure BeforeShow; virtual; // override in descendants for specific behaviour
procedure BeforeClose; virtual; // override in descendants for specific behaviour
end;
implementation
{$R *.dfm}
procedure TScrollingBaseFrame.BeforeClose;
// Clean up
begin
PosList.Free;
end;
procedure TScrollingBaseFrame.BeforeShow;
//Setup scroller and save initial control positions
var
i,XMax,YMax,Idx: Integer;
AControl: TControl;
begin
pnlTitle.Height := TitleHeight;
PosList := TControlPosList.Create;
XMax := 0;
YMax := 0;
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
if Acontrol.Tag <> IgnoreTag then
begin
Idx := PosList.Add(TcontrolPos.Create);
if AControl.Name = '' then // deal with empty names
AControl.Name := StrControl + IntToStr(i);
PosList[Idx].Name := AControl.Name;
PosList[Idx].X := AControl.Left;
PosList[Idx].Y := AControl.Top;
if YMax < AControl.Top + AControl.Height then
YMax := AControl.Top + AControl.Height;
if XMax < AControl.Left + AControl.Width then
XMax := AControl.Left + AControl.Width;
end; // Ignored
end; // is control
end; // count
VertScrollBar.Max := (YMax + BottomMargin) - Height; // bit of a bottom margin :)
VertScrollBar.LargeChange := VertScrollBar.Max div MaxLargeSteps;
ShowVScroller := VertScrollBar.Max > BottomMargin;
VertScrollBar.Visible := ShowVScroller;
HorzScrollBar.Max := (XMax + RightMargin) - Width; // bit of a Right margin :)
HorzScrollBar.LargeChange := HorzScrollBar.Max div MaxLargeSteps;
ShowHScroller := HorzScrollBar.Max > RightMargin;
HorzScrollBar.Visible := ShowHScroller;
end;
procedure TScrollingBaseFrame.FrameResize(Sender: TObject);
begin
BackGroundImage.Width := Width;
BackGroundImage.Height := Height;
end;
procedure TScrollingBaseFrame.HorzScrollBarChange(Sender: TObject);
// Move the controls left and right relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := PosList.IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored
Acontrol.Left := PosList[j].X - HorzScrollBar.Position;
end;
end;
end;
procedure TScrollingBaseFrame.BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
// Show/Hide the scrollbars using mouse position
var
ScrollBarWidth: Integer;
begin
ScrollBarWidth := GetSystemmetrics(SM_CXVSCROLL); // assume the same for horizontal
VertScrollBar.Visible := (X > Width - ScrollBarWidth) and ShowVScroller;
HorzScrollBar.Visible := (Y > (Height - TitleHeight) - ScrollBarWidth) and ShowHScroller;
end;
procedure TScrollingBaseFrame.VertScrollBarChange(Sender: TObject);
// Move the controls Up and down relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := PosList.IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored
Acontrol.Top := PosList[j].Y - VertScrollBar.Position;
end;
end;
end;
{ TcontrolPosList }
function TControlPosList.Add(APos: TControlPos): Integer;
begin
Result := TheList.Add(APos);
end;
constructor TControlPosList.Create;
begin
TheList := TObjectList.Create;
TheList.OwnsObjects := True;
end;
destructor TControlPosList.Destroy;
begin
TheList.Free;
inherited;
end;
function TControlPosList.GetCount: Integer;
begin
Result := TheList.Count;
end;
function TControlPosList.GetItems(Index: Integer): TControlPos;
begin
Result := TControlPos(TheList[Index]);
end;
function TControlPosList.IndexOfPos(AName: string): Integer;
// Find a control position in the list by name
var
Idx: Integer;
begin
Result := -1;
Idx := 0;
while (Result < 0) and (Idx < TheList.Count) do
begin
if Items[idx].Name = AName then
Result := idx;
inc(idx);
end;
end;
procedure TControlPosList.SetItems(Index: Integer; const Value: TControlPos);
begin
TheList[Index] := Value;
end;
end.
Reverse the order on the Base frame :)
Put the ScrollBox on, then put the image on the Scrollbox (align Client) and make it transparent. Then Place controls all over it and it allows scrolling...
I'm sure you will have tried this, so what gives you a problem...
What is the best way to make a delphi application (delphi 2007 for win32 here) go completely full screen, removing the application border and covering windows task bar ?
I am looking for something similar to what IE does when you hit F11.
I wish this to be a run time option for the user not a design time decision by my good self.
As Mentioned in the accepted answer
BorderStyle := bsNone;
was part of the way to do it. Strangely I kept getting a E2010 Incompatible types: 'TFormBorderStyle' and 'TBackGroundSymbol' error when using that line (another type had bsNone defined).
To overcome this I had to use :
BorderStyle := Forms.bsNone;
Well, this has always worked for me. Seems a bit simpler...
procedure TForm52.Button1Click(Sender: TObject);
begin
BorderStyle := bsNone;
WindowState := wsMaximized;
end;
A Google search turned up the following, additional methods:
(though I think I'd try Roddy's method first)
Manually fill the screen (from: About Delphi)
procedure TSomeForm.FormShow(Sender: TObject) ;
var
r : TRect;
begin
Borderstyle := bsNone;
SystemParametersInfo
(SPI_GETWORKAREA, 0, #r,0) ;
SetBounds
(r.Left, r.Top, r.Right-r.Left, r.Bottom-r.Top) ;
end;
Variation on a theme by Roddy
FormStyle := fsStayOnTop;
BorderStyle := bsNone;
Left := 0;
Top := 0;
Width := Screen.Width;
Height := Screen.Height;
The WinAPI way (by Peter Below from TeamB)
private // in form declaration
Procedure WMGetMinMaxInfo(Var msg: TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
Procedure TForm1.WMGetMinMaxInfo(Var msg: TWMGetMinMaxInfo);
Begin
inherited;
With msg.MinMaxInfo^.ptMaxTrackSize Do Begin
X := GetDeviceCaps( Canvas.handle, HORZRES ) + (Width - ClientWidth);
Y := GetDeviceCaps( Canvas.handle, VERTRES ) + (Height - ClientHeight
);
End;
End;
procedure TForm1.Button2Click(Sender: TObject);
Const
Rect: TRect = (Left:0; Top:0; Right:0; Bottom:0);
FullScreen: Boolean = False;
begin
FullScreen := not FullScreen;
If FullScreen Then Begin
Rect := BoundsRect;
SetBounds(
Left - ClientOrigin.X,
Top - ClientOrigin.Y,
GetDeviceCaps( Canvas.handle, HORZRES ) + (Width - ClientWidth),
GetDeviceCaps( Canvas.handle, VERTRES ) + (Height - ClientHeight ));
// Label2.caption := IntToStr(GetDeviceCaps( Canvas.handle, VERTRES ));
End
Else
BoundsRect := Rect;
end;
Maximize the form and hide the title bar. The maximize line is done from memory, but I'm pretty sure WindowState is the property you want.
There's also this article, but that seems too complicated to me.
procedure TForm1.FormCreate(Sender: TObject) ;
begin
//maximize the window
WindowState := wsMaximized;
//hide the title bar
SetWindowLong(Handle,GWL_STYLE,GetWindowLong(Handle,GWL_STYLE) and not WS_CAPTION);
ClientHeight := Height;
end;
Edit: Here's a complete example, with "full screen" and "restore" options. I've broken out the different parts into little procedures for maximum clarity, so this could be greatly compressed into just a few lines.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
btnGoFullScreen: TButton;
btnNotFullScreen: TButton;
btnShowTitleBar: TButton;
btnHideTitleBar: TButton;
btnQuit: TButton;
procedure btnGoFullScreenClick(Sender: TObject);
procedure btnShowTitleBarClick(Sender: TObject);
procedure btnHideTitleBarClick(Sender: TObject);
procedure btnNotFullScreenClick(Sender: TObject);
procedure btnQuitClick(Sender: TObject);
private
SavedLeft : integer;
SavedTop : integer;
SavedWidth : integer;
SavedHeight : integer;
SavedWindowState : TWindowState;
procedure FullScreen;
procedure NotFullScreen;
procedure SavePosition;
procedure HideTitleBar;
procedure ShowTitleBar;
procedure RestorePosition;
procedure MaximizeWindow;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnQuitClick(Sender: TObject);
begin
Application.Terminate;
end;
procedure TForm1.btnGoFullScreenClick(Sender: TObject);
begin
FullScreen;
end;
procedure TForm1.btnNotFullScreenClick(Sender: TObject);
begin
NotFullScreen;
end;
procedure TForm1.btnShowTitleBarClick(Sender: TObject);
begin
ShowTitleBar;
end;
procedure TForm1.btnHideTitleBarClick(Sender: TObject);
begin
HideTitleBar;
end;
procedure TForm1.FullScreen;
begin
SavePosition;
HideTitleBar;
MaximizeWindow;
end;
procedure TForm1.HideTitleBar;
begin
SetWindowLong(Handle,GWL_STYLE,GetWindowLong(Handle,GWL_STYLE) and not WS_CAPTION);
ClientHeight := Height;
end;
procedure TForm1.MaximizeWindow;
begin
WindowState := wsMaximized;
end;
procedure TForm1.NotFullScreen;
begin
RestorePosition;
ShowTitleBar;
end;
procedure TForm1.RestorePosition;
begin
//this proc uses what we saved in "SavePosition"
WindowState := SavedWindowState;
Top := SavedTop;
Left := SavedLeft;
Width := SavedWidth;
Height := SavedHeight;
end;
procedure TForm1.SavePosition;
begin
SavedLeft := Left;
SavedHeight := Height;
SavedTop := Top;
SavedWidth := Width;
SavedWindowState := WindowState;
end;
procedure TForm1.ShowTitleBar;
begin
SetWindowLong(Handle,gwl_Style,GetWindowLong(Handle,gwl_Style) or ws_Caption or ws_border);
Height := Height + GetSystemMetrics(SM_CYCAPTION);
Refresh;
end;
end.
Put to the form onShow event such code:
WindowState:=wsMaximized;
And to the OnCanResize this:
if (newwidth<width) and (newheight<height) then
Resize:=false;
How to constrain a sub-form within the Mainform like it was an MDI app., but without the headaches! (Note: The replies on this page helped me get this working, so that's why I posted my solution here)
private
{ Private declarations }
StickyAt: Word;
procedure WMWINDOWPOSCHANGING(Var Msg: TWMWINDOWPOSCHANGING); Message M_WINDOWPOSCHANGING;
Procedure WMGetMinMaxInfo(Var msg: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
later...
procedure TForm2.WMWINDOWPOSCHANGING(var Msg: TWMWINDOWPOSCHANGING);
var
A, B: Integer;
iFrameSize: Integer;
iCaptionHeight: Integer;
iMenuHeight: Integer;
begin
iFrameSize := GetSystemMetrics(SM_CYFIXEDFRAME);
iCaptionHeight := GetSystemMetrics(SM_CYCAPTION);
iMenuHeight := GetSystemMetrics(SM_CYMENU);
// inside the Mainform client area
A := Application.MainForm.Left + iFrameSize;
B := Application.MainForm.Top + iFrameSize + iCaptionHeight + iMenuHeight;
with Msg.WindowPos^ do
begin
if x <= A + StickyAt then
x := A;
if x + cx >= A + Application.MainForm.ClientWidth - StickyAt then
x := (A + Application.MainForm.ClientWidth) - cx + 1;
if y <= B + StickyAt then
y := B;
if y + cy >= B + Application.MainForm.ClientHeight - StickyAt then
y := (B + Application.MainForm.ClientHeight) - cy + 1;
end;
end;
and yet more...
Procedure TForm2.WMGetMinMaxInfo(Var msg: TWMGetMinMaxInfo);
var
iFrameSize: Integer;
iCaptionHeight: Integer;
iMenuHeight: Integer;
Begin
inherited;
iFrameSize := GetSystemMetrics(SM_CYFIXEDFRAME);
iCaptionHeight := GetSystemMetrics(SM_CYCAPTION);
iMenuHeight := GetSystemMetrics(SM_CYMENU);
With msg.MinMaxInfo^.ptMaxPosition Do
begin
// position of top when maximised
X := Application.MainForm.Left + iFrameSize + 1;
Y := Application.MainForm.Top + iFrameSize + iCaptionHeight + iMenuHeight + 1;
end;
With msg.MinMaxInfo^.ptMaxSize Do
Begin
// width and height when maximized
X := Application.MainForm.ClientWidth;
Y := Application.MainForm.ClientHeight;
End;
With msg.MinMaxInfo^.ptMaxTrackSize Do
Begin
// maximum size when maximised
X := Application.MainForm.ClientWidth;
Y := Application.MainForm.ClientHeight;
End;
// to do: minimum size (maybe)
End;
In my case, the only working solution is:
procedure TFormHelper.FullScreenMode;
begin
BorderStyle := bsNone;
ShowWindowAsync(Handle, SW_MAXIMIZE);
end;
You need to make sure Form position is poDefaultPosOnly.
Form1.Position := poDefaultPosOnly;
Form1.FormStyle := fsStayOnTop;
Form1.BorderStyle := bsNone;
Form1.Left := 0;
Form1.Top := 0;
Form1.Width := Screen.Width;
Form1.Height := Screen.Height;
Tested and works on Win7 x64.
Try:
Align = alClient
FormStyle = fsStayOnTop
This always align to the primary monitor;
Hm. Looking at the responses I seem to remember dealing with this about 8 years ago when I coded a game. To make debugging easier, I used the device-context of a normal, Delphi form as the source for a fullscreen display.
The point being, that DirectX is capable of running any device context fullscreen - including the one allocated by your form.
So to give an app "true" fullscreen capabilities, track down a DirectX library for Delphi and it will probably contain what you need out of the box.