How to correctly declare an array property in Delphi? - arrays

I'm trying to create an array to store every item of a TStringList into an array which size may vary depending on the quantity of items in said TStringList.
I know my syntax is wrong and what I want is probably a dynamic array, so the [0..100] is probably wrong aswell but I could not find any alternative syntax online.
ProductAvailabilityResult = Class(TRemotable)
private
FResultArray : array[1..100] of string;
published
property ResultArray[Index: Integer]: array of string read FResultArray write FResultArray;
End;
And this is how I would invoke it and populate it. conditionList being my TStringList which I would populate into my array.
for I := 0 to conditionList.Count - 1 do
begin
aProductAvailabilityResult.ResultArray[I] := conditionList[I];
end;
In case you may or may not have alternative suggestions to what i'm doing, the reason for this setup is because it's a web service app sending results over a SOAP server, and I don't think my PHP/Soap client can read TStringLists, so I need to pass it to an array first.
Let me know, Thanks!

Your syntax for declaring an array property is close, but you need to use getter/setter methods instead of direct field access, and array properties cannot be declared as published:
type
ProductAvailabilityResult = class(TRemotable)
private
FResultArray : array of string;
function GetResultArray(Index: Integer): string;
function GetResultArrayCount: Integer;
procedure SetResultArray(Index: Integer; const Value: string);
procedure SetResultArrayCount(Value: Integer);
public
property ResultArray[Index: Integer]: string read GetResultArray write SetResultArray default;
property ResultArrayCount: Integer read GetResultArrayCount write SetResultArrayCount;
end;
function ProductAvailabilityResult.GetResultArray(Index: Integer): string;
begin
Result := FResultArray[Index];
end;
function ProductAvailabilityResult.GetResultArrayCount: Integer;
begin
Result := Length(FResultArray);
end;
procedure ProductAvailabilityResult.SetResultArray(Index: Integer; const Value: string);
begin
FResultArray[Index] := Value;
end;
procedure ProductAvailabilityResult.SetResultArrayCount(Value: Integer);
begin
SetLength(FResultArray, Value);
end;
Then you can do this:
aProductAvailabilityResult.ResultArrayCount := conditionList.Count;
for I := 0 to conditionList.Count - 1 do
begin
aProductAvailabilityResult[I] := conditionList[I];
end;
You might want to consider adding a method to copy strings from a source TStrings:
type
ProductAvailabilityResult = class(TRemotable)
private
...
public
procedure AssignStrings(AStrings: TStrings);
...
end;
procedure ProductAvailabilityResult.AssignStrings(AStrings: TStrings);
var
I: Integer;
begin
SetLength(FResultArray, AStrings.Count);
for I := 0 to AStrings.Count - 1 do
FResultArray[I] := AStrings[I];
end;
aProductAvailabilityResult.AssignStrings(conditionList);

You've declared an array property, albeit with some syntax errors. However, you state in the question and comments that you want a property that is a dynamic array. That's different from an array property.
Declare a dynamic array property like so:
type
ProductAvailabilityResult = class(TRemotable)
private
FResultArray: TArray<string>;
published
property ResultArray: TArray<string> read FResultArray write FResultArray;
end;
Populate it like this:
var
i: Integer;
List: TStringList;
par: ProductAvailabilityResult;
arr: TArray<string>;
....
List := ...;
par := ...;
SetLength(arr, List.Count);
for i := 0 to List.Count-1 do
arr[i] := List[i];
par.ResultArray := arr;

Related

How can I use the value of a stringgrid cell to be stored in an array which should hold the current game state?

I'm new to programming and am trying to make a delphi tictactoe game using a 3x3 array, as a way to help me learn the basics.
I'm using a stringgrid which I wanted to show the state of the board, the user places an X on the cell (via mouse click). I was hoping at this part in the code it would add the 'X' to the gameboard array using the stringgrid.cell(ARow,ACol) for the position. I've now learnt that this cannot work as the array is a char type. Is there a way around this?
I know this is not the best way to make a game, but it's helping me learn. Any help would be very appreciated. Here's my code:
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.ButtonGroup,
Vcl.Grids;
type
TForm1 = class(TForm)
Button1: TButton;
label1: TLabel;
StringGrid1: TStringGrid;
Button2: TButton;
procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
procedure Button2Click(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure restart();
procedure compTurn();
end;
var
Form1: TForm1;
gameBoard: array [1 .. 3, 1 .. 3] of char; // array for the game board.
iScore: Integer;
bTurn: Boolean = True; // O's go = false, X's go = true.
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.Button2Click(Sender: TObject);
begin
restart;
end;
procedure TForm1.compTurn;
begin
// if (bTurn = false) then
label1.Caption := 'Computer`s turn';
end;
procedure TForm1.FormShow(Sender: TObject);
begin
restart;
end;
procedure TForm1.restart;
var
i, j: Integer;
begin
// place ? in the cells
StringGrid1.Enabled := True;
for i := 1 to 3 do
for j := 1 to 3 do
StringGrid1.cells[i, j] := '?';
label1.Caption := 'Player X`s turn';
end;
procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
Var
sX: string;
pos: Integer;
begin
sX := 'X';
CanSelect := false;
// Place a X or O in the cell depending on whose turn it is
if (bTurn = True) then
StringGrid1.cells[ACol, ARow] := sX // and gameBoard[ACol, ARow]
else
bTurn := false; // computer's turn to move
compTurn; // make sure user can't add an X
StringGrid1.Enabled := false;
end;
end.```
I was hoping at this part in the code it would add the 'X' to the
gameboard array using the stringgrid.cell(ARow,ACol) for the position.
I've now learnt that this cannot work as the array is a char type. Is
there a way around this?
The problem you are facing here is that in your StringGrid1SelectCell event handler you defined your sX variable as string. So if you try to assign value of sX to your array of char Delphi won't let you since you cant store entire string into a sigle char.
To achieve desired functionality you should change your sX variable into a Char type instead. This will then allow you to write its value into your array.
Now you might be thinking that this would lead to problems since you also need to assign value of your sX variable to specific String Grid Cell since String Grid Cells are of String type. You won't.
You can always assign a single char to a string. This will change the length of that string to one an it will contain only the specific character you provided.
So in short:
Assigning String to Char does not work
Assigning Char to string does work
PS Also don't forget that if you need to execute multiple commands when if clause condition is met you need to enclose those commands with begin..end block.
So your code should look something like this (haven't tested it)
procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
Var
sX: Char; //Change type from String to Char
pos: Integer;
begin
sX := 'X';
CanSelect := false;
// Place a X or O in the cell depending on whose turn it is
if (bTurn = True) then
//Enclose multiple commands with begin..end block
begin
StringGrid1.cells[ACol, ARow] := sX // and gameBoard[ACol, ARow]
GameBoard[ACol,ARow] := sX;
else
bTurn := false; // computer's turn to move
compTurn; // make sure user can't add an X
StringGrid1.Enabled := false;
end;

Cannot assign value to object with array of records

I'm writing a simple object that contain array of records. Just like invoice.(ID,date,customer name and array of records about the items).
type
Trows = record
private
Fcode: string;
Qty: Double;
cena: Currency;
procedure Setcode(const value: string);
public
property code: string read Fcode write SetCode;
end;
Tcart = class(TObject)
private
Frow: array of Trows;
function Getrow(Index: Integer): Trows;
procedure Setrow(Index: Integer; const Value: Trows);
public
ID: integer;
CustName: string;
Suma: currency;
Payed: boolean;
constructor Create(const Num: Integer);
destructor Destroy;
function carttostr: string;
procedure setcode(Index: integer;val: string);
property Row[Index: Integer]: Trows read Getrow write setrow;
end;
Everything seems fine since I'm trying to change value of one record. I have found 3 ways to do it. First, and second worked ok, but I'd like to simplify the code modifying the value of this record like this:
cart.row[0].code:='333';
but it doesn't work.
What am I'm missing?
Here is the code:
procedure TForm1.Button1Click(Sender: TObject);
var
Arows: Trows;
begin
Cart:=Tcart.Create(0);
cart.custName:='Customer 1';
cart.Suma:=5.55;
cart.Payed:=false;
Arows.code:='123';
cart.setrow(0,Arows); // this way working
cart.setcode(0,'333'); // this way also working
cart.row[0].code:='555'; //this way doesn''t change value. How to make it work?
memo1.Lines.Text:=cart.carttostr;
end;
It doesn't work because your Row[] property returns a TRows record by value, which means the caller receives a copy of the original record. Any modifications you make to the copy are not reflected in the original.
You need to assign the copy back to the property to apply changes:
procedure TForm1.Button1Click(Sender: TObject);
var
Arows: Trows;
begin
...
Arows := cart.row[0];
Arows.code:='555';
cart.row[0] := Arows; // <-- equivalent to 'cart.setrow(0,Arows);'
...
end;

Delphi - How can I pass an array of TAlignment itens to property?

I want to configure the alignment at runtime of some columns of a DBGrid that's inside a component, passing the alignment of each column as an array to a property, but I'm confusing about that.
I guess the code in component should be something like that (according Andreas Rejbrand reply in How can I declare an array property?):
property MyAlignment[Index: Integer]: TAlignment read GetMyAlignment;
function MyComponent.GetMyAlignment(Index: Integer): TAlignment;
begin
result := FMyAlignment[Index];
end;
How do I mount the array that will be pass to the component's property ?
I try the code below but I'm getting the error "[dcc32 Error] uCad.pas(31): E2188 Published property 'ColumnTitleName' cannot be of type ARRAY".
type
TCad = class
private
FColumnTitleName: array of String;
FColumnTitleAlign: array of TAlignment;
FColumnAlign: array of TAlignment;
function GetColumnAlignment(index: Integer): TAlignment;
function GetColumnTitleAlignment(index: Integer): TAlignment;
function GetColumnTitleName(index: Integer): String;
public
published
property ColumnTitleName[index: Integer]: String read GetColumnTitleName;
property ColumnTitleAlign[index: Integer]: TAlignment read GetColumnTitleAlign;
property ColumnAlign[index: Integer]: TAlignment read GetColumnAlignment;
end;
implementation
function TCad.GetColumnAlign(index: Integer): TAlignment;
begin
Result := FColumnAlign[index];
end;
function TCad.GetColumnTitleAlign(index: Integer): TAlignment;
begin
Result := FColumnTitleAlign[index];
end;
function TCad.GetColumnTitleName(index: Integer): String;
begin
Result := FColumnTitleName[index];
end;
end.

How to save multiple images from database to TMemoryStream at runtime and extract them later on

I am creating a project at delphi(RAD Studio). There are images stored at database table at some rows. I want to extract images at runtime (for which I am using Array of TMemoryStream) and show them at frxReport.
My code snippet as follows
Public variable TStream is declared as
Stream2 : Array of TStream; i,k: integer
Code segment for view button click event, which is placed on MainForm and expected to show frxReport.
`
procedure TFrmMain.btnViewClick(Sender: TObject);
begin
i := 0;
k := 0;
UniTable1.SQL.Text := 'Select * from userplays order by id';
UniTable1.Execute;
rowcount := UniTable1.RecordCount;
SetLength(myid, rowcount);
SetLength(mydesc, rowcount);
SetLength(myimg, rowcount);
SetLength(Stream2, rowcount);
while not UniTable1.Eof do
begin
try
Stream2[k] := TMemoryStream.Create;
myid[k] := UniTable1.FieldByName('id').Value;
Stream2[k] := UniTable1.CreateBlobStream(TBlobField(UniTable1.FieldByName('image')), bmRead);
mydesc[k] := UniTable1.FieldByName('description').Value;
UniTable1.Next;
inc(k);
finally
//Stream2[k].Free;
end;
end;
frxUserDataSet1.RangeEnd := reCount;
frxUserDataSet1.RangeEndCount := rowcount;
frxReport1.ShowReport;
i := 0;
end;
`
However this method is not loading any image to Stream2 array. There is an option to use array of JPEGImage however if JPEGImage array used then it would be problem to display it on frxRaport at
procedure TFrmMain.frxReport1GetValue(const VarName: string; var Value: Variant);
`
Graphic := TJPEGImage.Create;
Graphic.LoadFromStream(Stream2[j]);
TfrxPictureView(frxreport1.FindObject('Picture1')).Picture.Graphic := Graphic;
`
Kindly let me know how to do this.
However this method is not loading any image to Stream2 array
In your current code you are first assigning a newly created TMemoryStream to Stream2[k]:
Stream2[k] := TMemoryStream.Create;
then you are throwing the TMemoryStream away (creating a memory leak) and replacing it with the blob stream you create:
Stream2[k] := UniTable1.CreateBlobStream(TBlobField(UniTable1.FieldByName('image')), bmRead);
But you never read from the blob stream.
Here's the while loop rewritten (untested)
var
blobstream: TStream;
Stream2: array of TMemoryStream;
....
// read 'id', 'description' and 'image' fields to respective arrays
while not UniTable1.Eof do
begin
myid[k] := UniTable1.FieldByName('id').Value;
mydesc[k] := UniTable1.FieldByName('description').Value;
blobstream := UniTable1.CreateBlobStream(TBlobField(UniTable1.FieldByName('image')), bmRead);
try
Stream2[k] := TMemoryStream.Create;
Stream2[k].LoadFromStream(blobstream);
finally
blobstream.Free;
end;
UniTable1.Next;
inc(k);
end;
Btw, I would recommend to define a record to hold the id, description and image together, and then an array of those records, instead of three separate arrays. Much simpler to manage only one array instead of three.
I think in your situation most obvious thing would be just connect existing DataSet ( Unitable1) directly into DataBand in frxReport. I don't see why you would need to use frxUserDataset instead of frxDBDataset with its DataSet property set to Unitable1 since you created it anyway.
If you really need a list of objects then I would take a different approach.
Your multiple global variables look ugly and are dangerous (i,k...)
I would create a class that holds your data and populate this data into a
TObjectList from System.Generics.Collection:
unit DataPack;
interface
uses ExtCtrls...System.SysUtils;
type
TMyDataPack = class(TObject)
private
Query: TFDQuery; //O r TUniQery
_MyId: Integer;
_MyDescription: String;
_MyImage: TImage;
_Loaded: Boolean;
function LoadData: boolean;
protected
public
constructor Create(Data: TFDQuery); //Or Uniquery
property MyId: Integer read _MyId;
property MyDescription: String read _MyDescription;
property MyImage: TImage read _MyImage;
property Loaded: Boolean read _Loaded;
end;
implementation
{ TMyDataPack }
constructor TMyDataPack.Create(Data: TFDQuery);
begin
Inherited Create;
Query:=Data;
_Loaded:=true;
if not (LoadData) then
_Loaded:=false;
end;
function TMyDataPack.LoadData: boolean;
var
Stream: TStream;
begin
Stream:= TStream.Create;
Stream:=Query.CreateBlobStream(TBlobField(Query.FieldByName('image')), bmRead);
try
_MyImage:=TImage.Create(nil);
_MyImage.Picture.Bitmap.LoadFromStream(Stream);
_MyDescription:=Query.FieldByName('description').AsString;
_MyId:=Query.FieldByName('CategoryId').AsInteger;
except on E: Exception do
begin
FreeAndNil(Stream);
FreeAndNil(_MyImage);
Exit(false);
end;
end;
FreeAndNil(Stream);
Result:=true;
end;
end.
Then in your main code:
procedure TMain.Button2Click(Sender: TObject);
var
DataRow: TMyDataPack;
List: TObjectList<TMyDataPack>;
Num: integer;
CurrentImage: TImage;
CurrentDescription: String;
CurrentId: integer;
begin
UniTable1.SQL.Text:='SELECT * FROM Userplays';
UniTable1.Open();
List:=TObjectList<TMyDataPack>.Create;
while not (UniTable1.Eof) do
begin
DataRow:=TMyDataPack.Create(UniTable1);
if(DataRow.Loaded) then
List.Add(DataRow);
UniTable1.Next;
End;
for Num:=0 to List.Count do
begin
CurrentDescription:=List[Num].MyDescription;
CurrentImage:=List[Num].MyImage;
CurrentId:=List[Num].MyId;
//List[Num].MyImage.Picture.SaveToFile('Some'+IntToStr(Num)+'.bmp');
// You might save it to file then...
end;
end;
end.
Then pass an ObjectList further, just remember to free it at the end.
You might further expand it by creating class factory to create different objects if you'd need for example use different database with same data,
perform check if image is actually Bmp or Jpg and so on.
You might even just pass a query without setting it's SQL and get TobjectList as property.

Delphi 2007 and Dynamic Variant Array as Var Parameter

I have a form which allows a use to select a record and this form then returns the ID of the record and an arbitrary number of fields values that the calling form may need. To do this, I have a function which handles creating the select form and passes all the values to and from the calling form:
Function Execute(AOwner: TComponent; AConnection: TADOConnection;
AEditor: String; AButtons: TViewButtons; Var AID: Integer;
Var ARtnVals: Array of Variant): TModalResult;
Var
I: Integer;
Begin
frmSelectGrid := TfrmSelectGrid.Create(AOwner);
Try
With frmSelectGrid Do
Begin
Connection := AConnection;
Editor := AEditor;
Buttons := AButtons;
ID := AID;
Result := ShowModal;
If Result = mrOK Then
Begin
AID := ID;
//VarArrayRedim(ARtnVals, Length(RtnVals)); !! Won't compile
//SetLength(ARtnVals, Length(RtnVals)); !! Won't compile either
For I := 0 To High(RtnVals) Do
ARtnVals[I] := RtnVals[I]; // Causes runtime exception
End;
End;
Finally
FreeAndNil(frmSelectGrid);
End;
End;
The selector form has the following public properties:
public
Connection: TADOConnection;
Editor: String;
Buttons: TViewButtons;
ID: Integer;
RtnVals: Array of Variant;
end;
And in the OK click, I have the following code:
Var
I, Idx, C: Integer;
// Count how many fields are being returned
C := 0;
For I := 0 To Config.Fields.Count - 1 Do
If Config.Fields[I].Returned Then
Inc(C);
// If no fields to be returned, then just exit.
If C = 0 Then
Exit;
// Set the size of the RtnVals and assign the field values to the array.
SetLength(RtnVals, C);
Idx := 0;
For I := 0 To Config.Fields.Count - 1 Do
If Config.Fields[I].Returned Then
Begin
RtnVals[Idx] := aqItems.FieldByName(Config.Fields[I].FieldName).Value;
Inc(Idx);
End;
So, once the user clicks OK to select a record, the RtnVals array is populated with the field values of the fields to be returned. I now need to copy these values to ARtnVals in the Execute function so that they are returned to the calling form.
My question is how do I set the size of the ARtnVals array so that I can copy the fields? SetLength doesn't work like it does in the OK click function above. VarArrayRedim doesn't work either.
When written in a procedure parameter list, this code
Var ARtnVals: Array of Variant
is an open array, and not a dynamic array. An open array cannot be resized. An open array is no use to you here.
Instead define a type for the array:
type
TDynamicArrayOfVariant = array of Variant;
Use that type for your parameter, which is actually best as an out parameter:
function Execute(..., out RtnVals: TDynamicArrayOfVariant): TModalResult;
And then pass the function a TDynamicArrayOfVariant to be populated.
Now you have a dynamic array rather than an open array in Execute, and you can use SetLength to size it.

Resources