Crystal reports array not adding items to array after the first item in the group - crystal-reports-2016

I have two reports set up the same way one works yet the other does not.
I initialize the variables in the first group
shared StringVar Array WcAr := [""];
shared StringVar Array ShAr := [""];
shared StringVar Shstr :="";
shared StringVar Array RhAr := [""];
shared StringVar Array UcAr := [""];
shared StringVar Array TcAr := [""];
shared StringVar Array BuAr := [""];
shared StringVar Array RqAr := [""];
shared StringVar Array SqAr := [""];
shared StringVar Array BcAr := [""];
shared NumberVar x := 1;
I then load the array in Group 4(Work Center)
shared StringVar Array WcAr;
shared StringVar Array ShAr;
shared StringVar Shstr :="";
shared StringVar Array RhAr;
shared StringVar Array UcAr;
shared StringVar Array TcAr;
shared StringVar Array BuAr;
shared StringVar Array RqAr;
shared StringVar Array SqAr;
shared StringVar Array BcAr;
shared NumberVar x;
(if not IsNull({#Work Center}) then WcAr [x] := {#Work Center};
ReDim preserve WcAr[UBound (WcAr)+1];
ShAr [x] := Cstr({Job_Operation.Act_Setup_Hrs},"####.##",2,",",".");
ReDim preserve ShAr[UBound (ShAr)+1];
RhAr [x] := Cstr({Job_Operation.Act_Run_Hrs},"####.##",2,",",".");
ReDim preserve RhAr[UBound (RhAr)+1];
UcAr [x] := Cstr({Job_Operation.Act_Unit_Cost},"####.##",2,",",".");
ReDim preserve UcAr[UBound (UcAr)+1];
TcAr [x] := Cstr({Job_Operation.Act_Total_Cost}+
{Job_Operation.Act_Run_Labor},"####.##",2,",",".");
ReDim preserve TcAr[UBound (TcAr)+1];
x := x+1;);
I output these in the group footer using for each array
shared StringVar Array WcAr;
Join(WcAr,ChrW(10));
Only the first item is added to any of the arrays. x has incremented and is equal to 17 which indicates it going through the formula. I have tried without the if statement and anything else I can think of but I always get the same result.

That should work, at least for your WcAr. As you're using Chr(10) as divider for the array elements, I guess you just see only the first element because the field formatting does not allow it to increase. => Either activate "Can grow" in output field format, or use another divider.
My test scenario is these three formulae, where COMMAND.STRING is the input field of course:
init
shared StringVar Array WcAr := [""];
shared NumberVar x := 1;
add
shared StringVar Array WcAr;
shared NumberVar x;
WcAr [x] := {COMMAND.STRING};
ReDim preserve WcAr[UBound (WcAr)+1];
x := x+1;
output
shared StringVar Array WcAr;
Join(WcAr,',');
Important: The add formula field must be placed in a repeated section, in my case it's the detail section, inyour case probably a group header. The output formula can only show the collected values when it's placed in a section after the one where the last value is collected (or in it).

Related

In function always false

I have this formula in CR
I initialize in the group header
shared stringvar array HandAr := [""];
shared numbervar x :=1;
shared numbervar y :=0;
then in the detail section
shared stringvar array HandAr;
shared numbervar x;
shared numbervar y;
(if CStr({Material_Location.Material_Location},"######",0,"","") in HandAr[x] then x := x+0 else
y := y+{Material_Location.On_Hand_Qty};
HandAr[x] := CStr({Material_Location.Material_Location},"######",0,"","");
ReDim preserve HandAr[UBound (HandAr)+1];
x := x+1;);
The formula never returns true
The correct detail formula would look like this:
shared stringvar array HandAr;
shared numbervar x;
shared numbervar y;
(if CStr({Material_Location.Material_Location},"######",0,"","") in HandAr then x := x+0 else
(
y := y+{Material_Location.On_Hand_Qty};
HandAr[x] := CStr({Material_Location.Material_Location},"######",0,"","");
ReDim preserve HandAr[UBound (HandAr)+1];
x := x+1;
)
);
Where the following changes were made:
brackets around commands in else block (I guessed from your last comment that's the way you wanted it to work)
in if condition in HandAr instead of in HandAr[x], to compare with all array elements

Delphi: How to reference an array from within a class

I work with arrays and have tested the functionality within the context of a class, like:
Ttest = class
values : array of integer;
procedure doStuff;
end;
The methods, like doStuff all operate on the values array without the need to pass the array as a parameter. This suits me and it is fast.
Now I want to use this class to work with an external array, like Ttest.create(myValues) In the constructor I could copy myValues to the internal values but that would be quite a waste and moreover, at the end the copy would have to be reversed to pass the updated values back.
My question is how can I extend this class so that it can efficiently work with an external array. In pseudo code like this:
constructor create(var myValues : array of integer);
begin
address of values := address of myValues;
doSTuff;
end;
Lesson 1
In Delphi, dynamic arrays are reference types. A variable of dynamic array type contains only a pointer to the actual dynamic array heap object, and in an assignment,
A := B
where A and B are dynamic arrays of the same type, the dynamic array heap object isn't copied. The only thing that happens is that A and B will point to the same dynamic array heap object (that is, the 32- or 64-bit B pointer is copied to A) and that the reference count of the heap object is increased by 1.
Lesson 2
When you write
constructor Create(var AValues: array of Integer);
you need to realise that this, despite the appearance, isn't a dynamic array parameter, but an open array parameter.
If you explicitly want a dynamic array parameter, you need to use such a type explicitly:
constructor Create(AValues: TArray<Integer>);
By definition, TArray<Integer> = array of Integer is a dynamic array of Integers.
Please note that the language only has two types of arrays – static and dynamic; the open array concept is only about function parameters.
If you want to work with dynamic arrays, taking advantage of their nature as reference types, I would suggest you use dynamic array parameters. Then the only thing that is passed to the function (the constructor in this case) is the pointer to the heap object. And the heap object's reference count is increased, of course.
Lesson 3 – An example
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
After SetLength, Arr points to a dynamic array heap object of reference count 1. You can see it in your RAM (press Ctrl+Alt+E, then Ctrl+G and goto Arr[0]). When you enter Test, the reference count is increased to 2 because both Arr and A refer to it. When you leave Test, the reference count is reduced back to 1 because A goes out of scope: now again only Arr refers to it.
Lesson 4
Now, a “gotcha”: if you change the number of elements of a dynamic array, it needs to be reallocated (*). Hence, a new dynamic array heap object is created with a reference count of 1, and the old object has its reference count decreased by 1 (and removed if it becomes zero).
Thus, while the previous example works as expected, the following will not:
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
The SetLength will create a new dynamic array heap object with refcount 1 and copy half of the old array into this, put the new address in the local A parameter, and Test will transform this new array, not touching the old one which the global Arr variable points to. The reference count of the original heap object is decreased by one.
However, if you use a var parameter,
procedure Test(var A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
it will work as before. This effectively works with Arr. If we had increased the number of elements, the array would probably have been reallocated and the global Arr variable would have been updated with the new address.
Conclusion
As long as you don't need to reallocate the memory (change the number of elements), Delphi already gives you what you want, because dynamic arrays are reference types. If you do need to reallocate, at least now you know enough of the technical details in order to reason about it.
Update: Hence, the suggestion is to do
type
TTest = class
FData: TArray<Integer>;
constructor Create(AData: TArray<Integer>);
procedure Enlarge;
procedure Shrink;
procedure ShowSum;
end;
{ TTest }
constructor TTest.Create(AData: TArray<Integer>);
begin
FData := AData; // will NOT copy the array, since dynamic arrays are reference types
end;
procedure TTest.Enlarge;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := 2*FData[i];
end;
procedure TTest.ShowSum;
var
s: Integer;
i: Integer;
begin
s := 0;
for i := 0 to High(FData) do
Inc(s, FData[i]);
ShowMessage(s.ToString);
end;
procedure TTest.Shrink;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := FData[i] div 2;
end;
To test it:
procedure TForm1.FormCreate(Sender: TObject);
var
MyArray: TArray<Integer>;
t: TTest;
begin
MyArray := [1, 2, 3, 4, 5];
t := TTest.Create(MyArray);
try
t.ShowSum;
t.Enlarge;
t.ShowSum;
t.Shrink;
t.ShowSum;
finally
t.Free;
end;
end;
Footnotes
If you (1) decrease the number of elements and (2) the reference count is 1, typically the data isn't moved in memory. If the reference count is > 1, the data is always moved, because SetLength guarantees that the reference count of its argument is 1 when it returns.

Runtime Arrays using Create

Say i have
ArrayOfTXSDecimal = array of TXSDecimal;
Then during runtime i do
Ids := ArrayOfTXSDecimal.create(14450);
What did i just create? an array(ids) with 14450 indexs or just index 14450
You are creating a dynamic array with one element whose value is 14450. You are doing the equivalent of this:
SetLength(Ids, 1);
Ids[0] := 14450;
This Create() syntax for dynamic arrays is documented on Embarcadero's DocWiki:
An alternative method of allocating memory for dynamic arrays is to invoke the array constructor:
type
TMyFlexibleArray = array of Integer;
begin
MyFlexibleArray := TMyFlexibleArray.Create(1, 2, 3 {...});
end;
which allocates memory for three elements and assigns each element the given value.

Delphi dynamic array with tlist

I'm creating a simple interpreter in Delphi/Lazarus for a language similar to BASIC.
I already achieved a lot of functionalities. At this moment, I'm trying to create a DIM like command in order to handle multidimensional numeric arrays.
My idea is to use TList to simulate multidimensional arrays limited only by available memory. For example, when a declare in my interpreter a command like the following:
DIM num_arr[3,3,3]
I would like to create a three dimensional array of double, with each index varying from 0 to 2.
So far I have only the function to create the "TList array". I'm using two TList objects to keep the array dimensions and the data items list, I have a third one that holds the indexes for store/retrieve data. What I can't figure is how to associate a list of indexes to a specific entry in the TList. It's simple when the array is up to two dimensions, I can convert each pair of indexes to a numeric sequence, but no success to three and more dimensions.
Is there any algorithm I could use to solve that? It's really hard to find something related to that matter.
Any suggestion about how to implement something similar is welcome. Below I'm publishing part of the code I developed so far:
//Creates a "n" dimensional array
function array_allocate(Dim: TList; var DataArray: TList): integer;
var
i: integer;
s: string;
begin
Result := 1;
//Simple way to find the array length
//For example. An array with dimensions [3,3,3] will handle 27 items
for i := 0 to Dim.Count-1 do
begin
Result := Result * Integer(Dim[i]);
end;
Result := Result;
DataArray.Capacity := Result; //DataArray now handles 27 items
//************************************
//Every line below is just for testing
//************************************
fmMain.Memo1.Lines.Add('Allocating size for array with '+IntToStr(Dim.Count)+' dimension(s).');
s := '';
for i := 0 to Dim.Count-1 do
s := s + '['+IntToStr(Integer(Dim[i]))+']';
fmMain.Memo1.Lines.Add('DIM Sizes: '+s);
fmMain.Memo1.Lines.Add('Manage: '+IntToStr(Result)+' Items.');
end;
{*************************************}
{NOT functional, this is the challenge}
{*************************************}
function calculate_offset(Dim, Indexes: TList; var DataArray: TList; BaseAddr: integer): integer;
var
i, Depth, dimIdx: Integer;
k,index,sizeProduct: integer;
begin
for Depth := 0 to Dim.Count-1 do
for dimIdx := 0 to Integer(Dim[Depth])-1 do
fmMain.mmOut.Lines.Add('Dim: '+IntToStr(Depth)+' ,Index: '+IntToStr(dimIdx));
result := 0;
end;
procedure TfmMain.FormShow(Sender: TObject);
var
dataList: TList; //keep the data
dimList: TList; //keep the dimensions
indexList: TList; //keep the indexes
offset: integer;
begin
dimList := TList.Create; //create the dim array
//simulate the creation of an array with dimension [3,3,3]
//something like DIM myVar[3,3,3]
dimList.Add(Pointer(3));
dimList.Add(Pointer(3));
dimList.Add(Pointer(3));
dataList := TList.Create; //create the data list
array_allocate(dimList, dataList); //allocates memory
//indexList is the way to indicate which index to retrieve/store data
indexList := TList.Create;
indexList.Add(Pointer(1));
indexList.Add(Pointer(1));
indexList.Add(Pointer(1));
indexList.Add(Pointer(1));
//The idea is to relate indexes like [0,0,0], [0,0,1], [0,1,1] to
//a numeric sequence between 0 to 26 in this case (DIM = [3,3,3])
offset := calculate_offset(dimList, indexList, dataList, 1, 0);
indexList.Free;
dimList.Free;
dataList.Free;
end;
As I read your question, you want to convert from a tuple of indices to a linear index, assuming row major storage.
Suppose the dimensions are held in a dynamic array, dim and the indices are in a dynamic array, idx. Then the linear index is calculated like this:
function LinearIndex(const dim, idx: array of Integer): Integer;
var
i: Integer;
begin
Assert(Length(dim)=Length(idx));
Assert(Length(dim)>0);
Result := idx[0];
for i := 1 to high(dim) do
Result := Result*dim[i-1] + idx[i];
end;
The LinearIndex function described above was not functional to certain unbalanced arrays. For example, if I properly allocate "dim" to hold the value [2,3], the function would return 2 as the index for both contents of "idx": [0,2], [1,0].
I created a Pascal function based on another post I found here at StackOverflow and this one seems to work.
function LinearIndex2(const dim, idx: array of Integer): Integer;
var
i,index,mult: Integer;
begin
Assert(Length(dim)=Length(idx));
Assert(Length(dim)>0);
index := 0; mult := 1;
for i := 0 to High(dim) do
begin
index := index + idx[i] * mult;
mult := mult * dim[i];
end;
result := index;
end;

Reference a Delphi variant array without copying

This might seem like a strange request, but there is a good reason (code generation application). I pass a variant array to a procedure, contained within an array of variants, as follows:
TVarArray = array of variant;
procedure TMainForm.Button1Click(Sender: TObject);
var
params: TVarArray;
numRows: integer;
numCols: integer;
i: integer;
j: integer;
begin
SetLength(params, 2);
numRows := 2;
numCols := 2;
params[0] := 5;
params[1] := VarArrayCreate([1, numRows, 1, numCols], varVariant);
for i := 1 to numRows do
for j := 1 to numCols do
params[1][i, j] := i + j;
TestProc(params);
end;
procedure TMainForm.TestProc(params: TVarArray);
var
arr: variant;
p: PVariant;
v: variant;
begin
arr := params[1]; // -- Copies the array to arr.
arr[2, 2] := 99;
p := #(params[1]);
p^[2, 2] := 88; // -- Directly reference the passed-in array.
v := p^; // -- Copies the array to v -> How to prevent?
v[2, 2] := 77; // -- This should change the value in the original array.
edit1.Text := VarToStr(arr[2, 2]); // -- 99
edit2.Text := VarToStr(params[1][2, 2]); // -- 88 - should be 77
edit3.Text := VarToStr(v[2, 2]); // -- 77
end;
I don't want to create a copy of the array, so could use p^[] to directly access the passed-in array. However, I don't want to use the p^ syntax in TestProc but would prefer to use a variable name without the ^. Of course, if I try v := p^ I just get a copy. Is there any way around this? Thanks!
What you're looking for is a local variable that can act as a reference for something else (in particular, an element in an array of Variant). However, Delphi provides no way of creating a "local reference" variable. References only exist in the context of parameters passed as var, out, or sometimes const.
Maybe you could introduce a subroutine and pass param[1] as a var parameter. Inside the subroutine, you could refer to that parameter, and it would alias the array element form the caller. For example:
procedure ModifyVariant(var p: Variant);
begin
p[2, 2] := 77;
end;
procedure TMainForm.TestProc(params: TVarArray);
var
p: PVariant;
begin
p := #params[1];
ModifyVariant(params[1]);
Assert(params[1][2, 2] = p^[2, 2]);
end;
ModifyVariant could even be an anonymous procedure so you can implement within the same scope as the caller:
procedure TMainForm.TestProc(params: TVarArray);
var
ModifyVariant: reference to procedure(var x: Variant);
p: PVariant;
begin
p := #params[1];
ModifyVariant := procedure(var v: Variant)
begin
v[2, 2] := 77;
end;
ModifyVariant(params[1]);
Assert(params[1][2, 2] = p^[2, 2]);
end;
Neither of those looks particularly appealing, though, especially if you're afraid that mere pointer access will "spook" your code's consumers.
You've mentioned that you expect your users to incorporate their own code into the code you're generating. I wouldn't advise that. After all, what are they expected to do after they re-run your code generator? Surely they'll lose whatever customizations they've made. It's better to keep generated code separate, ideally in a separate file. For user customization, you can provide hooks in the form of callback functions that users can implement. That way, for example, a user could provide something analogous to ModifyVariant, and then your generated code can simply call it. You'll have your "Variant references," and you'll have generated code cleanly separated from user code.

Resources