When I work with TServerSocket I can use the property Data to store a pointer to a class for example for each client.
Now I will use Indy TIdCmdTCPServer and I'd like to know if there is an equivalent property.
Yes, there is - the TIdContext.Data property. In TIdCmdTCPServer events that give you a TIdCommand parameter instead of a TIdContext parameter, you can access the TIdContext object from the TIdCommand.Context property. For example:
type
TMyClass = class
// add whatever you need...
end;
procedure TForm1.IdCmdTCPServer1Connect(AContext: TIdContext);
var
MyCls: TMyClass;
begin
MyCls := TMyClass.Create;
// initialize MyCls as needed...
AContext.Data := MyCls;
end;
procedure TForm1.IdCmdTCPServer1Disconnect(AContext: TIdContext);
begin
AContext.Data.Free;
AContext.Data := nil;
end;
procedure TForm1.IdCmdTCPServer1CommandHandlerCommand(ACommand: TIdCommand);
var
MyCls: TMyClass;
begin
MyCls := TMyClass(ACommand.Context.Data);
// use MyCls as needed...
end;
Indy also has another useful feature. You can derive a custom class from TIdServerContext, add whatever you want to it, and then assign it to the server's ContextClass property before activating the server. That way, you can simply typecast any TIdContext pointer to your class type when you need to access your custom members. For example:
type
TMyContext = class(TIdServerContext)
public
// add whatever you need...
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
//...
end;
destructor TMyContext.Destroy;
begin
//...
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdCmdTCPServer1.ContextsClass := TMyContext;
end;
procedure TForm1.IdCmdTCPServer1Connect(AContext: TIdContext);
var
MyCtx: TMyContext;
begin
MyCtx := TMyClass(AContext);
// initialize MyCtx as needed...
end;
procedure TForm1.IdCmdTCPServer1CommandHandlerCommand(ACommand: TIdCommand);
var
MyCtx: TMyContext;
begin
MyCtx := TMyClass(ACommand.Context);
// use MyCtx as needed...
end;
This way, you don't need to waste time and memory allocating a separate class per client, when you can use the one that the server already creates for you.
Related
I have a problem, see if you can help me. I have a base form.
type
TForm_Base = class(TForm)
oObjectoVO : TObject;
...
procedure Search<M:class,constructor>;
...
procedure TForm_Base.Search<M>;
begin
TBussinesObj<M>.Pesquisa(FDMemTableGrid);
end;
And I have a form that inherits the base form.
procedure TForm_Client.FormCreate(Sender: TObject);
begin
// TClient is class simple with the properties(write, read) of id, name, ...
oObjectoVO := TClient.Create;
end;
procedure TForm_Client.ButtonSearchClick(Sender: TObject);
begin
inherited;
end;
procedure TForm_Client.FormDestroy(Sender: TObject);
begin
FreeAndNil(oObjectoVO);
end;
My problem is here. I cannot pass the type of the object instantiated in the client form, to the generic method (Search ) to the base form. I don't know if it's possible.
procedure TForm_Base.ButtonSearchClick(Sender: TObject);
begin
Search<oObjectoVO.ClassType>; ******* Error *******
end;
Tanks.
Generics are a compile time construct. Consider this code:
Search<oObjectoVO.ClassType>
You are attempting to instantiate the generic with a type that is not known until run time.
You need to change Search from being a generic to being non-generic and accepting a parameter that specifies the class.
Sorry for having to open new question but I can't find an answer anywhere.
My app is still in progress, but basically I'm trying to call another Form from my MainForm when initializing players, however I get an Access Violation error. Would you please explain to me why this could be happening?
My MainForm code:
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ExtCtrls, jpeg, pngimage, getPlayer_u;...
procedure TfrmMain.FormCreate(Sender: TObject);
begin
Randomize;
InitGameSetup();
end;...
procedure TfrmMain.InitGameSetup();
begin
SetWindowProperties();
InitBackGround();
InitMainMenu();
InitGameBoard();
InitScrabbleTileRack();
InitPlayers();
// GameLoop();
end; ...
procedure TfrmMain.InitPlayers();
var
I : Integer;
sName, sSurname : string;
begin
setLength(Players, NUMBER_OF_PLAYERS);
for I := 1 to High(Players) do
begin
GetPlayer(); ---------------- problem is here
with Players[I] do
begin
Name := sName;
Surname := sSurname;
end;
end;
end;...
procedure TfrmMain.GetPlayer();
begin
frmGetPlayer.Show;
end;
My frmGetPlayer:
unit getPlayer_u;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TfrmGetPlayer = class(TForm)
btnSubmit: TButton;
edtName: TEdit;
edtSurname: TEdit;
procedure FormCreate(Sender: TObject);
procedure btnSubmitClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
sPlayerName, sPlayerSurname : string;
end;
var
frmGetPlayer: TfrmGetPlayer;
implementation
{$R *.dfm}
procedure TfrmGetPlayer.btnSubmitClick(Sender: TObject);
begin
sPlayerName := edtName.Text;
sPlayerSurname := edtSurname.Text;
if not ((Length(sPlayerName) >= 1) and (Length(sPlayerSurname) >= 1)) then
MessageDlg('Please enter a name and surname.', mtInformation, [mbOK], 0)
else
Self.Free;
end;
procedure TfrmGetPlayer.FormCreate(Sender: TObject);
begin
with Self do
begin
Position := poScreenCenter;
BorderStyle := bsDialog;
end;
end;
end.
My dpr:
program main_p;
uses
Forms,
main_u in 'main_u.pas' {frmMain},
getPlayer_u in 'getPlayer_u.pas' {frmGetPlayer};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
The error:
Only your MainForm object is created automatically at program startup. Inside its OnCreate event, your Player Form object hasn't been created yet, so the frmGetPlayer variable is not pointing at a valid object.
frmGetPlayer is a global variable, so it is initially nil. The error message is telling you that you are accessing invalid memory near address 0, which is almost always an indication of accessing a member of a class via a nil pointer.
So, you can't call frmGetPlayer.Show() until after you have created the Player Form object and assigned frmGetPlayer to point at it. Which the code you showed is not doing.
I'm trying to create TObjectList class in Delphi XE8, but i get errors when i try to get the value.
compiler error message: "[dcc32 Error] : can't access to private symbol {System.Generics.Collections}TList.GetItem"
Here is my code:
unit Unit2;
interface
uses
Classes, System.SysUtils, System.Types, REST.Types, System.JSON, Data.Bind.Components,
System.RegularExpressions, System.Variants,
Generics.Collections;
type
TTruc = class
public
libelle : string;
constructor Create(pLibelle : string);
end;
TListeDeTrucs = class(TObjectList<TTruc>)
private
function GetItem(Index: Integer): TTruc;
procedure SetItem(Index: Integer; const Value: TTruc);
public
function Add(AObject: TTruc): Integer;
procedure Insert(Index: Integer; AObject: TTruc);
procedure Delete(Index: Integer);
property Items[Index: Integer]: TTruc read GetItem write SetItem; default;
end;
implementation
{ TTruc }
constructor TTruc.Create(pLibelle: string);
begin
libelle := pLibelle;
end;
{ TListeDeTrucs }
function TListeDeTrucs.Add(AObject: TTruc): Integer;
begin
result := inherited Add(AObject);
end;
procedure TListeDeTrucs.Insert(Index: Integer; AObject: TTruc);
begin
inherited Insert(index, AObject);
end;
procedure TListeDeTrucs.Delete(Index: Integer);
begin
inherited delete(index);
end;
function TListeDeTrucs.GetItem(Index: Integer): TTruc;
begin
result := inherited GetItem(index);
end;
procedure TListeDeTrucs.SetItem(Index: Integer; const Value: TTruc);
begin
inherited setItem(index, value);
end;
end.
the testing code is :
procedure TForm1.Button1Click(Sender: TObject);
var
l : TListeDeTrucs;
i : integer;
Obj : TTruc;
begin
l := TListeDeTrucs.Create(true);
l.Add(TTruc.Create('one'));
l.Add(TTruc.Create('two'));
Obj := TTruc.Create('three');
l.Add(Obj);
for i := 0 to l.count - 1 do
begin
showMessage(l[i].libelle);
end;
L.Delete(0);
l.extract(Obj);
l.Free;
end;
How can i make it work ?
Well, GetItem, and indeed SetItem are private. Your code cannot see them. Private members can be seen only in the unit in which they are declared. You need to use members that are at least protected.
This compiles:
function TListeDeTrucs.GetItem(Index: Integer): TTruc;
begin
Result := inherited Items[Index];
end;
procedure TListeDeTrucs.SetItem(Index: Integer; const Value: TTruc);
begin
inherited Items[Index] := Value;
end;
In this case your class is a little pointless because none of the methods in your class vary behaviour from the base class. But peut-ĂȘtre your real class does more.
I am trying to use an array of a custom class as a property for my component, but the problem is that the values are not been saved to the component, that means that if I set the values, save everything and open again the project, the values for the component disappears... My code looks like the following:
unit Unit1;
interface
uses Windows, ExtCtrls,Classes,Controls;
type
TMyClass=class(TPersistent)
private
FName: string;
FValue: double;
public
property Name: string read FName write FName;
property Value: double read FValue write FValue;
end;
TMyComponent= class(TCustomPanel)
private
FMyArray: array[0..200] of TMyClass;
function GetmyArray(Index: Integer): TMyClass;
procedure SetMyArray(index: Integer; Value: TMyClass);
public
property myArray[index: Integer]: TMyClass read GetMyArray write SetMyArray;
end;
implementation
function TMyComponent.GetmyArray(Index: Integer): TMyClass;
begin
result:= FmyArray[Index];
end;
procedure TMyComponent.SetMyArray(index: Integer; Value: TMyClass);
begin
FMyArray[index].FName:= Value.FName;
FMyArray[index].FValue:= Value.FValue;
end;
end.
I know that that only published properties can be streamed, but the problem is that my property is an array and it can not be published...
A suggestion that I had was to use DefineProperties() to provide a custom streaming but I don't see how to do this with an array.
Other possibility that I thought was to modify TMyClass to a kind of class that TMyComponent could be the parent of it, like it is done in TChart, which you can add different classes of series to it. But I don't know What class this should be
TMyClass=class(T???????????)
With that I could take out the property MyArray and create TMyClass and add to TMyComponent as the following:
MyArray1.parent:= MyComponent1;
MyArray2.parent:= MyComponent2;
...
. Which one is the better option? Or is there any other better idea?
The simpliest (and preferred) solution is to change TMyClass to derive from TCollectionItem and change TMyComponent.FMyArray to TOwnedCollection. Then the DFM will stream the items automatically for you, and you gain native design-time support for creating and manipulating TMyClass objects and their properties.
Try this:
unit Unit1;
interface
uses
Windows, ExtCtrls, Classes, Controls;
type
TMyClass = class(TCollectionItem)
private
FName: string;
FValue: double;
procedure SetName(const AValue: string);
procedure SetValue(AValue: double);
public
procedure Assign(ASource: TPersistent); override;
published
property Name: string read FName write SetName;
property Value: double read FValue write SetValue;
end;
TMyArray = class(TOwnedCollection)
private
function GetItem(Index: Integer): TMyClass;
procedure SetItem(Index: Integer; const Value: TMyClass);
public
constructor Create(AOwner: TPersistent);
function Add: TMyClass; reintroduce;
function Insert(Index: Integer): TMyClass; reintroduce;
property Items[Index: Integer]: TMyClass read GetItem write SetItem; default;
end;
TMyComponent = class(TCustomPanel)
private
FMyArray: TMyArray;
procedure SetMyArray(Value: TMyArray);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property myArray: TMyArray read FMyArray write SetMyArray;
end;
implementation
procedure TMyClass.Assign(ASource: TPersistent);
begin
if ASource is TMyClass then
begin
with TMyClass(ASource) do
begin
Self.FName := Name;
Self.FValue := Value;
end;
Changed(False);
end else
inherited;
end;
procedure TMyClass.SetName(const AValue: string);
begin
if FName <> AValue then
begin
FName := AValue;
Changed(False);
end;
end;
procedure TMyClass.SetValue(AValue: double);
begin
if FValue <> AValue then
begin
FValue := AValue;
Changed(False);
end;
end;
constructor TMyArray.Create(AOwner: TPersistent);
begin
inherited Create(AOwner, TMyClass);
end;
function TMyArray.GetItem(Index: Integer): TMyClass;
begin
Result := TMyClass(inherited GetItem(Index));
end;
procedure TMyArray.SetItem(Index: Integer; const Value: TMyClass);
begin
inherited SetItem(Index, Value);
end;
function TMyArray.Add: TMyClass;
begin
Result := TMyClass(inherited Add);
end;
function TMyArray.Insert(Index: Integer): TMyClass;
begin
Result := TMyClass(inherited Insert(Index));
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FMyArray := TMyArray.Create(Self);
end;
destructor TMyComponent.Destroy;
begin
FMyArray.Free;
inherited;
end;
procedure TMyComponent.SetMyArray(Value: TMyArray);
begin
FMyArray.Assign(Value);
end;
end.
I'd vote for DefineProperties! The necessary code might look like this (assuming none of the instances in the array is nil):
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('MyArray', ReadMyArray, WriteMyArray, true);
end;
procedure TMyComponent.ReadMyArray(Reader: TReader);
var
N: Integer;
begin
N := 0;
Reader.ReadListBegin;
while not Reader.EndOfList do begin
Reader.ReadListBegin;
FMyArray[N].Name := Reader.ReadString;
FMyArray[N].Value := Reader.ReadFloat;
Reader.ReadListEnd;
Inc(N);
end;
Reader.ReadListEnd;
end;
procedure TMyComponent.WriteMyArray(Writer: TWriter);
var
I: Integer;
begin
Writer.WriteListBegin;
for I := 0 to High(FMyArray) do begin
Writer.WriteListBegin;
Writer.WriteString(FMyArray[I].Name);
Writer.WriteFloat(FMyArray[I].Value);
Writer.WriteListEnd;
end;
Writer.WriteListEnd;
end;
I'm looking for a way of scanning all loaded classes for classes which contain a custom attribute, if possible, without using RegisterClass().
at first you have to create TRttiContext, then get all loaded classes using getTypes. after that you can filter types by TypeKind = tkClass;
next step is to enumerate attributes and check if it has your attribute;
attribute and test-class delcaration:
unit Unit3;
interface
type
TMyAttribute = class(TCustomAttribute)
end;
[TMyAttribute]
TTest = class(TObject)
end;
implementation
initialization
TTest.Create().Free(); //if class is not actually used it will not be compiled
end.
and then find it:
program Project3;
{$APPTYPE CONSOLE}
uses
SysUtils, rtti, typinfo, unit3;
type TMyAttribute = class(TCustomAttribute)
end;
var ctx : TRttiContext;
t : TRttiType;
attr : TCustomAttribute;
begin
ctx := TRttiContext.Create();
try
for t in ctx.GetTypes() do begin
if t.TypeKind <> tkClass then continue;
for attr in t.GetAttributes() do begin
if attr is TMyAttribute then begin
writeln(t.QualifiedName);
break;
end;
end;
end;
finally
ctx.Free();
readln;
end;
end.
output is Unit3.TTest
Call RegisterClass to register a class with the streaming system.... Once classes are registered, they can be loaded or saved by the component streaming system.
so if you don't need component streaming (just find classes with some attribute), there is no need to RegisterClass
You can use the new RTTI functionality exposed by the Rtti unit.
var
context: TRttiContext;
typ: TRttiType;
attr: TCustomAttribute;
method: TRttiMethod;
prop: TRttiProperty;
field: TRttiField;
begin
for typ in context.GetTypes do begin
for attr in typ.GetAttributes do begin
Writeln(attr.ToString);
end;
for method in typ.GetMethods do begin
for attr in method.GetAttributes do begin
Writeln(attr.ToString);
end;
end;
for prop in typ.GetProperties do begin
for attr in prop.GetAttributes do begin
Writeln(attr.ToString);
end;
end;
for field in typ.GetFields do begin
for attr in field.GetAttributes do begin
Writeln(attr.ToString);
end;
end;
end;
end;
This code enumerates attributes associated with methods, properties and fields, as well as with types. Naturally you will want to do more than Writeln(attr.ToString), but this should give you an idea for how to proceed. You can test for your specific attribute in the normal way
if attr is TMyAttribute then
....