Access violation with TDictionary.Add - delphi

Anyone know why I am getting an access violation with the following:
unit TestForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Generics.Collections, Vcl.Grids,
Vcl.ValEdit;
type
TClientThing = class
private
iCDic: TDictionary<string, string>;
published
property Dic: TDictionary<string, string> read iCDic write iCDic;
end;
TForm1 = class(TForm)
vleHeader: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
ClientThing: TClientThing;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
vCustomParamKey: string;
vCustomValueKey: string;
J: integer;
begin
with ClientThing do
begin
// Get the header params from the config and list edit...
for J := 0 to vleHeader.RowCount - 1 do
begin
vCustomParamKey := vleHeader.Cells[0, J];
vCustomValueKey := vleHeader.Cells[1, J];
Dic.Add(vCustomParamKey, vCustomValueKey);
end;
end;
end;
end.
The Access violation is at the Dic.Add line. The exception is:
I have been up all night and so have probably missed something. The TValueListEditor contents are (code editor view):
X-Application=g9V0rB9a3J5UF8
X-Authentication=kQNvuuimr0yMtEYZtXAZntTScPlvjecEAGtvbnNIU=
JSONRpc=2.0

The form never assigns a value to ClientThing, so it's still nil. Assign it a value, and then assign a value to iCDic. You should have been able to detect this problem when you stepped through the code with the debugger.

Related

Does VCL components have onCreate event?

Need to access some components right after program start but I have found that doing so from form's onCreate event is bad because at the moment they may still be unavailable (access violation occurs). Can not find onCreate event in any component. Am I missing something?
Here is code. Empty form with ValueListEditor.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure: Load;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Load;
end;
procedure Load;
begin
(Application.MainForm.FindComponent('ValueListEditor1') as TValueListEditor)
.Strings.LoadFromFile('c:\li');
end;
end.
The problem is not because your component hasn't been created yet, because it has been. The real problem is because the Application.MainForm property hasn't been assigned yet when your main Form's OnCreate event is fired, so you are calling FindComponent() on a nil Form pointer.
Since Load() is merely accessing a member of TForm1 then Load() should also be a member of TForm1 as well, and then you can call it, and thus access your component, via the implicit Self pointer, which is valid during the Form's OnCreate event, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure LoadValues;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadValues;
end;
procedure TForm1.LoadValues;
begin
ValueListEditor1.Strings.LoadFromFile('c:\li');
end;
end.
If, for whatever reason, Load() must be a standalone procedure, then at least have it use your global Form1 variable, which the call to Application.CreateForm() will assign before the main Form's OnCreate event is fired, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure LoadValues;
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure Load;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadValues;
end;
procedure TForm1.LoadValues;
begin
ValueListEditor1.Strings.LoadFromFile('c:\li');
end;
procedure Load;
begin
if Form1 <> nil then
Form1.LoadValues;
end;
end.
Alternatively, you can fallback to looking for the Form1 object in the Screen.Forms[] array, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure LoadValues;
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure Load;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadValues;
end;
procedure TForm1.LoadValues;
begin
ValueListEditor1.Strings.LoadFromFile('c:\li');
end;
procedure Load;
var
I: Integer;
Frm: TForm;
begin
for I := 0 to Screen.FormCount-1 do
begin
Frm := Screen.Forms[I];
if Frm is TForm1 then
begin
TForm1(Frm).LoadValues;
Exit;
end;
end;
end;
end.
Use the form's onShow() -event.
But be aware, that the onShow() -event is called every time the form is shown, not only the first time.
In your form, you should override the DoShow method and insert your code there. Since DoShow is called everytime the form changes from invisible to visible, you should also add a boolean variable in your form and check in the DoShow override, if it is false. In that case, it is the first time DoShow is called. Then set the variable to true and do whatever you need to do the first time.
Please note that within DoShow the form is not visible yet. If you need to do something once the form is made visible, you can post a custom message from DoShow and put your code in the corresponding message handler. At the time it is executed, the form just became visible.
const
WM_APP_STARTUP = WM_USER + 1;
type
TForm1 = class(TForm)
protected
FInitialized : Boolean;
procedure DoShow; override;
procedure WMAppStartup(var Msg: TMessage); message WM_APP_STARTUP;
end;
implementation
procedure TForm1.DoShow;
begin
// Form is NOT visible but will soon be
if not FInitialized then begin
FInitialized := TRUE;
// Insert your code here
PostMessage(Handle, WM_APP_STARTUP, 0, 0);
end;
inherited DoShow;
end;
procedure TForm1.WMAppStartup(var Msg: TMessage);
begin
// The form is now visible
// Insert your code here
end;

Delphi Spinedit: Get the previous value

Lets say I have a Spinedit on my Form.
The current Value of the Spinedit is e.g 5
When the user clicks on the SpinButton the next Value could be
4 or 6.
In the onChange event
I can get the new Value
4 or 6
but I need to know also the old Value
5
How can I get the previous value 5 in Delphi?
I need to know the old and the new Value
Store the previous value in a variable at the end of the OnChange event and at startup as below.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Samples.Spin;
type
TForm1 = class(TForm)
SpinEdit1: TSpinEdit;
procedure SpinEdit1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FSpinPrev : Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FSpinPrev := SpinEdit1.Value;
end;
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
if SpinEdit1.Value > FSpinPrev then Caption := 'Increasing'
else Caption := 'Decreasing';
FSpinPrev := SpinEdit1.Value;
end;
end.

TJclStringList crashes on Free

Create a simple VCL application:
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TForm1 = class(TForm)
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
JclStringLists;
var
MyList1: TJclStringList;
MyList2: TJclStringList;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyList1.Free;
MyList2.Free;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyList1 := TJclStringList.Create;
MyList2 := TJclStringList.Create;
MyList1.LoadFromFile('C:\ONE.txt');
MyList2.LoadFromFile('C:\TWO.txt');
Self.Caption := Self.Caption + ' ' + IntToStr(MyList1.Count);
Self.Caption := Self.Caption + ' ' + IntToStr(MyList2.Count);
end;
end.
It crashes in the TForm1.FormDestroy event-handler when attempting to free the MyList1 object instance. Why?
TJclStringList is a reference counted type (it's declared in JCLStringLists.pas as type TJclStringList = class(TJclInterfacedStringList, IInterface, IJclStringList) and implements both _AddRef and _Release to handle reference counting), so you shouldn't be creating them as objects at all, and you shouldn't manually free them - they will automatically be free'd when the reference to them goes out of scope. (This also means you should not declare them as global variables, because you then don't maintain control over their lifetime.)
The JclStringLists unit provides several functions that will properly create an instance of the interface for you. You can see them in that unit, just above the implementation keyword:
function JclStringList: IJclStringList; overload;
function JclStringListStrings(AStrings: TStrings): IJclStringList; overload;
function JclStringListStrings(const A: array of string): IJclStringList; overload;
function JclStringList(const A: array of const): IJclStringList; overload;
function JclStringList(const AText: string): IJclStringList; overload;
The proper way to use TJclStringList to do what you want is something like this:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, JCLStringLists;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
MyList1, MyList2: IJCLStringList; // Note I and not T in type.
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
MyList1 := JclStringList;
MyList1.LoadFromFile('C:\Work\Data\FirstName.txt');
MyList2 := JclStringList
MyList2.LoadFromFile('C:\Work\Data\LastName.txt');
// Only to demonstrate that both files got loaded by the code above.
Self.Caption := Format('First: %d Last: %d', [MyList1.Count, MyList2.Count]);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// Do NOT free the JclStringLists here - they will automatically be released when
// the form is destroyed because the reference count will reach zero (as long as
// you don't have any other references to those variables, which by putting them into
// the private section is unlikely.
end;
end.

Is there any way in delphi to execute previously written code in a single line?

I'm a high school student taking programming as one of my subjects, so I'm rather new to Delphi.
I'm writing a game that requires the same (very long) block of code to be run when multiple different events occur. I was wondering if there was a way to write it at the beginning and call it in these different parts of the program, or perhaps get multiple senders to run the same event? The code sets the brush color of 42 different objects to different colors depending on what the user selects (the game is Risk) and when i try using a procedure it get errors for every object telling me it is undeclared.
type
TForm1 = class(TForm)
shpTerr1: TShape;
private
{ Private declarations }
public
procedure CheckOwner;
end;
var
Form1: TForm1;
iArmies, iTemp, i : integer;
iSelected, iSelectedOld : integer;
arrTerrArmies, arrTerrOwners : array[0..41] of integer;
arrPlayerColour : array[0..3] of string;
arrPlayers : array of string;
AttackMode : boolean;
implementation
{$R *.dfm}
procedure CheckOwner;
begin
shpTerr1.Brush.Color := StringToColor('cl' + arrPlayerColour[arrTerrOwners[0]]);
end;
The error is with the TShape.
Any help?
Quick answer:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
shpTerr1: TShape;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure CheckOwner;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.CheckOwner;
begin
shpTerr1.Brush.Color:= Color; // I don't know what is arrPlayerColour[arrTerrOwners[0]]
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
CheckOwner;
end;
end.

Get TAdvEdit.Text directly from procedure/function

Hello i get error E2197: [DCC Error] proj1.pas(34): E2197 Constant object cannot be passed as var parameter:
unit proj1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, AdvEdit;
type
TForm1 = class(TForm)
AdvEdit1: TAdvEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure SetEditText(const instr: string; out outstr: string);
begin
outstr := instr;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SetEditText('Pippo', AdvEdit1.Text);
end;
end.
Of course, i can solve writing:
procedure TForm1.Button1Click(Sender: TObject);
var sText: string
begin
SetEditText('Pippo', sText);
AdvEdit1.Text := sText;
end;
But when i have many AdvEdit, then it is hard. Then i ask, is possible solve the problem in some mode giving directly TAdvEdit.Text as parameter in mine procedure?
Thanks very much.
I presume that Text is a property. And you cannot pass a property to a var or out parameter. You can only pass variables to parameters of those kinds.
You'll need to find a different way to write your code. You've come up with one such idea, but it seems needlessly complex to me. I cannot see anything simpler than:
AdvEdit1.Text := 'Pippo';
How could there be any code simpler than this? You need to specify at a bare minimum the following:
The target control.
That we are dealing with the Text property.
The fact that we are assigning.
The new value.
The code above does that and nothing more.

Resources