Exposing A TStringList - delphi

I'm wanting to implement some logging functions in a class I have here. Basically, my thought is to create a TStringList within the class which contains the log. I can do this without any trouble, but my question is how to expose it outside of the class in a way that a control (TMemo or TListBox) can dynamically show the contents if the containing form is present. I could make a direct association to the control within the class, but I'm wanting to keep the class discreet from the form code itself, and create a procedure in the class which makes this association.
Basically, if I have LogFile: TStringList in my class, how do I make it so adding a line there makes it show up in a TMemo from a form that is separate from the class?

Let the form register a callback event in your class.
If this event is assigned when adding an item to your list, use the callback to send the string.
Type
TMyCallback = procedure(const aLogStr: String) of object;
TMyClass = Class
private
FCallback : TMyCallback;
FLogFile : TStringList;
procedure SetCallback(ACallback: TMyCallback);
public
property Callback : TMyCallback write SetCallback;
end;
...
// Update FLogFile
FLogFile.Items.Add(SomeText);
if Assigned(FCallback) then
FCallBack(SomeText);
...
In your form class:
Type
TMyForm = Class(TForm)
private
procedure IncomingLogString(const AStr: String);
end;
procedure TMyForm.IncomingLogString(const AStr: String);
begin
MyMemo.Lines.Add(AStr);
end;
...
// Register callback
FMyClass.Callback := Self.IncomingLogString;
Now, your TMyClass is decoupled from any dependence from the form.

Related

Lazarus Error: Anonymous Class definitions are not allowed

I have to develop a game in Lazarus for school, and I ran into an error that I can't find a solution for.
I have a dynamic array where I want to store classes in so that I can call procedures on those classes.
TKarte is the ancestor class, and I have many different classes (all representing different Cards) that have the same procedures as the ancestor class.
unit Karten;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Dialogs, ExtCtrls;
type
TKarte=class
public
class procedure GetPicture(Objekt:TImage);virtual;
class procedure OnPlay;virtual;
end;
type
Karte = class(TKarte)
public
class procedure GetPicture(Objekt:TImage);override;
class procedure OnPlay;override;
end;
type
Karte2 = class(TKarte)
public
class procedure GetPicture(Objekt:TImage);override;
class procedure OnPlay;override;
end;
implementation
class procedure Karte.OnPlay();
begin
ShowMessage(ClassName);
end;
class procedure Karte.GetPicture(Objekt:Timage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Karten\Mindcontrol.png');
end;
class procedure Karte2.GetPicture(Objekt:Timage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Karten\Mindcontrol.png');
end;
class procedure Karte2.OnPlay();
begin
ShowMessage(Karte2.ClassName);
end;
class procedure TKarte.OnPlay();
begin
ShowMessage(ClassName);
end;
class procedure TKarte.GetPicture(Objekt:TImage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Sprites\Buttons\Button 1.png');
end;
end.
This is how I add them and call them from the array at the moment:
Hand: array of Class of TKarte;
procedure TSplashScreen.Button2Click(Sender: TObject);
begin
SetLength(Hand,Length(Hand)+1);
Hand[High(Hand)] := Karte;
Hand[High(Hand)].OnPlay();
Hand[High(Hand)].GetPicture(Image1);
end;
There is no problem with running the program, but when I try to add a new component, or I press CTRL + Space for the Auto-Complete, it gives me an error at the declaration of the array:
Error: Anonymous Class definitions are not allowed
I have tried to find an answer to this problem, but there seems to be noone with the same problem :(
Can somebody help me?
Offhand, I see nothing wrong with the code, and as you said the code does run correctly. It is only the IDE that is having a problem with it. As such, I would not suggest declaring the array's element type directly in the array's declaration. I would suggest declaring an alias for it before declaring the array, eg:
type
TKarte=class
...
end;
TKarteClass = Class of TKarte;
...
Hand: array of TKarteClass;
There are few problems in your code.
When creating array of some type you don't define the said type in the array itself but only tell which type needs to be used. So your array definition would be:
Hand: array of TKarte;
I also see that you have declared all your procedures as class procedure. There is a fundamental difference between class methods and ordinary methods. Most likely you won't need them to be declared as class methods based on what you are trying to achieve. While I could not find suitable Lazarus documentation on this topic you may refer to Delphi documentation on Class methods to get better understanding about their difference.

Indy 10.6 IdContext needs an ID field

In working with setting up TCPServer and FTPServer, the thing I noticed most is the need for a UserID field and a UserFlag field in IdContext. The simple addition of these would greatly facilitate setting up the components for multiple clients. You could create a descendant, but that takes a lot of unnecessary coding for something so easily added to the source code. I modified IdContext.pas as follows:
Protected
FUserFlag: Boolean;
FUserID: Integer;
...
Public
Property UserFlag: Boolean Read FUserFlag Write FUserFlag Default False;
Property UserID: Integer Read FUserID Write FUserID Default -1;
By using these I'm able to signal a state between events and I have the reference readily available whenever an event is fired. I tried to say something in the Indy project but I couldn't find anywhere to say it :/
Thank you for your suggestion, but I am not inclined to make these kind of additions to the base TIdContext class. They simply do not belong there. Deriving a custom class and assigning it to the server's ContextClass property is the correct and appropriate solution in this situation. This is why that property exists in the first place. It is really not that much coding, eg:
type
TMyContext = class(TIdServerContext)
protected
FUserFlag: Boolean;
FUserID: Integer;
...
public
Property UserFlag: Boolean Read FUserFlag Write FUserFlag;
Property UserID: Integer Read FUserID Write FUserID;
end;
procedure TMyForm.FormCreate(Sender: TObject);
begin
// must do this before activating the server...
IdTCPServer1.ContextClass := TMyContext;
end;
And then you can type-cast TIdContext object pointers to TMyContext when needed.
Various Indy servers do exactly this internally. For instance TIdFTPServer uses a TIdFTPServerContext class that has Account and Username properties for the logged in session.
That being said, if you do not want to derive a custom class, the base TIdContext class does already have a public Data property (or DataObject and DataValue properties in Delphi ARC-based compilers), which can be used for storing user-defined data, eg:
type
TMyData = class
protected
FUserFlag: Boolean;
FUserID: Integer;
...
public
Property UserFlag: Boolean Read FUserFlag Write FUserFlag;
Property UserID: Integer Read FUserID Write FUserID;
end;
procedure TMyForm.IdTCPServer1Connect(AContext: TIdContext);
begin
AContext.Data := TMyData.Create;
...
end;
And then you can simply type-cast AContext.Data to TMyData when needed.

About different ways of calling a static class in delphi

I am creating a class with class constructor
TStaticDynSettings = class
public
class constructor create;
class destructor destroy;
class procedure Reload;
end;
Do all the other method in the class apart from create and destroy need to be a class methods?
I know for sure all the variables need to be a class var else the automatic initialization wont work.
what is the difference in calling the procedure as?..
var StDyn : TStaticDynSettings;`
StDyn.Reload;
and
TStaticDynSettings.Reload;
1 - If you are not planning to create object instances of TStaticDynSettings you can use any of 3 forms:
type
TStaticDynSettings = class
public
procedure Reload1;
class procedure Reload2;
class procedure Reload3; static;
end;
call examples:
procedure Call1;
var Instance: TStaticDynSettings;
begin
Instance.Reload1;
end;
procedure Call2;
begin
TStaticDynSettings.Reload2;
end;
procedure Call3;
begin
TStaticDynSettings.Reload3;
end;
The difference is a hidden argument passed in Reload1 and Reload2 methods and not passed in Reload3.
The hidden argument is a reference to object instance in Reload1 (and not used since you are not creating object instance), and a class reference in Reload2 (which probably also unnecessary in your case, assuming a class reference is known at compile time).
The first form (Reload1) is misleading because it assumes using an object reference and so should be avoided (though it works).
The third form (Reload3) is preferable if you are not using class references that are unknown at compile time.
2 - There is no difference (not counting an overhead of using unnecessary object variable if the first case).

How to override an inherited property?

I have a class (TMyClass) which have a property (Items: TItems)
TItems = class;
TMyClass = class(TComponent)
private
FItems: TItems;
procedure SetItems(const Value: TItems);
protected
public
protected
property Items: TItems read FItems write SetItems;
end.
TExItems = class(TItems)
private
FNewProb: Integer;
protected
public
published
property NewProp: Integer read FNewProb write FNewProb;
end.
TExMyClass = class(TMyClass)
private
FItems: TExItems;
procedure SetItems(const Value: TItems);
protected
public
published
property Items: TExItems read FItems write SetItems;
end.
The new "Items" property is inherited from TItems but when I installed the component the new property of TExItems which is "NewProb" did not appear and it looks like the "Items" property is still TItems not TExItems...how to override it?
Thanks
Modification :
Here is the Real code
type
TKHAdvSmoothDock = class;
TKHAdvSmoothDockItem = class(TAdvSmoothDockItem)
private
FImageIndex: TImageIndex;
procedure SetImageIndex(const Value: TImageIndex);
protected
public
published
property ImageIndex: TImageIndex read FImageIndex write SetImageIndex default -1;
end;
TKHAdvSmoothDockItems = class(TAdvSmoothDockItems)
private
FOwner: TKHAdvSmoothDock;
FOnChange: TNotifyEvent;
function GetItem(Index: Integer): TKHAdvSmoothDockItem;
procedure SetItem(Index: Integer; const Value: TKHAdvSmoothDockItem);
protected
function GetOwner: TPersistent; override;
public
constructor Create(AOwner: TKHAdvSmoothDock);
function Add: TKHAdvSmoothDockItem;
function Insert(Index: Integer): TKHAdvSmoothDockItem;
property Items[Index: Integer]: TKHAdvSmoothDockItem read GetItem write SetItem; default;
procedure Delete(Index: Integer);
published
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
TKHAdvSmoothDock = class(TAdvSmoothDock)
private
FImageChangeLink: TChangeLink;
FImages: TCustomImageList;
FItems: TKHAdvSmoothDockItems;
procedure ImageListChange(Sender: TObject);
procedure SetImages(const Value: TCustomImageList);
procedure SetItems(const Value: TKHAdvSmoothDockItems);
function GetItems: TKHAdvSmoothDockItems;
{ Private declarations }
protected
procedure UpdateImagesFromImageList;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Images: TCustomImageList read FImages write SetImages;
property Items: TKHAdvSmoothDockItems read GetItems write SetItems;
end;
Regards.
Property getters and setters can be virtual, and then overridden by inheriting classes, see below for your example updated. There's one caveat with you're example code and that's you're trying to change the type of the property, which is not allowed. I would advise you check for Value is TExItems in TExMyClass.SetItems but use the inherited Items property and cast to TExItems in all methods of TExMyClass and further inheritants.
TItems = class;
TMyClass = class(TComponent)
private
FItems: TItems;
procedure SetItems(const Value: TItems); virtual;
protected
property Items: TItems read FItems write SetItems;
end;
TExItems = class(TItems)
private
FNewProb: Integer;
protected
public
published
property NewProp: Integer read FNewProb write FNewProb;
end;
TExMyClass = class(TMyClass)
private
procedure SetItems(const Value: TItems); override;
end;
Properties cannot be virtual, so they cannot be overridden. They can be hidden, in that within the context of TExMyClass, references to Items will resolve to the property declared in that class, not the one declared in the ancestor.
If you have something whose static (declared, compile-time) type is TMyClass, Items will always refer to the one from that class, even if its run-time type is TExMyClass.
You could declare SetItems as protected and virtual in the base class, and then override it in the descendant instead of declaring a whole new property that happens to have the same name.
You can implement and override methods getItem and setItem;
Implement property Item only for TMyClass
property Items: TItems read getItems write setItemps;
For TMyClass:
public:
function getItems : TItems; virtual;
procedure setItems(items: TItems); virtual;
For TExMyClass:
public:
function getItems : TItems; override;
procedure setItems(items: TItems); override;
function TExMyClass.getItems : TItems;
begin
result := fItems;
end;
procedure TExMyClass.setItems(items : TItems);
begin
self.itmes := items;
end;
so, TExMyClass.items.className = TExItems !
Technically, you can't override a property, but you can mimic override in several ways. See for example this answer for the most basic manners.
Now I do not have the code for TAdvSmoothDock so the rest is just guessing. When the property getter and setter of TAdvSmoothDock.Items are virtual, you could override them. But in more advanced components, and I suppose the ones from TMS are, then there is a good chance of TAdvSmoothDock having a protected CreateItem method which is called whenever there is need of a new item which could be overriden. If that is the case, then you should implement it like:
function TKHAdvSmoothDock.CreateItem: TAdvSmoothDockItem;
begin
Result := TKHAdvSmoothDockItem.Create;
end;
And use it like:
TKHAdvSmoothDockItem(AKHAdvSmoothDock.Items[I]).ImageIndex := ...
It is the same problem as trying to have more than one TMemo that uses the same Lines object.
Since TCustomMemo delcares on its private section FLines: TStrings; it is not possible to have more than one TMemo that uses the same FLines.
The only way (i still know) of working is to fully duplicate the whole TCustomMemo class as TLinkedCustomMemo, but defining FLines: TStrings; on the public section; also need cloning the 'TMemo' but referencing TLinkedCustomMemo, not TCustomMemo
Then using the hack of delcaring TMemo=class(TLinkedMemo) you will have public access to FLines, so you can replace that object, with the object of the main TMemo, on all the rest linked memos.
Why do such Link on the content of the TMemos?
Easy answer could be: Have more than one TMemo that shows the same text, so user can see (and edit) two (or more) different parts at a time, like some SpreadSheets do... it normally also involve an horizontal splitter between them, syncing horizontal scrollbars, etc.
Sometimes doing something that seems so easy, it is really so complicated!
Just caused by faulty on VCL design.
For the sample of the TMemos... why on the hell they have not been defined its Lines property based on TStringList insead of TStrings? That way we could use the same String List for more than one TMemo at the same time.
If want to see, how internally is dependant on such... search for class TMemoStrings (it appears on implementation section of StdCtrls). Why it must have only one TMemo?, and why it must have at all? why not had used TStringList instead of all that hell?
When some classes are so closed... it comes the hacking way of declaring classes... but how to change only a property of a control without needing to whole duplicate some classes (just to change so little things)?
Oh, yes, a real life sample for a content linked memo could be:
Let the user see (at the same time) two different parts of a text file, without double the memory storage
Or better sample: the user wants to edit a word on line 3 and another on line ten million without the need to scroll
So you put two TMemo and link the Lines property, so user has the controls to edit such Lines property (on different points) from both memo without need to scroll down and up all the times.
Another sample:
User wants to edit lines one million to one million plus twenty but needs to see lines ten to twenty at the same time.
Having to memos with a copy is not possible, more than 3GiB of RAM on a 32Bits Windows (not allowed), each Memo would need >1.6GiB of ram each... etc.
Duplicating such data is neither an option, user edit on second memo, but first must be on sync... so after loose focus (or just after edit if want good looking) you would must copy all data form one to the others memos... computing time?
-etc
There are so much samples... and most important ones are the ones i can not figure.
So answering your question in a general form, "how to hack a class to modify it a little":
Clone the code that define the class in a new unit, call that unit something that let clear it is for cloning that class with some modifications
Use the same class name only if your modifications would be backward compatile (like when additg to TEdid the Align property, use the hack of declaring it as TTheClass=class(TheUnit.TTheClass)), else use a different name
Add such new unit at the end of the uses of interface section
That is it... simple to say, hard work to do on some cases.
I allways recomend using nes classes names, except as on the sample of adding Alignment property to a TEdit.
When use the hack of declaration? When you have a full application already coded and want to add to all TEdit on it the alignment... insetead of creating a new component, adding it to component tools, redefine all forms to not use :TEdit; and use :TMyAlignedEdit... you can just simple add to uses your unit and voila... all TEdit now have such property and IDE inspertor also sees it, etc.

Method pointer and regular procedure incompatible

I have an app, which has multiple forms. All these forms have a PopupMenu. I build the menu items programatically, all under a common root menu item. I want ALL the menu items to call the same procedure, and the menu item itself is basically acting as an argument....
I had this working when I just had one form doing this functionality. I now have multiple forms needing to do this. I am moving all my code to a common unit.
Example.
Form A has PopupMenu 1. When clicked, call code in Unit CommonUnit.
Form B has PopupMenu 2. When clicked, call code in unit CommonUnit.
When I need to call my popup from each form, I call my top level procedure (which is in unit CommonUnit), passing the name of the top menu item from each form to the top level procedure in the common unit.
I am adding items to my PopupMenu with with code.
M1 := TMenuItem.Create(TopMenuItem);
M1.Caption := FieldByName('NAME').AsString;
M1.Tag := FieldByName('ID').AsInteger;
M1.OnClick := BrowseCategories1Click;
TopMenuItem.Add(M1);
I am getting an error message when I compile. Specifically, the OnClick line is complaining about
Incompatible types: 'method pointer and regular procedure'.
I have defined BrowseCategories1Click exactly like it was before when I was doing this on a single form. The only difference is that it is now defined in a common unit, rather than as part of a form.
It is defined as
procedure BrowseCategories1Click(Sender: TObject);
begin
//
end;
What is the easiest way to get around this?
Thanks
GS
A little background...
Delphi has 3 procedural types:
Standalone or unit-scoped function/procedure pointers declared like so:
var Func: function(arg1:string):string;
var Proc: procedure(arg1:string);
Method pointers declared like so:
var Func: function(arg1:string):string of object;
var Proc: procedure(arg1:string) of object;
And, since Delphi 2009, anonymous(see below) function/method pointers declared like so:
var Func: reference to function(arg1:string):string;
var Proc: reference to procedure(arg1:string);
Standalone pointers and method pointers are not interchangeable. The reason for this is the implicit Self parameter that is accessible in methods. Delphi's event model relies on method pointers, which is why you can't assign a standalone function to an object's event property.
So your event handlers will have to be defined as part of some class definition, any class definition to appease the compiler.
As TOndrej suggested you can hack around the compiler but if these event handlers are in the same unit then they should already be related anyway so you may as well go ahead and wrap them into a class.
One additional suggestion I have not seen yet is to backtrack a little. Let each form implement its own event handler but have that handler delegate responsibility to a function declared in your new unit.
TForm1.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
TForm2.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
unit CommonUnit
interface
procedure BrowseCategories;
begin
//
end;
This has the added benefit of separating the response to the user's action from the control that triggered the action. You could easily have the event handlers for a toolbar button and a popup menu item delegate to the same function.
Which direction you choose is ultimately up to you but I'd caution you to focus on which option will make maintainability easier in the future rather than which is the most expedient in the present.
Anonymous methods
Anonymous methods are a different beast all together. An anonymous method pointer can point to a standalone function, a method or a unnamed function declared inline. This last function type is where they get the name anonymous from. Anonymous functions/methods have the unique ability to capture variables declared outside of their scope
function DoFunc(Func:TFunc<string>):string
begin
Result := Func('Foo');
end;
// elsewhere
procedure CallDoFunc;
var
MyString: string;
begin
MyString := 'Bar';
DoFunc(function(Arg1:string):string
begin
Result := Arg1 + MyString;
end);
end;
This makes them the most flexible of the procedural pointer types but they also have potentially more overhead. Variable capture consumes additional resources as does inline declarations. The compiler uses a hidden reference counted interface for inline declarations which adds some minor overhead.
You can wrap your procedures into a class. This class might look like this in a separate unit:
unit CommonUnit;
interface
uses
Dialogs;
type
TMenuActions = class
public
class procedure BrowseCategoriesClick(Sender: TObject);
end;
implementation
{ TMenuActions }
class procedure TMenuActions.BrowseCategoriesClick(Sender: TObject);
begin
ShowMessage('BrowseCategoriesClick');
end;
end.
And to assign the action to a menu item in a different unit is enough to use this:
uses
CommonUnit;
procedure TForm1.FormCreate(Sender: TObject);
begin
PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick;
end;
Update:
Updated to use class procedures (instead of object methods) by David's suggestion. For those who want to use the object methods with the need of object instance, follow this version of the post.
This is the difference between a "procedure" and a "procedure of object"
The OnClick is defined as a TNotifyEvent:
type TNotifyEvent = procedure(Sender: TObject) of object;
You cannot assign a procedure to the OnClick as it is the wrong type. It needs to be a procedure of object.
You could choose one of these:
Derive your forms from a common ancestor and declare the method in it so it's available to descendants
Use a global instance of a class (e.g. data module) shared by all forms
Use a procedure as a fake method like this:
procedure MyClick(Self, Sender: TObject);
begin
//...
end;
var
M: TMethod;
begin
M.Data := nil;
M.Code := #MyClick;
MyMenuItem.OnClick := TNotifyEvent(M);
end;
One solution is to place the OnClick method into a TDatamodule.

Resources