How to capture an event, when TTreeview structure changes? - delphi

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...

Related

Form Controll ( Edit , ComboBox , Memo etc ) Query isModified?

when a User adds or changes something in the Programm , on the FormQuery I check if there was something modified and no Save done and I warn the user that if he quits all data will be lost .
Problem is I am checking the Components one at a time . Edit has Modified , but DateTimePicker has none for example .
My question is : if possible how can you check with one command perhaps if anything on the Form was altered ? Any Control ?
UPDATE
I was thinking about something universal if such a thing exists , something like this but for every controller that can be altered by the user in any way .
Drop 4 TEdit's on the form and one TLabel .
procedure TForm1.SomethingChanged(Sender: TObject);
begin
Label1.Caption:='SOMETHING CHANGED!';
end;
on TForm.Create I do this :
procedure TForm1.FormCreate(Sender: TObject);
var i : integer;
Child : TComponent;
begin
for i := 0 to ComponentCount-1 do
begin
Child := Components[i];
if Child is TEdit then
TEdit(Child).OnChange:=SomethingChanged;
if Child is TDateTimePicker then
TDateTimePicker(Child).OnChange:=SomethingChanged;
if Child is TComboBox then
TComboBox(Child).OnChange:=SomethingChanged;
end;
end;
I Could make this for all controls like : Editors , DateTimePickers , ComboBoxes etc... but I was thinking that maybe there is some cool "secret" smarter way to do this .
Thank you
UPDATE 2
now I have another problem , dunno if possible . Say one of the TEdit's have a onChange event defined like this :
procedure TForm1.Edit1Change(Sender: TObject);
begin
Label2.Caption:='THIS WAS EDIT1CHANGE';
end;
When the Application starts this is reset to my custom onChange event and this one is never run .
Is it possible to somehow chain onChange events ?
Like I have the one where I only check if something changed ... and yet I allow the TEdit to execute it's "normal" onChange event .
Thank you
I think The key Here is that these components are mostly TWinControl descendant, So why not hook to their OnChange Message CM_CHANGED and this way you will not have a problem with OnChange event chaining as you say it (I wish Delphi had some thing like C# += operator when it comes to events).
you will need the following classes to achieve this
1. TListener
TListener = class
private
FOnChangeHappend: TNotifyEvent;
FWinControl: TWinControl;
FMsgToListen: Cardinal;
FOldWndProc: System.Classes.TWndMethod;
procedure FWindowProc(var Message: TMessage);
public
constructor Create(aWinControl: TWinControl; aMsg: Cardinal);
Destructor Destroy;
property OnChangeHappend: TNotifyEvent read FOnChangeHappend write FOnChangeHappend;
end;
{ TListener }
constructor TListener.Create(aWinControl: TWinControl; aMsg: Cardinal);
begin
FMsgToListen := aMsg;
FWinControl := aWinControl;
FOldWndProc := aWinControl.WindowProc;
aWinControl.WindowProc := FWindowProc;
end;
destructor TListener.Destroy;
begin
if Assigned(FOldWndProc) then
FWinControl.WindowProc := FOldWndProc;
inherited Destroy;
end;
procedure TListener.FWindowProc(var Message: TMessage);
begin
if ((Message.Msg = FMsgToListen) and (Assigned(FOnChangeHappend))) then
begin
FOnChangeHappend(FWinControl);
end;
FOldWndProc(Message);
end;
2. TListenerList
TListenerList = class
private
FListners: TObjectList<TListener>;
FOnChangeHappend: TNotifyEvent;
public
constructor Create;
Destructor Destroy;
procedure ListenTo(aWinControl: TWinControl; aMsg: Cardinal);
property OnChangeHappend: TNotifyEvent read FOnChangeHappend write FOnChangeHappend;
end;
{ TListenerList }
constructor TListenerList.Create;
begin
FListners := TObjectList<TListener>.Create;
FListners.OwnsObjects := True;
end;
destructor TListenerList.Destroy;
begin
FListners.Free;
end;
procedure TListenerList.ListenTo(aWinControl: TWinControl; aMsg: Cardinal);
var
aListener: TListener;
begin
aListener := TListener.Create(aWinControl, aMsg);
aListener.OnChangeHappend := FOnChangeHappend;
Flistners.Add(aListener);
end;
And you can use it like this in your form OnCreate event
procedure TForm8.FormCreate(Sender: TObject);
begin
FListenerList := TListenerList.Create();
FListenerList.OnChangeHappend := TextChanged;
FListenerList.ListenTo(DBEdit1, CM_CHANGED);
FListenerList.ListenTo(DBMemo1, CM_CHANGED);
FListenerList.ListenTo(DBComboBox1, CM_CHANGED);
FListenerList.ListenTo(DBCheckBox1, CM_CHANGED);
FListenerList.ListenTo(DBRichEdit1, CM_CHANGED);
FListenerList.ListenTo(Memo1, CM_CHANGED);
FListenerList.ListenTo(Edit1, CM_CHANGED);
FListenerList.ListenTo(ComboBox1, CM_CHANGED);
FListenerList.ListenTo(DateTimePicker1, CM_CHANGED);
FListenerList.ListenTo(CheckBox1, CM_CHANGED);
end;
procedure TForm8.TextChanged(Sender: TObject);
begin
memo2.Lines.Add(TWinControl(Sender).Name + 'Changed');
end;
but this message has a limitation. For example if the edit control had the text 'Hello' and you wanted to delete it (back key press) the Listener event will be fired five times (one for each letter) so instead you should use the CM_ENTER and CM_EXIT messages were you record the value of each TWinControl when entered (has focus) and compare that to its value when exited (lost focus).
This approach will work with any TWinControl descendant (pretty much any control that the user can interact with)
if you use dbedit,dbcombobax.. you can do control.
because
you must have linked them to a table or query.
you must use datasource for links.
if table1.state=dsedit then
begin
end;
Define a variable if you are using edit.
Assign value to the variable in the onchange event of all fields. Then check this variable.
procedure Tform1.editChange (Sender: TObject);
begin
variable_change:= 'YES';
end;
if variable_change = 'YES' then
begin
end;

Subclass TSwitch in Firemonkey

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.

How can I prevent duplication of sub components in Firemonkey compound component?

I am trying to write a compound component which is derived from TDummy. The component source is:
TMyObjectType=(otCube,otSphere);
TMyGameObject=class(TDummy)
private
FObj:TCustomMesh;
FMyObjectType: TMyObjectType;
procedure SetMyObjectType(const Value: TMyObjectType);
public
constructor Create(AOwner:TComponent);override;
destructor Destroy;override;
property MyObjectType:TMyObjectType read FMyObjectType write SetMyObjectType;
end;
{ TMyGameObject }
constructor TMyGameObject.Create(AOwner: TComponent);
begin
inherited;
MyObjectType:=otCube;
end;
destructor TMyGameObject.Destroy;
begin
FObj.Parent.RemoveObject(FObj);
FreeAndNil(FObj);
inherited;
end;
procedure TMyGameObject.SetMyObjectType(const Value: TMyObjectType);
begin
FMyObjectType := Value;
if(Assigned(FObj))then begin
FObj.Parent.RemoveObject(FObj);
FreeAndNil(FObj);
end;
case FMyObjectType of
otCube: FObj:=TCube.Create(Self);
otSphere: FObj:=TSphere.Create(Self);
end;
FObj.SetSubComponent(True);
FObj.Parent:=Self;
end;
after I register the component and put one instance on a TViewport3D in the code of a Tbutton I try to change the MyObjectType to otSphere.
MyGameObject1.MyObjectType:=otSphere;
but it seems there is nothing happening. So I wrote a piece of code as fallow.
procedure MyParseObj(obj:TFmxObject;var s:string);
var
i: Integer;
a:string;
begin
s:=s+obj.ClassName+'(';
a:='';
for i := 0 to obj.ChildrenCount-1 do begin
s:=s+a;
MyParseObj(obj.Children.Items[i],s);
a:=',';
end;
s:=s+')'
end;
and call it in another button.
procedure TForm1.Button2Click(Sender: TObject);
var s:string;
begin
s:='';
MyParseObj(myGameObject1,s);
ShowMessage(s);
end;
the result was strange.
if I press the button2 result is: TMyGameObject(TCube(),TCube())
and when I press the button1 and after that press button2 result is: TMyGameObject(TCube(),TSphere())
why there is two TCustomMesh as child in my object? (TCube and TSphere are derived from TCustomMesh)
how can I fix this?
and there is another test that I performed. if I create the object not in design time it work properly. problem happens if I put an instance of TMyGameObject in design time.
When you save a form (from the IDE) all controls and all their children are saved. If your control creates it's own children then you need to set Stored = False to prevent them being streamed by the IDE.

Delphi custom TTreeNode

Im trying to make my custom TTreeNode Class
for example
TCustomTreeNode = class(TTreeNode)
private
public
Comment:string;
end;
and i create and add the node in the tree view like this:
var
NewCustomTreeNode:TCustomTreeNode;
begin
NewCustomTreeNode:= TCustomTreeNode.Create(TreeView.Items);
NewCustomTreeNode.Comment:='blqblq';
TreeView.Items.AddChild(NewCustomTreeNode,'NodeText');
and when i try to access the custom created tree nodes error pops up. For example i do like this:
TCustomTreeNode(TreeNode).Comment:='asdadssadas';
plase help
The problem with your code is that the call to AddChild results in the tree view creating a new node. And since you didn't tell the tree view to create a node of your sub-class it creates a plain TTreeNode. And then when you try to cast it to a TCustomTreeNode, the world ends.
You need to use the OnCreateNodeClass method to make sure that the tree view is able to create new nodes. Like this:
type
TCustomTreeNode = class(TTreeNode)
protected
procedure Assign(Source: TPersistent); override;
public
Comment: string;
end;
procedure TCustomTreeNode.Assign(Source: TPersistent);
begin
if Source is TCustomTreeNode then
Comment := TCustomTreeNode(Source).Comment;
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
NewNode: TCustomTreeNode;
begin
NewNode := TreeView1.Items.Add(nil, 'Node1') as TCustomTreeNode;
NewNode.Comment := 'A comment';
NewNode := TreeView1.Items.Add(nil, 'Node2') as TCustomTreeNode;
NewNode.Comment := 'Another comment';
end;
procedure TForm1.TreeView1Click(Sender: TObject);
var
Node: TCustomTreeNode;
begin
Node := TreeView1.Selected as TCustomTreeNode;
if Assigned(Node) then
ShowMessage(Node.Comment);
end;
procedure TForm1.TreeView1CreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
begin
NodeClass := TCustomTreeNode;
end;
I can't claim to being the world's greatest expert on Delphi tree views but in my experience you never create a tree node yourself. You should always call one of the AddXXX methods on TTreeView.Items to create new nodes.

Passing object in reference / one place to style objects

I got quite a large application which is currently being styled up.
To save me changing all the buttons in the IDE/Object Inspector I am planning on just doing a few functions for the main objects like
procedure StyleButton(AButton : TButton)
begin
AButton.Color := clGreen;
AButton.Font.Style = [fsBold];
end;
etc etc and then add that to the forms onCreates as needed
StyleButton(Button1); whatever etc
There is no issue passing objects in params like this. It does just reference the first object right?
It works fine and I can't think of any issues, but because this is a large application which thousands of users I just want to be sure there will be no issues/memory leaks/resource consumpution issues.
Will also be doing similar things with TAdvStringGrid and TEdit/TMemo components.
Then allows just 1 place to change these settings.
Or someone have a better idea?
This is an excellent idea. The function will modify whichever object you pass to it.
You are not passing by reference. You are passing by value. The value you are passing is a reference. "Passing by reference" means you'd use the var or out keywords, which are not appropriate in this situation.
Your idea is just fine, as the other answerers have already said. Just want to propose a solution that goes even further than David's and something you may want to consider in order to avoid having to add many statements like:
StyleButton(Button1);
StyleButton(Button2);
to each and every form for each and every control you would like to style;
What I would propose is to add a single method call to for example each form's OnShow event:
procedure TForm1.FormShow(Sender: TObject);
begin
TStyler.StyleForm(Self);
end;
The TStyler could be implemented in a separate unit that looks like this:
interface
type
TStyler = class;
TStylerClass = class of TStyler;
TStyler = class(TObject)
public
class procedure StyleForm(const aForm: TCustomForm);
class procedure StyleControl(const aControl: TControl); virtual;
class function GetStyler(const aControl: TControl): TStylerClass;
end;
implementation
uses
Contnrs;
type
TButtonStyler = class(TStyler)
public
class procedure StyleControl(const aControl: TControl); override;
end;
TEditStyler = class(TStyler)
public
class procedure StyleControl(const aControl: TControl); override;
end;
TLabelStyler = class(TStyler)
public
class procedure StyleControl(const aControl: TControl); override;
end;
var
_Controls: TClassList;
_Stylers: TClassList;
{ TStyler }
class function TStyler.GetStyler(const aControl: TControl): TStylerClass;
var
idx: Integer;
begin
Result := TStyler;
idx := _Controls.IndexOf(aControl.ClassType);
if idx > -1 then
Result := TStylerClass(_Stylers[idx]);
end;
class procedure TStyler.StyleForm(const aForm: TCustomForm);
procedure _StyleControl(const aControl: TControl);
var
i: Integer;
StylerClass: TStylerClass;
begin
StylerClass := TStyler.GetStyler(aControl);
StylerClass.StyleControl(aControl);
if (aControl is TWinControl) then
for i := 0 to TWinControl(aControl).ControlCount - 1 do
_StyleControl(TWinControl(aControl).Controls[i]);
end;
var
i: Integer;
begin
_StyleControl(aForm);
end;
class procedure TStyler.StyleControl(const aControl: TControl);
begin
// Do nothing. This is a catch all for all controls that do not need specific styling.
end;
{ TButtonStyler }
class procedure TButtonStyler.StyleControl(const aControl: TControl);
begin
inherited;
if aControl is TButton then
begin
TButton(aControl).Font.Color := clRed;
TButton(aControl).Font.Style := [fsBold];
end;
end;
{ TEditStyler }
class procedure TEditStyler.StyleControl(const aControl: TControl);
begin
inherited;
if aControl is TEdit then
begin
TEdit(aControl).Color := clGreen;
end;
end;
{ TLabelStyler }
class procedure TLabelStyler.StyleControl(const aControl: TControl);
begin
inherited;
if aControl is TLabel then
begin
TLabel(aControl).Font.Color := clPurple;
TLabel(aControl).Font.Style := [fsItalic];
end;
end;
initialization
_Controls := TClassList.Create;
_Stylers := TClassList.Create;
_Controls.Add(TButton);
_Stylers.Add(TButtonStyler);
_Controls.Add(TEdit);
_Stylers.Add(TEditStyler);
_Controls.Add(TLabel);
_Stylers.Add(TLabelStyler);
finalization
FreeAndNiL(_Controls);
FreeAndNiL(_Stylers);
end.
This solution basically employs polymorphism and a registry that links control classes to styler classes. It also uses class procedures and functions to avoid having to instantiate anything.
Please note that the registry is implemented in this example as two lists that need to be kept in sync manually as the code assumes that finding a class at index X will find the styler at the same index in the other list. This can of course be improved upon very much, but is sufficient here to show the concept.
No, There is no issue (in your specific case) passing a object as parameter
procedure StyleButton(AButton : TButton)
when you do this you are passing a address memory (reference) and setting some properties of the referenced object, so there is not problem.
To add to what Rob and RRUZ have already said, you could consider an extra helper using open array parameters:
procedure StyleButtons(const Buttons: array of TButton);
var
i: Integer;
begin
for i := low(Buttons) to high(Buttons) do
StyleButton(Buttons[i]);
end;
You can then call this as:
StyleButtons([btnOK, btnCancel, btnRelease64bitDelphi]);
which is, in my view, more readable at the call-site than:
StyleButton(btnOK);
StyleButton(btnCancel);
StyleButton(btnRelease64bitDelphi);
Note that I passed the open array as a const parameter because that is more efficient when dealing with arrays. Because each element of the array is itself a reference to the button, you are able to modify the actual button. The const just means that you cannot change the reference.

Resources