Subclass TSwitch in Firemonkey - delphi

I have done a very simply subclass of the TSwitch that will not respond to mouse clicks or even allow setting IsChecked at runtime. I have not created this as a component so its only runtime constructed. It works if I create a TSwitch at runtime but will not work if its my subclassed switch.
TLayoutSwitch = class(TCustomSwitch, ILayoutBaseControl)
The issue appears to be in SendMessage called by TSwitchModel.SetValue. In TMessageSender.SendMessage. I cannot figure out how TSwitchModel is constructed so that the Receiver object is set.
RAD Studio 10 Seattle
TLayoutSwitch = class(TCustomSwitch, ILayoutBaseControl)
private
FGroupID: integer;
procedure SetGroupID(const Value: integer);
function GetIBHeight: Single;
function GetIBWidth: Single;
procedure SetIBHeight(const Value: Single);
procedure SetIBWidth(const Value: Single);
procedure DoSwitchEvent(Sender: TObject);
public
LayoutControlType: TLayoutControlType;
property LFIBGroup_ID: integer read FGroupID write SetGroupID;
property LFIBWidth: Single read GetIBWidth write SetIBWidth;
property LFIBHeight: Single read GetIBHeight write SetIBHeight;
procedure WriteToStream(ms: TStream);
procedure ReadFromStream(ms: TStream; NewWidth: Single = 1; NewHeight: Single = 1);
constructor Create(AOwner: TComponent); override;
end;
Instantiate code
ctrl := TLayoutSwitch.Create(Background);
ctrl.Parent := Background;
ctrl.BringToFront;
(ctrl as ILayoutBaseControl).ReadFromStream(ms, Background.Width/tmpW, Background.Height/tmpH);

Your class name TLayoutSwitch "misguides" FMX to search for a presenter named LayoutSwitch-style which of course doesn't exist in the framework. However, it is possible to change that name to the ordinary Switch-style in the OnPresentationNameChoosing event which is fired directly after the standard name construction.
Declare a TPresenterNameChoosingEvent procedure in your class, for example:
procedure ChoosePresentationName(Sender: TObject; var PresenterName: string);
and assign this to the event in the constructor
constructor TLayoutSwitch.Create(Owner: TComponent);
begin
inherited;
OnPresentationNameChoosing := ChoosePresentationName;
...
end;
Implementation could be as simple as
procedure TLayoutSwitch.ChoosePresentationName(Sender: TObject; var PresenterName: string);
begin
PresenterName := 'Switch-style';
end;
The Switch-style presenter/presentation is the one used by TSwitch. Therefore it now looks and behaves the same.

Related

Delphi DefineProperties unpublished property DFM order

I'm developing a component for Query. It works like the "Properties" feature of DevExpress, but I need to place the order of the Unpublished Property I wrote to DFM with DefineProperties in the DFM file at the top of the TCollectionItem.
It works the same way in DevExpress. If you add a Field to the cxGrid and assign a value to the Properties property, you will see the value "PropertiesClassName" in the DFM file at the top.
When I open the DFM file and bring this Property to the top, the setter property of the "PropertiesClassName" Property works and I create that Class. It works seamlessly when reading data from the DFM stream. But no matter what I did I couldn't get the "PropertiesClassName" Property value to the top.
If you create a cxGrid on the form and add Field, and then take the "PropertiesClassName" property from DFM to the bottom of the DFM file, when you open the form again, you will see that it cannot find the relevant Class and an error occurs.
To change the DFM flow, I first assigned a value to the "PropertiesClassName" Property and then created the Class, but the problem was not solved. I did the opposite of this but the problem is still the same.
DFM Context
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
FieldName = 'TestField'
Properties.Convert2String = True
PropertiesClassName = 'TSearchBooleanProperties'
end>
DFM Context should be like
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
PropertiesClassName = 'TSearchBooleanProperties'
FieldName = 'TestField'
Properties.Convert2String = True
end>
Classes
TSearchField = class(TCollectionItem)
private
FFieldName: string;
FProperties: TSearchFieldProperties;
FPropertiesClassName: string;
private
procedure SetFieldName(const Value: string);
procedure SetProperties(const Value: TSearchFieldProperties);
private
procedure ReaderProc(Reader: TReader);
procedure WriterProc(Writer: TWriter);
procedure SetPropertiesClassName(const Value: string);
protected
constructor Create(Collection: TCollection); override;
procedure DefineProperties(Filer: TFiler); override;
public
property PropertiesClassName: string read FPropertiesClassName write SetPropertiesClassName;
published
property FieldName: string read FFieldName write SetFieldName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
procedure TSearchField.SetPropertiesClassName(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesClassName(Value, Item) then
begin
if not Assigned(FProperties) or not (FProperties.ClassType = Item.ClassType) then
begin
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
FPropertiesClassName := Item.ClassType.ClassName;
FProperties := Item.ClassType.Create;
end;
end
else
begin
FPropertiesClassName := '';
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
end;
end;
Property Editor
type
TSearchFieldPropertiesProperty = class(TClassProperty)
private
function GetInstance: TPersistent;
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc: TGetStrProc); override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;
function TSearchFieldPropertiesProperty.GetValue: string;
begin
for var I := 0 to Self.PropCount - 1 do
begin
var Inst := Self.GetComponent(I);
if Assigned(Inst) and Self.HasInstance(Inst) then
begin
if Inst is TSearchField then
begin
var PropInst := GetObjectProp(Inst, Self.GetPropInfo);
if Assigned(PropInst) then
begin
for var Item in SearchFieldPropertiesList do
begin
if PropInst.ClassType = Item.ClassType then
begin
Result := Item.Name;
Exit;
end;
end;
end;
end;
end;
end;
end;
procedure TSearchFieldPropertiesProperty.SetValue(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesName(Value, Item) then
begin
var Inst := GetInstance;
if Assigned(Inst) then
begin
var Context := TRttiContext.Create;
var Rtype := Context.GetType(Inst.ClassType);
for var Prop in Rtype.GetProperties do
begin
if SameText(Prop.Name, 'PropertiesClassName') then
begin
Prop.SetValue(Inst, TValue.From<string>(Item.ClassType.ClassName));
Break;
end;
end;
end;
end;
end;
Pic for Design Time
The only problem is changing the order of the Property in that DFM flow.
Original answer at the bottom, here is a new suggestion:
We actually have something very similar in the JVCL where TJvHotTrackPersistent publishes a HotTrackOptions property.
This property is backed by an instance of TJvHotTrackOptions that gets derived in other classes that need specialized versions of it.
To tell the streaming subsystem to use the actual class found at streaming time, the constructor of that options class calls SetSubComponent(True); which places csSubComponent in the ComponentStyle property.
So what you should do is get rid of your DefineProperties, have TSearchFieldProperties inherit from TComponent and call SetSubComponent(True) in its constructor.
Then you create as many classes derived from TSearchFieldProperties as you need, each with its own set of published properties.
This means you should also get rid of the methods you showed in your submission.
In the end, you should have something along those lines:
type
TSearchFieldProperties = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
end;
TIntegerSearchFieldProperties = class(TSearchFieldProperties)
private
FIntValue: Integer;
published
property IntValue: Integer read FIntValue write FIntValue;
end;
constructor TSearchFieldProperties.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetSubComponent(True);
end;
With this you do not fight against the streaming system but rather work with it in the way it is meant to be used.
But if you stop there, you'll notice there is no way for you to specify the actual class name to be used for the TSearchFieldProperties instance used for the TSearchField.Properties property.
The only way to get the class name to be streamed before the subcomponent is streamed is to actually declare the class name as a published property, declared before the subcomponent like this:
type
TSearchField = class(TCollectionItem)
published
// DO NOT change the order of those two properties, PropertiesClassName must come BEFORE Properties for DFM streaming to work properly
property PropertiesClassName: string read GetPropertiesClassName write SetPropertiesClassName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
function TSearchField.GetPropertiesClassName: string;
begin
Result := Properties.ClassName;
end;
procedure TSearchField.SetPropertiesClassName(const AValue: string);
begin
FProperties.Free; // no need to test for nil, Free already does it
FProperties := TSearchFieldPropertiesClass(FindClass(AValue)).Create(self);
end;
It might work if you just declare the published property like without creating a csSubComponent hierarchy but you'll most likely stumble on other hurdles along the way.
Note: this answer is wrong because DefineProperties is called last in TWriter.WriteProperties and so there is no way to change the order properties defined like this are written.
What if you change your DefineProperties override from this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
to this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
inherited DefineProperties(Filer);
end;
Basically, call the inherited method AFTER you have defined your own property.
Note that I also specified which inherited method is called. I know it's not required, but it makes intent clearer and allows for Ctrl-Click navigation.

How to capture an event, when TTreeview structure changes?

I am making descendant of TTreeview and I want to implement an event in the case, when the TTreeview structure changes. For example, one TTreeNode is moved from one position to another, or it becomes the child of any other TTreenode.
When I call for example: Treeview1.Selected.MoveTo(ADropNode,naAddChildFirst);
no event fires.
How can I catch this?
Thanx.
I have found no message so far, what could respond to the change of structure.
The solution in this case was to make descendand of TTreeNode, where I overwrite dynamical procedure MoveTo and attach an event handler to it:
THierarchyTreeNode = class (TTreeNode)
private
FOnNodeMove:TTVNodeMoveEvent;
public
procedure MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode); override;
property OnNodeMove:TTVNodeMoveEvent read FOnNodeMove write FOnNodeMove;
end;
...
procedure THierarchyTreeNode.MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode);
begin
inherited;
if Assigned(FOnNodeMove) then FOnNodeMove(Treeview, Self);
end;
then I have done necessary changes in TTreeview descendand, where the procedure CreateNode is the key, where are THierarchyTreeNodes created instead of TTreenode. It is somewhat dirty, but... just an example:
TTreeViewHierarchy = class(TTreeView)
private
FOnNodeMove : TTVNodeMoveEvent;
protected
function CreateNode: TTreeNode; override;
procedure DoNodeMove(Sender: TObject; Node: TTreeNode);
published
property OnNodeMove: TTVNodeMoveEvent read FOnNodeMove write FOnNodeMove;
function TTreeViewHierarchy.CreateNode: TTreeNode;
var
LClass: TTreeNodeClass;
begin
LClass := THierarchyTreeNode;
if Assigned(OnCreateNodeClass) then
OnCreateNodeClass(Self, LClass);
Result := LClass.Create(Items);
(Result as THierarchyTreeNode).FOnNodeMove := DoNodeMove;
end;
procedure TTreeViewHierarchy.DoNodeMove(Sender: TObject; Node: TTreeNode);
begin
if Assigned(FOnNodeMove) then FOnNodeMove(Sender, Node);
end;
And it works...

How to update dataset fields' values using the changed data in user defined FMX TGrid coumns

I use the following way to define and populate a column with TCalendarEdit ancessor cells (here is the interface part). Let me know if I need to expose implementation.
type
TDynamicRecord = record
Field1, Field2, Field3: string;
DateSBU: TDate;
TimeSBU: TTime;
end;
TDateCell = class(TCalendarEdit)
protected
procedure SetData(const Value: TValue); override;
public
constructor Create(AOwner: TComponent); override;
end;
TDateColumn = class(TColumn)
protected
function CreateCellControl: TStyledControl; override;
public
constructor Create(AOwner: TComponent); override;
end;
The code works fine and the column of type TDateColumn can be added to the grid.
I asked a question and opened a bounty for it looking for a technique to populate the user defined column by LiveBindings but I have got no answers by the bounty expiry date. That is why now I am trying another way.
I declare a collection DynamicData: TList<TDynamicRecord>; and populate it from the dataset. This approach is quite clearly explained in the answer of #Mike-Sutton and in his blog. I have managed to do this. The data is displayed properly in the grid and in its newly defined columns.
But now I am stuck with the other problem. I need to update dataset on user activities. I tried the OnSetValue and OnEditingDone events of the parent grid (according to the documentation these event are to be used for data updates) but strangely it does not fire.
What will be the proper event or other way suitable for updating the dataset?
I think that the issue is, at least partially, resolved.
It was necessary to redeclare almost all the classes. I would be glad if someone gives me an advice how to reduce the complecity of the code.
I had to declare this class to expose FOnEditingDone and procedure SetValue(...) making them accessible:
type
TDynamicCustomGrid = class(TGrid)
private
FOnEditingDone: TOnEditingDone;
protected
procedure SetValue(Col, Row: Integer; const Value: TValue);override;
end;
implementation
procedure TDynamicCustomGrid.SetValue(Col, Row: Integer; const Value: TValue);
begin
inherited;
end;
Then I redeclared
type
TDateColumn = class(TColumn)
protected
...
function Grid: TDynamicCustomGrid; overload;
procedure DoDateChanged(Sender: TObject);
public
...
end;
implementation
procedure TDateColumn.DoDateChanged(Sender: TObject);
var
P: TPointF;
LGrid: TDynamicCustomGrid;
begin
LGrid := Grid;
if not Assigned(LGrid) then
Exit;
if FUpdateColumn then
Exit;
if FDisableChange then
Exit;
P := StringToPoint(TFmxObject(Sender).TagString);
LGrid.SetValue(Trunc(P.X), Trunc(P.Y), TStyledControl(Sender).Data);
if Assigned(LGrid.FOnEditingDone) then
LGrid.FOnEditingDone(Grid, Trunc(P.X), Trunc(P.Y));
end;
function TDateColumn.Grid: TDynamicCustomGrid;
var
P: TFmxObject;
begin
Result := nil;
P := Parent;
while Assigned(P) do
begin
if P is TCustomGrid then
begin
Result := TDynamicCustomGrid(P);
Exit;
end;
P := P.Parent;
end;
end;
And finally I assigned TDateCell(Result).OnChange := DoDateChanged; in function TDateColumn.CreateCellControl: TStyledControl;
I am far from thinking that this way is optimal and will be grateful for any comments and ideas how to optimize the code.

Delphi: Use public procedures from a dynamic form

I have a Delphi XE+ application with 3 forms, 2 of them created dynamically, like so:
form_main is triggering form_equip
form_equip is triggering form_certif
form_main -> form_equip -> form_certif
1'st: Open form_equip
procedure Tform_main.button_equip_addClick(Sender: TObject);
var
form_equip: Tform_equip;
begin
form_equip:= Tform_equip.Create(Self);
form_equip.equip_id:= 0;
form_equip.ShowModal;
FreeAndNil(form_equip);
end;
On form_equip I have a public procedure has_changes
2'nd: Open form_certif
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(Self);
form_certif.index:= 0;
form_certif.ShowModal;
FreeAndNil(form_certif);
end;
Now, when I press OK button from form_certif
procedure Tform_certif.button_okClick(Sender: TObject);
begin
//do something...
form_equip.has_changes; //this public procedure from form_equip is not visible because form was created as local var on form_main
end;
The question is, how can I transmit the sender/parent name to form_certif so can I see the public procedures and variables from form_equip?
A simple way is to declare inside unit_equip as global:
var
form_equip: Tform_equip
but I avoid to do this because form_equip is made to be opened dynamically in multiple windows with different names...
Pass all needed information from form_equip to form_certif. That way form_certif is decoupled from any dependence of form_equip.
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(nil);
try
form_certif.index:= 0;
// Pass all other needed variable values to form_certif
// including callback methods
form_certif.Has_Changes_Method := Self.Has_Changes();
if form_certif.ShowModal = mrOk then
begin
// take care of changes
end;
finally
FreeAndNil(form_certif);
end;
end;
And this is how it would look in the form_certif unit:
type
THas_Changes_Method = procedure of Object;
TForm_Certif = class(TForm)
...
private
FIndex: Integer;
FHasChanges: THas_Changes_Method;
public
property Index: Integer read FIndex write FIndex;
property Has_Changes_Method: THas_Changes_Method read fHasChanges write fHasChanges;
end;
Pass the information as a parameter in the constructor. Declare the constructor like this:
constructor Create(AOwner: TComponent; const ParentName: string);
In the implementation of the constructor, make a note of the name that was passed.
Since you are creating forms in code, you can add custom constructor to the form and pass other form as parameter.
Tform_certif = class(TForm)
...
protected
form_equip: Tform_equip;
public
constructor Create(AOwner: TComponent; Aform_equip: Tform_equip); reintroduce;
end;
constructor Tform_certif.Create(AOwner: TComponent; Aform_equip: Tform_equip);
begin
inherited Create(AOwner);
form_equip := Aform_equip;
end;
So now you can call form_equip.has_changes, because it is a field that you have initialized during construction of your Tform_certif form and it points to particular instance of Tform_equip that created this particular instance of Tform_certif.
procedure Tform_certif.button_okClick(Sender: TObject);
begin
//do something...
// test whether form_equip is assigned to avoid AV by calling methods on nil object
if Assigned(form_equip) then form_equip.has_changes;
end;
And to create your Tform_certif you would use following code
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(Self, Self);
form_certif.index:= 0;
form_certif.ShowModal;
FreeAndNil(form_certif);
end;
There is also variation of above constructor code, where you only need to send one parameter and then test in constructor whether AOwner it is of Tform_equip type and you don't need to change code in button_certif_addClick for using that kind of solution.
Tform_certif = class(TForm)
...
protected
form_equip: Tform_equip;
public
constructor Create(AOwner: TComponent); override;
end;
constructor Tform_certif.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
if AOwner is Tform_equip then form_equip := Tform_equip(AOwner);
end;

Changing properties class in custom component at designtime

I'm writing simple component. What I want to achieve is that my MethodOptions will change in Object Inspector according to Method I choose.
Something like this:
So far I coded:
TmyMethod = (cmFirst, cmSecond);
TmyMethodOptions = class(TPersistent)
published
property SomethingInBase: boolean;
end;
TmyMethodOptionsFirst = class(TmyMethodOptions)
published
property SomethingInFirst: boolean;
end;
TmyMethodOptionsSecond = class(TmyTMethodOptions)
published
property SomethingInSecond: boolean;
end;
TmyComponent = class(TComponent)
private
fMethod: TmyMethod;
fMethodOptions: TmyMethodOptions;
procedure ChangeMethod(const Value: TmyMethod);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Method: TmyMethod read fMethod write ChangeMethod default cmFirst;
property MethodOptions: TmyMethodOptions read fMethodOptions
write fMethodOptions;
end;
implementation
procedure TmyComponent.ChangeMethod(const Value: TmyMethod);
begin
fMethod := Value;
fMethodOptions.Free;
// case...
if Value = cmFirst then
fMethodOptions := TmyMethodOptionsFirst.Create
else
fMethodOptions := TmyMethodOptionsSecond.Create;
// fMethodOptions.Update;
end;
constructor TmyComponent.Create(AOwner: TComponent);
begin
inherited;
fMethodOptions := TmyMethodOptions.Create;
fMethod := cmFirst;
end;
destructor TmyComponent.Destroy;
begin
fMethodOptions.Free;
inherited;
end;
Of course it does almost nothing (except hanging IDE) and I don't have any starting point where to search the suitable knowledge to achieve this.
If I understand correctly I believe that this the same technique the Developer Express implemented in their Quantum Grid component, for dynamically showing different properties for various field types in the grid. There is an explanation of the mechanism here: Technology of the QuantumGrid

Resources