is there a way to reference an object instance that is created using the "with" statement?
Example:
with TAnObject.Create do
begin
DoSomething(instance);
end;
Where DoSomething would use the instance reference as if you were passing an instance from a variable declared reference to the object created.
Example:
AnObject := TAnObject.Create;
Thanks.
Well, you can use such approach:
// implement:
type
TSimpleMethod = procedure of object;
function GetThis(const pr: TSimpleMethod): TObject;
begin
Result := TMethod(pr).Data;
end;
// usage:
with TStringList.Create do
try
CommaText := '1,2,3,4,5,6,7,8,9,0';
ShowText(TStringList(GetThis(Free)));
finally
Free;
end;
or class helpers:
type
TObjectHelper = class helper For TObject
private
function GetThis: TObject; Inline;
public
property This: TObject read GetThis;
end;
...
function TObjectHelper.GetThis: TObject;
begin
Result := Self;
end;
But, actually, previous replies are correct: you should better forget about "with" statement.
You should never use with either because future changes might introduce more into that scope than you intended.
Take this for instance:
procedure Test;
var
x: Integer;
begin
with TSomeObject.Create do
begin
DoSomethingWithX(x);
Free;
end;
end;
and then later on you tuck on a X property on the TSomeObject class. Now, which X do you think it's going to use? The local variable or the X property of the object?
The best solution is always to just create a local variable with a short name, and alias the object to that variable.
procedure Test;
var
x: Integer;
o: TSomeObject;
begin
o := TSomeObject.Create;
o.DoSomethingWithX(x);
o.Free;
end;
You gave the answer yourself: declare local variable. If you want you can use the with keyword on that.
var
MyInstance: TMyObject;
begin
MyInstance := TMyObject.Create;
with MyInstance do
try
Foo;
Bar;
DoSomething(MyInstance);
finally
Free;
end;
end;
In above example the only reason to use with is code readability, which is very subjective, you could also ditch the with keyword and use MyInstance directly. It's just a matter of personal taste. I don't agree on the "never use with" answers, but you should be aware of it's drawbacks.
See also this question: Is delphi "with" keyword a bad practice?
An addition to Brian's example on a Notify handler is to use an absolute variable (win32 only):
procedure Notify( Sender : TObject );
var
Something : TSomeThing absolute Sender;
begin
if Sender is TSomething then
begin
VerySimpleProperty := Something.Something;
OtherProperty := Something.SomethingElse;
end;
end;
It basically avoids having to assign a local variable or have a lot of type casts.
I've learnt the hard way - only use 'With' in the following scenarios:
With TMyForm.Create( Owner ) do
try
ShowModal
finally
Free;
end;
procedure Notify( Sender : TObject );
begin
With Sender as TSomething do
VerySimpleProperty := Something
end;
i.e keep the visibility of With as simple as possible. When you take into account the fact that the debugger cant resolve 'With', it's actually better and clearer to use a simple local variable or to fully declare the target i.e MyRecord.Something
This is not possible now, but we can make it a reality by persuading the compiler creators:
With TForm1.Create (Nil) Do // New TForm1 instance
Try
LogForm ("); // That same instance as parameter to an outer method (solution)
"ShowModal; // Instance.ShowModal
Finally
"Free; // Instance.Free
End;
My proposal is:
No more than one object/record per With header.
Nested Withs not allowed.
Usage of " to indicate the object/record (double quotes are similar
to the ditto mark: http://en.wikipedia.org/wiki/Ditto_mark).
There is a working fine hack to do so.
Define this workaround function somwhere in project unit.
// use variable inside 'with ... do'
// WSelf function returns TObject associated with its method.
// I would recommend to use the method 'Free'
// WSelf(Free) as <TObjectN>
type TObjectMethod = procedure of object;
function WSelf(const MethodPointer: TObjectMethod): TObject;
begin
Result := TMethod(MethodPointer).Data;
end;
Usage example.
var
SL: TStringList;
begin
SL := TStringList.Create;
try
with TStringList.Create do
try
Add('1');
Add('2');
Add('3');
// (WSelf(Free) as TStringList) references to the object
// created by TStringList.Create
SL.Assign(WSelf(Free) as TStringList);
finally
Free;
end;
finally
ShowMessage(SL.Text);
SL.Free;
end;
end;
for FMX, you should use GetObject
example:
with TLabel.Create(box1) do
begin
Font.Size := 34;
Font.Style := [TFontStyle.fsBold];
TextAlign := TTextAlign.taCenter;
box1.AddObject(GetObject);
end;;
The best aproach in Delphi is use a variable to handle that instance.
Related
is there a way to reference an object instance that is created using the "with" statement?
Example:
with TAnObject.Create do
begin
DoSomething(instance);
end;
Where DoSomething would use the instance reference as if you were passing an instance from a variable declared reference to the object created.
Example:
AnObject := TAnObject.Create;
Thanks.
Well, you can use such approach:
// implement:
type
TSimpleMethod = procedure of object;
function GetThis(const pr: TSimpleMethod): TObject;
begin
Result := TMethod(pr).Data;
end;
// usage:
with TStringList.Create do
try
CommaText := '1,2,3,4,5,6,7,8,9,0';
ShowText(TStringList(GetThis(Free)));
finally
Free;
end;
or class helpers:
type
TObjectHelper = class helper For TObject
private
function GetThis: TObject; Inline;
public
property This: TObject read GetThis;
end;
...
function TObjectHelper.GetThis: TObject;
begin
Result := Self;
end;
But, actually, previous replies are correct: you should better forget about "with" statement.
You should never use with either because future changes might introduce more into that scope than you intended.
Take this for instance:
procedure Test;
var
x: Integer;
begin
with TSomeObject.Create do
begin
DoSomethingWithX(x);
Free;
end;
end;
and then later on you tuck on a X property on the TSomeObject class. Now, which X do you think it's going to use? The local variable or the X property of the object?
The best solution is always to just create a local variable with a short name, and alias the object to that variable.
procedure Test;
var
x: Integer;
o: TSomeObject;
begin
o := TSomeObject.Create;
o.DoSomethingWithX(x);
o.Free;
end;
You gave the answer yourself: declare local variable. If you want you can use the with keyword on that.
var
MyInstance: TMyObject;
begin
MyInstance := TMyObject.Create;
with MyInstance do
try
Foo;
Bar;
DoSomething(MyInstance);
finally
Free;
end;
end;
In above example the only reason to use with is code readability, which is very subjective, you could also ditch the with keyword and use MyInstance directly. It's just a matter of personal taste. I don't agree on the "never use with" answers, but you should be aware of it's drawbacks.
See also this question: Is delphi "with" keyword a bad practice?
An addition to Brian's example on a Notify handler is to use an absolute variable (win32 only):
procedure Notify( Sender : TObject );
var
Something : TSomeThing absolute Sender;
begin
if Sender is TSomething then
begin
VerySimpleProperty := Something.Something;
OtherProperty := Something.SomethingElse;
end;
end;
It basically avoids having to assign a local variable or have a lot of type casts.
I've learnt the hard way - only use 'With' in the following scenarios:
With TMyForm.Create( Owner ) do
try
ShowModal
finally
Free;
end;
procedure Notify( Sender : TObject );
begin
With Sender as TSomething do
VerySimpleProperty := Something
end;
i.e keep the visibility of With as simple as possible. When you take into account the fact that the debugger cant resolve 'With', it's actually better and clearer to use a simple local variable or to fully declare the target i.e MyRecord.Something
This is not possible now, but we can make it a reality by persuading the compiler creators:
With TForm1.Create (Nil) Do // New TForm1 instance
Try
LogForm ("); // That same instance as parameter to an outer method (solution)
"ShowModal; // Instance.ShowModal
Finally
"Free; // Instance.Free
End;
My proposal is:
No more than one object/record per With header.
Nested Withs not allowed.
Usage of " to indicate the object/record (double quotes are similar
to the ditto mark: http://en.wikipedia.org/wiki/Ditto_mark).
There is a working fine hack to do so.
Define this workaround function somwhere in project unit.
// use variable inside 'with ... do'
// WSelf function returns TObject associated with its method.
// I would recommend to use the method 'Free'
// WSelf(Free) as <TObjectN>
type TObjectMethod = procedure of object;
function WSelf(const MethodPointer: TObjectMethod): TObject;
begin
Result := TMethod(MethodPointer).Data;
end;
Usage example.
var
SL: TStringList;
begin
SL := TStringList.Create;
try
with TStringList.Create do
try
Add('1');
Add('2');
Add('3');
// (WSelf(Free) as TStringList) references to the object
// created by TStringList.Create
SL.Assign(WSelf(Free) as TStringList);
finally
Free;
end;
finally
ShowMessage(SL.Text);
SL.Free;
end;
end;
for FMX, you should use GetObject
example:
with TLabel.Create(box1) do
begin
Font.Size := 34;
Font.Style := [TFontStyle.fsBold];
TextAlign := TTextAlign.taCenter;
box1.AddObject(GetObject);
end;;
The best aproach in Delphi is use a variable to handle that instance.
If you like to use a method's pointer as an argument, you need to type the method as function of object like this works good:
type TAcceptor = function(filename:string):boolean of object;
function acceptor(filename:string):boolean;
begin
result := filename <> '';
end;
What if you like to use the pointer of a sub-method? It does not work:
procedure TForm1.Button1Click(Sender:TObject);
function acceptor(filename:string):boolean of object;
begin
result := filename <> '';
end;
begin
end;
The error occour: ; expected but OF found!
Question: Is there any subfunction-pointer? Can i cast it?
I don't see how that this would be possible.
http://docwiki.embarcadero.com/RADStudio/XE6/en/Procedural_Types
If you look under the method pointers section, it specifically says that nested procedures and functions cannot be used:
"Nested procedures and functions (routines declared within other
routines) cannot be used as procedural values, nor can predefined
procedures and functions."
You might be able to work around it using an anonymous method. Something like:
procedure TForm1.Button1Click(Sender:TObject);
begin
DoSomethingWithAcceptor(function(FileName: string): Boolean
begin
Result := FileName <> '';
end);
end;
CAUTION
I know that the following is not universally applicable, but it works for all known Win32 versions of Delphi. As long as you are aware of this, and check its functionality in new versions, it is a viable hack, IMO.
Passing nested functions to methods
In older code, I used this to do some "poor man's anonymous methods":
type
TLocal = packed record
Code: Pointer; // local (nested) function
Frame: Pointer; // outer stack frame for local function
end;
To fill such a local inside a method, I wrote the function Local:
function Local(LocalFunction: Pointer): TLocal;
asm
MOV [EDX].TLocal.Frame,EBP
MOV [EDX].TLocal.Code,EAX
end;
Inside my unit (some kind of generic collection), I wrote a function to call them, passing one parameter (of type TGeneric, in this case, which is not important here, you can also pass a pointer or some such).
// Calls local function using local closure provided, passing
// T as parameter to the local.
function CallLocal(T: TGeneric; const Local: TLocal): TGeneric;
asm
PUSH [EDX].TLocal.Frame
CALL [EDX].TLocal.Code
ADD ESP,4
end;
It was used like this:
function TStdCollection.AsArray: TGenericArray;
var
I: Integer;
A: TGenericArray;
procedure ToArray(E: TGeneric);
begin
Result[I] := E.Traits.Copy(E);
Inc(I);
end;
begin
SetLength(A, Count);
I := 0;
ForEach(Local(#ToArray));
Assert(I = Count);
Result := A;
end;
The code in the nested function makes a copy of the element and stores it in the array. The main procedure then passes the nested function ToArray (together with its stack frame) as parameter to ForEach, which is implemented this way:
function TStdCollection.ForEach(Operation: TLocal): ICollection;
var
Enum: IEnumerator;
Elem: TGeneric;
begin
Enum := GetEnumerator;
Elem := Enum.First;
while Elem <> nil do
begin
CallLocal(Elem, Operation);
Elem := Enum.Next;
end;
Result := Self;
end;
These examples show how to use the Locals. I hope this more or less answers your question.
Note
Note that this code was written in the Delphi 6 timeframe. I know there are better alternatives these days, like generics and anonymous methods. But if compatibility with Delphi 7 is required, the above might be a solution.
is this piece of code safe from memory leaks?
s := TStringList.Create; // create first object
try
// Here line comes that seems to be dangerous
s := GetSomeSettings; // Overrides reference to first object by second one
finally
s.free; // Destroying only second object, leave first object to live somewhere in memory
end;
function GetSomeSettings : TStringList;
var
rawString : string;
settings : TStringList;
begin
// Singleton pattern implementation
// Trying to find already existing settings in class variable
settings := TSettingsClass.fSettings;
// If there is no already defined settings then get them
if not Assigned(settings) then
begin
GetSettingsInDB(rawString);
TSettingsClass.fSettings := ParseSettingsString(rawString);
settings := TSettingsClass.fSettings;
end;
Result := settings;
end;
I'm wondering s := GetSomeSettings; potentially harmful and ignoring first object, keeps it in the memory?
Yes, the StringList created on line 1 is leaked.
Essentialy, you are doing:
s := TStringList.Create;
s := AnotherStringList;
AnotherStringList.Free;
As for the GetSomeSettings routine:
Normally it is not wise or encouraged to return newly created instances as function results, because you transfer the responsibility for ownership and destruction to the calling code. Unless you have a mechanism/framework in place that takes care of it, which seems to be the case with your TSettingsClass, but there is not enough evidence for that in this little piece of code.
Nevertheless, the combination of both pieces of code display another problem: After s.Free, TSettingsClass.fSettings is destroyed but not nil. Thus the second time GetSomeSettings is called, it returns a dangling pointer.
1) you should not ask when you can check in two minutes!
program {$AppType Console};
uses Classes, SysUtils;
type TCheckedSL = class(TStringList)
public
procedure BeforeDestruction; override;
procedure AfterConstruction; override;
end;
procedure TCheckedSL.BeforeDestruction;
begin
inherited;
WriteLn('List ',IntToHex(Self,8), ' going to be safely destroyed.');
end;
procedure TCheckedSL.AfterConstruction;
begin
WriteLn('List ',IntToHex(Self,8), ' was created - check whether it is has matched destruction.');
inherited;
end;
procedure DoTest; var s: TStrings;
function GetSomeSettings: TStrings;
begin Result := TCheckedSL.Create end;
begin
Writeln('Entered DoTest procedure');
s := TCheckedSL.Create; // create first object
try
// Here line comes that seems to be dangerous
s := GetSomeSettings; // Overrides reference to first object by second one
finally
s.free; // Destroying only second object, leave first object
end;
Writeln('Leaving DoTest procedure');
end;
BEGIN
DoTest;
Writeln;
Writeln('Check output and press Enter when done');
ReadLn;
END.
2) Still that could be safe in few niche cases.
in FPC (http://FreePascal.org) S could be a "global property" of some unit, having a setter which would free old list.
in Delphi Classic S could be of some interface type, supported by the created object. Granted, standard TStringList lacks any interface, but some libraries ( for example http://jcl.sf.net ) do offer interface-based string lists, with richer API (iJclStringList type and related).
in Delphi/LLVM all objects were made reference-counted, like interfaces without GUID's. So that code would be safe there.
You can declare S as a record - a so-called Extended Record having re-defined class operator Implicit so that the typecast s{record} := TStringList.Create would free the previous instance before assigning a new one. That is dangerous though, as it is VERY fragile and easy to misuse, and destroy the list in some other place leaving a dangling pointer inside the S record.
Your object may be not that vanilla TStringList, but some subclass, overriding constructors or AfterConstruction to register itself in some list, that would be all-at-once in some place. Kind of Mark/Sweep heap management around large chunk of workload. VCL TComponent may be seen as following this pattern: form is owning its component and frees them when needed. And this is what you - in reduced form - are trying to do with TSettingsClass.fSettings containter (any reference is 1-sized container). However those frameworks do require a loopback: when the object is freed it should also remove itself from all the containers, referencing it.
.
procedure TCheckedSL.BeforeDestruction;
begin
if Self = TSettingsClass.fSettings then TSettingsClass.fSettings := nil;
inherited;
end;
class procedure TSettingsClass.SetFSettings(Value);
var fSet2: TObject;
begin
if fSettings <> nil then begin
fSet2 := fSettings;
f_fSettings := nil; // breaking the loop-chain
fSet2.Destroy;
end;
f_fSettings := Value;
end;
class destructor TSettingsClass.Destroy;
begin
fSettings := nil;
end;
However then - by the obvious need to keep design symmetric - the registration should also be done as a part of the class. Who is responsible for de-registration is usually the one responsible for registration as well, unless there are reasons to skew the design.
procedure TCheckedSL.AfterConstruction;
begin
inherited;
TSettingsClass.fSettings := Self;
end;
...
if not Assigned(settings) then
begin
GetSettingsInDB(rawString);
TCheckedSL.Create.Text := ParseSettingsString(rawString);
settings := TSettingsClass.fSettings;
Assert( Assigned(settings), 'wrong class used for DB settings' );
end;
Result := settings;
How would you "extract" nested try/finally blocks from a routine into a reusable entity? Say I have
procedure DoSomething;
var
Resource1: TSomeKindOfHandleOrReference1;
Resource2: TSomeKindOfHandleOrReference2;
Resource3: TSomeKindOfHandleOrReference3;
begin
AcquireResource1;
try
AcquireResource2;
try
AcquireResource3;
try
// Use the resources
finally
ReleaseResource3;
end;
finally
ReleaseResource2;
end;
finally
ReleaseResource1;
end;
end;
and want something like
TDoSomething = record // or class
strict private
Resource1: TSomeKindOfHandleOrReference1;
Resource2: TSomeKindOfHandleOrReference2;
Resource3: TSomeKindOfHandleOrReference3;
public
procedure Init; // or constructor
procedure Done; // or destructor
procedure UseResources;
end;
procedure DoSomething;
var
Context: TDoSomething;
begin
Context.Init;
try
Context.UseResources;
finally
Context.Done;
end;
end;
I want this to have the same exception-safety as the nested original. Is it enough to zero-initialize the ResourceN variables in TDoSomething.Init and do some if Assigned(ResourceN) then checks in TDoSomething.Done?
There are three things about classes that make this idiom safe and easy:
During the memory-allocation phase of the constructor (before the real constructor body runs), class-reference fields get initialized to nil.
When an exception occurs in a constructor, the destructor is called automatically.
It's always safe to call Free on a null reference, so you never need to check Assigned first.
Since the destructor can rely on all fields to have known values, it can safely call Free on everything, regardless of how far the constructor got before crashing. Each field will either hold a valid object reference or it will be nil, and either way, it's safe to free it.
constructor TDoSomething.Create;
begin
Resource1 := AcquireResource1;
Resource2 := AcquireResource2;
Resource3 := AcquireResource3;
end;
destructor TDoSomething.Destroy;
begin
Resource1.Free;
Resource2.Free;
Resource3.Free;
end;
Use it the same way you use any other class:
Context := TDoSomething.Create;
try
Context.UseResources;
finally
Context.Free;
end;
Yes, you can use a single try/finally/end block for multiple resources with zero-initialization.
Another possible solution can be found in Barry Kelly blog
The pattern with testing on Assigned in finally is used in the Delphi source. You do kind of the same thing but I think you should move Context.Init to capture exception from Context.Init.
procedure DoSomething;
var
Context: TDoSomething;
begin
try
Context.Init;
Context.UseResources;
finally
Context.Done;
end;
end;
Edit 1 This is how you should do it without Context.Init and Context.Done. If you place all AquireResource code before try you will not free Resource1 if you get an exception in AcquireResource2
procedure DoSomething;
var
Resource1: TSomeKindOfHandleOrReference1;
Resource2: TSomeKindOfHandleOrReference2;
Resource3: TSomeKindOfHandleOrReference3;
begin
Resource1 := nil;
Resource2 := nil;
Resource3 := nil;
try
AcquireResource1;
AcquireResource2;
AcquireResource3;
//Use the resources
finally
if assigned(Resource1) then ReleaseResource1;
if assigned(Resource2) then ReleaseResource2;
if assigned(Resource3) then ReleaseResource3;
end;
end;
I've got a pretty big setup form which I'd like to populate with data from a class. so I'm doing a lot of
Edt1.text := ASettings.FirstThing;
I'd like to avoid
Edt1.onchange := nil;
Edt1.text := ASettings.FirstThing;
Edt1.onchange := edt1Onchange;
How do I change the text in a text box and sidestep the onchange event.
I have used something like changing the OnChange handler, but more often, I use a flag.
updatingFromCode := true;
Edt1.Text := ASettings.FirstThing;
updatingFromCode := false;
then
procedure TForm1.OnChange(...);
begin
if updatingFromCode then
Exit;
...
Also, rather than hardcoding the OnChange the the actual OnChange procedure, I would store the Edit control's current value, then reset it (which will work if it is not set, or if another place has changed it, etc.)
oldOnChange := Edt1.OnChange;
Edt1.OnChange := nil;
Edt1.Text := ASettings.FirstThing;
Edt1.OnChange := oldOnChange;
You might consider using an object to manage the NIL'ing of the event and restoring the previously installed event handler. It's a little dangerous to assume that the event to be restored just happens to be the one assigned at design-time/which happens to have the "name that fits" - you should always save/restore the currently assigned handler, just to be safe.
This would provide an even more re-usable utility than the SetTextWithoutOnChange() routine:
TSuspendEvent = class
private
fObject: TObject;
fEvent: String;
fHandler: TMethod;
public
constructor Create(const aObject: TObject; aEvent: String);
destructor Destroy; override;
end;
constructor TSuspendEvent.Create(const aObject: TObject; aEvent: String);
const
NILEvent : TMethod = (Code: NIL; Data: NIL);
begin
inherited Create;
fObject := aObject;
fEvent := aEvent;
fHandler := GetMethodProp(aObject, aEvent);
SetMethodProp(aObject, aEvent, NILEvent);
end;
destructor TSuspendEvent.Destroy;
begin
SetMethodProp(fObject, fEvent, fHandler);
inherited;
end;
In usage, this would look something like:
with TSuspendEvent.Create(Edit1, 'OnChange') do
try
Edit1.Text := 'Reset!';
finally
Free;
end;
For the "Thou shalt not use 'with' crowd" - by all means declare yourself an additional local variable and use that if it will help you sleep easier at night. :)
Or, to make it even more convenient to use and eliminate "with", I would make the TSuspendEvent class an interfaced object and wrap its use in a function that yielded an interface reference to it that could be allowed to "live in scope", as exemplified by my AutoFree() implementation. In fact, you could use AutoFree() as-is to manage this already:
AutoFree(TSuspendEvent.Create(Edit1, 'OnChange'));
Edit1.Text := 'Reset!';
Dsabling events for a period that extends beyond the scope of a single procedure requires more management than any helper utilities are likely to be able to provide in a generic fashion I think, at least not without also having specific means for restoring events explicitly, rather than automatically.
If you simply wrapped TSuspendEvent inside it's own interface yielding function, following the same pattern as AutoFree() you could simplify this further to:
SuspendEvent(Edit1, 'OnChange');
Edit1.Text := 'Reset!';
As a final note, I think it should be fairly easy to see how this could be quite simply extended to support suspending multiple events on an object in a single call, if required, for example:
SuspendEvent(Edit1, ['OnChange', 'OnEnter']);
As far as I know if the OnChange of your object is designed to fire when the Text property is changed you have to stick with setting the event to nil temporarly. Myself, I do it this way (in a try finally):
Edt1.onchange := nil;
try
Edt1.text := ASettings.FirstThing;
finally
Edt1.onchange := edt1Onchange;
end;
You could also do some procedure to handle it for you:
procedure SetTextWithoutOnChange(anEdit: TEdit; str: String);
var
anEvent: TNotifyEvent;
begin
anEvent := anEdit.OnChange;
anEdit.OnChange := nil;
try
anEdit.Text := str;
finally
anEdit.OnChange := anEvent;
end;
end;
I know this is an old question but I thought I would add my solution that does not involve any of the complicated procedures outlined in the previous answers in case it comes up in another search.
The problem is the onChange event itself. I don't use it at all for text fields.
remove all OnChange and use the OnExit instead and tie it to the OnKeyUp.
All Edits have a common ancestor TCustomEdit.
I generally use one method called CustomEditKeyUp and point all the edits on a form to this single method (TEdit, TLabeledEdit etc etc.)
type THack = class(TCustomEdit);
procedure TForm1.CustomeEditKeyUP(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key=VK_RETURN) and (Sender is TCustomEdit) then
{This is just to isolate the one occasion when they press the
enter but don't exit immediately}
if Assigned(THack(Sender).OnExit) then THack(Sender).OnExit(Sender);
end;
For some reason, the OnExit is private in a TCustomEdit so the Hack is needed. If you know that the edits are from a different route where the OnExit is public, cast if differently and the Hack is not necessary.
Then For each Edit control, use a specific OnExit
procedure TForm1.MyEditExit(Sender: TObject);
begin
if MyEdit.Modified then
begin
{Do Something here}
MyEdit.Modified := false;
end;
end;
If you want to change the value programmatically without it firing 'OnExit'
....
MyEdit.Text :='I've changed it'
MyEdit.Modified := false;
....
The big advantage for me is that when I am parsing the input for say a valid number, I only have to do this once when editing is completed and not for every single backspace, delete insert etc all surrounded by try except as the various formating functions error out as they don't understand spaces etc.. For database etc, the number of calls will be greatly reduced.
That's my two penneth.
Another way is by using Class Helpers introduced in Delphi 8.
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Class_and_Record_Helpers_(Delphi)
You could write:
type
TEditHelper = class helper for TEdit
public
procedure SetTextDisableOnChange(const AText: string);
end;
{ TEditHelper }
procedure TEditHelper.SetTextDisableOnChange(const AText: string);
var
OnChangeTmp: TNotifyEvent;
begin
OnChangeTmp:=OnChange;
try
OnChange:=nil;
Text:=AText;
finally
OnChange:=OnChangeTmp;
end;
end;
and then:
EditCtrl.SetTextDisableOnChange('I do not invoke OnChange!');
EditCtrl.Text:='I invoke OnChange';