I want to create an Tinterfacedobject that contains (ao) two properties ObjectLinks and ObjectBacklinks. Objectlinks contains interface references to other objects, ObjectBacklinks contains the reverse of those links
Both properties consist of a TWeakIntfDictionary = class(TDictionary)
The Backlinks property is maintained in the ObjectLinks.ValueNotify event
To make sure the interface referecnes are removed from the dictionary when the original object is free'd, a notification algorith (same as TFmxObject uses) is put into place
As suspected I'm running into all kinds of circular reference problems when creating so many references to the same interfacedobjects :( but i can't seem to get out of this problem. When the FreeNotification is called from the object being destroyed, everything goes fine until it return fro the FreeNOtification. At that point the .Destroy of the object is called again :-(
{1 Dictionary of interfaces (using weak references) using Free notification }
TWeakIntfDictionary = class(TDictionary<int64, IInterface>, IFreeNotificationMonitor)
protected
procedure ValueNotify(const Value: IInterface; Action: TCollectionNotification); override;
public
procedure FreeNotification(aObject: TObject);
end;
implementation
procedure TWeakIntfDictionary.FreeNotification(aObject: TObject);
var
lObj: TPair<int64, IInterface>;
begin
//Object is going to be destroyed, remove it from dictionary
for lObj in Self do
begin
if (lObj.Value as TObject).Equals(aObject) then
begin
Remove(lObj.Key);
Break;
end;
end;
end;
procedure TWeakIntfDictionary.ValueNotify(const Value: IInterface; Action: TCollectionNotification);
var
lDestrIntf: IFreeNotificationBehavior;
begin
// When a TObject is added to the dictionary, it must support IDestroyNotification
// This dictionary is than added to the notificationlist of the TObject
if Supports(Value, IFreeNotificationBehavior, lDestrIntf) then
case Action of
cnAdded: begin
lDestrIntf.AddFreeNotify(Self);
lDestrIntf._Release;
end;
cnRemoved,
cnExtracted: begin
lDestrIntf.RemoveFreeNotify(Self);
end;
end
else
raise EWeakInftDictionaryException.Create('Object added to TWeakIntfDictionary does not support IFreeNotificationBehavior');
inherited;
end;
Anyone know of a existing implementation of a WeakReferences Dictionary?
Anyone any suggestions how to solve this ?
Found the solution in the following code
procedure TWeakIntfDictionary.FreeNotification(aObject: TObject);
var
...
begin
//Object is going to be destroyed, remove it from dictionary
lSavedEvent := FDict.OnValueNotify;
FDict.OnValueNotify := nil;
lRemoveList := TList<TKey>.Create;
try
for lPair in FDict do
begin
pointer(lIntf) := lPair.Value;
if (lIntf as TObject) = aObject then
lRemoveList.Add(lPair.Key);
end;
pointer(lIntf):=nil; // avoid _release for the last item
for lKey in lRemoveList do
FDict.Remove(lKey);
finally
FDict.OnValueNotify := lSavedEvent;
lRemoveList.Free;
end;
end;
Related
I am adding components to a form at run time and I am also adding events that change properties of these components in a dictionary to call them later.
TEventBeforeInsert = function(var AComponent: TComponent; AForm: TForm): Boolean of Object;
TFieldBase = class
private
FEvent:TEventBeforeInsert;
....
function TFieldBase.EventBeforeInsert: TEventBeforeInsert;
begin
Result:=FEvent;
end;
function TFieldBase.EventBeforeInsert(AEvent: TEventBeforeInsert): TFieldBase ;
begin
FEvent:=AEvent;
Result:=Self;
end;
....
The Form Call
TFormBase.New
.addStringField
(
TFieldBase.New
.Enabled(True)
.Description('User')
.EventBeforeInsert(TEvents.New.EditFillUser), TTabsNames.Tab1
).Show();
The Form Class
TFormBase = class(TForm)
private
FDictionary: TDictionary<String, TEventBeforeInsert>;
...
function TFormBase.addStringField(AField: TFieldBase; ATab: TTabsNames): TFormBase;
var
FLink: TLinkControlToField;
FEdit: TEdit;
begin
Result := Self;
FEdit := TEdit.Create(Self);
FEdit.Visible := True;
FEdit.Parent := TPanel(PanelParent.FindComponent('PanelTab' + Ord(ATab).ToString));
FEdit.Enabled:=AField.Enabled;
if Assigned(AField.EventBeforeInsert) then
begin
FDictionary.Add(FEdit.Name,AField.EventBeforeInsert);
end;
end;
...
procedure TFormBase.rectInsertClick(Sender: TObject);
var
Item:String;
begin
for Item in FDictionary.Keys do
begin
if Not FDictionary.Items[Item](Self.FindComponent(Item),Self) then
Exit;
end;
end;
I'm having a problem here, when debugging I see the text property being changed correctly, but no changes are made to the form being displayed.
TEvents = class
...
function TEvents.EditFillUser(AComponent: TComponent;AForm: TForm): Boolean;
begin
TEdit(AComponent).Text:=IntToStr(0);
Result:=True;
end
I'm thinking it may be a problem that the variable is being passed by value ... Can someone help me?
Edit 1:
I've tried with the dictionary declared like this:
FDictionary: TDictionary<TComponent, TEventBeforeInsert>;
...
if Not FDictionary.Items[Item](Item,Self) then //call
And I also tried use TForm reference like this:
function TEvents.EditFillUser(AComponent: String;AForm: TForm): Boolean;
begin
TEdit(AForm.FindComponent(AComponent)).Text:=IntToStr(0);
Result:=True;
end
In TFormBase.addStringField(), you are not assigning a Name value to the newly create TEdit object before inserting it into FDictionary.. Only components created at design-time have auto-generated Names. Components created at run-time do not. So, you are tracking your objects using blank Names. If you want to track the objects by Name, you need to actually assign your own value to FEdit.Name, eg:
function TFormBase.addStringField(AField: TFieldBase; ATab: TTabsNames): TFormBase;
var
...
FEdit: TEdit;
FEvent: TEventBeforeInsert;
begin
...
FEdit := TEdit.Create(Self);
FEdit.Name := 'SomeUniqueNameHere'; // <-- for you to decide on...
...
FEvent := AField.EventBeforeInsert;
if Assigned(FEvent) then
FDictionary.Add(FEdit.Name, FEvent);
end;
However, in this particular case, I see no reason to use a TDictionary at all. Consider using a TList instead, then you don't need the Names at all. This will also boost the performance of the iteration in TFormBase.rectInsertClick() since it won't have to hunt for every TComponent object using FindComponent() anymore:
TFormBase = class(TForm)
private
type TEventBeforeInsertPair = TPair<TComponent, TEventBeforeInsert>;
FBeforeInsertEvents: TList<TEventBeforeInsertPair>;
...
public
constructor Create;
destructor Destroy; override;
...
end;
...
constructor TFormBase.Create;
begin
inherited;
FBeforeInsertEvents := TList<TEventBeforeInsertPair>.Create;
end;
destructor TFormBase.Destroy;
begin
FBeforeInsertEvents.Free;
inherited;
end;
function TFormBase.addStringField(AField: TFieldBase; ATab: TTabsNames): TFormBase;
var
...
FEdit: TEdit;
FEvent: TEventBeforeInsert;
begin
...
FEdit := TEdit.Create(Self);
...
FEvent := AField.EventBeforeInsert;
if Assigned(FEvent) then
FBeforeInsertEvents.Add(TEventBeforeInsertPair.Create(FEdit, FEvent));
end;
procedure TFormBase.rectInsertClick(Sender: TObject);
var
Item: TEventBeforeInsertPair;
begin
for Item in FBeforeInsertEvents do
begin
if not Item.Value(Item.Key, Self) then
Exit;
end;
end;
...
Also, your TEvents.EditFillUser() method does not match the definition of TEventBeforeInsert. The 1st parameter of TEventBeforeInsert is declared as passing the TComponent pointer by var reference (why?), but the 1st parameter of EditFillUser() is not doing that. Unless you want your event handlers to alter what the TComponent pointers are pointing at (which won't work the way you are currently using TEventBeforeInsert with TDictionary), then there is no reason to pass around the TComponent pointers by var reference at all:
TEventBeforeInsert = function(AComponent: TComponent; AForm: TForm): Boolean of Object;
Also, your use of TEvents.New appears to be a memory leak, as nobody is taking ownership of the newly created TEvents object (unless its constructor is adding the object to some internal list that we can't see). Same with TFieldBase.New. And even TFormBase.New (assuming there is no OnClose event that sets Action=caFree when the Form is closed). At some point, you need to call Free() any class object that you Create().
Delphi XE6 - I have a Unit (EMAIL1.pas) which does related processing. This is meant to be a standalone unit I can incorporate into multiple programs. My initial procedure is called GetDetailsFromEmailAddress. It has two parameters, an email address which I lookup and a "group of data" which will get updated, currently defined as a var. This can be a record or a class, I don't really care. It is just a group of related strings (firstname, last name, city, etc). Let's call this EmpRec.
My challenge is that this procedure creates an instance of a class (JEDI VCL HTMLParser) which uses a method pointer to call a method (TableKeyFound). This routine needs to update EmpRec. I do not want to change this code (HTMLPArser routine) to add additional parameters. There are several other procedures that my UNIT creates. All of them need to read/update EmpRec. How do I do this?
I need a way to "promote" the variable EmpRec which is passed in this one routine (GetDetailsFromEmailAddress) to be GLOBAL within this UNIT so that all the routines can access or change the various elements. How do I go about this? I do NOT really want to have to define this as a GLOBAL / Application wide variable.
Code sample below. So.. How does the routine TableKeyFoundEx get access to the EmpRec variable?
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
begin
...
// Now create the HTML Parser...
JvHtmlParser1 := TJvHTMLParser.Create(nil);
// On event KeyFoundEx, call Parsehandlers.TableKeyFoundEx;
JvHtmlParser1.OnKeyFoundEx := ParseHandlers.TableKeyFoundEx;
...
end.
procedure TParseHandlers.TableKeyFoundEx(Sender: TObject; Key, Results, OriginalLine: String; TagInfo: TTagInfo;
Attributes: TStrings);
begin
..
// NEED ACCESS to EmpRec here, but can't change procedure definition
end;
There are two different ways I would approach this:
use the parser's Tag property:
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
begin
...
JvHtmlParser1 := TJvHTMLParser.Create(nil);
JvHtmlParser1.OnKeyFoundEx := ParseHandlers.TableKeyFoundEx;
JvHtmlParser1.Tag := NativeInt(#EmpRec);
...
end;
procedure TParseHandlers.TableKeyFoundEx(Sender: TObject; Key, Results, OriginalLine: String; TagInfo: TTagInfo; Attributes: TStrings);
var
EmpRec: PEmpRec; // assuming PEmpRec = ^TEmpRec
begin
EmpRec := PEmpRec(TJvHTMLParser(Sender).Tag);
...
end;
use a little TMethod hack to pass the record DIRECTLY to the event handler:
// Note: this is declared as a STANDALONE procedure instead of a class method.
// The extra DATA parameter is where a method would normally pass its 'Self' pointer...
procedure TableKeyFoundEx(Data: Pointer: Sender: TObject; Key, Results, OriginalLine: String; TagInfo: TTagInfo; Attributes: TStrings);
var
EmpRec: PEmpRec; // assuming PEmpRec = ^TEmpRec
begin
EmpRec := PEmpRec(Data);
...
end;
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
var
M: TMethod;
begin
...
JvHtmlParser1 := TJvHTMLParser.Create(nil);
M.Code := #TableKeyFoundEx;
M.Data := #EmpRec;
JvHtmlParser1.OnKeyFoundEx := TJvKeyFoundExEvent(M);
...
end;
In addition to the two options that Remy offers, you could derive a sub-class of TJvHTMLParser.
type
PEmpRec = ^TEmpRec;
TMyJvHTMLParser = class(TJvHTMLParser)
private
FEmpRec: PEmpRec;
public
constructor Create(EmpRec: PEmpRec);
end;
....
constructor TMyJvHTMLParser.Create(EmpRec: PEmpRec);
begin
inherited Create(nil);
FEmpRec := EmpRec;
end;
When you create the parser, do so like this:
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
var
Parser: TMyJvHTMLParser;
begin
Parser := TMyJvHTMLParser.Create(#EmpRec);
try
Parser.OnKeyFoundEx := ParseHandlers.TableKeyFoundEx;
....
finally
Parser.Free;
end;
end.
And in your OnKeyFoundEx you cast Sender back to the parser type to gain access to the record:
procedure TParseHandlers.TableKeyFoundEx(Sender: TObject; ...);
var
EmpRec: PEmpRec;
begin
EmpRec := (Sender as TMyJvHTMLParser).FEmpRec;
....
end;
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.
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.
I have a TObjectList with OwnsObjects = true. It contains quite a few objects. Now I want to remove the object at index Idx from that list, without freeing it.
Is the Extract method the only option?
ExtractedObject := TheList.Extract(TheList[Idx]);
All other methods seem to free the object. I am looking for something a little bit more efficient, that does not do a linear search every time, since I already know the index of the object. Something like an overloaded ...
ExtractedObject := TheList.Extract(Idx);
... which does not exist.
Why not just set OwnsObjects to false, do your removal, then set it to true again?
If you look at the code for delete, it's the notify method which causes the freeing to happen.
This should work :
TMyObjectList = Class(TObjectList)
private
fNotify: Boolean;
{ Private declarations }
procedure EnableNotification;
procedure DisableNotification;
protected
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
public
constructor Create(AOwnsObjects: Boolean);overload;
constructor Create; overload;
function Extract(const idx : Integer) : TObject;
end;
constructor TMyObjectList.Create(AOwnsObjects: Boolean);
begin
inherited Create(AOwnsObjects);
fNotify := True;
end;
constructor TMyObjectList.Create;
begin
inherited Create;
fNotify := True;
end;
procedure TMyObjectList.DisableNotification;
begin
fnotify := False;
end;
procedure TMyObjectList.EnableNotification;
begin
fNotify := True;
end;
function TMyObjectList.Extract(const idx: Integer) : TObject;
begin
Result := Items[idx];
DisableNotification;
try
Delete(idx);
finally
EnableNotification;
end;
end;
procedure TMyObjectList.Notify(Ptr: Pointer; Action: TListNotification);
begin
if fNotify then
inherited;
end;
This is where class helpers can be usefull
TObjectListHelper = class helper for TObjectList
function ExtractByIndex(const AIndex: Integer): TObject;
end;
function TObjectListHelper.ExtractByIndex(const AIndex: Integer): TObject;
begin
Result := Items[AIndex];
if Result<>nil then
Extract(Result);
end;
You can now use:
MyObjList.ExtractByIndex(MyIndex);
The proposed helperclass (by Gamecat) will result in the same lookup that Thomas would like to get rid of.
If you take a look at the source, you can see what Extract() really does, and then use the same approach.
I will suggest something like tis:
obj := list[idx];
list.list^[idx] := nil; //<- changed from list[idx] := nil;
list.delete(idx);
This will give you the object, as Extract() does, and then delete it from the list, without any lookups. Now you can put this in a method some where, a helperclass or subclass or wher ever you like.
I don't use Delphi/C++Builder some time ago, but as far as I can renmember thats the only way.
My suggestion is to use a TList instead, and manually delete the objects when required.
Anything wrong with:
ExtractedObject := TExtractedObject.Create;
ExtractedObject.Assign(Thelist[Idx]);
TheList.Delete(idx);
There is time needed for the create and assign but not for the search of the list. Efficiency depends on the size of the object -v- the size of the list.