I have a Delphi XE+ application with 3 forms, 2 of them created dynamically, like so:
form_main is triggering form_equip
form_equip is triggering form_certif
form_main -> form_equip -> form_certif
1'st: Open form_equip
procedure Tform_main.button_equip_addClick(Sender: TObject);
var
form_equip: Tform_equip;
begin
form_equip:= Tform_equip.Create(Self);
form_equip.equip_id:= 0;
form_equip.ShowModal;
FreeAndNil(form_equip);
end;
On form_equip I have a public procedure has_changes
2'nd: Open form_certif
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(Self);
form_certif.index:= 0;
form_certif.ShowModal;
FreeAndNil(form_certif);
end;
Now, when I press OK button from form_certif
procedure Tform_certif.button_okClick(Sender: TObject);
begin
//do something...
form_equip.has_changes; //this public procedure from form_equip is not visible because form was created as local var on form_main
end;
The question is, how can I transmit the sender/parent name to form_certif so can I see the public procedures and variables from form_equip?
A simple way is to declare inside unit_equip as global:
var
form_equip: Tform_equip
but I avoid to do this because form_equip is made to be opened dynamically in multiple windows with different names...
Pass all needed information from form_equip to form_certif. That way form_certif is decoupled from any dependence of form_equip.
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(nil);
try
form_certif.index:= 0;
// Pass all other needed variable values to form_certif
// including callback methods
form_certif.Has_Changes_Method := Self.Has_Changes();
if form_certif.ShowModal = mrOk then
begin
// take care of changes
end;
finally
FreeAndNil(form_certif);
end;
end;
And this is how it would look in the form_certif unit:
type
THas_Changes_Method = procedure of Object;
TForm_Certif = class(TForm)
...
private
FIndex: Integer;
FHasChanges: THas_Changes_Method;
public
property Index: Integer read FIndex write FIndex;
property Has_Changes_Method: THas_Changes_Method read fHasChanges write fHasChanges;
end;
Pass the information as a parameter in the constructor. Declare the constructor like this:
constructor Create(AOwner: TComponent; const ParentName: string);
In the implementation of the constructor, make a note of the name that was passed.
Since you are creating forms in code, you can add custom constructor to the form and pass other form as parameter.
Tform_certif = class(TForm)
...
protected
form_equip: Tform_equip;
public
constructor Create(AOwner: TComponent; Aform_equip: Tform_equip); reintroduce;
end;
constructor Tform_certif.Create(AOwner: TComponent; Aform_equip: Tform_equip);
begin
inherited Create(AOwner);
form_equip := Aform_equip;
end;
So now you can call form_equip.has_changes, because it is a field that you have initialized during construction of your Tform_certif form and it points to particular instance of Tform_equip that created this particular instance of Tform_certif.
procedure Tform_certif.button_okClick(Sender: TObject);
begin
//do something...
// test whether form_equip is assigned to avoid AV by calling methods on nil object
if Assigned(form_equip) then form_equip.has_changes;
end;
And to create your Tform_certif you would use following code
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(Self, Self);
form_certif.index:= 0;
form_certif.ShowModal;
FreeAndNil(form_certif);
end;
There is also variation of above constructor code, where you only need to send one parameter and then test in constructor whether AOwner it is of Tform_equip type and you don't need to change code in button_certif_addClick for using that kind of solution.
Tform_certif = class(TForm)
...
protected
form_equip: Tform_equip;
public
constructor Create(AOwner: TComponent); override;
end;
constructor Tform_certif.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
if AOwner is Tform_equip then form_equip := Tform_equip(AOwner);
end;
Related
I'm developing a component for Query. It works like the "Properties" feature of DevExpress, but I need to place the order of the Unpublished Property I wrote to DFM with DefineProperties in the DFM file at the top of the TCollectionItem.
It works the same way in DevExpress. If you add a Field to the cxGrid and assign a value to the Properties property, you will see the value "PropertiesClassName" in the DFM file at the top.
When I open the DFM file and bring this Property to the top, the setter property of the "PropertiesClassName" Property works and I create that Class. It works seamlessly when reading data from the DFM stream. But no matter what I did I couldn't get the "PropertiesClassName" Property value to the top.
If you create a cxGrid on the form and add Field, and then take the "PropertiesClassName" property from DFM to the bottom of the DFM file, when you open the form again, you will see that it cannot find the relevant Class and an error occurs.
To change the DFM flow, I first assigned a value to the "PropertiesClassName" Property and then created the Class, but the problem was not solved. I did the opposite of this but the problem is still the same.
DFM Context
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
FieldName = 'TestField'
Properties.Convert2String = True
PropertiesClassName = 'TSearchBooleanProperties'
end>
DFM Context should be like
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
PropertiesClassName = 'TSearchBooleanProperties'
FieldName = 'TestField'
Properties.Convert2String = True
end>
Classes
TSearchField = class(TCollectionItem)
private
FFieldName: string;
FProperties: TSearchFieldProperties;
FPropertiesClassName: string;
private
procedure SetFieldName(const Value: string);
procedure SetProperties(const Value: TSearchFieldProperties);
private
procedure ReaderProc(Reader: TReader);
procedure WriterProc(Writer: TWriter);
procedure SetPropertiesClassName(const Value: string);
protected
constructor Create(Collection: TCollection); override;
procedure DefineProperties(Filer: TFiler); override;
public
property PropertiesClassName: string read FPropertiesClassName write SetPropertiesClassName;
published
property FieldName: string read FFieldName write SetFieldName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
procedure TSearchField.SetPropertiesClassName(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesClassName(Value, Item) then
begin
if not Assigned(FProperties) or not (FProperties.ClassType = Item.ClassType) then
begin
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
FPropertiesClassName := Item.ClassType.ClassName;
FProperties := Item.ClassType.Create;
end;
end
else
begin
FPropertiesClassName := '';
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
end;
end;
Property Editor
type
TSearchFieldPropertiesProperty = class(TClassProperty)
private
function GetInstance: TPersistent;
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc: TGetStrProc); override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;
function TSearchFieldPropertiesProperty.GetValue: string;
begin
for var I := 0 to Self.PropCount - 1 do
begin
var Inst := Self.GetComponent(I);
if Assigned(Inst) and Self.HasInstance(Inst) then
begin
if Inst is TSearchField then
begin
var PropInst := GetObjectProp(Inst, Self.GetPropInfo);
if Assigned(PropInst) then
begin
for var Item in SearchFieldPropertiesList do
begin
if PropInst.ClassType = Item.ClassType then
begin
Result := Item.Name;
Exit;
end;
end;
end;
end;
end;
end;
end;
procedure TSearchFieldPropertiesProperty.SetValue(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesName(Value, Item) then
begin
var Inst := GetInstance;
if Assigned(Inst) then
begin
var Context := TRttiContext.Create;
var Rtype := Context.GetType(Inst.ClassType);
for var Prop in Rtype.GetProperties do
begin
if SameText(Prop.Name, 'PropertiesClassName') then
begin
Prop.SetValue(Inst, TValue.From<string>(Item.ClassType.ClassName));
Break;
end;
end;
end;
end;
end;
Pic for Design Time
The only problem is changing the order of the Property in that DFM flow.
Original answer at the bottom, here is a new suggestion:
We actually have something very similar in the JVCL where TJvHotTrackPersistent publishes a HotTrackOptions property.
This property is backed by an instance of TJvHotTrackOptions that gets derived in other classes that need specialized versions of it.
To tell the streaming subsystem to use the actual class found at streaming time, the constructor of that options class calls SetSubComponent(True); which places csSubComponent in the ComponentStyle property.
So what you should do is get rid of your DefineProperties, have TSearchFieldProperties inherit from TComponent and call SetSubComponent(True) in its constructor.
Then you create as many classes derived from TSearchFieldProperties as you need, each with its own set of published properties.
This means you should also get rid of the methods you showed in your submission.
In the end, you should have something along those lines:
type
TSearchFieldProperties = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
end;
TIntegerSearchFieldProperties = class(TSearchFieldProperties)
private
FIntValue: Integer;
published
property IntValue: Integer read FIntValue write FIntValue;
end;
constructor TSearchFieldProperties.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetSubComponent(True);
end;
With this you do not fight against the streaming system but rather work with it in the way it is meant to be used.
But if you stop there, you'll notice there is no way for you to specify the actual class name to be used for the TSearchFieldProperties instance used for the TSearchField.Properties property.
The only way to get the class name to be streamed before the subcomponent is streamed is to actually declare the class name as a published property, declared before the subcomponent like this:
type
TSearchField = class(TCollectionItem)
published
// DO NOT change the order of those two properties, PropertiesClassName must come BEFORE Properties for DFM streaming to work properly
property PropertiesClassName: string read GetPropertiesClassName write SetPropertiesClassName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
function TSearchField.GetPropertiesClassName: string;
begin
Result := Properties.ClassName;
end;
procedure TSearchField.SetPropertiesClassName(const AValue: string);
begin
FProperties.Free; // no need to test for nil, Free already does it
FProperties := TSearchFieldPropertiesClass(FindClass(AValue)).Create(self);
end;
It might work if you just declare the published property like without creating a csSubComponent hierarchy but you'll most likely stumble on other hurdles along the way.
Note: this answer is wrong because DefineProperties is called last in TWriter.WriteProperties and so there is no way to change the order properties defined like this are written.
What if you change your DefineProperties override from this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
to this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
inherited DefineProperties(Filer);
end;
Basically, call the inherited method AFTER you have defined your own property.
Note that I also specified which inherited method is called. I know it's not required, but it makes intent clearer and allows for Ctrl-Click navigation.
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().
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.
I have a TFrame that I use for searching for entities in a Delphi 2010 VCL project, in the TFrame I have a button edit, that allows the user to open a specific form to browse for that entity. (All the browse forms inherit from a common base browse form)
Currently I achieve this by inheriting from the base frame, then implement the Browse event that fires off the specific form. The only difference each time is what form (type) is shown on the click event, is there a way I can achieve this with generics.
That way I can reuse the same base frame without having to rewrite the same code for each entity (there are over 100), and at form create of the host form pass the type constraint to open the appropriate form on browse.
I have tried adding a generic type to the frame:
type
Browser<T: TfrmBrowser, constructor> = class
class function BrowseForm(Owner: Tcomponent): T;
end;
class function Browser<T>.BrowseForm(Owner: Tcomponent): T;
var
_browseForm: T;
begin
_browseForm := T.Create; // 1st problem T.Create(Owner); throws a comile error
Result := _browseForm;
end;
and then in the picker frame I expose Start that can be called from the the host form's create event:
procedure TPickerFrame.Start<T>(const idProp, nameProp, anIniSection: string; aDto: IDto);
begin
_browseForm:= Browser<T>.BrowseForm(self);
_iniSectionName:= anIniSection;
_idField:= idProp;
_descriptionField:= nameProp;
_dto := aDto;
end;
the truth is, I don't really get generics in Delphi, and none of this is working.
Below are excerpts from the frame:
_browseForm: TfrmBrowser;
procedure TPickerFrame.Browse(var DS: TDataSet; var Txt: string; var mr: TModalResult);
begin
// How do I achieve this with Generics
// _browseForm := T.Create(nil); // <-- this line is what needs to know the form type at runtime
// Everything else from here is the same
_browseForm.ProductName := Application.Title;
_browseForm.PageSize := 20;
_browseForm.DatabaseType := bdbtADO;
_browseForm.ADOConnection := dmdbWhereHouse.BaseADOConnection;
_browseForm.INISectionName := _iniSectionName;
_browseForm.DoSelBrowse(DS, Txt, mr, _descriptionField, _text);
if mr = mrOk then
begin
DoSelect(DS);
end;
end;
Does anyone have any experience with a similar requirement? Any help would be appreciated.
Thanks
Below is an example of the rack master browser:
type
TfrmMbfRACK_MASTER = class(TMxfrmBrowseHoster)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
//...
private
FWHID: Integer;
procedure SetWHID(const Value: Integer);
{ Private declarations }
public
{ Public declarations }
procedure BuildADO(Sender: TObject; Q: TADOQuery); override;
end;
implementation
{$R *.DFM}
{ TfrmMbfRACK_MASTER }
procedure TfrmMbfRACK_MASTER.FormCreate(Sender: TObject);
begin
inherited;
fmeMxFrmBrowseHoster1.KeyField := 'RACK_ID';
// FWHID := -2; // 22/06/04
FWHID := 0; // 22/06/04
end;
procedure TfrmMbfRACK_MASTER.BuildADO(Sender: TObject; Q: TADOQuery);
begin
Q.Close;
Q.SQL.Clear;
Q.SQL.Add(
'SELECT R.RACK_DESC, R.RACK_BARCODE, W.ERP_WH, WC.CLASS_NAME, W.DESCRIPTION WAREHOUSE, R.RACK_PACKING_ORDER, ');
//...
end;
The base class
type
TMxfrmBrowseHoster = class(TfrmMxForm)
protected
// ...
procedure FormCreate(Sender: TObject);
procedure BuildADO(Sender: TObject; ADOQ: TADOQuery); virtual; abstract;
public
procedure TMxfrmBrowseHoster.FormCreate(Sender: TObject);
begin
TMxFormProductName := Application.Title;
fmeMxFrmBrowseHoster1.Initialise;
INISectionName := Name;
AbortAction := False;
fmeMxFrmBrowseHoster1.OnSelect := SelectNormaliser;
fmeMxFrmBrowseHoster1.OnNeedADO := BuildADO;
fmeMxFrmBrowseHoster1.INISectionName := self.Name;
fmeMxFrmBrowseHoster1.MultiSelect := dxBarLargeButton10.Down;
fmeMxFrmBrowseHoster1.AutoSaveGrid := True;
dxBarEdit1.OnChange := ActPageSizeChangedExecute;
FormStorage.RestoreFormPlacement;
ActConfirmDelete.Execute;
end;
I find your question a little on the vague side and I'm not 100% sure I understand exactly what you are asking. However, I know how to deal with your problem when calling the constructor. Perhaps that's all you need help with.
You need to use virtual constructor polymorphism and a bit of casting:
class function Browser<T>.BrowseForm(Owner: Tcomponent): T;
var
_browseForm: T;
begin
_browseForm := TfrmBrowser(T).Create(Owner);
Result := _browseForm;
end;
This relies on virtual constructor polymorphism. So you must make sure that each constructor for every class derived from TfrmBrowser is marked with the override directive.
Is it possible to pass interface's method as parameters?
I'm trying something like this:
interface
type
TMoveProc = procedure of object;
// also tested with TMoveProc = procedure;
// procedure of interface is not working ;)
ISomeInterface = interface
procedure Pred;
procedure Next;
end;
TSomeObject = class(TObject)
public
procedure Move(MoveProc: TMoveProc);
end;
implementation
procedure TSomeObject.Move(MoveProc: TMoveProc);
begin
while True do
begin
// Some common code that works for both procedures
MoveProc;
// More code...
end;
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := GetSomeInterface;
o.Move(i.Next);
// somewhere else: o.Move(i.Prev);
// tested with o.Move(#i.Next), ##... with no luck
o.Free;
end;
But it is not working because:
E2010 Incompatible types: 'TMoveProc' and 'procedure, untyped pointer or untyped parameter'
Of course I can do private method for each call, but that is ugly. Is there any better way?
Delphi 2006
Edit:
I know that I can pass whole interface, but then I have to specify which function use. I don't want two exactly same procedures with one different call.
I can use second parameter, but that is ugly too.
type
SomeInterfaceMethod = (siPred, siNext)
procedure Move(SomeInt: ISomeInterface; Direction: SomeInterfaceMethod)
begin
case Direction of:
siPred: SomeInt.Pred;
siNext: SomeInt.Next
end;
end;
Thanks all for help and ideas. Clean solution (for my Delphi 2006) is Diego's Visitor. Now I'm using simple ("ugly") wrapper (my own, same solution by TOndrej and Aikislave).
But true answer is "there is no (direct) way to pass interface's methods as parameters without some kind of provider.
If you were using Delphi 2009, you could do this with an anonymous method:
TSomeObject = class(TObject)
public
procedure Move(MoveProc: TProc);
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := GetSomeInterface;
o.Move(procedure() begin i.Next end);
The problem with trying to pass a reference to just the interface method is that you are not passing a reference to the interface itself, so the interface cannot be reference counted. But anonymous methods are themselves reference counted, so the interface reference inside the anonymous method here can be reference counted as well. That is why this method works.
I don't know the exact reason why you need to do that, but, personally, I think it would be better to pass the whole "Mover" object instead of one of its methods. I used this approach in the past, it's called "Visitor" pattern.
tiOPF, an object persistence framework, uses it extensively and gives you a good example of how it works: The Visitor Pattern and the tiOPF.
It's relatively long, but it proved very useful to me, even when I didn't use tiOPF. Note step 3 in the document, titled "Step #3. Instead of passing a method pointer, we will pass an object".
DiGi, to answer your comment: If you use Visitor pattern, then you don't have an interface implementing multiple methods, but just one (Execute). Then you'd have a class for each action, like TPred, TNext, TSomething, and you pass an instance of such classes to the object to be processed. In such way, you don't have to know what to call, you just call "Visitor.Execute", and it will do the job.
Here you can find a basic example:
interface
type
TVisited = class;
TVisitor = class
procedure Execute(Visited: TVisited); virtual; abstract;
end;
TNext = class(TVisitor)
procedure Execute (Visited: TVisited); override;
end;
TPred = class(TVisitor)
procedure Execute (Visited: TVisited); override;
end;
TVisited = class(TPersistent)
public
procedure Iterate(pVisitor: TVisitor); virtual;
end;
implementation
procedure TVisited.Iterate(pVisitor: TVisitor);
begin
pVisitor.Execute(self);
end;
procedure TNext.Execute(Visited: TVisited);
begin
// Implement action "NEXT"
end;
procedure TPred.Execute(Visited: TVisited);
begin
// Implement action "PRED"
end;
procedure Usage;
var
Visited: TVisited;
Visitor: TVisitor;
begin
Visited := TVisited.Create;
Visitor := TNext.Create;
Visited.Iterate(Visitor);
Visited.Free;
end;
Although the wrapper class solution works, I think that's an overkill. It's too much code, and you have to manually manage the lifetime of the new object.
Perhaps a simpler solution would be to create methods in the interface that returns TMoveProc
ISomeInterface = interface
...
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
...
end;
The class that implements the interface can provide the procedure of object and it will be accessible through the interface.
TImplementation = class(TInterfaceObject, ISomeInterface)
procedure Pred;
procedure Next;
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
end;
...
function TImplementation.GetPredMeth: TMoveProc;
begin
Result := Self.Pred;
end;
function TImplementation.GetNextMeth: TMoveProc;
begin
Result := Self.Next;
end;
How about this:
type
TMoveProc = procedure(const SomeIntf: ISomeInterface);
TSomeObject = class
public
procedure Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc);
end;
procedure TSomeObject.Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc);
begin
MoveProc(SomeIntf);
end;
procedure MoveProcNext(const SomeIntf: ISomeInterface);
begin
SomeIntf.Next;
end;
procedure MoveProcPred(const SomeIntf: ISomeInterface);
begin
SomeIntf.Pred;
end;
procedure Usage;
var
SomeObj: TSomeObject;
SomeIntf: ISomeInterface;
begin
SomeIntf := GetSomeInterface;
SomeObj := TSomeObject.Create;
try
SomeObj.Move(SomeIntf, MoveProcNext);
SomeObj.Move(SomeIntf, MoveProcPred);
finally
SomeObj.Free;
end;
end;
Here is another solution that is working in Delphi 20006. It is similar to the idea of #Rafael, but using interfaces:
interface
type
ISomeInterface = interface
//...
end;
IMoveProc = interface
procedure Move;
end;
IMoveProcPred = interface(IMoveProc)
['{4A9A14DD-ED01-4903-B625-67C36692E158}']
end;
IMoveProcNext = interface(IMoveProc)
['{D9FDDFF9-E74E-4F33-9CB7-401C51E7FF1F}']
end;
TSomeObject = class(TObject)
public
procedure Move(MoveProc: IMoveProc);
end;
TImplementation = class(TInterfacedObject,
ISomeInterface, IMoveProcNext, IMoveProcPred)
procedure IMoveProcNext.Move = Next;
procedure IMoveProcPred.Move = Pred;
procedure Pred;
procedure Next;
end;
implementation
procedure TSomeObject.Move(MoveProc: IMoveProc);
begin
while True do
begin
// Some common code that works for both procedures
MoveProc.Move;
// More code...
end;
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := TImplementation.Create;
o.Move(i as IMoveProcPred);
// somewhere else: o.Move(i as IMoveProcNext);
o.Free;
end;
You can't. Because of the scoping of Interfaces it would be possible (perhaps?) for the Interface to be released before you called the .Next function. If you want to do this you should pass the whole interface to your method rather than just a method.
Edited...
Sorry, this next bit, specifically the "Of Interface" bit was meant in jest.
Also, and I could be wrong here, i.Next is not a method Of Object, as per your type def, it would be a method Of Interface!
Redefine your function
TSomeObject = class(TObject)
public
procedure Move(Const AMoveIntf: ISomeInterface);
end;
Procedure TSomeObject.Move(Const AMoveIntf : ISomeInterface);
Begin
....;
AMoveIntf.Next;
end;
O.Move(I);
Hope this helps.
You currently have TMoveProc defined as
TMoveProc = procedure of object;
Try taking out the "of object" which implies a hidden "this" pointer as first parameter.
TMoveProc = procedure;
That should allow a normal procedure to be called.