procedure TForm1.Button1Click(Sender: TObject);
var
xlap,xlbook,xlsheet:variant;
y:integer;
begin
xlap:=createoleobject('excel.application');
xlbook:=xlap.workbooks.add;
xlap.visible:=true;
xlsheet:=xlbook.worksheets.add;
for y:=1 to 50 do
begin
xlsheet.cells[y,2].value:= concat('=IF(A',y,'>6,"Good","Bad")')
inc(y);
end;
end;
That's my code to make an Excel through Delphi. I want to input the IF formula into column B of the Excel, but there is error when I use the concat('=IF(A',y,'>6,"Good","Bad")').
May be I need another way to include y between those strings.
Any suggestion? Thanks before
Delphi has a format statement bit like sprintf printf in c, well nearly
xlsheet.cells[y,2].value:= format('=IF(A%d>6,"Good", "Bad")',[y])
%d is a place holder for an int. Look it up for loads of other stuff.
NB the variables you want to interpolate are assed in an array.
In addition to Tony's answer about Format, here are a couple of other approaches. (Format is great if you have mixed types or many values, but it carries some overhead that might not be needed.)
You can concatenate strings with a simple + - in fact, the Concat documentation says it does the same thing but is faster:
Temp := 'ing';
Str := 'Test' + Temp; // Str = 'Testing'
As your y variable is an integer, you'll need to convert it to a string first (note you don't need to Inc(y);, as the loop will do that already - it's automatically incremented from the starting value to the ending value on each pass through the loop):
for y:=1 to 50 do
begin
xlsheet.cells[y,2].value:= '=IF(A' + IntToStr(y) + '>6,"Good","Bad")';
end;
Related
I am trying to pass an array of bytes from VBscript to my windows Delphi Application and can't seem to find the correct syntax to interpret the passed data.
The requirement is fairly simple as the VBscript snippet below demonstrates
Dim inst,arr(5)
Sub Main
set inst=instruments.Find("EP1")
arr(0) = 0
arr(1) = 1
arr(2) = 2
arr(3) = 3
arr(4) = 4
inst.writebytes arr,5
end Sub
I can get the server to accept the olevariant passed by the script but the data seems garbled, my example server code is shown below and is based on the Stackoverflow question here How to use variant arrays in Delphi
procedure TInstrument.WriteBytes(Data: OleVariant; Length: Integer);
var i,n:integer; Pdat:Pbyte; Adata:PvarArray;
begin
if VarIsArray(data) then
begin
n:=TVarData(Data).VArray^.Bounds[0].ElementCount;
Adata:= VarArrayLock(Data);
Getmem(Pdat,length);
try
for i:=0 to length-1 do
Pdat[i]:=integerArray(Adata.data^)[i];
Finstrument.WriteBytes(Pdat,Length);
finally
freemem(Pdat)
end;
end;
end;
So the idea is to accept the integers passed by the script, convert it to the local data representation (array of byte) then pass it on to my function to use the data.
I have tried several different data types and methods to try and get some ungarbled data out of the variant all to no avail.
What is the correct method of extracting the array data from the passed variant?
Also, TVarData(Data).VArray^.Bounds[0].ElementCount has a value of zero, why would that be?
Arrays created in VBScript are
zero based
untyped
declared with upper bound (not size as you assumed; size of array declared as Dim arr(5) is 6)
include dimension info in them (so you don't need to pass it along with the array)
When used in COM, they are passed as variant arrays of type varVariant (as the Ondrej Kelle points out in his comment). To process such an array in your method you have to assert that:
the value is a single dimensional array
each element can be converted to byte
You can write helper routine for that:
function ToBytes(const Data: Variant): TBytes;
var
Index, LowBound, HighBound: Integer;
ArrayData: Pointer;
begin
if not VarIsArray(Data) then
raise EArgumentException.Create('Variant array expected.');
if VarArrayDimCount(Data) <> 1 then
raise EArgumentException.Create('Single dimensional variant array expected.');
LowBound := VarArrayLowBound(Data, 1);
HighBound := VarArrayHighBound(Data, 1);
SetLength(Result, HighBound - LowBound + 1);
if TVarData(Data).VType = varArray or varByte then
begin
ArrayData := VarArrayLock(Data);
try
Move(ArrayData^, Result[0], Length(Result));
finally
VarArrayUnlock(Data);
end;
end
else
begin
for Index := LowBound to HighBound do
Result[Index - LowBound] := Data[Index];
end;
end;
for loop in the routine will be horribly slow when processing large arrays, so there's optimization for special case (variant array of bytes) that uses Move to copy bytes to result. But this will never happen with VBScript array. You might consider using VB.Net or PowerShell.
Using such a routine has downside of keeping 2 instances of the array in memory - as variant array and as byte array. Use it as a guide when applying it to your use case.
I use the following function to search a TStringList I am reading from a file.
I know that when I search a value, then the return value I need is always on the line after the one with the item I search.
It has always worked using POS to search, but now the file has been expanded and I have to look for 2 items 'Adresse' and 'Adresse 2'
That gives me an issue since pos finds 'Adresse' in both cases and my data is then wrong.
Is there another method of searching a string for a substring that I don't know of or do I have to make my own.
function FindValue(const aFilename, aSearch: string): string;
var
InfoList: TStringList;
Counter: integer;
begin
InfoList := TStringList.Create;
try
InfoList.LoadFromFile(aFilename);
if InfoList.Count > 0 then
for Counter := 0 to InfoList.Count - 1 do
begin
if Pos(aSearch, Infolist.Strings[Counter]) > 0 then
Result := Infolist.Strings[Counter + 1]
end
else
Result := '';
finally
InfoList.Free;
end;
end;
For info: the input to the TStringList comes from a textfile extracted from a HTML file.
A sample of a file could be:
OZ8HP
Hugo Pedersen
Radioamatør
Nykøbing M
Sendeposition:
Adresse:
Prinsensvej 18
Postnummer:
7900
Bynavn:
Nykøbing M
Antenne højde (m):
Kote (m):?Kote (m):Brugerens/tilladelsesindehaverens øvrige adresseoplysninger så som Stednavn og/eller Postboks. Hjælpetegnet * kan anvendes, som beskrevet i hjælp.
Koordinater:
Geografisk anvendelse:
Frekvensmaske:
Tekniske specifikationer:
Sendeeffekt basisstation:
Sendeeffekt mobile anlæg:
Båndbredde (MHz):
Antal anlæg:
MMSI:
Kaldesignaltype:
Personlig
Frekvenskategori:
Udstedelses-metode:
Intention om overdragelse:
Nej
Udløbsdato:
Brugerdata:
Brugernummer:
956078
Adresse:
Prinsensvej 18
Adresse 2:
Sejerslev
Postnr.:
7900
Bynavn:
Nykøbing M
Kaldesignal-kategori:
Bestået A
It looks to me as though the real mistake is being too lax in your search. Why accept partial matches? It would seem more robust to look for complete matches
if SameText(aSearch, Infolist[Counter]) then
or perhaps to account for leading and trailing whitespace:
if SameText(aSearch, Trim(Infolist[Counter])) then
You'd need to pass 'Adresse:' or 'Adresse 2:' as the search string, or add the colon in the search function.
Use AnsiSameText if you want locale sensitive comparison. Use = if you want case sensitive comparison, etc.
You might pass multiple search strings and be able to loop only once over the file. As it stands you read it twice which seems wasteful. Indeed surely better to operate on a string list and not be coupled to file storage.
You return the last match in the data rather than the first, for instance. What if there are multiple matches? Does your code behave as intended?
You should also be aware that if no match is found your function does not assign to the Result variable which means it is undefined.
So I'm trying to determine if two different strings are the same with
if DerobModel.ConstructionCount > 22 then
begin
for i := 22 to DerobModel.ConstructionCount-1 do
begin
ConstructionName[i] := DerobModel.Constructions[i].Name;
ShowMessage(ConstructionName[i]);
ShowMessage(DerobModel.HouseProperties.StringValue['NWall']);
if ConstructionName[i]=DerobModel.HouseProperties.StringValue['NWall'] then
begin
ShowMessage('Hej');
igSurf[0]:=idWallCon[i];
end;
LayerCount[i] := DerobModel.Constructions[i].LayerCount;
idWallCon[i] := i+1;
end;
end;
The ShowMessage for both of the strings returns the same string but somehow it won't go in the if statement. Any ideas?
The = operator for strings is known to work. When strings s1 and s2 are equal, s1 = s2 evaluates true. Otherwise it evaluates false. The = operator has been known to work correctly in all versions of Delphi.
The conclusion to draw is that if the body of your if does not execute, then the two strings are not equal. Now that you know that the two strings are not equal, you can debug the program to work out why two things that you believed to be equal are in fact not equal.
Note that equality testing with = is exact. Letter case is significant. Whitespace is significant. And so on.
Your strings are different, simple as that.
If you want to figure out what exactly is different, you could write an else block portion to compare the strings in detail and show you exactly what is different.
if ConstructionName[i]=DerobModel.HouseProperties.StringValue['NWall'] then
begin
ShowMessage('Hej');
igSurf[0]:=idWallCon[i];
end
else
begin
if (Length(ConstructionName[i]) <>
Length(DerobModel.HouseProperties.StringValue['NWall'])) then
begin
ShowMessage('Length('+IntToStr(Length(ConstructionName[i]))+') <> Length('+
IntToStr(Length(DerobModel.HouseProperties.StringValue['NWall']))+')');
end
else
begin
for LCharPos := 1 to Length(ConstructionName[i]) do
begin
if (ConstructionName[i][LCharPos] <>
DerobModel.HouseProperties.StringValue['NWall'][LCharPos]) then
begin
//Here you might need to rather show the ordinal values of the
//characters to see the difference if they **look** the same due
//to the font of the message.
ShowMessage('Pos['+IntToStr(LCharPos)+'] "'+
ConstructionName[i][LCharPos]+'" <> "'+
DerobModel.HouseProperties.StringValue['NWall'][LCharPos]+'"');
end;
end;
end;
end;
The only thing I can think of that might unexpectedly cause "same" strings to be reported as different is: if they are different string types. E.g. if one is WideString and the other AnsiString, then:
There would have to be an implicit conversion to do the comparison.
And this means one of the strings would be changed.
The change could cause two strings that look the same to actually be different.
I am taking VB6 code and translating it to Delphi.
VB6 code that opens a file using sequential order:
Dim bytNumDataPoints As Byte
Dim bytcount as Byte
Dim lintData(0 To 23) As Long
Input #intFileNumber, bytNumDataPoints
For bytcount = 0 To (bytNumDataPoints - 1)
Input #intFileNumber, lintData(bytcount) '
lintData(bytcount) = (lintData(bytcount) + 65536) Mod 65536
Next bytcount
File data:
24 <<<<<<<<<<<< Number Data Points
200 300 400 450 500 600 750 1000 1250 1500 1750 2000 2500 3000 3500 3750 4000 4500 5000 5250 5500 5750 6000 6250 <<<< data
This is some neat stuff. You keep calling Input and fill out the array.
As far I know there is no equivalent for this phenomena in Delphi. You cannot use ReadLn like that, right? To me in Delphi you would have to
ReadLn(F, S); //S is a string
z.Delimiter := ' '; //z is a stringlist
z.DelimitedText := S; //and then breakdown the array
Any thought? Thanks.
Use Read instead of Readln;
Something like this:
var
ArrLng, Index: Integer;
Arr: array of Integer;
F: Text;
begin
Assign(F, 'your-fie-name');
OpenFile(F);
try
Readln(F, ArrLng);
SetLength(Arr, ArrLng);
Index := 0;
while (not Eof(F)) and (Index < ArrLng) do
begin
Read(F, Arr[Index]);
Inc(Index);
end;
finally
CloseFile(F);
end;
end;
Splitting string would be generally better approach, with StringList or without.
How to split a string of only ten characters e.g."12345*45688" into an array
Split a string into an array of strings based on a delimiter
How to split the string in delphi
But i think you can also use old Pascal approach there. Just forget about end-of-line, you probably don't need it.
var F: TextFile; I, J, K: integer;
begin
...
ReadLN(F, J);
for i := 1 to J do
Read(F, K);
...
end
But i think it would only be nice for niche approaches (like out of memory) and overall SplitString approach would be faster.
And if you have multi-line files, then two chained stringlist would be IMHO most easy approach, providing those files are not GB-sized.
https://stackoverflow.com/a/14454614/976391
or utilizing more SL features - https://stackoverflow.com/a/14649862/976391
The TStringList class and it's LoadFromFile method. It's got delimiter properties as well.
When you are doing Delphi, treat it like .net. If there's something that should be there, it is, you just haven't found it yet.
You can chuck the number of values thing away as well.
I'm doing some work with code generation, and one of the things I need to do is create a function call where one of the parameters is a function call, like so:
result := Func1(x, y, Func2(a, b, c));
TStringList.CommaText is very useful for generating the parameter lists, but when I traverse the tree to build the outer function call, what I end up with looks like this:
result := Func1(x, y, "Func2(a, b, c)");
It's quoting the third argument because it contains commas, and that produced invalid code. But I can't do something simplistic like StringReplace all double quotes with empty strings, because it's quite possible that a function argument could be a string with double quotes inside. Is there any way to make it just not escape the lines that contain commas?
You could set QuoteChar to be a space, and you'd merely get some extra spaces in the output, which is generally OK since generated code isn't usually expected to look pretty. String literals would be affected, though; they would have extra spaces inserted, changing the value of the string.
Free Pascal's TStrings class uses StrictDelimiter to control whether quoting occurs when reading the DelimitedText property. When it's true, quoting does not occur at all. Perhaps Delphi treats that property the same way.
Build an array of "unlikely characters" : non-keyable like †, ‡ or even non-printable like #129, #141, #143, #144.
Verify you don't have the 1st unlikely anywhere in your StringList.CommaText. Or move to the next unlikely until you get one not used in your StringList.CommaText. (Assert that you find one)
Use this unlikely char as the QuoteChar for your StringList
Get StringList.DelimitedText. You'll get the QuoteChar around the function parameters like: result := Func1(x, y, †Func2(a, b, c)†);
Replace the unlikely QuoteChar (here †) by empty strings...
What about using the Unicode version of AnsiExtractQuotedStr to remove the quotes?
Write your own method to export the contents of your TStringList to a string.
function MyStringListToString(const AStrings: TStrings): string;
var
i: Integer;
begin
Result := '';
if AStrings.Count = 0 then
Exit;
Result := AStrings[0];
for i := 1 to AStrings.Count - 1 do
Result := Result + ',' + AStrings[i];
end;
Too obvious? :-)
Alternatively, what would happen if you set StringList.QuoteChar to #0 and then called StringList.DelimitedText?
We have written a descendant class of TStringList in which reimplemented the DelimitedText property. You can copy most of the code from the original implementation.
var
LList: TStringList;
s, LOutput: string;
begin
LList := TStringList.Create;
try
LList.Add('x');
LList.Add('y');
LList.Add('Func2(a, b, c)');
for s in LList do
LOutput := LOutput + s + ', ';
SetLength(LOutput, Length(LOutput) - 2);
m1.AddLine('result := Func1(' + LOutput + ')');
finally
LList.Free;
end;
end;
Had the same problem, here's how I fixed it:
s := Trim(StringList.Text)
that's all ;-)