I have the following situation.
I made one component. I want to get some feedback from the component when I use one method.
Like component.Method(param1 : callback function);
all good and shiny so far.
the project have the following units.
formMain - visible form.
dataModule - working with the component.
form main need to pass to the datamodule one callback function to receive his feedback. this callback function is a different function from the component (has more params).
I dont know how to do this.
TFeedBackProcedure = procedure(param1 : Integer) of object;
TFeedBackProcedureByTypeOf = procedure(aTypeOf : Integer; param1 : Integer) of object;
// component
procedure Syncro(feedBack : TFeedBackProcedure);
begin
//somewhere inside
for i := 0 to 15 do begin
feedBack(i);
end;
end;
// the feeedback received so far when someone use the Syncro procedure is the value of i
// the datamodule
// inside of a method I need to pass also the i received from compoent also a typeof value used only inside datamodule
procedure UseMethod(feedbackProcedure : TFeedBackProcedureByTypeOf); // the extended callback
begin
typeof = 1;
if component.Syncro(???) then begin // <-------- how to ???
// do stuff
end;
end;
// the main form
// the real callback function
procedure TFormMain.Feddback(aTypeOf : Integer; param1: Integer);
begin
if aTypeOf = 0 then begin
label1.caption = IntToStr(param1);
end else begin
label2.caption = IntToStr(param1);
end;
end;
// usage of datamodule
procedure TFormMain.btn1Click(Sender: TObject);
begin
dataModule.UseMethod(Feddback);
end;
any ideas? any other methods to do this? (I also need this in FMX enviroment)
tks alot
Razvan
If you've written the component yourself then the easiest thing to do is to change the type declaration of TFeedbackProcedure so that it also accepts anonymous methods (as well as object methods):
TFeedBackProcedure = reference to procedure(param1 : Integer);
This lets you wrap the extended method in place and call it like so:
procedure UseMethod(feedbackProcedure : TFeedBackProcedureByTypeOf);
var
_typeOf : integer;
begin
_typeOf = 1;
component.Syncro(procedure(AParam : integer)
begin
feedbackProcedure(_typeOf, AParam);
end);
end;
I've simply shown calling the method here because your example writes .Synchro as if it were a function returning a boolean when, in fact, you've declared it as a simple procedure.
As an alternative, if you cannot change the method signature or add a wrapper to an existing class, you can always write a wrapper class to do the job. I've shown a dedicated wrapper class here, but you could just as easily add these fields and methods to any suitable class to wrap the functionality into an object method with the correct signature.
TCallbackContainer = class
private
FParam : integer;
FFeedbackProcByTypeOf : TFeedBackProcedureByTypeOf;
public
constructor Create(AProcByTypeOf : TFeedBackProcedureByTypeOf);
procedure WrapCallback(AParam:integer);
property IntParam : integer read FParam write FParam;
end;
with implementation :
constructor TCallbackContainer.Create(AProcByTypeOf : TFeedBackProcedureByTypeOf);
begin
FFeedbackProcByTypeOf := AProcByTypeOf;
end;
procedure TCallbackContainer.WrapCallback(AParam: Integer);
begin
FFeedbackProcByTypeOf(FParam, AParam);
end;
You can then call this like :
procedure UseMethod(feedbackProcedure : TFeedBackProcedureByTypeOf);
var
LCallbackContainer : TCallbackContainer;
begin
LCallBackContainer := TCallbackContainer.Create(feedbackProcedure);
try
LCallBackContainer.IntParam := 1;
component.Syncro(LCallbackContainer.WrapCallback);
finally
LCallBackContainer.Free;
end;
{ Or, make it FCallBackContainer and manage lifetime somehow...}
end;
Unlike with anonymous methods, which are reference counted, you have to manage the LCallbackContainer object lifetime here somehow. I've shown it as a local, which is fine if .Synchro is actually fully synchronous and you can free the callback container when it returns. If .Synchro is actually an async method, however, and returns before its work is complete then you need some way to manage the callback wrapper's lifetime.
You should also avoid naming a variable TypeOf since this will hide the standard method with that name. System.TypeOf is deprecated but it is still good practice to avoid naming conflicts like this.
Related
I am using generics to define a list of objects that themselves hold generic lists. I have written a method for retrieving the aggregate of each of these lists using specific methods that basically do the same thing. Below is the structure:
unit Unit1;
interface
uses
System.Generics.Collections;
type
TListType=(ltSType,ltFType);
TMyList<T> = class(TList<T>)
{Do some stuff in here to help load lists etc}
end;
TListObject=class(TObject)
private
FSList:TMyList<string>;
FIList:TMyList<integer>;
function GetSList: TMyList<string>;
function GetIList: TMyList<integer>;
public
property MySList:TMyList<string> read GetSList;
property MyIList:TMyList<integer> read GetIList;
constructor create;
end;
TListOfObject<T:TListObject> = class(TObjectList<T>)
public
Function AggrSList:TMyList<string>;
Function AggrIList:TMyList<integer>;
end;
implementation
{ TListObject }
constructor TListObject.create;
begin
FSList:=TMyList<string>.create;
FIList:=TMyList<integer>.create;
end;
function TListObject.GetIList: TMyList<integer>;
begin
result:=FIlist;
end;
function TListObject.GetSList: TMyList<string>;
begin
result:=FSList;
end;
{ TListOfObject<T> }
function TListOfObject<T>.AggrIList: TMyList<integer>;
var
i,j:integer;
begin
result:=TMyList<integer>.create;
for I := 0 to count-1 do
for j := 0 to items[i].MyIList.Count-1 do
result.Add(items[i].MyIList[j]);
end;
function TListOfObject<T>.AggrSList: TMyList<string>;
var
i,j:integer;
begin
result:=TMyList<string>.create;
for I := 0 to count-1 do
for j := 0 to items[i].MySList.Count-1 do
result.Add(items[i].MySList[j]);
end;
end.
I am still fairly new to generics but feel that the aggregate methods AggrIList and AggrSlist could be written using generics that uses a single method to extract the data then is cast at the result.
Is this possible and how would I approach this? I plan to do some more advanced functions that would benefit from this approach as well.
You can use (pseudocode):
TNestedList<T> = class(TList<TList<T>>)
function GetAggregateList : TList<T>;
end;
function TNestedList<T>.GetAggregateList : TList<T>;
var
List : TList<T>;
Item : T;
begin
Result := TList<T>.Create;
for List in Self do
for Item in List do
Result.Add(Item);
end;
No need to implement GetAggregateList for specific types - that's what you use generics for.
Also note that it might be better to either call your method CreateAggregateList (to make clear that the caller is responsible for destroying the created list) or to pass in an already created list instance into a procedure:
procedure GetAggregateList(List : TList<T>);
...which leaves list ownership completely to the caller and is quite a common pattern in Delphi.
I'm making a framework (for internal use only) that has common code among 3 o 4 delphi database CRUD applications..
A common object of mi framework is a TContext,
TContext = class (IContext)
function DB: IDatabase;
function CurrentSettings: ISettings;
..
end;
that is passed to the initialization method of lots of other objects.. example (this will be application code):
TCustomer.Initialize(Context: IContext)
TProjectList.Initialize(Context: IContext)
..
Every application has some specific context functions (that only will be called from application code):
IApp1Context = interface (IContext)
procedure DoSomethingSpecificToApp1;
procedure DoOtherThing;
..
end;
So when I create a Context, Im creating a IApp1Context, and sending it to the initialization methods.. from the framework code everything is fine, the problem is that from the application code I'm constantly casting from IContext to IApp1Context to access the specific
App1 functions.. so all my application code looks like (and its a lot of code like this):
(FContext as IApp1Context).DoSomethingSpecificToApp1
(FContext as IApp1Context).DoOtherThing;
..
The thing is clearly usable, but doesnt reads well in my opinion. Maybe I'm exaggerating; is there is a clever design technique for this situation that I'm not aware of?
Use a temporary variable. Assign it once at the start of the method, and then use it where you need it.
var
AppContext: IApp1Context;
begin
AppContext := FContext as IApp1Context;
AppContext.DoSomethingSpecificToApp1;
AppContext.DoOtherThing;
end;
Or, since it looks like your IContext object is a field of an object, make your IApp1Context variable be a field of the same object. It could even replace the IContext field since IApp1Context already exposes everything the IContext does.
procedure TCustomer.Initialize(const Context: IContext);
begin
FContext := Context;
FAppContext := FContext as IApp1Context;
// ...
end;
What do you think of this possible solution using generics?
pro: no casting necesary
down: the generic invades almost every interface and class of the framework, making it more complicated..
// framework //
type
IContext = interface
function DB;
..
end;
TContext = class (TInterfaedObject, IContext)
function DB;
..
end;
IBusinessObj<T: IContext> = interface
procedure Initialize(AContext: T);
end;
TBusinessObj<T: IContext> = class (TInterfacedObject, IBusinessObj<T>)
protected
FContext: T;
public
procedure Initialize(Context: T); virtual;
end;
procedure TBusnessObj<T>.Initialize(Context: T);
begin
FContext := Context;
FContext.DB.Connect;
end;
// application //
type
IApp1Context = interface (IContext)
procedure DoSomethingElse;
..
end;
TApp1Context = class(TContext, IContext, IApp1Context)
function DB;
procedure DoSomethingElse;
end;
TCustomer = class(TBusinessObj<IApp1Context>)
public
procedure Initialize(AContext: IApp1Context); override;
end;
procedure Start;
var
C: IBusinessObj<IApp1Context>;
begin
C := TCustomer.Create;
C.Initializate(TApp1Context.Create);
..
end;
procedure TCustomer.Initialize(AContext: IApp1Context);
begin
inherited;
FContext.DoSomethingElse // here I can use FContext as a IApp1Context..
end;
Comment please, Thanks!
You could also give your class a private function AppContext defined like this:
function AppContext : IApp1Context;
begin
Result := FContext as IApp1Context;
end;
This avoids the additional variable declaration and keeps the cast local. From client code you can just write:
AppContext.DoSomethingSpecificToApp1;
AppContext.DoOtherThing
I am not sure if I am doing this right. I have a list of objects in the listbox and need to use IndexOf to get an object's index in the list.
if AlarmListBox.items.indexOf(alrm.Tagname) = -1 then
alrm is an object of TAlarm class.
Based on a StackOverflow C# question (How Can I Get the Index of An Item in a ListBox?), I try to override GetHashCode and Equals method, but still it doesn't work right.
Overriden Method:
TAlarm = class(System.Object)
TagName:string;
private
protected
public
method Equals(obj:System.Object):Boolean; override;
method GetHashCode:Int32; Override;
end;
method TAlarm.Equals(obj: system.Object):Boolean;
begin
result := TAlarm(obj).Tagname.Equals(self.Tagname);
end;
method TAlarm.GetHashCode:Int32;
begin
result := self.GetHashCode;
end;
This is how I populate AlarmListBox:
AlmGrp:= new TAlarmGroup;
AlarmListBox.items.Add(AlmGrp);
Compiler compiles without any errors, but when I debug the program line by line it always returns -1 and these overridden methods are never called or fired.
Am I implementing these overrides correctly? If not, how should I override them?
Sample code or hints or clues will be appreciated. Thanks,
UPDATE: To David Heffernan and others who have commented or answered, I think the problem might be that I am passing in two different object as Rob's last comment states. I do populate Listbox (UI) with TAlarmGroup but pass in TAlarm into IndexOf, although they both are identical classes. This is probably my problem. What I am really trying to do is populate Listbox with TAlarmGroup objects and through listbox.indexof by passing in the string (Tagname) I search for the object location. That's how it is done on Delphi XE it works great. The code above is not the actual code. Once I clean up the confusion in my code, it will probably work without overriding the GetHashcode and Equals method.
UPDATE: I think, I have stumbled onto something here. On Delphi XE or below, ListBox (UI) provides a method called AddObject. It's parameters are a string and an object respectively. So, when I populated objects into listbox I also provided the string to go along with it. When I searched I passed in a string or the alarm group name. IndexOf searched on this string against the string it had for each object I provided and not against the object's field (TagName). In Delphi Prism, listbox doesn't have a similar method as AddObject method but only Add that only accepts object as a parameter.
Here's an example of doing what you want with the base TAlarm class you provided. I've also provided implementations of the overloaded Equals and GetHashCode that seem to work. (Again, I'm not a Prism/.NET developer; just trying to help out here.)
// In AlarmClass.pas
type
TAlarm = class(System.Object)
TagName:string;
private
protected
public
constructor;
method Equals(obj:System.Object): Boolean; override;
method GetHashCode:Int32; Override;
method ToString(): String; override;
end;
implementation
method TAlarm.GetHashCode: Int32;
begin
if Self = nil then
Result := inherited
else
Result := Self.TagName.GetHashCode;
end;
constructor TAlarm;
begin
inherited;
end;
method TAlarm.Equals(obj: System.Object): Boolean;
begin
if (obj = nil) or (GetType() <> obj.GetType()) then
Exit(False);
Result := TAlarm(obj).TagName.Equals(Self.TagName);
end;
method TAlarm.ToString(): String;
begin
Result := Self.TagName;
end;
// In MainForm.pas
method MainForm.button1_Click(sender: System.Object; e: System.EventArgs);
var
Idx: Integer;
begin
Idx := ComboBox1.SelectedIndex;
if Idx <> -1 then
ListBox1.SelectedIndex := ListBox1.Items.IndexOf(ComboBox1.Items[Idx]);
end;
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
var
i, j: Integer;
Alarm: TAlarm;
aList: Array[0..4] of Object;
aFind: Array[0..1] of Object;
begin
j := 0;
for i := 0 to 4 do
begin
Alarm := new TAlarm;
Alarm.TagName := String.Format('Alarm{0}', i);
aList[i] := Alarm;
// Place items 1 & 3 in another array of searchable items -
// just for fun. Not suggesting implementing your app this way
// by any means.
if (i mod 2) > 0 then
begin
aFind[j] := Alarm;
Inc(j);
end;
end;
ListBox1.Items.AddRange(aList);
ComboBox1.Items.AddRange(aFind);
end;
Here's how it looks with an item selected in the ComboBox after clicking the Button:
What's the Delphi equivalent of 'this' in C++? Could you please give some examples of its use?
In delphi Self is the equivalent of this. It is also assignable as described in here.
In most cases, you should not use self in the methods.
In fact, it's like if there was an implicit self. prefix when you access the class properties and methods, within a class method:
type
TMyClass = class
public
Value: string;
procedure MyMethod;
procedure AddToList(List: TStrings);
end;
procedure TMyClass.MyMethod;
begin
Value := 'abc';
assert(self.Value='abc'); // same as assert(Value=10)
end;
The self is to be used when you want to specify the current object to another method or object.
For instance:
procedure TMyClass.AddToList(List: TStrings);
var i: integer;
begin
List.AddObject(Value,self);
// check that the List[] only was populated via this method and this object
for i := 0 to List.Count-1 do
begin
assert(List[i]=Value);
assert(List.Objects[i]=self);
end;
end;
this above code will add an item to the TStrings list, with List.Objects[] pointing to the TMyClass instance. And it will check this has been the case for all items of the List.
I'm new to generics and need some help to structure a class and implement methods.
I'm trying to use generics to serialize any TObject-JSON. Moreover, I want to be able to reuse the code.
These are my questions:
How do I create a generic constructor? I want to be able to use Self or Default(T), but it returns just nil.
V := Marshal.Marshal(ReturnObject) - This method requires a TObject, but I do not know how to reference the current object that was passed in.
How can I use this inside a method? Look at the code snipped below, marked with "Question 3".
This is my code:
TFileOperationResult = class(TObject)
private
FSuccess: Boolean;
//Error: PException;
FLastError: Integer;
function GetFailure: Boolean;
property Failure: Boolean read GetFailure;
public
property Success: Boolean read FSuccess write FSuccess;
property LastError: Integer read FLastError write FLastError;
end;
TResponseObject<T: class> = class(TObject)
private
FReturnObject: T;
function GetReturnObject: T;
function BaseStringsConverter(Data: TObject): TListOfStrings;
public
constructor Create; overload;
property ReturnObject: T read GetReturnObject;
procedure Serialize;
end;
constructor TResponseObject<T>.Create;
begin
// Question 1 - What should go in here?
end;
function TResponseObject<T>.GetReturnObject: T;
begin
Result := Default(T);// Is this correct?
end;
procedure TResponseObject<T>.Serialize;
var
Marshal: TJSONMarshal;
V: TJSONValue;
begin
Marshal := TJSONMarshal.Create(TJSONConverter.Create);
Marshal.RegisterConverter(TResponseObject<T>, BaseStringsConverter);
V := Marshal.Marshal(ReturnObject); // Question 2 - How Can I refer to 'Self'?
OutPut := V.ToString;
Marshal.Free;
end;
Calling code:
procedure TForm1.Test;
var
FileOperationResult: TResponseObject<TFileOperationResult>;
begin
FileOperationResult := TResponseObject<TFileOperationResult>.Create;
FileOperationResult.Serialize;
end;
Question 3:
procedure TForm1.MoveCopyFile<THowNowResponse>(ASource, DDestination: String);
var
FileOperationResult: TFileOperationResult;
begin
FileOperationResult := TFileOperationResult.Create;
// What to do?
end;
Any other comments are much appreciated.
It's hard to tell exactly what you're trying to do here, but I can make a guess. For TResponseObject, you want an object that can contain another object and operate on it. In that case, you probably want to pass it in to the constructor, like so:
constructor TResponseObject<T>.Create(value: T);
begin
FReturnObject := value;
end;
Likewise, if you make a GetReturnObject method, it should probably return the value of the FReturnObject field. (Or you could make the read accessor of the property just reference FReturnObject directly.)
function TResponseObject<T>.GetReturnObject: T;
begin
Result := FReturnObject;
end;
It's really hard to answer #3 since I don't know what you're trying to do with this, but hopefully my answers to the first two will help you get back on track. Just remember that generics don't have to be confusing; they're basically just type substitution. Anywhere you'd use a normal type in a non-generic routine, you can replace it with a <T> to create a generic routine, and then substitute any type that fits the constraints for that particular T.