I have Parent object
type PNode=^Node;
Node=Object
Left,Right:PNode;
Balance:integer;
Function Is_Greater(Node1:PNode);
end;
and a child object of object Node
Type ChildNode=object(Node);
X:integer;
end;
and I have 2 pointers P,Q:PNode and use command
P^:=Q^;
but it doesn't change the X value of node P.
Is there a way I can do that, without using pointers to child objects ?
Use a virtual method that you override in the child.
type
PNode = ^TNode;
TNode = object
Constructor Init;
procedure assign(t:pNode); virtual;
end;
PChildNode=^TChildNode;
TChildNode = Object(TNode)
x:integer;
procedure assign(t:pNode); virtual;
end;
Constructor TNode.Init;
begin
end;
procedure TNode.Assign(t:pNode);
begin
// assign the tnode fields in t to the fields of self
end;
procedure TChildNode.Assign(t:pNode);
begin
inherited Assign(t); // parent's fields first
// we don't have Delphi's "IS" here, so can't test if t really is a pchildnode.
x:=pchildnode(t)^.x;
end;
function CreateChildNode(x:integer):PNode;
var chld : pChildNode;
begin
chld:=new(PChildNode,Init); chld^.x:=x;
createchildnode:=chld;
end;
// below this no knowledge of TChildnode except for printing.
var p1,p2 : PNode;
begin
p1:=createchildnode(10);
p2:=createchildnode(5);
// p2^ must be equal to P1^ or derive from it.
// See comment in TChildNode.assign
p1^.assign(p2);
writeln(pchildnode(p1)^.x);
end.
Note that in the equivalent Delphi dialect (CLASS) code, which you SHOULD use, you can test if the incoming element of assign really is a tchildnode using the IS operator
Looking at FPC's packages/tv and objects unit sources is a good resource to learn the older TP dialect.
The X field was not changed because PNode (and TNode) have not X field. Here is working code with some explanations and use cases:
program Project1;
type
{ TNode }
PNode = ^TNode;
TNode = object
a: Integer;
procedure assign(t:TNode);
end;
{ TChildNode }
PChildNode = ^TChildNode;
TChildNode = object(TNode)
x: Integer;
// Overload parent method for the new type
// Parent method still available via inherited (see below)
procedure assign(t:TChildNode); overload;
end;
{ TNode }
procedure TNode.assign(t: TNode);
begin
a := t.a;
end;
{ TChildNode }
procedure TChildNode.assign(t: TChildNode);
begin
inherited assign(t); // Call parent method to assign inherited fields values
x := t.x;
end;
var
n: TNode;
cn1, cn2: TChildNode;
pn, pcn2: PNode;
pcn1: PChildNode;
begin
n.a := 10;
cn1.a := 11;
cn1.x := 12;
cn2.a := 21;
cn2.x := 22;
Writeln(cn1.a: 10, cn1.x: 10);
cn1.assign(n);
//cn1 := n; // Error here, type incompatibility
Writeln(cn1.a: 10, cn1.x: 10);
cn1.assign(cn2);
Writeln(cn1.a: 10, cn1.x: 10);
cn2.x := 44;
// Actually this code works like previous
cn1 := cn2;
cn2.a := 55;
Writeln(cn1.a: 10, cn1.x: 10);
Readln;
pn := New(PNode);
pcn1 := New(PChildNode);
pcn2 := New(PChildNode); // Here we assign PChildNode object to the variable with type PNode
pn^.a := 10;
pcn1^.a := 11;
pcn1^.x := 12;
pcn2^.a := 21;
PChildNode(pcn2)^.x := 22; // This explicit conversion is necessary because PNode type of the pcn2 variable
Writeln(pcn1^.a: 10, pcn1^.x: 10);
//pcn1^.assign(pn^);
//pcn1^ := pn^; // Error here, type incompatibility
PNode(pcn1)^ := pn^;
Writeln(pcn1^.a: 10, pcn1^.x: 10);
// Here is X value does not changed because it is absent in the pcn2^ have TNode type
// and assign method of the TNode will be called
pcn1^.assign(pcn2^);
Writeln(pcn1^.a: 10, pcn1^.x: 10);
// This code works well
pcn1^ := PChildNode(pcn2)^;
pcn2^.a := 55;
Writeln(pcn1^.a: 10, pcn1^.x: 10);
Readln;
end.
You can play with this code to learn more about objects behavior.
Related
I have a really slow query that always make windows mark my program as Not responding. I decided to create a background worker to perform this query while the main thread show a GIF. I did everything and it works! =D
But... When I close my Form I got a EInvalidPointer Exception, ONLY when I use the worker thread.
Here is my code:
Main thread Call to worker thread
if not TThreadDB.ExecutarThreadDB(cdsSolicitacao,
FConsultaSql,
nil,
tpHigher) then
begin
Exit;
end;
Where:
cdsSolicitacao is a clientDataSet I want to share between the threads,
FConsultaSql string (my query)
My thread Unit
unit UThreadDB;
interface
uses Classes, DBClient, DB, SysUtils, Variants, JvJCLUtils;
type
TParametros = class
private
FTotal: Integer;
FCampo: array of string;
FTipo: array of TFieldType;
FValor: array of Variant;
public
constructor Create(ACampos: string; ATipos: array of TFieldType; AValores: array of Variant); reintroduce;
end;
TThreadDB = class(TThread)
private
FExecutou: Boolean;
FClientDataSet: TClientDataSet;
FConsultaSQL: string;
FParametros: TParametros;
procedure CarregarDados;
protected
procedure Execute; override;
public
constructor Create(ACreateSuspended: Boolean; AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
AParametros: TParametros = nil); reintroduce;
class function ExecutarThreadDB(AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal): Boolean;
class procedure ExecutarThreadDBParalela(AThreadDB: TThreadDB; AClientDataSet: TClientDataSet;
AConsultaSQL: string = ''; AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal);
end;
implementation
uses
BIBLIO;
{ TThreadDB }
class function TThreadDB.ExecutarThreadDB(AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal): Boolean;
var
lThreadDB: TThreadDB;
begin
lThreadDB := TThreadDB.Create(True, AClientDataSet, AConsultaSQL, AParametros);
try
//lThreadDB.FreeOnTerminate := True;
lThreadDB.Priority := APriority;
lThreadDB.Resume;
lThreadDB.WaitFor;
Result := lThreadDB.FExecutou;
finally
lThreadDB.Terminate;
//lThreadDB := nil;
FreeAndNil(lThreadDB);
end;
end;
class procedure TThreadDB.ExecutarThreadDBParalela(AThreadDB: TThreadDB; AClientDataSet: TClientDataSet;
AConsultaSQL: string = ''; AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal);
begin
AThreadDB := TThreadDB.Create(True, AClientDataSet, AConsultaSQL, AParametros);
AThreadDB.FreeOnTerminate := True;
AThreadDB.Priority := APriority;
AThreadDB.Resume;
end;
procedure TThreadDB.CarregarDados;
var
lIndex: Integer;
begin
FClientDataSet.Close;
try
if (FConsultaSQL <> '') then
begin
FClientDataSet.CommandText := FConsultaSQL;
end;
if (FParametros <> nil) then
begin
for lIndex := 0 to (FParametros.FTotal - 1) do
begin
case FParametros.FTipo[lIndex] of
ftInteger : FClientDataSet.Params.ParamByName(FParametros.FCampo[lindex]).AsInteger := FParametros.FValor[lIndex];
ftString : FClientDataSet.Params.ParamByName(FParametros.FCampo[lindex]).AsString := FParametros.FValor[lIndex];
ftDate : FClientDataSet.Params.ParamByName(FParametros.FCampo[lindex]).AsDate := FParametros.FValor[lIndex];
end;
end;
end;
FClientDataSet.Open;
FExecutou := True;
except
on E: Exception do
begin
Erro('Não foi possível carregar os dados solicitados!' + #13 +
'Classe do erro: ' + E.ClassName + #13 +
'Mensagem: ' + E.Message);
end;
end;
if (FParametros <> nil) then
begin
FreeAndNil(FParametros);
end;
end;
constructor TThreadDB.Create(ACreateSuspended: Boolean; AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
AParametros: TParametros = nil);
begin
inherited Create(ACreateSuspended);
FClientDataSet := AClientDataSet;
FConsultaSQL := AConsultaSQL;
FParametros := AParametros;
FExecutou := False;
end;
procedure TThreadDB.Execute;
begin
CarregarDados;
end;
{ TParametros }
constructor TParametros.Create(ACampos: string; ATipos: array of TFieldType; AValores: array of Variant);
var
lIndex: Integer;
begin
inherited Create;
FTotal := ContaCaracteres(ACampos, ';') + 1;
SetLength(FCampo, FTotal);
SetLength(FTipo, FTotal);
SetLength(FValor, FTotal);
for lIndex := 0 to FTotal - 1 do
begin
FCampo[lIndex] := ExtractDelimited(lIndex + 1, ACampos , [';']);
end;
for lIndex := 0 to FTotal - 1 do
begin
FTipo[lIndex] := ATipos[lIndex];
end;
for lIndex := 0 to FTotal - 1 do
begin
FValor[lIndex] := AValores[lIndex];
end;
end;
end.
Any ideas of what I'm missing?
You haven't mentioned that in your question, but I guess the problem you describe may be caused by modifying dataset object, which is at the same time consumed by the main thread (displayed in a grid, for example). In other words, that you're passing to a worker thread dataset, which is linked to some controls in your main thread. Or, yet another way described, your cdsSolicitacao dataset object is linked through data source object to some control(s) on your main form.
Do note that main thread cannot ever work with an object that is being modified by a worker thread.
And even modal animation won't stop main thread from consuming that just modified dataset. For example DB grid can be requested for repaint which needs access to its underlying dataset which is at the same time being modified by a worker thread.
If that is your case, and you don't want to create a new dataset instance that you then replace with the consumed one when the worker thread finishes,
you'll need to (ideally) disconnect that dataset object from every linked control before passing to the worker thread and reconnect when finishes.
I'm working on TLogger class that is logging my application logs to file...
I have to way of getting Logs from File to TMemo:
1. assign TMemo to TLogger class then, assign True to DisplayInMemo property, then just call GetLogFromFile();
2. call GetLogsFromFile(); then Self.Memo1.Text := TLogger.LogsResult;
Below... Commented solution works fine... Uncommented solution works only every 2 click on button 4
procedure TForm1.Button4Click(Sender: TObject); // get log.file to memo
begin
// automatic forwarding logs from File to TMemo - it works!
//logger.DisplayMemo := Self.Memo1;
//logger.DisplayInMemo := True;
//logger.GetLogsFromFile();
// tested - half-automatic method of formwarding logs to TMemo - works every 2 clicks :(
logger.DisplayInMemo := False;
logger.GetLogsFromFile();
Self.Memo1.Text := logger.LogsResult;
end;
Whole TLogger implementation:
unit Logger;
interface
uses
System.IOUtils, System.TypInfo, System.SysUtils, FMX.Forms, FMX.Dialogs, System.Classes, FMX.Graphics, FMX.ExtCtrls, LoggerThread, FMX.Memo;
type
TLogger = class
private
FileName : String; // name of file to log
FilePath : String; // path to app / log-file
LStringResult : String; // result of thread log.file reading
LLoggerMemo : TMemo; // copy of memo - place where GetLogsFromFile put results
LDisplayInMemo : Boolean; // bool - if True GetLogsFromFile puts results to DisplayMemo, otherwise waiting in LogsResult
NewLoggerThread : TLoggerThread; // thread object - created in Create()
procedure GetLogsFromFileThreadTerminateHandler(sender: TObject);
public
constructor Create(); overload; // open or create 'development.log'
constructor Create(LogFileName : String); overload; // open or create LogFileName for logging
destructor Destroy(); overload; // cleaner of TLogger object
// main procedures
procedure Log(LogString : String); // add line to log file
procedure GetLogsFromFile(); // get all logs from log file to string
// settings, reading results,
property DisplayInMemo : Boolean read LDisplayInMemo write LDisplayInMemo; //bool - if True GetLogsFromFile puts results to DisplayMemo, otherwise waiting in LogsResult
property LogsResult : String read LStringResult write LStringResult; //string results after Getters from TLogger usage
property DisplayMemo : TMemo read LLoggerMemo write LLoggerMemo; // sets TMemo where results will be put if DisplayInMemo set to True
end;
implementation
constructor TLogger.Create();
begin
{$IFDEF Android}
FilePath := TPath.GetDownloadsPath + System.SysUtils.PathDelim;
{$ELSE}
FilePath := ExtractFilePath(ParamStr(0));
{$ENDIF}
FileName := 'development.log';
LDisplayInMemo := False;
// inherited
inherited Create;
end;
constructor TLogger.Create(LogFileName : String);
begin
{$IFDEF Android}
FilePath := TPath.GetDownloadsPath + System.SysUtils.PathDelim;
//TPath.Combine(TPath.GetDocumentsPath,'test.txt'); // to have / \ auto-change
{$ELSE}
FilePath := ExtractFilePath(ParamStr(0));
{$ENDIF}
FileName := LogFileName;
LDisplayInMemo := False;
// inherited
inherited Create;
end;
destructor TLogger.Destroy();
begin
inherited Destroy;
end;
// adds a sigle line to log file with date time
procedure TLogger.Log(LogString : String);
begin
NewLoggerThread := TLoggerThread.Create(True);
NewLoggerThread.FreeOnTerminate := True;
NewLoggerThread.Log := LogString; //log to write - date time then added in execute
NewLoggerThread.LoggerInstruction := TLoggerInstruction.liLogToFile; //set instuction for thread - LogToFile
NewLoggerThread.FileName := FileName; //file to write
NewLoggerThread.FilePath := FilePath; //path to file
try
NewLoggerThread.Start;
except
NewLoggerThread.Free();
end;
end;
// results String with LogFile content
procedure TLogger.GetLogsFromFile();
begin
NewLoggerThread := TLoggerThread.Create(True);
NewLoggerThread.FreeOnTerminate := True;
NewLoggerThread.OnTerminate := GetLogsFromFileThreadTerminateHandler;
NewLoggerThread.FileName := FileName; //file to write
NewLoggerThread.FilePath := FilePath; //path to file
NewLoggerThread.LoggerInstruction := TLoggerInstruction.liGetLogsFromFile; //set instuction for thread - GetLogFromFile
try
NewLoggerThread.Start;
except
NewLoggerThread.Free();
end;
end;
procedure TLogger.GetLogsFromFileThreadTerminateHandler(sender: TObject);
begin
LStringResult := (Sender as TLoggerThread).StringResult;
if LDisplayInMemo then
LLoggerMemo.Text := (Sender as TLoggerThread).StringResult;
end;
end.
As you can see only difference is in LDisplayInMemo: if is True TMemo fills with logs... when is False I need 2 clicks on Button 4 to get results in TMemo...
procedure TLogger.GetLogsFromFileThreadTerminateHandler(sender: TObject);
begin
LStringResult := (Sender as TLoggerThread).StringResult;
if LDisplayInMemo then
LLoggerMemo.Text := (Sender as TLoggerThread).StringResult;
end;
Any ideas? To be honest I have no idea what's the reason of diffenerce in working of both solutions :( I also tried ProcessMessages after Self.Memo1.Text := logger.LogsResult;
The reason the following code only works the second time you click the button is that your code to actually get the log information runs in another thread... it's asynchronous!
logger.DisplayInMemo := False;
logger.GetLogsFromFile();
Self.Memo1.Text := logger.LogsResult; //This line runs AT THE SAME TIME you're getting logs!
NOTE: You're reading the value of logger.LogsResult before it gets a value from your LoggerThread.
When you click the button a second time, the thread has finished running (the first time), and you're now able to read a value.
The reason your commented section works, is that you are only assigning the memo text when the thread terminates - i.e. finished doing it's work.
I have the following problem/question.
I have a unit named "myGlobalFunctions.pas".
Inside this unit I have implemented multiple procedures/functions that are used by several projects.
project 1 use this unit
project 3 use this unit
project 6 use this unit
etc
inside "project 1" there is a thread that use functions inside the "global function" unit.
inside project 3 there is no thread but the functions are used.
so far this thread (project1) provide almost no update of the application interface and the update was made AFTER or BEFORE calling a function from "myGlobalFunctions.pas"
like "before start function1"
... the calling
"after function1".
this way I can know what the program is doing.
However now I want to implement inside the "function1" update of the application interface (with synchronize).
I want to reflect in the application interface "processing step1 ... xx records". (there is a while loop there for a dataset).
using Synchronize for "project1" and with normal label1.caption = 'message'; application.process messages for any other project.
is it possible?
how can I do such a thing.
can be Thread Safe ?
tks a lot
Razvan
here is some code to understand better
THREAD UNIT
procedure TThreadSyncronizeProcess.SignalStart;
begin
frmMain.sbMain.Panels[2].Text := 'Syncronizare in desfasurare...'; -- exist all the time
if Assigned(frmSyncronize) then begin -- check if exist this
frmSyncronize.logMain.WriteFeedBackMessage('Pornire syncronizare...', '', EVENTLOG_INFORMATION_TYPE, True);
end;
end;
procedure TThreadSyncronizeProcess.Execute;
var ..... declarations
begin
Synchronize(SignalStart); -- this is normal call within thread update interface
try
try
workSession := TIB_Session.Create(nil);
workDatabase := TIB_Database.Create(workSession);
... creating more components and setup them ...
uSyncronizareFunctions.SetupDatabase(workDatabase, workSession, transactionWrite, transactionRead);
uSyncronizareFunctions.SetupDataSnapConnection(workConnectionRead, providerRead);
if Assigned(frmSyncronize) then begin
uSyncronizareFunctions.SetupFeedBack(frmSyncronize.logMain);
end;
try
Synchronize(SignalMessage);
// this next function is from the "global unit"
isAllOk := uSyncronizareFunctions.ImportOperatoriAutorizati(workImage, workLabelProgress, True);
isAllOk := isAllOk and uSyncronizareFunctions.ImportJudete;
isAllOk := isAllOk and uSyncronizareFunctions.ImportLocalitati;
isAllOk := isAllOk and uSyncronizareFunctions.ImportUM;
isAllOk := isAllOk and uSyncronizareFunctions.ImportFurnizori;
isAllOk := isAllOk and uSyncronizareFunctions.ImportClasificari;
except
on e : Exception do begin
raise Exception.Create(dmMain.GetDataSnapExceptionMessage(e.Message));
end;
end;
except
on e : Exception do begin
baseMessage := e.Message;
Synchronize(SignalMessage);
end;
end;
finally
workDatabase.ForceDisconnect;
FreeAndNil(transactionRead);
... etc
end;
Synchronize(SignalFinish);
end;
global function unit
unit uSyncronizareFunctions;
function ImportOperatoriAutorizati(imgDone : TImage; labelProgress : TLabel; isThread : Boolean) : Boolean;
var workQuery : TIB_Query;
serverData : TClientDataSet;
begin
Result := True;
try
... create all that we need
serverData.Close;
serverData.CommandText := 'SELECT * FROM OPERATORI_AUTORIZATI WHERE REC_VERSION > :ARECVERSION ORDER BY REC_VERSION, ID';
serverData.Params.Clear;
serverData.Params.CreateParam(ftInteger, 'ARECVERSION', ptInput);
serverData.Params.ParamByName('ARECVERSION').AsInteger := lastVersion;
serverData.Active := True;
...... I want here to signal start
while not serverData.Eof do begin
try
globalInsert_Tran.StartTransaction;
workQuery.Close;
workQuery.ParamByName('AIDGLOBAL').AsString := serverData.FieldByName('IDGLOBAL').AsString;
workQuery.Open;
if workQuery.IsEmpty then begin
workQuery.Insert;
workQuery.FieldByName('IDGLOBAL').AsString := serverData.FieldByName('IDGLOBAL').AsString;
end else begin
workQuery.Edit;
end;
workQuery.FieldByName('NUME').AsString := serverData.FieldByName('NUME').AsString;
workQuery.FieldByName('COD_AUTORIZARE').AsString := serverData.FieldByName('COD_AUTORIZARE').AsString;
workQuery.FieldByName('OTHER_INFO').AsString := serverData.FieldByName('OTHER_INFO').AsString;
workQuery.FieldByName('DATASTERGERE').AsVariant := GetValueDate(serverData.FieldByName('DATASTERGERE').AsDateTime);
workQuery.FieldByName('REC_VERSION').AsInteger := serverData.FieldByName('REC_VERSION').AsInteger;
workQuery.Post;
MarkRecordAsDirtyFalse(workQuery);
globalInsert_Tran.Commit;
...... I want here to signal progress and to see in the application interface "processing record xx/100" or any other message
except
on e : Exception do begin
Result := False;
globalInsert_Tran.Rollback;
end;
end;
serverData.Next;
end;
finally
FreeAndNil(serverData);
FreeAndNil(workQuery);
end;
end;
It looks like you would like your global function to execute a callback. You might try an approach like this:
unit MyGlobalMethods;
interface
uses
System.SysUtils;
type
// define a method signature for your callback
TSomeCallback = procedure(progress : integer) of object;
// add a callback argument to your function (initializing to nil will make
// the parameter optional and will not break your previous implementations)
function GlobalFunction(arg1 : integer;
AMethodCallback : TSomeCallback = nil) : boolean;
implementation
function GlobalFunction(arg1 : integer;
AMethodCallback : TSomeCallback) : boolean;
var i : integer;
begin
for i := 0 to arg1 do begin
sleep(10); // Do some work
// report progress by executing the callback method
// only do this if a method has been passed as argument
if (i mod 100 = 0) and (Assigned(AMethodCallback)) then AMethodCallback(i);
end;
result := true;
end;
end.
Adding a method callback as an argument allows you to pass in any function you like to have the method execute. For example :
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
procedure UpdateProgress(progress : integer);
end;
TSomeThread = class(TThread)
private
FProgressCallback : TSomeCallback;
FProgress : integer;
procedure SynchronizeCallback(progress : integer);
procedure DoCallback;
public
procedure Execute; override;
property OnFunctionProgress : TSomeCallback
read FProgressCallback write FProgressCallback;
end;
implement as :
procedure TSomeThread.Execute;
begin
GlobalFunction(1000, SynchronizeCallback);
end;
procedure TSomeThread.SynchronizeCallback(progress: Integer);
begin
FProgress := progress;
Synchronize(DoCallback);
end;
procedure TSomeThread.DoCallback;
begin
if Assigned(FProgressCallback) then FProgressCallback(FProgress);
end;
You haven't told us what version of Delphi you are using. If you are using D2009 or newer you can bundle the above two calls into one using anonymous methods (and get rid of FProgress) :
procedure TSomeThread.SynchronizeCallback(progress: Integer);
begin
Synchronize(procedure
begin
if Assigned(FProgressCallback) then FProgressCallback(progress);
end;);
end;
Where in your form you would do :
procedure TForm1.UpdateProgress(progress: Integer);
begin
label1.Caption := IntToStr(progress);
end;
procedure TForm1.Button1Click(Sender: TObject);
var someThread : TSomeThread;
begin
someThread := TSomeThread.Create(true);
someThread.FreeOnTerminate := true;
someThread.OnFunctionProgress := UpdateProgress;
someThread.Start;
end;
This nicely separates the responsibilities. The main form passes an update method to the thread (a method, in this case, to update a label). The thread is responsible for synchronizing the call and the global function, therefore, does not need to care whether or not the callback it is executing originates from the main thread or from any other thread. The thread knows it needs to synchronize the method so it should take that responsibility.
Here is the system.object:
TTrendGroup = class(System.Object)
SigList:ArrayList;
Rate,Phase,Delay:SmallInt;
RateIndex,PhaseIndex:SmallInt;
firstTime:Boolean;
base:integer;
Enabled:Boolean;
name:string;
public
constructor;
method AddTGroup(signal:TTrendSignal);
method Clear;
method Poll(list:ArrayList);
method ReadTGroup(bTGr:BinaryReader);
method WriteTGroup(bTGw:BinaryWriter);
method WriteSignals(bWSw:BinaryWriter);
method ToString:String;override;
end;
constructor TTrendGroup;
begin
SigList := new ArrayList;
Rate := 30;
Phase := 0;
Delay := Phase;
RateIndex := 4;
PhaseIndex := 0;
firsttime := true;
enabled := true;
name := '';
end;
Here is how I create an object from the above system.object and add it to my GroupList ListBox:
method HTrendFrm.AddGroup1_Click(sender: System.Object; e: System.EventArgs);
var
i:integer;
grp:TTrendGroup;
begin
if ReadWrite then
begin
grp := new TTrendGroup;
grp.name:='New Group';
i := GroupList.Items.Add(grp);
GroupList.SelectedIndex := i;
grpName.Text := 'New Group';
PollBtn.Checked := grp.Enabled;
RateBox.SelectedIndex := grp.RateIndex;
PhaseBox.SelectedIndex:= grp.PhaseIndex;
SignalListBox.Items.Clear;
UpdateButtons;
end;
end;
Here is how I try to retrieve the system.object I just added back out:
method HTrendFrm.GroupList_Click(sender: System.Object; e: System.EventArgs);
var
grp:TTrendGroup;
begin
if (GroupList.SelectedIndex = -1) then exit;
with GroupList do
begin
grp := TTrendGroup(items[SelectedIndex]); <<<<< HERE is WHERE THE PROBLEM IS. grp always returns NIL.
end;
end;
I don't know why. I have very similar code in other part of this program and they work as expected.
What am I doing wrong?
When the returned object is nil, did you verify the SelectedIndex value is actually valid? Your code has a logic bug in it that allows SelectedIndex to be -1 when the ListBox is not empty. Your if statement needs to use the or operator instead of the and operator:
// if (GroupList.Items.Count<=0) and (GroupList.SelectedIndex = -1) then exit;
if (GroupList.Items.Count<=0) or (GroupList.SelectedIndex = -1) then exit;
For instance
Font.Style = StringToSet('[fsBold, fsUnderline]');
of course there would need to be some typeinfo stuff in there, but you get the idea. I'm using Delphi 2007.
check this code, is not exactly the same syntax which you propose , but works setting the value of a set from a string.
uses
TypInfo;
procedure StringToSet(Const Values,AProperty:string;Instance: TObject);
begin
if Assigned(GetPropInfo(Instance.ClassInfo, AProperty)) then
SetSetProp(Instance,AProperty,Values);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
StringToSet('[fsBold, fsUnderline, fsStrikeOut]','Style',Label1.Font);
end;
Also see my old post: SetToString, StringToSet for a solution (Delphi 2007, IIRC) without a need for published property RTTI:
uses
SysUtils, TypInfo;
function GetOrdValue(Info: PTypeInfo; const SetParam): Integer;
begin
Result := 0;
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Result := Byte(SetParam);
otSWord, otUWord:
Result := Word(SetParam);
otSLong, otULong:
Result := Integer(SetParam);
end;
end;
procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Byte(SetParam) := Value;
otSWord, otUWord:
Word(SetParam) := Value;
otSLong, otULong:
Integer(SetParam) := Value;
end;
end;
function SetToString(Info: PTypeInfo; const SetParam; Brackets: Boolean): AnsiString;
var
S: TIntegerSet;
TypeInfo: PTypeInfo;
I: Integer;
begin
Result := '';
Integer(S) := GetOrdValue(Info, SetParam);
TypeInfo := GetTypeData(Info)^.CompType^;
for I := 0 to SizeOf(Integer) * 8 - 1 do
if I in S then
begin
if Result <> '' then
Result := Result + ',';
Result := Result + GetEnumName(TypeInfo, I);
end;
if Brackets then
Result := '[' + Result + ']';
end;
procedure StringToSet(Info: PTypeInfo; var SetParam; const Value: AnsiString);
var
P: PAnsiChar;
EnumInfo: PTypeInfo;
EnumName: AnsiString;
EnumValue, SetValue: Longint;
function NextWord(var P: PAnsiChar): AnsiString;
var
I: Integer;
begin
I := 0;
// scan til whitespace
while not (P[I] in [',', ' ', #0,']']) do
Inc(I);
SetString(Result, P, I);
// skip whitespace
while P[I] in [',', ' ',']'] do
Inc(I);
Inc(P, I);
end;
begin
SetOrdValue(Info, SetParam, 0);
if Value = '' then
Exit;
SetValue := 0;
P := PAnsiChar(Value);
// skip leading bracket and whitespace
while P^ in ['[',' '] do
Inc(P);
EnumInfo := GetTypeData(Info)^.CompType^;
EnumName := NextWord(P);
while EnumName <> '' do
begin
EnumValue := GetEnumValue(EnumInfo, EnumName);
if EnumValue < 0 then
begin
SetOrdValue(Info, SetParam, 0);
Exit;
end;
Include(TIntegerSet(SetValue), EnumValue);
EnumName := NextWord(P);
end;
SetOrdValue(Info, SetParam, SetValue);
end;
Example usage:
var
A: TAlignSet;
S: AnsiString;
begin
// set to string
A := [alClient, alLeft, alTop];
S := SetToString(TypeInfo(TAlignSet), A, True);
ShowMessage(Format('%s ($%x)', [S, Byte(A)]));
// string to set
S := '[alNone, alRight, alCustom]';
StringToSet(TypeInfo(TAlignSet), A, S);
ShowMessage(Format('%s ($%x)', [SetToString(TypeInfo(TAlignSet), A, True), Byte(A)]));
end;
You have right function name already - StringToSet. However, usage is tricky:
procedure TForm1.FormClick(Sender: TObject);
type PFontStyles = ^TFontStyles; // typecast helper declaration
var Styles: Integer; // receives set bitmap after parsing
{$IF SizeOf(TFontStyles) > SizeOf(Integer)}
{$MESSAGE FATAL 'Panic. RTTI functions will work with register-sized sets only'}
{$IFEND}
begin
Styles := StringToSet( // don't forget to use TypInfo (3)
PTypeInfo(TypeInfo(TFontStyles)), // this kludge is required for overload (1)
'[fsBold, fsUnderline]'
);
Font.Style := PFontStyles(#Styles)^; // hack to bypass strict typecast rules (2)
Update(); // let form select amended font into Canvas
Canvas.TextOut(0, 0, 'ME BOLD! ME UNDERLINED!');
end;
(1) because initially borland limited this function family to PropInfo pointers and TypeInfo() intrinsic returns untyped pointer, hence the typecast
(2) typecasting requires types to be of same size, hence the referencing and dereferencing to different type (TFontStyles is a Byte)
Nitpicker special: (3) This snippet works out of the box in D2010+. Earlier versions has required dependency missing - namely StringToSet(TypeInfo: PTypeInfo; ... overload (see docwiki link above). This problem is solvable by copypasting (yeah, but TTypeInfo is lower-level than TPropInfo) original function and doing 2 (two) minor edits. By obvious reasons i'm not going to publish copyrighted code, but here is the relevant diff:
1c1,2
< function StringToSet(PropInfo: PPropInfo; const Value: string): Integer;
---
> {$IF RTLVersion < 21.0}
> function StringToSet(TypeInfo: PTypeInfo; const Value: string): Integer; overload;
37c38
< EnumInfo := GetTypeData(PropInfo^.PropType^)^.CompType^;
---
> EnumInfo := GetTypeData(TypeInfo)^.CompType^;
47a49
> {$IFEND}