Using Delphi 'with' keyword in brackets - delphi

I need to add some initialization without adding a variable declaration to the var section, so I try to do that by doing this:
sql.columns.Add(with TColumn.Create do
begin
ColName := 'Price';
As_ := 'MaxPrice';
end);
but Delphi raises an error while compiling.
Any ideas?

TList<T>.Add() expects a fully constructed object as input. The with keyword does not provide you access to the object it is operating on. You need to use a variable, whether you like it or not:
var
col: TColumn;
col := TColumn.Create;
col.ColName := 'Price';
col.As_ := 'MaxPrice';
sql.columns.Add(col);
The alternative is to write a function instead, and use its special Resultvariable:
function MakeColumn(const AName, AAs: string): TColumn;
begin
Result := TColumn.Create;
Result.ColName := AName;
Result.As_ := AAs;
end;
sql.columns.Add(MakeColumn('Price', 'MaxPrice'));

Related

Left side cannot be assigned for a record type

I am trying to upgrade my application from Delphi 2007 to Delphi 10 Seattle. I understand that a record needs to be copied to a local variable before changing and then assigned back. I am trying the same but I still get the error that I cannot assign to a left side. Could someone please help.
procedure TMydlg.WMGetMinMaxInfo(var Msg:TMessage);
var
MinMaxInfo: TMinMaxInfo;
begin
inherited;
MinMaxInfo := (PMinMaxInfo(Msg.LParam)^);
with MinMaxInfo do
begin
ptMinTrackSize.X := MinWidth;
ptMinTrackSize.Y := MinHeight;
ptMaxTrackSize.X := MinWidth;
end;
// Error here. Left side cannot be assigned to
(PMinMaxInfo(Msg.LParam)^) := MinMaxInfo;
TMinMaxInfo is from Winapi.windows
The compiler error is emitted because the compiler rejects the outermost parens on the left hand side of the final assignment. In essence, your code is akin to the following:
type
TMyRecord = record
end;
procedure Foo;
var
rec1, rec2: TMyRecord;
begin
rec1 := rec2; // compiles
(rec1) := rec2; // E2064 Left side cannot be assigned to
end;
Writing it in this simplified manner brings the issue into very sharp relief.
I'm not sure why the compiler rejects these parens. I suspect that the formal grammar of the language renders your left hand side invalid. Serg provides a plausible explanation in the comments, that is that (...) is an expression, and an expression is not valid as the left hand side of an assignment. I'm inclined to believe that is accurate.
Anyway, it is simple to fix your code. Instead of
(PMinMaxInfo(Msg.LParam)^) := MinMaxInfo;
write
PMinMaxInfo(Msg.LParam)^ := MinMaxInfo;
Note that it is not necessary to make a copy of the record, modify it, and then copy it back. You can modify the record directly, once you have cast LParam to a pointer to the record.
I would do so like this:
procedure TMydlg.WMGetMinMaxInfo(var Msg:TMessage);
var
pmmi: PMinMaxInfo;
begin
inherited;
pmmi := PMinMaxInfo(Msg.LParam);
pmmi.ptMinTrackSize.X := MinWidth;
pmmi.ptMinTrackSize.Y := MinHeight;
pmmi.ptMaxTrackSize.X := MinWidth;
end;
I've omitted the ^ pointer dereference operator since it is optional in this scenario. If you prefer you might write the assignments like this:
pmmi^.ptMinTrackSize.X := MinWidth;
pmmi^.ptMinTrackSize.Y := MinHeight;
pmmi^.ptMaxTrackSize.X := MinWidth;
It is because you do not use a Record type and not a pointer type.
Change your code to this:
procedure TMydlg.WMGetMinMaxInfo(var Msg: TMessage);
begin
with pMinMaxInfo(Msg.LParam)^ do
begin
ptMinTrackSize.X := MinWidth;
ptMinTrackSize.Y := MinHeight;
ptMaxTrackSize.X := MinWidth;
end;
end;
I've created a dummy test program:
procedure TForm9.FormCreate(Sender: TObject);
var
MinMaxInfo: pMinMaxInfo;
Msg: TMessage;
begin
MinMaxInfo := new(pMinMaxInfo);
Msg.LParam := integer(MinMaxInfo);
WMGetMinMaxInfo(Msg);
Assert( pMinMaxInfo(Msg.LParam)^.ptMinTrackSize.X = 10);
end;
procedure TForm9.WMGetMinMaxInfo(var Msg: TMessage);
var
MinMaxInfo: pMinMaxInfo;
begin
MinMaxInfo := pMinMaxInfo(Msg.LParam);
with MinMaxInfo^ do
begin
ptMinTrackSize.X := 10;
ptMinTrackSize.Y := 10;
ptMaxTrackSize.X := 10;
end;
end;

Strings getting corrupted in ComboBox.AddObject. How to add them the proper way?

I'm adding strings with objects (also strings) to a TComboBox, but getting corrupted strings when trying to retrieve them later.
This is how I'm adding them:
var
i: Integer;
sl: TStringList;
c: Integer;
s: PChar;
begin
for i := 1 to tblCalls.FieldCount do
if tblCalls.Fields[i - 1].Tag = 1 then
ListBox1.Items.Append(tblCalls.Fields[i - 1].FieldName);
sl := TStringList.Create;
try
LoadStyles(TStrings(sl));
for c := 0 to sl.Count - 1 do
begin
s := PChar(sl.Values[sl.Names[c]]);
ComboBox1.Items.AddObject(sl.Names[c], TObject(s));
end;
finally
sl.Free;
end;
end;
procedure LoadStyles(var AStylesList: TStrings);
var
f, n: String;
filelist: TStringDynArray;
begin
f := ExtractFilePath(ParamStr(0)) + 'Styles';
if (not DirectoryExists(f)) then
Exit;
filelist := TDirectory.GetFiles(f);
for f in filelist do
begin
n := ChangeFileExt(ExtractFileName(f), EmptyStr);
AStylesList.Add(n + '=' + f);
end;
end;
..and this is where I'm trying to retrieve a string object:
procedure TfrmOptions.ComboBox1Change(Sender: TObject);
var
si: TStyleInfo;
i: Integer;
s: String;
begin
i := TComboBox(Sender).ItemIndex;
s := PChar(TComboBox(Sender).Items.Objects[i]);
Showmessage(s); // --> Mostly shows a corrupted string (gibberish chars)
if (TStyleManager.IsValidStyle(s, si)) then
begin
if (not MatchStr(s, TStyleManager.StyleNames)) then
TStyleManager.LoadFromFile(s);
TStyleManager.TrySetStyle(si.Name);
end;
end;
I suspect that its something with the way I'm adding them. Perhaps I need to allocate memory at:
s := PChar(sl.Values[sl.Names[c]]);
Not sure. Looking at the help on StrNew, NewStr and StrAlloc, it says that those functions are deprecated. Can you help point out whats wrong?
There's nothing to keep the string alive. When you write:
s := PChar(sl.Values[sl.Names[c]]);
an implicit local variable of type string is created to hold whatever sl.Values[sl.Names[c]] evaluates to. That local variable goes out of scope, as far as the compiler is aware, nothing references it, and the string object is destroyed.
In fact, it's even worse than that. Because the assignment above happens in a loop, there is only one implicit local variable. Each time round the loop, the string that you asked the combo box to remember is destroyed.
You need to find a way to extend the lifetime of the string. You could do it like this:
var
StrPtr: ^string;
....
for c := 0 to sl.Count - 1 do
begin
New(StrPtr);
StrPtr^ := sl.Values[sl.Names[c]];
ComboBox1.Items.AddObject(sl.Names[c], TObject(StrPtr));
end;
Then when you need to access the string you can do so like this:
var
StrPtr: ^string;
....
TObject(StrPtr) := TComboBox(Sender).Items.Objects[i];
// do something with StrPtr^
When you clear the combo box you must also run through each item and call Dispose on the pointer.
Having said that, it's going to be much easier not to do it that way. Stop trying to force strings into the TObject data associated with each item. Instead keep a parallel string list containing these strings. When you need to look up a name look it up in that list rather than in the combo box.
I know this is an old question but I came across this problem again and rather than use the separate string list I used an object with a string value (I think someone suggested it in a comment) as follows:
Declare a type as TObject with a string value:
TStringObject = class(TObject)
StringValue : string;
end;
Then when adding your items declare a local var of TStringObject and create a new instance for each item:
var
strObj : TStringObject
begin
...
for c := 0 to sl.Count - 1 do
begin
strObj := TStringObject.Create;
strObj.StringValue := sl.Values[sl.Names[c]];
ComboBox1.Items.AddObject(sl.Names[c], strObj);
end;
And when retrieving the values:
s := TStringObject(TComboBox(Sender).Items.Objects[i]).StringValue;
As #Dejan Dozet mentions in the comments - you should always free the data objects before freeing the TStringList!

How can I loop through a delimited string and assign the contents of the string to local delphi variables?

I have written a Delphi function that loads data from a .dat file into a string list. It then decodes the string list and assigns to a string variable. The contents of the string use the '#' symbol as a separator.
How can I then take the contents of this string and then assign its contents to local variables?
// Function loads data from a dat file and assigns to a String List.
function TfrmMain.LoadFromFile;
var
index, Count : integer;
profileFile, DecodedString : string;
begin
// Open a file and assign to a local variable.
OpenDialog1.Execute;
profileFile := OpenDialog1.FileName;
if profileFile = '' then
exit;
profileList := TStringList.Create;
profileList.LoadFromFile(profileFile);
for index := 0 to profileList.Count - 1 do
begin
Line := '';
Line := profileList[Index];
end;
end;
After its been decoded the var "Line" contains something that looks like this:
example:
Line '23#80#10#2#1#...255#'.
Not all of the values between the separators are the same length and the value of "Line" will vary each time the function LoadFromFile is called (e.g. sometimes a value may have only one number the next two or three etc so I cannot rely on the Copy function for strings or arrays).
I'm trying to figure out a way of looping through the contents of "Line", assigning it to a local variable called "buffer" and then if it encounters a '#' it then assigns the value of buffer to a local variable, re-initialises buffer to ''; and then moves onto the next value in "Line" repeating the process for the next parameter ignoring the '#' each time.
I think I have been scratching around with this problem for too long now and I cannot seem to make any progress and need a break from it. If anyone would care to have a look, I would welcome any suggestions on how this might be achieved.
Many Thanks
KD
You need a second TStringList:
lineLst := TStringList.Create;
try
lineLst.Delimiter := '#';
lineLst.DelimitedText := Line;
...
finally
lineLst.Free;
end;
Depending on your Delphi version you can set lineLst.StrictDelimiter := true in case the line contains spaces.
You can do something like this:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, StrUtils;
var
S : string;
D : string;
begin
S := '23#80#10#2#1#...255#';
for D in SplitString(S,'#') do //SplitString is in the StrUtils unit
writeln(D);
readln;
end.
You did not tag your Delphi version, so i don't know if it applies or not.
That IS version-specific. Please do!
In order of my personal preference:
1: Download Jedi CodeLib - http://jcl.sf.net. Then use TJclStringList. It has very nice split method. After that you would only have to iterate through.
function Split(const AText, ASeparator: string; AClearBeforeAdd: Boolean = True): IJclStringList;
uses JclStringLists;
...
var s: string; js: IJclStringList.
begin
...
js := TJclStringList.Create().Split(input, '#', True);
for s in js do begin
.....
end;
...
end;
2: Delphi now has somewhat less featured StringSplit routine. http://docwiki.embarcadero.com/Libraries/en/System.StrUtils.SplitString
It has a misfeature that array of string type may be not assignment-compatible to itself. Hello, 1949 Pascal rules...
uses StrUtils;
...
var s: string;
a_s: TStringDynArray;
(* aka array-of-string aka TArray<string>. But you have to remember this term exactly*)
begin
...
a_s := SplitString(input, '#');
for s in a_s do begin
.....
end;
...
end;
3: Use TStringList. The main problem with it is that it was designed that spaces or new lines are built-in separators. In newer Delphi that can be suppressed. Overall the code should be tailored to your exact Delphi version. You can easily Google for something like "Using TStringlist for splitting string" and get a load of examples (like #Uwe's one).
But you may forget to suppress here or there. And you may be on old Delphi,, where that can not be done. And you may mis-apply example for different Delphi version. And... it is just boring :-) Though you can make your own function to generate such pre-tuned stringlists for you and carefully check Delphi version in it :-) But then You would have to carefully free that object after use.
I use a function I've written called Fetch. I think I stole the idea from the Indy library some time ago:
function Fetch(var VString: string; ASeperator: string = ','): string;
var LPos: integer;
begin
LPos := AnsiPos(ASeperator, VString);
if LPos > 0 then
begin
result := Trim(Copy(VString, 1, LPos - 1));
VString := Copy(VString, LPos + 1, MAXINT);
end
else
begin
result := VString;
VString := '';
end;
end;
Then I'd call it like this:
var
value: string;
line: string;
profileFile: string;
profileList: TStringList;
index: integer;
begin
if OpenDialog1.Execute then
begin
profileFile := OpenDialog1.FileName;
if (profileFile = '') or not FileExists(profileFile) then
exit;
profileList := TStringList.Create;
try
profileList.LoadFromFile(profileFile);
for index := 0 to profileList.Count - 1 do
begin
line := profileList[index];
Fetch(line, ''''); //discard "Line '"
value := Fetch(line, '#')
while (value <> '') and (value[1] <> '''') do //bail when we get to the quote at the end
begin
ProcessTheNumber(value); //do whatever you need to do with the number
value := Fetch(line, '#');
end;
end;
finally
profileList.Free;
end;
end;
end;
Note: this was typed into the browser, so I haven't checked it works.

Syntax for local variable absolute to another variable with some offset

Is there a way that I can declare a variable with an absolute address that has some offset to the variable that it refers to. For instance, instead of:
function RefCount(const s: string): Integer;
begin
Result := PInteger(Integer(s) - 8)^;
end;
is there some way that I can do:
function RefCount(const s: string): Integer;
var
Count: PInteger absolute s {- 8 ?} ;
begin
Result := Count^;
end;
(The example is to illustrate only, it is not necessarily useful..)
No, I don't think there is an 'extended syntax' of the absolute keyword. The documentation is here, and, as far as I know, there are no undocumented features related to this keyword.
There is no syntax for what you ask.
What you can do, however, is use pointer arithmetic (if you are using a version that supports it), eg:
function RefCount(const s: string): Integer;
begin
if s <> '' then
Result := (PInteger(s) - 2)^;
else
Result := 0;
end;
A more reliably approach is to use the StrRec record type instead, which is what a String actually contains internally:
function RefCount(const s: string): Integer;
begin
if s <> '' then
Result := (PStrRec(s) - 1)^.refCnt
else
Result := 0;
end;
Or, the non pointer arithmetic version:
function RefCount(const s: string): Integer;
begin
if s <> '' then
Result := PStrRec(LongInt(s) - SizeOf(StrRec))^.refCnt
else
Result := 0;
end;
BTW, starting with D2009+, the System unit has its own StringRefCount() function that retreive a String's reference count.

Delphi 2010+ and "Left side cannot be assigned to" in read-only records: can this be disabled?

I know what changed. I know why. But..
TComplicatedCallMaker = record
Param1: TRecordType;
Param2: TRecordType;
{...}
Param15: TRecordType;
procedure Call;
end;
function ComplicatedCall: TComplicatedCallMaker;
begin
{ Fill default param values }
end;
procedure DoingSomeWorkHere;
begin
with ComplicatedCall do begin
Param7 := Value7;
Param12 := Value12;
Call;
end;
end;
This has perfectly worked before Delphi 2010. An extremely useful technique for making calls which accept a load of parameters but usually only need two or three. Never the same ones though.
And now it gives... guess what?
E2064: Left side cannot be assigned to.
Can't this helpful new behavior be disabled somehow? Any ideas on how to modify the pattern so it works?
Because seriously, losing such a handy technique (and rewriting a bunch of code) for no apparent reason...
I find it a little surprising that this ever worked but since you say it did I'm sure you are right. I'd guess the change was made without consideration for record methods. Without the ability to call methods then this construct would be rather pointless.
Anyway, the compiler isn't going to let you off the hook on this one so you'll have to do this:
type
TRecordType = record end;
TComplicatedCallMaker = record
Param1: TRecordType;
procedure Call;
end;
function ComplicatedCall: TComplicatedCallMaker;
begin
{ Fill default param values }
end;
procedure DoingSomeWorkHere(const Value: TRecordType);
var
CallMaker: TComplicatedCallMaker;
begin
CallMaker := ComplicatedCall;
with CallMaker do begin
Param1 := Value;
Call;
end;
end;
I... think I did it
I hope Delphi developers see what they make their programmers do!
type
PCallMaker = ^TCallMaker;
TCallMaker = record
Param1: integer;
Param2: integer;
function This: PCallMaker; inline;
procedure Call; inline;
end;
function TCallMaker.This: PCallMaker;
begin
Result := #Self;
{ Record functions HAVE to have correct self-pointer,
or they wouldn’t be able to modify data. }
end;
procedure TCallMaker.Call;
begin
writeln(Param1, ' ', Param2);
end;
function CallMaker: TCallMaker; inline
begin
Result.Param1 := 0;
Result.Param2 := 0;
end;
procedure DoingSomeWorkHere;
var cm: TCallMaker;
begin
{Test the assumption that cm is consistent}
cm := CallMaker;
if cm.This <> #cm then
raise Exception.Create('This wasn''t our lucky day.');
{Make a call}
with CallMaker.This^ do begin
Param1 := 100;
Param2 := 500;
Call;
end;
end;
This works, preserves all the good points of the old version (speed, simplicity, small call overhead) but aren't there any hidden problems with this approach?

Resources