Delphi - callback wrong code - delphi

I believe I'm too tired, and I don't understand why a small callback doesn't work. I have 2 frames, created dynamically, I show the first one and at a click, I show the second one. When I finish the work with the second one, I want to show the first frame and free the second frame. Code is bellow:
code for the first frame:
procedure CommingBackFromFrame(aFrame:TFrame);
procedure TfraMain.ComingBackFromFrame(aFrame:TFrame);
begin
if Assigned(aFrame) then
begin
try
aFrame.Hide;
FreeAndNil(aFrame);
except on e:Exception do
//make a log
end;
Self.Show;//first frame show
end;
//code which creates the second frame
wFrm := TFrameType.Create(Application);//create the second frame
with wFrm do
begin
GoBack:=ComingBackFromFrame(wFrm);//error here
parent:=Self;
Show;
end; //with
Application.ProcessMessages;
code for the second frame:
TCallBack = procedure(aFrame:TFrame) of object;//callback declaration
TFrameType = class(Tframe)
...
private
FGoBack:TCallBack;
public
property GoBack:TCallBack read FGoBack write FGoBack;//publish callback
....
//at a moment, return to frame 1
if Assigned(fgoback) then
GoBack(Self);
Can anyone help me this simple thing?

BTW this is bad practice - free an object from its own code. Try to do that by message handler via PostMessage() to ensure that VCL finished all its work before freeing object.
Something like this:
TFrameType = class(TFrame)
protected
procedure FreeMe(var Msg TMessage) message WM_FREE_MY_FRAME;
public
procedure PostponedFree;
end;
procedure TFrameType.FreeMe(var Msg TMessage);
begin
Free;
end;
procedure TFrameType.PostponedFree;
begin
PostMessage(Self.Handle, WM_FREE_MY_FRAME, 0, 0);
end;
And call PostponedFree.
PS Code may not be accurate - I haven't started Delphi now. Sorry.

You're calling CommingBackFromFrame. So unless it's return-type is TCallBack it's obvious that it doesn't compile.
You might want to do GoBack:=CommingBackFromFrame; instead which subscribes the method CommingBackFromFrame to the even GoBack. Or perhaps GoBack:=wFrm.CommingBackFromFrame; depending on where CommingBackFromFrame is declared.
SideNote: You have a typo, the word is "coming" and not comming

Specify where CommingBackFromFrame comes from and what it does; without that, CodeInChaos` answer is the best you can get.
Is it part of wFrmDblDet, or part of your encompassing scope (the usage of with obfuscates that)?
In your current code, ComingBackFromFrame(wFrm) should return a TCallBack, but I think that was not your intent.
--jeroen

+1 to all.Thank you for your answers, especially to Abelisto et Jeroen, I've resolved now all the problems. Until Abelisto suggested PostMessage I've encountered lots of errors. Entire solution is bellow :
first frame, or FrmMain :
const WM_MY_MESSAGE = WM_USER + 0;
type
TfraMain = class(TFrame)
...
private
FFraChild : TFraChild;//second frame
procedure OnMyMessage(var Msg: TMessage); message WM_MY_MESSAGE;
procedure ComingBackFromFrame(aFrame:TFrame);
....
//step when the second frame is created
FFraChild := TFraChild.Create(Application);
with FFraChild do
begin
GoBack:= ComingBackFromFrame;
parent:=Self;
Show;
end; //with
....
procedure TfraMain.ComingBackFromFrame(aFrame:TFrame);
begin
if aFrame<>nil then
begin
try
aFrame.Hide;
PostMessage(Self.Handle,WM_MY_MESSAGE,0,0);
except on e:Exception do
// log error
end;
end;
end;
procedure TfraMain.OnMyMessage(var Msg: TMessage);
begin
FreeAndNil(FFraChild);
end;
second frame or frame 'child'
type
TCallBack = procedure(aFrame:TFrame) of object;
TFraChild = class(TFrame)
...
private
FGoBack:TCallBack;
public
property GoBack:TCallBack read FGoBack write FGoBack;
....
//after all operations with it are finished
if Assigned(fgoback) then
FGoBack(Self);
#Jeroen, I didn't found something related to Frames as the 'Release' existing in the TForm's implementation.
Best regards,
Radu

Related

Access violation on recursive function with interfaces

I'm trying to resolve this problem. It's weird because it doesn't throw a Stack Overflow error but an Access Violation error. (See code below.)
Whenever CallDestructor function is called, DestroyChildren is called. So it's a recursive function.
When I'm handling only a few objects it works fine. My trouble is when I have a lot of instances to destroy.
unit AggregationObject;
interface
uses
System.Classes, System.Generics.Collections, System.Contnrs;
type
IParentObject = Interface;
IChildObject = Interface
['{061A8518-0B3A-4A1C-AA3A-4F42B81FB4B5}']
procedure CallDestructor();
procedure ChangeParent(Parent: IParentObject);
End;
IParentObject = Interface
['{86162E3B-6A82-4198-AD5B-77C4623481CB}']
procedure AddChild(ChildObject: IChildObject);
function RemoveChild(ChildObject: IChildObject): Integer;
function ChildrenCount(): Integer;
procedure DestroyChildren();
End;
TName = type String;
TChildObject = class(TInterfacedPersistent, IChildObject)
protected
FParentObject: IParentObject;
public
constructor Create( AParent: IParentObject ); virtual;
{IChildObject}
procedure CallDestructor();
procedure ChangeParent(Parent: IParentObject);
end;
TParentObject = class(TInterfacedPersistent, IParentObject)
strict private
FChildren: TInterfaceList;
private
FName: TName;
public
constructor Create();
{Polimórficos}
procedure BeforeDestruction; override;
{IParentObject}
procedure AddChild(AChildObject: IChildObject);
function RemoveChild(AChildObject: IChildObject): Integer;
function ChildrenCount(): Integer;
procedure DestroyChildren();
property Name: TName read FName write FName;
end;
TAggregationObject = class(TChildObject, IParentObject)
private
FController: IParentObject;
function GetController: IParentObject;
public
constructor Create( AParent: IParentObject ); override;
destructor Destroy(); override;
{Controller implementation}
public
property Controller: IParentObject read GetController implements IParentObject;
end;
implementation
uses
System.SysUtils, Exceptions;
{ TChildObject }
procedure TChildObject.CallDestructor;
begin
Self.Free;
end;
procedure TChildObject.ChangeParent(Parent: IParentObject);
begin
if Self.FParentObject <> nil then
IParentObject( Self.FParentObject ).RemoveChild( Self );
Self.FParentObject := Parent;
if Parent <> nil then
Parent.AddChild( Self );
end;
constructor TChildObject.Create(AParent: IParentObject);
begin
if not (AParent = nil) then
begin
FParentObject := AParent;
FParentObject.AddChild( Self );
end;
end;
{ TParentObject }
procedure TParentObject.AddChild(AChildObject: IChildObject);
begin
if (FChildren = nil) then FChildren := TInterfaceList.Create();
FChildren.Add( AChildObject );
end;
procedure TParentObject.BeforeDestruction;
begin
inherited;
DestroyChildren();
end;
function TParentObject.ChildrenCount: Integer;
begin
Result := -1;
if Assigned(FChildren) then
Result := FChildren.Count;
end;
constructor TParentObject.Create;
begin
FName := 'NoName';
end;
procedure TParentObject.DestroyChildren;
var
Instance: IChildObject;
begin
while FChildren <> nil do
begin
Instance := FChildren.Last as IChildObject;
if Instance <> nil then
begin
if RemoveChild( Instance ) > -1 then
begin
try
Instance.CallDestructor();
except on E: Exception do
raise EChildAlReadyDestroyed.Create('Parent: ' + Self.FName + #13#10 + E.Message);
end;
end;
end;
end;
end;
function TParentObject.RemoveChild(AChildObject: IChildObject): Integer;
begin
Result := -1;{if has no children}
if (FChildren <> nil) then
begin
Result := 0;{ Index 0}
if ( ( FChildren.Items[0] as IChildObject) = AChildObject) then
FChildren.Delete(0)
else
Result := FChildren.RemoveItem( AChildObject, TList.TDirection.FromEnd );
if (FChildren.Count = 0) then
begin
FreeAndNil( FChildren );
end;
end;
end;
{ TAggregationObject }
constructor TAggregationObject.Create(AParent: IParentObject);
begin
inherited Create(AParent);
FController := TParentObject.Create();
( FController as TParentObject ).Name := Self.ClassName + '_Parent';
end;
destructor TAggregationObject.Destroy;
begin
( FController as TParentObject ).Free;
inherited;
end;
function TAggregationObject.GetController: IParentObject;
begin
Result := FController;
end;
end.
OP managed to identify the problem, but hasn't posted an answer. I provide an edited version of his comment and add a more detailed explanation.
I think the problem was with mixing object reference and interface. Even though my objects aren't controlled by RefCount something hapens backstage: "However, due to the nature of interface references, _AddRef and _Release are still going to be called when the reference goes out of scope. If the class has been destroyed prior to that time, then you have an AV in _IntfClear." My last call in stack is _IntfClear or _IntfCopy. I think this is the problem. I'm not sure about how to correct that, so I've changed to an abstract class.
The Access Violations aren't caused by mixing object references and interfaces; there are ways to do this safely.
But they are caused by the fact that Delphi attempts to _Release a reference on an object that has already been destroyed.
However this raises the question: "Why does the AV only happen sometimes, and not all the time?"
To explain, I'm going to talk about an illegal memory operation. By this I mean a piece of code (or object) that accesses memory it is not supposed to.
You don't get an AV every time your program performs an illegal memory operation. An AV will only be raised if the illegal memory operation is noticed! There are 2 main reasons it might be unnoticed:
It may be "illegal" for one object in your program to access certain memory, but if it is legal for another instance to access that memory - then there is no way for the system to notice that you've actually committed an illegal memory operation.
Most of the time, FastMem requests memory from the OS in larger "pages" than what you actually request from FastMem. It then keeps track of multiple smaller allocations on the page. The page is only returned to the OS when there are no smaller allocations left on the page. Therefore again, the OS won't notice any illegal memory operations on a page still allocated to your program.
The second reason above is why a small number of objects doesn't cause an AV: The page on which the object was allocated is still allocated to your program.
But when you have a large number of instances: sometimes when you destroy an object, it the last one on a page; and the page is returned to the OS... Therefore you get AV when _Release is called on that page.
So, how do you fix it?
Well, the option you chose (use an abstract class instead of an interface) works. But you lose the benefits of interfaces. However, I would suggest not trying to manually control the destruction of interface objects. One of the benefits of interface references is that the underlying objects will self-destruct (if you let them).
I suspect you're doing this because you're mixing object references and interface references. So instead of forcing your interfaces behave like objects (and you've gone to a lot of trouble to do so), rather simply let each of your object references manually add a reference to the interface. You can do this with the following code:
(ObjectRef as IUnkown)._AddRef;
//Do stuff with ObjectRef
(ObjectRef as IUnkown)._Release;
SIDE NOTE:
You found it weird that no Stack Overflow error was raised. (And obviously you figured out why the AV was raised.) I'd like to point out that typically recursion will only trigger SO errors: if the recursion is very deep (and I mean very); or if each recursion allocates a rather large amount of memory on the stack.
The detail was the difference.
TValueObject is a specialization of TAggregationObject and it implements IMasterValue, something like this:
IMasterValue = interface
//GUID Here
function MasterValue: variant;
end;
TValueObject = class(TAggregationObject , IMasterValue)
public
function MasterValue: variant;
end;
So I have:
TSomeService = class
public
function Find(AMasterValue: IMasterValue): TValueObject;
end;
procedure DoSome(AValueObject: TValueObject);
begin
with TSomeService.Create() do
begin
try
Find(AValueObject); //This will get cleared when method exits
finally
AValueObject.Free(); //But the object is destroyed before that
end;
end;
end;
//Occurs on great concurrency because the memory will be reused, otherwise the memory is still there hidding the problem. The threads running loop for destruction will show the problem.
The workaround for that, is:
procedure DoSome(AValueObject: TValueObject);
var
LMasterValue: IMasterValue;
begin
with TSomeService.Create() do
begin
try
LMasterValue := AValueObject;
try
Find(LMasterValue);
finally
LMasterValue := nil;
end;
finally
AValueObject.Free();
end;
end;
end;

RecordIndex out of range - DevExpress

I am having issues with a DevExpress VCL grid, throwing out a "RecordIndex out of range" error, despite not directly calling any record functions of the grid in that scenario.
What I am doing is fairly simple: once the record is changed(AfterScroll), a method is called. Inside this method, I call another method which assigns the data source and data fied name according to a field in the new record.
The code is pretty simple and goes like this:
procedure TValidatedOrders.UpdateDispenseNotes;
var Dataset : TDataSet;
GroupTypeFieldName : String;
DataSource : TDataSource;
DataFieldName : String;
GroupType : Integer;
procedure SetSpecsDataSource;
begin
DataSource := DMValidatedDispense.DSDispenseGroupSpecs;
DataFieldName := 'GLAZING_INSTRUCTIONS';
end;
procedure SetCLsDataSource;
begin
DataSource := DMValidatedDispense.DSDispenseGroupCLs;
DataFieldName := 'WEAR_INSTRUCTIONS';
end;
begin
// Step 1: Get the group type
If GetTopPage = cTopPageOrders Then
Dataset := DMValidatedDispense.CDSLabOrders
Else
Dataset := DMValidatedDispense.CDSLabDispenses;
GroupType := Dataset.FieldByName( 'GROUP_TYPE' ).AsInteger;
// Step 2: Assign the MemoDispenseNotes data binding appropriately
If GetTopPage = cTopPageOrders Then
Begin
case GroupType of
cOrderGroupSpecs, cOrderGroupFrame,
cOrderGroupLens, cOrderGroupGlazing: SetSpecsDataSource;
cOrderGroupCLs: SetCLsDataSource;
else SetSpecsDataSource;
end;
End
Else
Begin
case GroupType of
cDispenseGroupSpecs: SetSpecsDataSource;
cDispenseGroupCLs: SetCLsDataSource;
else SetSpecsDataSource;
end;
End;
MemoDispenseNotes.DataBinding.DataSource := DataSource;
MemoDispenseNotes.DataBinding.DataField := DataFieldName;
end;
The original code where the above method is called is just too long to report here, anyway it contains stuff like panels show/hide, checks of the type .FieldByName( 'GROUP_TYPE' ).AsInteger = SomeValue and so on: there's no locate, no FindKey or anything like that.
As I said, there's no record selection directly involved(not sure here what's going on inside the DevExpress grid though...) and I don't see why I'd be getting such error.
Anybody has got a brilliant idea of what could be going on?
Thank you very much!
AFAICS this method only changes the DataSource of a TcxDBMemo component. The culprit must be elsewhere and is most probably some event.
As a first step to debug it, I would try to use BeginUpdate and EndUpdate on the view of your grid at the beginning and end of the code.

Delphi 2010+ and "Left side cannot be assigned to" in read-only records: can this be disabled?

I know what changed. I know why. But..
TComplicatedCallMaker = record
Param1: TRecordType;
Param2: TRecordType;
{...}
Param15: TRecordType;
procedure Call;
end;
function ComplicatedCall: TComplicatedCallMaker;
begin
{ Fill default param values }
end;
procedure DoingSomeWorkHere;
begin
with ComplicatedCall do begin
Param7 := Value7;
Param12 := Value12;
Call;
end;
end;
This has perfectly worked before Delphi 2010. An extremely useful technique for making calls which accept a load of parameters but usually only need two or three. Never the same ones though.
And now it gives... guess what?
E2064: Left side cannot be assigned to.
Can't this helpful new behavior be disabled somehow? Any ideas on how to modify the pattern so it works?
Because seriously, losing such a handy technique (and rewriting a bunch of code) for no apparent reason...
I find it a little surprising that this ever worked but since you say it did I'm sure you are right. I'd guess the change was made without consideration for record methods. Without the ability to call methods then this construct would be rather pointless.
Anyway, the compiler isn't going to let you off the hook on this one so you'll have to do this:
type
TRecordType = record end;
TComplicatedCallMaker = record
Param1: TRecordType;
procedure Call;
end;
function ComplicatedCall: TComplicatedCallMaker;
begin
{ Fill default param values }
end;
procedure DoingSomeWorkHere(const Value: TRecordType);
var
CallMaker: TComplicatedCallMaker;
begin
CallMaker := ComplicatedCall;
with CallMaker do begin
Param1 := Value;
Call;
end;
end;
I... think I did it
I hope Delphi developers see what they make their programmers do!
type
PCallMaker = ^TCallMaker;
TCallMaker = record
Param1: integer;
Param2: integer;
function This: PCallMaker; inline;
procedure Call; inline;
end;
function TCallMaker.This: PCallMaker;
begin
Result := #Self;
{ Record functions HAVE to have correct self-pointer,
or they wouldn’t be able to modify data. }
end;
procedure TCallMaker.Call;
begin
writeln(Param1, ' ', Param2);
end;
function CallMaker: TCallMaker; inline
begin
Result.Param1 := 0;
Result.Param2 := 0;
end;
procedure DoingSomeWorkHere;
var cm: TCallMaker;
begin
{Test the assumption that cm is consistent}
cm := CallMaker;
if cm.This <> #cm then
raise Exception.Create('This wasn''t our lucky day.');
{Make a call}
with CallMaker.This^ do begin
Param1 := 100;
Param2 := 500;
Call;
end;
end;
This works, preserves all the good points of the old version (speed, simplicity, small call overhead) but aren't there any hidden problems with this approach?

Delphi - form maximized event

I want to call a function after a form has been maxmized or restored.
I know I can something like this:
procedure TfrmMain.WMSysCommand;
begin
if (Msg.CmdType = SC_MAXIMIZE) OR (Msg.CmdType = SC_RESTORE) then
begin
Showmessage(IntToStr(frmMain.Height));
end;
DefaultHandler(Msg) ;
end;
But the problem is: this event is fired before the form is actually resized - so when the form is maximized, I get the height of the form BEFORE it was maxmized (but I want the width of the form after it has been maximized).
How to do this? Thanks!
the following link maybe will help you:
http://www.tek-tips.com/viewthread.cfm?qid=809465&page=176
declare this into interface section of this unit
Procedure sizeMove (var msg: TWMSize); message WM_SIZE;
and implementation of this procedure:
Procedure TfrmMain.sizeMove (var msg: TWMSize);
begin
inherited;
if (msg.SizeType = SIZE_MAXIMIZED) OR (msg.SizeType = SIZE_RESTORED)then
resizeQlikViewReports();
end;
You can use OnResize either and check WindowState. It's easier way.

Delphi and prevent event handling

How do you prevent a new event handling to start when an event handling is already running?
I press a button1 and event handler start e.g. slow printing job.
There are several controls in form buttons, edits, combos and I want that a new event allowed only after running handler is finnished.
I have used fRunning variable to lock handler in shared event handler. Is there more clever way to handle this?
procedure TFormFoo.Button_Click(Sender: TObject);
begin
if not fRunning then
try
fRunning := true;
if (Sender = Button1) then // Call something slow ...
if (Sender = Button2) then // Call something ...
if (Sender = Button3) then // Call something ...
finally
fRunning := false;
end;
end;
Another option (that does not require a flag field) would be to temporarily assign NIL to the event:
procedure TForm1.Button1Click(Sender: TObject);
var
OldHandler: TNotifyEvent;
begin
OldHandler := (Sender as TButton).OnClick;
(Sender as TButton).OnClick := nil;
try
...
finally
(Sender as TButton).OnClick := OldHandler;
end;
end;
For convenience sake this could be wrapped into an interface:
interface
function TempUnassignOnClick(_Btn: TButton): IInterface;
implementation
type
TTempUnassignOnClick = class(TInterfacedObject, IInterface)
private
FOldEvent: TNotifyEvent;
FBtn: TButton;
public
constructor Create(_Btn: TButton);
destructor Destroy; override;
end;
constructor TTempUnassignOnClick.Create(_Btn: TButton);
begin
Assert(Assigned(_Btn), 'Btn must be assigned');
inherited Create;
FBtn := _Btn;
FOldEvent := FBtn.OnClick;
FBtn.OnClick := NIL;
end;
destructor TTempUnassignOnClick.Destroy;
begin
FBtn.OnClick := FOldEvent;
inherited;
end;
function TempUnassignOnClick(_Btn: TButton): IInterface;
begin
Result := TTempUnassignOnClick(_Btn);
end;
to be used like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
TempUnassignOnClick(Sender as TButton);
...
end;
Your solution is OK. You can also link button clicks to actions and enable/disable actions in TAction.OnUpdate event handler, but you still need fRunning flag to do it. The "if no fRunning" line may be not nessesary here, but I don't removed it because it is more safe:
// Button1.Action = acButton1, Button2.Action = acButton2, etc
procedure TForm1.acButtonExecute(Sender: TObject);
begin
if not fRunning then
try
fRunning:= True;
if (Sender = acButton1) then // Call something slow ...
if (Sender = acButton2) then // Call something ...
if (Sender = acButton3) then // Call something ...
finally
fRunning:= False;
end;
end;
procedure TForm1.acButtonUpdate(Sender: TObject);
begin
(Sender as TAction).Enabled:= not fRunning;
end;
You don't have to do this at all, since all of this is happening in the main (VCL) thread:
No other button (VCL) event can be entered until the previous (VCL) event handler has returned...
The simultaneous execution of another event handler could only happen unexpectedly if some other thread was preemptively entering a second button event (before the first one has completed), but that can't happen, since there is only one VCL thread.
Now if the lengthy thing you are doing is done in another thread because you don't want it to block the GUI, then you can simply set the Button.Enabled property to false until your processing is done.
And if you decide to just stick in the button event until everything has completed, use application.processmessages frequently enough in your processing loop to prevent the gui from freezing. In which case, yes, you must disable the original button to prevent reentry.
As Gerry already mentioned in one of the comments, you can disable entire form:
procedure TFormFoo.Button_Click(Sender: TObject);
begin
try
Enabled := False;
//...
finally
Enabled := True;
end;
end;
If your app is a single-threaded one, then while your event-handler code is running, your app cannot run other codes, so all calls to that event-handler will be serialized, and you don't need to be worried.
If your event-handler is running any asynchronous job, then you can use the technique you presented in your question.

Resources