Hello Peter,
The short answer is: **LazReport cannot sort data on its own.** It iterates through the dataset strictly in the order provided by the dataset itself. Therefore, if you use Group Headers/Footers based on Country or Prefix, the underlying data must be pre-sorted, otherwise, the report will create a new group every time the value changes (which explains your difficulty).
Since you are using a simple DBF, creating complex indexes involving substrings (like `SUBSTR`) directly on the DBF file can sometimes be fragile or limited by the DBase level.
The most robust and "classic" solution in Lazarus—without altering your original DBF structure—is to use a temporary in-memory dataset (`TBufDataset`). This allows you to parse the tracking number into distinct fields (`Country`, `Prefix`) and utilize standard indexing features reliably.
Here is a solution using `TBufDataset` (available in the standard `BufDataset` unit):
1. **Read** from your DBF.
2. **Parse** the string into a temporary memory dataset with separate columns for sorting.
3. **Index** the memory dataset.
4. **Print** from the memory dataset.
Here is a working example:
```pascal
uses
..., db, BufDataset; // Make sure BufDataset is in your uses clause
procedure TForm1.PrintSortedReport;
var
TempDS: TBufDataset;
FullCode, Country, Prefix: String;
begin
// 1. Setup the temporary in-memory dataset
TempDS := TBufDataset.Create(nil);
try
TempDS.FieldDefs.Add('TrackCode', ftString, 20);
TempDS.FieldDefs.Add('SortCountry', ftString, 2); // For the last 2 chars
TempDS.FieldDefs.Add('SortPrefix', ftString, 2); // For the first 2 chars
TempDS.CreateDataset;
// 2. Iterate your source DBF and populate the TempDS
SourceDBF.First;
while not SourceDBF.EOF do
begin
FullCode := SourceDBF.FieldByName('TRACKING_NO').AsString;
// Basic validation to avoid errors on empty/short strings
if Length(FullCode) >= 13 then
begin
// Extract Country (Last 2) and Prefix (First 2)
// Using standard Pascal 'Copy' function
Country := Copy(FullCode, Length(FullCode) - 1, 2);
Prefix := Copy(FullCode, 1, 2);
TempDS.Append;
TempDS.FieldByName('TrackCode').AsString := FullCode;
TempDS.FieldByName('SortCountry').AsString := Country;
TempDS.FieldByName('SortPrefix').AsString := Prefix;
TempDS.Post;
end;
SourceDBF.Next;
end;
// 3. Apply the Sort Order
// We create an ephemeral index on the memory data
TempDS.AddIndex('ByCountryPrefix', 'SortCountry;SortPrefix', [ixCaseInsensitive]);
TempDS.IndexName := 'ByCountryPrefix';
// 4. Link to LazReport
// Assuming frReport1 is your TfrReport and frDBDataSet1 is your TfrDBDataSet
frDBDataSet1.DataSet := TempDS;
// Now the report will receive data sorted by Country first, then Prefix.
// In LazReport designer:
// - Create a Group Header with condition: [frDBDataSet1."SortCountry"]
// - Inside that, you can have a Master Data band.
frReport1.LoadFromFile('YourReport.lrf');
frReport1.ShowReport;
finally
TempDS.Free;
end;
end;
```
**Why this approach?**
According to the `TBufDataset` documentation, it provides a strictly local, buffered dataset that mimics standard TDataSet behavior. By separating the logic (parsing the string) from the storage (the DBF), you gain full control over the sort order without fighting DBase expression syntax.
This ensures your "Total" and grouping logic in LazReport works perfectly because the data is guaranteed to be contiguous.
Best regards.