Can FireMonkey frames be created dynamically? - delphi

FireMonkey has the option of using frames (not TFrame, which is a specific kind of visual component), which basically are collections of visual components which can be reused.
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Working_with_Frames
However, there is nothing in the documentation about creating instances of frames dynamically, instead of placing them at design time on a TForm. Is there a way to create instances of a frame dynamically? When I try to do this, I get errors when attempting to access the frame in question's properties.

TFrame is a simple TControl, you can create and use it as usual Tcontrol component.
If you would change TFrame to TControl - nothing change because it is same.
var
MyFrame: TFrame;
begin
MyFrame := TFrame.Create(Self);
MyFrame.Parent := Self;
Self is TForm or any other TControl

Related

How to pass a component as a parameter of a procedure in Delphi?

I want to be able to use one procedure to center all the components on a form. This is the kind of thing I'm going for:
procedure TForm4.centerComponent(x: Tobject);
begin
x.Left := (Form4.ClientWidth - x.Width) div 2;
end;
I would only be passing built in components (memo, label, edit etc...)
I get the feeling this is either not possible or if it is its probably not best practice
This is easy, but you must be careful about terminology:
A TObject is any Delphi object. It need not be a control. It doesn't even need to be something you can drop on a form.
A TComponent is an object you can drop on a form. It might be a visual control (like a button, a label, or an edit box), or it might be a non-visual component (like a TActionList).
A TControl is a visual control, like a button, a label, an edit box, or an animated analogue clock.
The above classes are ordered by inheritance.
So, you want a procedure that acts on TControls in general:
procedure Centre(AControl: TControl);
var
Parent: TWinControl;
begin
Parent := AControl.Parent;
if Parent = nil then
Exit;
AControl.SetBounds(
(Parent.ClientWidth - AControl.Width) div 2,
(Parent.ClientHeight - AControl.Height) div 2,
AControl.Width,
AControl.Height
);
end;
Every TControl has Top, Left, Width, and Height properties, as well as the SetBounds method, which we use above.
Notice that I centre the control in its parent window. (A control's Top and Left values are always relative to its parent.)
Now, there are two kinds of controls in Delphi:
Controls that are actual Win32 windows (with HWNDs).
Controls that are not actual Win32 windows.
Only the former kind of control can have child controls. These controls derive from TWinControl. That's the reason I declare Parent as a TWinControl. This is also the type of the TControl.Parent property.
Some notes about your code
x.Left := (Form4.ClientWidth - x.Width) div 2;
Here there are two issues (except for x: TObject not having any Width or Left properties):
Form4 is one particular instance of the form class. It is much better to write Self.ClientWidth or simply ClientWidth, so you refer to the current instance of the form class.
But even this is not good enough, because this only works if the form is the parent of x. x might well have a different parent. For instance, x might have a TPanel as its parent (the TPanel's parent being the form).

Set DevExpress Grid Tableview Editable property and determine its parent via RTTI

I have a project with multiple TcxGrids on. The class hierarchy structure of the grids in my project are as follows:
TForm->TPanel->TcxPageControl->TcxTabSheet(subclassed)->TcxGrid->TcxGridLevel->TcxGridDBBandedTableView
In my subclassed TcxTabSheet I have added a new property "ReadOnly" that when set loops over the tabsheets controls and sets them to enabled/disabled respectively.
When a TcxGrid controls enabled property is set to true, the user can no longer interact with the grid at all (including navigating).
It seems I need to set the OptionsData.Editing property on the TcxGridDBBandedTableView to achieve a readonly but still navigable grid control.
Simple enough until you factor in that I want to do this in a loose coupled manner which I think leaves me the option of RTTI.
I have written the following code that loops over the forms controls (looping over the tabs controls or components doesn't give me access to the TcxGridDBBandedTableView). Once the control is found I can set its editing property via RTTI. I just don't seem to able to determine if that TcxGridDBBandedTableView belongs to the TabSheet it sits on.
var
compIdx: Integer;
begin
for compIdx := 0 to Pred(ComponentCount) do
if (Components[compIdx].ClassNameIs('TcxGridDBBandedTableView')) then
SetOrdProp(GetObjectProp(Components[compIdx], 'OptionsData'), 'Editing', Ord(not FReadOnly));
end;
TL;DR
How can I determine what pagectrl tab a cxgrid is on and set its TableView.OptionsData.Editable property without adding any devexpress units to the uses clause of the unit.
You are iterating over the components owned by the form. I think that's the wrong approach. You should be looking at the parent/child relationship rather than ownership. Not least because it's perfectly possible for a form to contain controls that it does not own. So, your approach can fail to find controls, particularly dynamically created controls.
So, if you have a tabsheet (or indeed any windowed control), you can iterate over its children like this:
for i := 0 to TabSheet.ControlCount-1 do
DoSomething(TabSheet.Controls[i]);
If your target grid control is a direct descendent of the tabsheet then this will be enough. If it is more than one level deep in the hierarchy then you will need a recursive solution. I will leave that recursive solution as an exercise for you.
Suppose that you have a control and want to find the tabsheet that it sits inside, then you need to walk up the parent chain. Like this:
function GetParentOfClass(Control: TControl; AClass: TWinControlClass): TWinControl;
var
Control: TWinControl;
begin
while Assigned(Control) and not (Control is AClass) do
Control := Control.Parent;
Result := TWinControl(Control);
end;

Differences in Form Initialization between Delphi and Lazarus?

MainForm creates some secondary Frame objects at runtime to display various option panels.
Here's a typical constructor for one of those frame classes (they each extend TFrame):
constructor Tframe2.Create(AOwner: TComponent);
begin
inherited;
edTime.Text := '12:00pm'; //edTime is a TEdit control. this line is where it throws the exception
//etc.
end;
This code worked fine in Delphi (whether or not it was the right way to do things), but the same code in Lazarus keeps throwing an EInvalidOperation exception, because the control (TEdit) has no parent "window" assigned yet (rsControlHasNoParentWindow), which actually kind of makes sense when I examine the code because the parent doesn't appear to be getting assigned until after the constructor is called.
Here is the code in MainForm the initializes the secondary frame:
if Assigned(frame) then FreeAndNil(frame);
case Node.AbsoluteIndex of
optInterval: frame := Tframe2.Create(Self); //here's where the constructor gets called.
//etc
end;
frame := TframeOther.Create(Self);
if Assigned(frame) then
begin
frame.Parent := panOptions; //here's where Tframe2's parent gets set
frame.Align := alClient;
end;
So can anyone explain whether there's any important differences between Delphi and Lazarus as far as form initialization sequence?
And what the most standard way to resolve this type of initialization order problem would be? Comparing to other languages I'm more familiar with, there might be different strategies to resolve such errors. I could add another parameter to the constructor, or if there's a method that gets called post constructor pre-drawing it on the screen that I could override I could relocate that code, or just make a helper method and call it after setParent gets called. Any particular best practice here?
Edit]: It appears this may be specific to TEdit in some way. It looks like the lines initializing the state for checkboxes are not having the same issue. Could this just be a bug in Lazarus?
After further experimentation, I have been able to solve most of the immediate problem of it crashing by adding a line to set the parent of the TEdit to be the Frame (versus setting the parent of the Frame). Like so:
edTime.Parent := Self;
edTime.Text := '12:00';
But I'd still love to understand better why this is "sometimes" needed.
edit: while this fixes being able to set text on a TEdit this doesn't fix the autosizing code I have that iterates through the components and resizes any that happen to be checkboxes. Apparently the form not having it's parent set is still "sort of" a problem.
edit2: Adding a second parameter to the constructor and setting the parent for the entire form in the constructor seemed to eliminate the need to set the Parent for the TEdit's entirely.

Centering a Tframe within a TTabSheet;

I have a delphi application that used a PageControl with a number of TabSheets. I also create new TabSheets at runtime and populate them with instances of predefined frames. These frames work well, except for the cosmetic problem of not centering on the TabSheet. I have tried to use Frame.Align := alClient, but that didn't do it. The relevant code follows:
CreateNewPage(3);
NewLimitedChoiceFrame := TLimitedChoiceFrame.Create(NewInputPage);
NewLimitedChoiceFrame.Parent := NewInputPage;
CreateNewPage creates a new instance of a TabSheet and makes the PageControl it's owner and parent. The result is assigned to the global variable NewInputPage.
To centre a control in its parent do this:
procedure CentreControl(Control: TControl);
begin
Control.Left := (Control.Parent.ClientWidth-Control.Width) div 2;
Control.Top := (Control.Parent.ClientHeight-Control.Height) div 2;
end;
Call this function, passing the frame. Obviously you need to wait until you've assigned the parent before doing so.
If the page control can be re-sized at runtime, add a call to this function from the tabsheet's OnResize event. Or, as NGLN points out simply set the control's Anchors to [] and the VCL framework will take care off centring the control when its parent is resized.

Adding Custom control to another custom control

What i am trying to accomplish is to create new touchkeyboard.
First i created buttons which i derive from speed buttons.
That done, now i need to create multiple buttons and layout them somewhere. This is were i get stuck.
I created a new component which i derive from TGraphicControl (this should be my new touchkeyboard), but i don't know how to add components to canvas. I actually don't know whether i'm supposed to add them to canvas or to some other component (eg. panel)!?!
Is my approach OK?
Thanks in advance.
If you're creating a custom visual control, you need to create the buttons and position them manually. For example:
TOnScreenKeyboard = class(TWinControl)
public
constructor Create(AOwner: TComponent);
end;
[...]
constructor TOnScreenKeyboard.Create(AOwner : TComponent)
var
TempButton : TSpeedButton;
begin
inherited;
TempButton := TSpeedButton.Create(self);
TempButton.Parent := self;
TempButton.Top := 10;
TempButton.Left := 15;
TempButton.Caption := 'A';
end;
You can put the button creation into a loop and position each one according to where it should be.
(I wrote this off the top of my head, and I don't write a lot of Pascal anymore, so there may be some minor mistakes! But it should get you started.)
Because of your wording and confusion between Panel, Canvas and custom controls in general, I assume you're a Delphi beginner. You need to learn about frames: embarcadero docwiki link on frames
Frames allow you to create re-usable portions of GUI. You use the IDE to "draw" the frame, you can then place that composite control (the frame) onto forms or other frames. It's a very powerful feature and it's conceptually very close to what other languages call "custom controls" (very close to what asp.net or WPF consider a custom control to be).
In the Delphi world, when you say "custom control", people would normally expect you to want to create an reusable control that's placed in a package and it's installed in the IDE. It's an fairly advanced subject. If that's what you want then I misunderstood the question, sorry.

Resources