How to handle subcomponents and properties created in runtime vs designtime? - delphi

I can' t realize this thing. I have a component in DELPHI that includes 2 other components: a Firemonkey Layout and inside of that an dynamic array of TLayout which includes a TRectangle.
This is achieved through the property BarNumber.
I have lots of problems about Design Time vs. Runtime behaviour, this is due to the DFM (FMX in Firemonkey) that stores the subcomponents as part of the Object.
Now. This is the code of the On Create part.
constructor TFluffyTable.Create(Owner: TComponent);
var
i: integer;
begin
inherited Create(Owner);
Width:=300;
Height:= 160;
BarNumber:=100;
SetLength(Column, FBarNumber);
for i := 0 to (FBarNumber-1) do
begin
Column[i]:= TColumn.Create(Self);
Column[i].Name:= 'Column_' + IntToStr(i);
Column[i].Parent:= Self;
Column[i].Height:=Height;
Column[i].Width:=Width/FBarNumber;
Column[i].Align:= TAlignLayout.alMostLeft;
end;
end;
If I register the component and I use it in design time I get the correct number of bars displayed. But if I run the program with the component, I get twice the number of bars, since the EXE loads the values. I managed to solve this with
if not (csDesigning in ComponentState) then
just before the for loop.
But I can't see, obviously, the BARS in design mode. Well I can stand that if this is the only solution.
That's not over..!
For a strange reason, The only one place I can set my values for Width, Height and BarNumber is that part of code. If I set them in the object inspector they won't be considered and reset to default when I run the program.
(BarNumber is a variable which reads and writes on FBarNumber)
In short: I don't know how to handle and manage my component to make BarNumber and other properties to be set in design time, and to see the correct number of bars in Runtime.
Thank you so much.

I had the similar problem. I used stored property to avoid this problem.
Example:
constructor TMachine.Create(AOwner: TComponent);
begin
inherited;
self.Width := 50;
self.Height := 90;
// create machine rectangle and set default properties
FMachine := TRectangle.Create(self);
FMachine.Parent := self;
FMachine.Height := 50;
FMachine.Align := TAlignLayout.alBottom;
FMachine.Fill.Color := TAlphaColorRec.red;
FMachine.Stroke.Color := TAlphaColorRec.Black;
FMachine.Stroke.Thickness := 3;
FMachine.Stored := false;
end;

The problem is that the component you create at design time will be stored in the fmx files. When you run the application you have twice controls, to resolve the problem you need to set the stored property to false to the sub objects of your component like this:
Column[i].Stored := False;

You have to make sure that you are starting with 0 columns at runtime.
Just add something like:
for [i] = pred(length(column)) downto 0 do
begin
column[i].free
end;
before you start making the columns.

Related

How to make main form appear in 2nd monitor

I use Delphi 10 and I have two monitors when I create default vcl application and run the application the main form always appear in the first monitor is there a way to make it appear in the second monitor by default ?
This may be option in the IDE or property or code
Thanks
TForm has a DefaultMonitor property that is set to dmActiveForm by default. When no Form is active, the primary monitor is used. However, there is no way to set the DefaultMonitor to the second monitor specifically.
TForm also has a Monitor property, but for whatever reason it is read-only.
So, to display a TForm on a specific monitor, you can locate the desired monitor in the global TScreen.Monitors[] list, and then either:
manually set the Form's Left/Top properties to an X/Y coordinate that is within the bounds of the monitor's BoundsRect or WorkareaRect property.
pass the monitor to the Form's public MakeFullyVisible() method.
I've two monitor of 1600px width. My primary monitor is on the right, so, for place a form on the left I've to pass a negative value for his Left property.
procedure TForm1.Button1Click(Sender: TObject);
begin
// (3200 / 2 = + 1600) * -1 = -1600
Left := (Screen.DesktopWidth div 2) * -1;
end;
The result is -1600 that means the most to the left of my secondary monitor.
You also can get the "most Left" position of each monitor by calling Screen.Monitors[i].Left, something like this code:
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
lMens: string;
begin
lMens := '';
for I := 0 to Screen.MonitorCount - 1 do
begin
lMens := lMens + ' | '
+ Format('%d - Left = %d', [i, Screen.Monitors[i].Left])
end;
ShowMessage(lMens);
end;
I hope that help's you.

Setting COM (RDPEncomAPI) property in Delphi

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

For Guis using Delphi ObjectPascal, does checking .Visible before (potentially) changing it serve any useful purpose?

I inherited a GUI implemented in Delphi RadStudio2007 targeted for Windows XP embedded. I am seeing a lot of code that looks like this:
procedure TStatusForm.Status_refresh;
begin
if DataModel.CommStatus = COMM_OK then begin
if CommStatusOKImage.Visible<>True then CommStatusOKImage.Visible:=True;
if CommStatusErrorImage.Visible<>False then CommStatusErrorImage.Visible:=False;
end else begin
if CommStatusOKImage.Visible<>False then CommStatusOKImage.Visible:=False;
if CommStatusErrorImage.Visible<>True then CommStatusErrorImage.Visible:=True;
end;
end
I did find this code sample on the Embarcadero site:
procedure TForm1.ShowPaletteButtonClick(Sender: TObject);
begin
if Form2.Visible = False then Form2.Visible := True;
Form2.BringToFront;
end;
That shows a check of Visible before changing it, but there is no explanation of what is served by checking it first.
I am trying to understand why the original developer felt that it was necessary to only set the Visible flag if it was to be changed, and did not choose to code it this way instead:
procedure TStatusForm.Status_refresh;
begin
CommStatusOKImage.Visible := DataModel.CommStatus = COMM_OK;
CommStatusErrorImage.Visible := not CommStatusOKImage.Visible;
end
Are there performance issues or cosmetic issues (such as screen flicker) that I need to be aware of?
As Remy Lebeau said, Visible setter already checks if new value differs.
For example, in XE, for TImage, assignment to Visible actually invokes inherited code:
procedure TControl.SetVisible(Value: Boolean);
begin
if FVisible <> Value then
begin
VisibleChanging;
FVisible := Value;
Perform(CM_VISIBLECHANGED, Ord(Value), 0);
RequestAlign;
end;
end;
So, there is no benefits of checking it. However, might there in your code are used some third-party or rare components - for them all may be different, though, I doubt it.
You can investigate it yourself, using "Find Declaration" context menu item in editor (or simply Ctrl+click), and/or stepping into VCL code with "Use debug dcus" compiler option turned on.
Like many properties, the Visible property setter checks if the new value is different than the current value before doing anything. There is no need to check the current property value manually.
Well, I doubt it will, but maybe there could be issues specifically for forms in recent Delphi versions. The Visible property is redeclared in TCustomForm to assure the execution of the OnCreate event prior to setting the visibility. It is technically not overriden since TControl.SetVisible is not virtual, but it has the same effect:
procedure TCustomForm.SetVisible(Value: Boolean);
begin
if fsCreating in FFormState then
if Value then
Include(FFormState, fsVisible) else
Exclude(FFormState, fsVisible)
else
begin
if Value and (Visible <> Value) then SetWindowToMonitor;
inherited Visible := Value;
end;
end;
This implementation in Delphi 7 still does not require checking the visibility manually, but check this yourself for more recent versions.
Also, I agree with Larry Lustig's comment because the code you provided does not testify of accepted syntax. It could have better been written as:
procedure TForm1.ShowPaletteButtonClick(Sender: TObject);
begin
if not Form2.Visible then Form2.Visible := True;
Form2.BringToFront;
end;

Delphi getting value of form components properties

I am implementing a Boilerplate feature - allow users to Change descriptions of some components - like TLabels - at run time.
e.g.
TFooClass = Class ( TBaseClass)
Label : Tlabel;
...
End;
Var FooClass : TFooClass;
...
At design time, the value Label's caption property is say - 'First Name', when the
application is run, there is a feature that allows the user to change the caption
value to say 'Other Name'. Once this is changed, the caption for the label for
the class instance of FooClass is updated immediately.
The problem now is if the user for whatever reason wants to revert back to the design
time value of say 'First Name' , it seems impossible.
I can use the RTTIContext methods and all that but I at the end of the day, it seems
to require the instance of the class for me to change the value and since this
has already being changed - I seem to to have hit a brick wall getting around it.
My question is this - is there a way using the old RTTI methods or the new RTTIContext
stuff to the property of a class' member without instantiating the class - i.e. getting
the property from the ClassType definition.
This is code snippet of my attempt at doing that :
c : TRttiContext;
z : TRttiInstanceType;
w : TRttiProperty;
Aform : Tform;
....
Begin
.....
Aform := Tform(FooClass);
for vCount := 0 to AForm.ComponentCount-1 do begin
vDummyComponent := AForm.Components[vCount];
if IsPublishedProp(vDummyComponent,'Caption') then begin
c := TRttiContext.Create;
try
z := (c.GetType(vDummyComponent.ClassInfo) as TRttiInstanceType);
w := z.GetProperty('Caption');
if w <> nil then
Values[vOffset, 1] := w.GetValue(vDummyComponent.ClassType).AsString
.....
.....
....
....
I am getting all sorts of errors and any help will be greatly appreciated.
The RTTI System does not provide what you are after. Type information is currently only determined at compile time. Initial form values are set at Runtime using the DFM resource. You can change values in a DFM Resource in a compiled application because it's evaluated at runtime.
Parse and use the DFM Resource where it is stored, or make a copy of the original value at runtime. Possibly at the point of initial change to reduce your memory footprint.
Masons Suggestion of using TDictionary<string, TValue> is what I would use. I would be careful of storing this information in a database as keeping it in sync could become a real maintenance nightmare.
Sounds like what you're trying to do is get the value of a certain property as defined in the DFM. This can't be done using RTTI, since RTTI is based on inspecting the structure of an object as specified by its class definition. The DFM isn't part of the class definition; it's a property list that gets applied to the objects after they've been created from the class definitions.
If you want to get the values of the properties of a form's controls, you'll probably have to cache them somewhere. Try putting something in the form's OnCreate that runs through all the controls and uses RTTI to populate a TDictionary<string, TValue> with the values of all the properties. Then you can look them up later on when you need them.
If what you are trying to achieve it to restore the value that was set at design-time (i.e. that one that is saved in the DFM), I'd use InitInheritedComponent as a starting point.
It's possible to get the content of the DFM at runtime. Could be a pain to parse though.
Check also InternalReadComponentRes.
Both routine can be found in the classes unit.
Well - I solved the problem. The trick is basically instantiating another instance of the form like so :
procedure ShowBoilerPlate(AForm : TForm; ASaveAllowed : Boolean);
var
vCount : Integer;
vDesignTimeForm : TForm;
vDesignTimeComp : TComponent;
vDesignTimeValue : String;
vCurrentValue : String;
begin
....
....
vDesignTimeForm := TFormClass(FindClass(AForm.ClassName)).Create(AForm.Owner);
try
// Now I have two instances of the form - I also need to have at least one
// overloaded constructor defined for the base class of the forms that will allow for
// boilerplating. If you call the default Constructor - no boilerplating
// is done. If you call the overloaded constructor, then, boilerplating is done.
// Bottom line, I can have two instances AForm - with boilerplated values and
// vDesignForm without boilerplated values.
for vCount := 0 to AForm.ComponentCount-1 do begin
vDummyComponent := AForm.Components[vCount];
if Supports (vDummyComponent,IdoGUIMetaData,iGetGUICaption) then begin
RecordCount := RecordCount + 1;
Values[vOffset, 0] := vDummyComponent.Name;
if IsPublishedProp(vDummyComponent,'Caption') then begin
vDesignTimeComp := vDesignTimeForm.FindComponent(vDummyComponent.Name);
if vDesignTimeComp <> nil then begin
// get Design time values here
vDesignTimeValue := GetPropValue(vDesignTimeComp,'Caption');
end;
// get current boilerplated value here
vCurrentValue := GetPropValue(vDummyComponent,'Caption');
end;
vOffset := RecordCount;;
end;
end;
finally
FreeAndNil(vDesignTimeForm);
end;
end;
Anyway - thank you all for all your advice.

Create an exact copy of TPanel on Delphi5

I have a TPanel pnlMain, where several dynamic TPanels are created (and pnlMain is their Parent) according to user actions, data validations, etc. Every panel contains one colored grid full of strings. Apart from panels, there are some open source arrows components and a picture. Whole bunch of stuff.
Now I want user to be able to print this panel (I asked how to do it on this question), but before printing, user must be presented with a new form, containing copy of pnlMain. On this form user has to do some changes, add few components and then print his customized copy of pnlMain. After printing user will close this form and return to original form with original pnlMain. And – as you can guess – original pnlMain must remain intact.
So is there any clever way to copy whole TPanel and it’s contents? I know I can make it manually iterating through pnlMain.Controls list.
Code based as iterating on child controls, but not bad in anyway ;-)
procedure TForm1.btn1Click(Sender: TObject);
function CloneComponent(AAncestor: TComponent): TComponent;
var
XMemoryStream: TMemoryStream;
XTempName: string;
begin
Result:=nil;
if not Assigned(AAncestor) then
exit;
XMemoryStream:=TMemoryStream.Create;
try
XTempName:=AAncestor.Name;
AAncestor.Name:='clone_' + XTempName;
XMemoryStream.WriteComponent(AAncestor);
AAncestor.Name:=XTempName;
XMemoryStream.Position:=0;
Result:=TComponentClass(AAncestor.ClassType).Create(AAncestor.Owner);
if AAncestor is TControl then TControl(Result).Parent:=TControl(AAncestor).Parent;
XMemoryStream.ReadComponent(Result);
finally
XMemoryStream.Free;
end;
end;
var
aPanel: TPanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control (here Panel1) itself first
TComponent(aPanel) := CloneComponent(pnl1);
with aPanel do
begin
Left := 400;
Top := 80;
end;
//now handle the childcontrols
for i:= 0 to pnl1.ControlCount-1 do begin
Ctrl := TComponent(pnl1.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := aPanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
code from Delphi3000 article
Too much code... ObjectBinaryToText and ObjectTextToBinary do the job nicely using streaming.
Delphi 7 have a code example, don't know 2009 (or 2006, never bothered to look) still have it.
See D5 help file for those functions (don't have d5 available here).
I'd do it by using RTTI to copy all the properties. You'd still have to iterate over all the controls, but when you need to set up the property values, RTTI can help automate the process. You can get an example towards the bottom of this article, where you'll find a link to some helper code, including a CopyObject routine.

Resources