1
votes

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

2
maybe this can help you stackoverflow.com/questions/38269301/…GuidoG
Do you mean literally any possible control the user can change or just a specific subset like db-controls?S. Langhammer
@S.Langhammer : yes if possible , see above . Something like that but for every control that can be somehow changed by the user. I was thinking about onChange event . But unfortunately not every Control has it.user1937012

2 Answers

2
votes

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)

1
votes

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;