From the query below
Select FIELD1,FIELD2,FIELD3,FIELD4 FROM MyTable Order By FIELD1,FIELD2,FIELD3,FIELD4 Group By FIELD1,FIELD2,FIELD3,FIELD4
I have a recordset like this:
I need to show data in a treeview like this:
I'm stuck with the code below.
var
Node: TTreeNode;
RootLevelCount: Integer;
X: Integer;
CurrentTextField: String;
MyTreeNodeText: array [0..10] of String;
begin
RootLevelCount := 4;
while not dm1.Q1.Eof do
begin
for X := 0 to RootLevelCount do
begin
CurrentTextField:=dm1.Q1.Fields[x].AsString;
if CurrentTextField='' then CurrentTextField := 'Level '+IntToStr(x);
if MyTreeNodeText[x]<>CurrentTextField then
begin
MyTreeNodeText[X]:=CurrentTextField;
if x=0 then
begin
Node:=tree.Items.AddFirst(Node, CurrentTextField);
end else
begin
node:=tree.Items.AddChild(node.Parent, CurrentTextField);
end;
end else
begin
node.GetNext;
end;
end;
dm1.Q1.Next;
end;
The result I have is the following and it's not I want:
After a good lunch, my mind has reborn then I found the solution.
var
Node: TTreeNode;
RootLevelCount: Integer;
X,X1: Integer;
CurrentTextField: String;
MyTreeNodeText: array [0..10] of String;
MyTreeNode: array [0..10] of TTreeNode;
begin
RootLevelCount := 4; //Number of fields that you want to show in the treeview
while not dm1.Q1.Eof do
begin
ROW_ID:=dm1.Q1.FieldByName('ROW_ID').AsString;
for X := 0 to RootLevelCount-1 do
begin
CurrentTextField:=dm1.Q1.Fields[4+x].AsString;
if CurrentTextField='' then CurrentTextField := 'Level '+IntToStr(x);
if MyTreeNodeText[x]<>CurrentTextField then
begin
MyTreeNodeText[X]:=CurrentTextField;
for X1 := x+1 to RootLevelCount-1 do
MyTreeNodeText[x1]:='';
if x=0 then
begin
Node:=tree.Items.Add(nil, CurrentTextField);
TMyTreeNode(Node).Indice:=StrToInt(ROW_ID);
MyTreeNode[x]:=node;
end else
begin
node:=tree.Items.AddChild(MyTreeNode[x-1], CurrentTextField);
TMyTreeNode(Node).Indice:=StrToInt(ROW_ID);
MyTreeNode[x]:=node;
end;
end;
end;
MyTreeNodeText[RootLevelCount]:='';
dm1.Q1.Next;
end;
then the result is the following:
Related
I have Grid with enabled columns moving, and code:
type
TRec = record
Col0,
Col1,
Col2: string;
end;
var
Data: TArray<TRec>;
procedure TFormMain.GridGetValue(Sender: TObject; const Col, Row: Integer; var Value: TValue);
begin
case Col of
0: Value := Data[Row].Col0;
1: Value := Data[Row].Col1;
2: Value := Data[Row].Col2;
end;
end;
When column is moved this OnGetValue code works incorrectly (shown columns data on the previous positions). How to fix this? Should I use OnColumnMoved event and remember new columns position manually?
Ok, this is my own answer:
We should add helper function to our TRec for reading fields by index:
type
TRec = record
Col0,
Col1,
Col2: string;
function GetField(AIndex: Integer): string;
end;
function TRec.GetField(AIndex: Integer): string;
begin
case AIndex of
0: Result := Col0;
1: Result := Col1;
2: Result := Col2;
else
Result := '';
end;
end;
Also 2 functions to save and restore TGrid columns using Ini file:
type
TColumnData = record
Pos: UInt8;
Visible: Boolean;
Width: UInt16;
end;
procedure LoadColumns(AGrid: TGrid; const ASection, AIdent: string);
var
I, J, ColsSize: Integer;
A: TArray<TColumnData>;
Col: TColumn;
begin
for I := 0 to AGrid.ColumnCount - 1 do
AGrid.Columns[I].Tag := I;
SetLength(A, AGrid.ColumnCount);
ColsSize := AGrid.ColumnCount*SizeOf(TColumnData);
if ReadIni(<FileName>, ASection, AIdent, (#A[0])^, ColsSize) = ColsSize then
for J := 0 to AGrid.ColumnCount - 1 do begin
for I := 0 to AGrid.ColumnCount - 1 do begin
Col := AGrid.Columns[I];
if Col.Tag = A[J].Pos then begin
Col.Index := J;
Col.Visible := A[J].Visible;
Col.Width := A[J].Width;
end;
end;
end;
end;
procedure SaveColumns(AGrid: TGrid; const ASection, AIdent: string);
var
I, ColsSize: Integer;
A: TArray<TColumnData>;
Col: TColumn;
begin
SetLength(A, AGrid.ColumnCount);
ColsSize := AGrid.ColumnCount*SizeOf(TColumnData);
for I := 0 to AGrid.ColumnCount - 1 do begin
Col := AGrid.Columns[I];
A[I].Pos := Col.Tag;
A[I].Visible := Col.Visible;
A[I].Width := Round(Col.Width);
end;
WriteIni(<FileName>, ASection, AIdent, (#A[0])^, ColsSize);
end;
Now we should call LoadColumns (which is also initialize Tag fields for Columns) from OnFormCreate and SaveColumns from OnFormDestroy. And finally OnGetValue code:
var
Data: TArray<TRec>;
procedure TFormMain.GridGetValue(Sender: TObject; const Col, Row: Integer; var Value: TValue);
begin
Value := Data[Row].GetField((Sender as TGrid).Columns[Col].Tag);
end;
in matrix(StringGrid) NxM sort the elements of each row in nondecreasing order?
var
Form1: TForm1;
n,m:integer;
I:integer;
implementation
{$R *.dfm}
procedure TForm1.btNapraviClick(Sender: TObject);
begin
with StringGrid1 do
begin
n:=StrToInt(edN.text)+1;
m:=StrToInt(edM.text)+1;
ColCount:=n;
RowCount:=m;
for I:=0 to n-1 do Cells[I,0]:=IntToStr(I);
for I:=1 to m-1 do Cells[0,I]:=IntToStr(I);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var J,P,K:integer;
begin
with StringGrid1 do
begin
for I:=1 to n do
for J:=1 to m-1 do
for K:=J+1 to m do
begin
if StrToInt(Cells[I,J]) <= StrToInt(Cells[I,K]) then
begin
P:=StrToInt(Cells[I,J]);
Cells[I,J]:=(Cells[I,K]);
Cells[I,K]:=IntToStr(P);
end;
end;
end;
end;
Each Row in a StringGrid decends from TStrings, so you can assign a row to a TStringList and do a custom sort on that one.
Here is some source code:
First I fill the grid with Random data:
procedure TForm60.FormCreate(Sender: TObject);
var
i, j: Integer;
begin
Randomize;
with StringGrid1 do
begin
ColCount := 10;
RowCount := 10;
for i := 0 to ColCount - 1 do
for j := 0 to RowCount - 1 do
Cells[i, j] := IntToStr(Random(5000));
end;
end;
Then at Button1.Click I sort each row in descending order:
function StringListSortCompare(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := StrToIntDef(List[Index2], 0) - StrToIntDef(List[Index1], 0)
end;
procedure TForm60.Button1Click(Sender: TObject);
var
i: Integer;
Buffer: TStringList;
begin
Buffer := TStringList.Create;
for i := 0 to StringGrid1.RowCount - 1 do
begin
Buffer.Assign(StringGrid1.Rows[i]);
Buffer.CustomSort(#StringListSortCompare);
StringGrid1.Rows[i].Assign(Buffer);
end;
FreeAndNil(Buffer);
end;
Since I subStract the integer value of List[Index2] from List[Index1] the list becomes sorted descending.
And the result:
Before
After
After reading your question again I'm not sure if you by "nondecreasing order" mean increasing order. If so just implement the sort procedure like this:
function StringListSortCompare(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := StrToIntDef(List[Index1], 0) - StrToIntDef(List[Index2], 0)
end;
I want to implement a sortable DBgrid (that sorts its rows when clicked on column title). I managed to make it sortable in an ascending order but I can't do it in a descending order. Here are my design settings:
Query1.DatabaseName:='Test';
DataSetProvider1.DataSet:=Query1;
ClientDataSet1.ProviderName:=DataSetProvider1;
DataSource1.DataSet:=ClientDataSet1;
DBGrid1.DatSource:=DataSource1;
And here are fragments of my code:
procedure TForm2.FormShow(Sender: TObject);
begin
Query1.Open;
ClientDataSet1.Data:=DataSetProvider1.Data;
ClientDataSet1.AddIndex('objnameDESC','objname',[ixDescending]);
ClientDataSet1.AddIndex('SUM(cd.worktime)DESC','SUM(cd.worktime)',[ixDescending]);
end;
procedure TForm2.DBGrid1TitleClick(Column: TColumn);
begin
case Column.Index of
0: if ClientDataSet1.IndexFieldNames='objname' then
ClientDataSet1.IndexFieldNames:='objnameDESC'
else
ClientDataSet1.IndexFieldNames:='objname';
1: if ClientDataSet1.IndexFieldNames='SUM(cd.worktime)' then
ClientDataSet1.IndexFieldNames:='SUM(cd.worktime)DESC'
else
ClientDataSet1.IndexFieldNames:='SUM(cd.worktime)';
end;
end;
When I click on a column title for the first time, sorting is done in an ascending order - so up to here everything is OK. When I click for the second time I expect sorting in a descending order to be done but instead I get the message:
Project ... raised Exception class EDatabaseError with message
'ClientDataSet1: Field 'objnameDESC' not found'.
Any ideas about what I am doing wrong?
As you are already using TClientDataSet you might make use of a component I made for exactly that purpose. Create an instance, set its Grid property and it will automatically connect to the OnTitleClick event.
type
TDBGridSorter = class(TComponent)
strict private
FSortColumn: TColumn;
FGrid: TDBGrid;
procedure CreateIndex(const FieldName: string; Descending: Boolean);
function GetDataSet: TClientDataSet;
function MakeIndexName(const FieldName: string; Descending: Boolean): string;
procedure SetSortColumn(const Value: TColumn);
procedure SortByField(const FieldName: string; out Descending: Boolean);
private
procedure SetGrid(const Value: TDBGrid);
strict protected
procedure GridTitleClick(Column: TColumn);
property DataSet: TClientDataSet read GetDataSet;
public
property Grid: TDBGrid read FGrid write SetGrid;
property SortColumn: TColumn read FSortColumn write SetSortColumn;
end;
procedure TDBGridSorter.CreateIndex(const FieldName: string; Descending: Boolean);
var
cds: TClientDataSet;
indexDef: TIndexDef;
indexName: string;
begin
cds := DataSet;
if cds <> nil then begin
indexName := MakeIndexName(FieldName, Descending);
if cds.IndexDefs.IndexOf(indexName) < 0 then begin
indexDef := cds.IndexDefs.AddIndexDef;
indexDef.Name := indexName;
indexDef.Fields := FieldName;
indexDef.CaseInsFields := FieldName;
if Descending then
indexDef.DescFields := FieldName;
end;
end;
end;
function TDBGridSorter.GetDataSet: TClientDataSet;
begin
if (Grid <> nil) and (Grid.DataSource <> nil) and (Grid.DataSource.DataSet is TClientDataSet) then
Result := TClientDataSet(Grid.DataSource.DataSet)
else
Result := nil;
end;
procedure TDBGridSorter.GridTitleClick(Column: TColumn);
begin
SortColumn := Column;
end;
function TDBGridSorter.MakeIndexName(const FieldName: string; Descending: Boolean): string;
const
cAscDesc: array[Boolean] of string = ('_ASC', '_DESC');
begin
Result := FieldName + cAscDesc[Descending];
end;
procedure TDBGridSorter.SetGrid(const Value: TDBGrid);
begin
if FGrid <> Value then begin
if FGrid <> nil then begin
FGrid.OnTitleClick := nil;
FGrid.RemoveFreeNotification(Self);
end;
FGrid := Value;
if FGrid <> nil then begin
FGrid.FreeNotification(Self);
FGrid.OnTitleClick := GridTitleClick;
end;
end;
end;
procedure TDBGridSorter.SetSortColumn(const Value: TColumn);
const
cOrder: array[Boolean] of string = ('˄', '˅');
var
descending: Boolean;
S: string;
begin
if FSortColumn <> nil then begin
S := FSortColumn.Title.Caption;
if StartsStr(cOrder[false], S) or StartsStr(cOrder[true], S) then begin
Delete(S, 1, 2);
FSortColumn.Title.Caption := S;
end;
end;
FSortColumn := Value;
if FSortColumn <> nil then begin
SortByField(FSortColumn.FieldName, descending);
FSortColumn.Title.Caption := Format('%s %s', [cOrder[descending], FSortColumn.Title.Caption]);
end;
end;
procedure TDBGridSorter.SortByField(const FieldName: string; out Descending:
Boolean);
var
cds: TClientDataSet;
curIndex: TIndexDef;
N: Integer;
begin
cds := DataSet;
if cds <> nil then begin
descending := false;
N := cds.IndexDefs.IndexOf(cds.IndexName);
if N >= 0 then begin
curIndex := cds.IndexDefs[N];
if SameText(FieldName, curIndex.Fields) then
descending := not (ixDescending in curIndex.Options)
end;
{ make sure the index exists }
CreateIndex(FieldName, descending);
cds.IndexName := MakeIndexName(FieldName, descending);
end;
end;
Wrong assignment
Apart from the fact that an incorrect assignment is made, a switch back to "ascending" is not possible.
For 2 Colums you need 4 Indexes.
Assuming 'objname' and 'SUM(cd.worktime)' are Fields.
procedure TForm2.FormShow(Sender: TObject);
....
ClientDataSet1.AddIndex('col0_asc','objname',[]);
ClientDataSet1.AddIndex('col0_desc','objname',[ixDescending]);
ClientDataSet1.AddIndex('col1_asc','SUM(cd.worktime)',[]);
ClientDataSet1.AddIndex('col1_desc','SUM(cd.worktime)',[ixDescending]);
....
Use ClientDataSet1.IndexName
procedure TForm2.DBGrid1TitleClick(Column: TColumn);
begin
case Column.Index of
0: if ClientDataSet1.IndexName='col0_asc' then
ClientDataSet1.IndexName:='col0_desc'
else
ClientDataSet1.IndexName:='col0_asc';
1: if ClientDataSet1.IndexName='col1_asc' then
ClientDataSet1.IndexName:='col1_desc'
else
ClientDataSet1.IndexName:='col1_asc';
end;
....
Or shorter
procedure TForm2.DBGrid1TitleClick(Column: TColumn);
begin
if ClientDataSet1.IndexName='col'+IntToStr(Column.Index)+'_asc' then
ClientDataSet1.IndexName:='col'+IntToStr(Column.Index)+'_desc'
else
ClientDataSet1.IndexName:='col'+IntToStr(Column.Index)+'_asc';
....
But it is better to test the number of columns that are active (AddIndex = done).
procedure TForm2.DBGrid1TitleClick(Column: TColumn);
begin
if Column.Index < 2 then begin
if ClientDataSet1.IndexName='col'+IntToStr(Column.Index)+'_asc' then
ClientDataSet1.IndexName:='col'+IntToStr(Column.Index)+'_desc'
else
ClientDataSet1.IndexName:='col'+IntToStr(Column.Index)+'_asc';
end;
....
You should be setting the IndexName and not IndexFieldNames. IndexFieldNames accepts field names and creates an index on the fly.
procedure TForm2.DBGrid1TitleClick(Column: TColumn);
begin
case Column.Index of
0: if ClientDataSet1.IndexFieldNames='objname' then
ClientDataSet1.IndexName:='objnameDESC'
else
ClientDataSet1.IndexFieldNames:='objname';
1: if ClientDataSet1.IndexFieldNames='SUM(cd.worktime)' then
ClientDataSet1.IndexFieldNames:='SUM(cd.worktime) DESC'
else
ClientDataSet1.IndexFieldNames:='SUM(cd.worktime)';
end;
end;
Many of my programs need this, so I wrote a general procedure which builds two indices for each field in the dataset
Procedure BuildIndices (cds: TClientDataSet);
var
i, j: integer;
alist: tstrings;
begin
with cds do
begin
open;
for i:= 0 to FieldCount - 1 do
if fields[i].fieldkind <> fkCalculated then
begin
j:= i * 2;
addindex ('idx' + inttostr (j), fieldlist.strings[i], [], '', '', 0);
addindex ('idx' + inttostr (j+1), fieldlist.strings[i], [ixDescending], '', '',0);
end;
alist:= tstringlist.create;
getindexnames (alist);
alist.free;
close;
end;
end;
At this stage, I have indices idx0 and idx1 for the first field, idx2 and idx3 for the second field, etc.
Then, in the form which displays the dbgrid (here the active query is called qShowFees)
procedure TShowFees.DBGrid1TitleClick(Column: TColumn);
var
n, ex: word;
begin
n:= column.Index;
try
dbGrid1.columns[prevcol].title.font.color:= clNavy
except
end;
dbGrid1.columns[n].title.font.color:= clRed;
prevcol:= n;
directions[n]:= not directions[n];
ex:= n * 2;
if directions[n] then inc (ex);
with qShowFees do
try
disablecontrols;
close;
indexname:= 'idx' + inttostr (ex);
open
finally
enablecontrols
end;
end;
'Directions' is a form array of booleans which 'remembers' which way each column is currently sorted (ascending or descending) so clicking the dbgrid's title bar a second time will cause the grid to be sorted in the opposing manner to which it was sorted before. 'Prevcol' is a form variable which stores the currently selected column; this is saved between invocations, so the next time the user opens the form, it is sorted in the same way as she left it previously.
First part of the code works OK while the second (commented) does not.
It overwrites my A1 file although it should write to A2.
procedure TForm1.AdvGlowButton12Click(Sender: TObject);
var
i,j: Integer;
Seznam: TStrings;
ApplicationPath: string;
begin
if (cxRadiogroup3.ItemIndex and cxRadiogroup2.ItemIndex) = 0 then begin
ApplicationPath:= ExtractFileDir(Application.ExeName);
Seznam:= TStringList.Create;
try
for i:=0 to advStringGrid2.ColCount-1 do
Seznam.AddStrings(advStringGrid2.Cols [i]);
for i:=0 to advStringGrid2.rowCount-1 do
Seznam.AddStrings(advStringGrid2.rows [j]);
Seznam.SaveToFile(ApplicationPath+'\A1.txt');
finally
seznam.free;
end;
end ;
//if cxRadiogroup3.ItemIndex = 1 and cxRadiogroup2.ItemIndex = 0 then begin
// ApplicationPath:= ExtractFileDir(Application.ExeName);
// Seznam:= TStringList.Create;
// try
// for i:=0 to advStringGrid2.ColCount-1 do
// Seznam.AddStrings(advStringGrid2.Cols [i]);
// for i:=0 to advStringGrid2.rowCount-1 do
// Seznam.AddStrings(advStringGrid2.rows [j]);
// Seznam.SaveToFile(ApplicationPath+'\A2.txt');
// finally
// seznam.free;
// end ;
//end
end;
What am I doing wrong ?
Also why is the stringgrid giving listindex out of bounds when I try to load into it contents from an empty text file? If I save empty stringgrid to that file,later ,though it has nothing in the file,it does not complain? Strange...
This is how I load A1 and A2 into the stringgrid.
procedure TForm1.cxRadioGroup2Click(Sender: TObject);
Var
I,j,k: Integer;
Seznam: TStrings;
ApplicationPath: string;
begin
case cxradioGroup2.ItemIndex of
0: begin
if cxradioGroup3.ItemIndex = 0 then begin
Seznam:= TStringList.Create;
AdvStringgrid2.ClearAll;
try
Seznam.LoadFromFile('A1.txt');
k:= 0;
for i:=0 to advStringGrid2.ColCount-1 do
for j:=0 to advStringGrid2.RowCount-1 do begin
advstringGrid2.Cells [i,j]:= Seznam.Strings [k];
Inc(k);
end;
finally
seznam.free;
end;
end;
if cxradioGroup3.ItemIndex = 1 then begin
Seznam:= TStringList.Create;
AdvStringgrid2.ClearAll;
try
Seznam.LoadFromFile('A2.txt');
k:=0;
for i:=0 to advStringGrid2.ColCount-1 do
for j:=0 to advStringGrid2.RowCount-1 do begin
advstringGrid2.Cells [i,j]:= Seznam.Strings [k];
Inc(k);
end;
finally
seznam.free;
end;
end;
end;
end;
end;
here is an old tipp from SwissDelphiCenter that could help you
// Save StringGrid1 to 'c:\temp.txt':
procedure TForm1.Button1Click(Sender: TObject);
begin
SaveStringGrid(StringGrid1, 'c:\temp.txt');
end;
// Load StringGrid1 from 'c:\temp.txt':
procedure TForm1.Button2Click(Sender: TObject);
begin
LoadStringGrid(StringGrid1, 'c:\temp.txt');
end;
// Save a TStringGrid to a file
procedure SaveStringGrid(StringGrid: TStringGrid; const FileName: TFileName);
var
f: TextFile;
i, k: Integer;
begin
AssignFile(f, FileName);
Rewrite(f);
with StringGrid do
begin
// Write number of Columns/Rows
Writeln(f, ColCount);
Writeln(f, RowCount);
// loop through cells
for i := 0 to ColCount - 1 do
for k := 0 to RowCount - 1 do
Writeln(F, Cells[i, k]);
end;
CloseFile(F);
end;
// Load a TStringGrid from a file
procedure LoadStringGrid(StringGrid: TStringGrid; const FileName: TFileName);
var
f: TextFile;
iTmp, i, k: Integer;
strTemp: String;
begin
AssignFile(f, FileName);
Reset(f);
with StringGrid do
begin
// Get number of columns
Readln(f, iTmp);
ColCount := iTmp;
// Get number of rows
Readln(f, iTmp);
RowCount := iTmp;
// loop through cells & fill in values
for i := 0 to ColCount - 1 do
for k := 0 to RowCount - 1 do
begin
Readln(f, strTemp);
Cells[i, k] := strTemp;
end;
end;
CloseFile(f);
end;
I'm trying to understand your code and tried him as good as it is possible for me to rewrite. (it's not tested)
procedure TForm1.AdvGlowButton12Click(Sender: TObject);
var
i, j: Integer;
Seznam: TStrings;
ApplicationPath: string;
fileName: string;
line: string;
begin
if (cxRadiogroup2.ItemIndex = 0) then begin
if (cxRadiogroup3.ItemIndex = 0) then
fileName:= 'A1.txt'
else
fileName:= 'A2.txt'
ApplicationPath:= ExtractFileDir(Application.ExeName);
Seznam:= TStringList.Create;
try
for k:=0 to advStringGrid2.RowCount-1 do begin
line:= '';
for i:=0 to advStringGrid2.ColCount-1 do
line = line + '|' + advStringGrid2.Cells[i, k];
Seznam.AddStrings(line);
end;
Seznam.SaveToFile(ApplicationPath + '\' + fileName);
finally
seznam.Free;
end;
end;
end;
procedure TForm1.cxRadioGroup2Click(Sender: TObject);
var
splitList: TStringList;
i, j: Integer;
Seznam: TStrings;
ApplicationPath: string;
fileName: string;
line: string;
sepIndex: integer;
begin
if (cxRadiogroup2.ItemIndex = 0) then begin
if (cxRadiogroup3.ItemIndex = 0) then
fileName:= 'A1.txt'
else
fileName:= 'A2.txt'
AdvStringgrid2.ClearAll; // don't know what this does
ApplicationPath:= ExtractFileDir(Application.ExeName);
Seznam:= TStringList.Create;
try
Seznam.LoadFromFile(fileName);
advstringGrid2.RowCount:= Seznam.Count;
splitList:= TStringList.Create;
for i:=0 to Seznam.Count-1 do begin
line:= Seznam.Strings [i];
Split('|', line, splitList);
advStringGrid2.ColCount:= Max(advStringGrid2.ColCount, splitList.Count);
for k:=0 to splitList.Count-1 do
advStringGrid2.Cells[i, k]:= splitList[k];
end;
finally
splitList.Free;
seznam.Free;
end;
end;
end;
procedure Split (const Delimiter: Char; Input: string; const Strings: TStrings);
begin
Assert(Assigned(Strings));
Strings.Clear;
Strings.Delimiter:= Delimiter;
Strings.DelimitedText:= Input;
end;
hope that helps
How do you know it is overwriting A1.txt? You are saving the exact same contents in both cases.
Founded and adapted to my needs. Then shared :-)
procedure LoadStringGrid(const AFileName: TFileName; AGrid: TStringGrid);
var
slRows: TStringList;
i: integer;
begin
slRows:= TStringList.Create;
try
slRows.LoadFromFile(AFileName);
for i:= 0 to slRows.Count -1 do
AGrid.Rows[i +1].CommaText:= slRows[i];
finally
slRows.Free;
end;
end;// LoadStringGrid
procedure SaveStringGrid(const AFileName: TFileName; AGrid: TStringGrid);
var
slRows: TStringList;
i: integer;
begin
slRows:= TStringList.Create;
try
for i:= 1 to AGrid.RowCount -1 do
slRows.Add(AGrid.Rows[i].CommaText);
slRows.SaveToFile(AFileName);
finally
slRows.Free;
end;
end;// SaveStringGrid
Here is what I am "trying" to achieve
I have a function to generate passwords which I then add into a TStringList after this I should populate the VirtualTreeView with the items but I am having no luck in getting anywhere fast with doing so. How should it be done the correct way? I am still learning and am not a professional.
My function for generating the passwords:
function Generate(AllowUpper,AllowLower,AllowNumbers,AllowSymbols:Boolean; PassLen:Integer):String;
const
UpperList = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
LowerList = 'abcdefghijklmnopqrstuvwxyz';
NumberList = '0123456789';
SymbolList = '!#$%&/()=?#<>|{[]}\*~+#;:.-_';
var
MyList : String;
Index : Integer;
i : Integer;
begin
Result:='';
MyList:='';
//here if the flag is set the elements are added to the main array (string) to process
if AllowUpper then MyList := MyList + UpperList;
if AllowLower then MyList := MyList + LowerList;
if AllowNumbers then MyList := MyList + NumberList;
if AllowSymbols then MyList := MyList + SymbolList;
Randomize;
if Length(MyList)>0 then
for i := 1 to PassLen do
begin
Index := Random(Length(MyList))+1;
Result := Result+MyList[Index];
end;
end;
Here is how I am calling it
procedure TMain.Button3Click(Sender: TObject);
var
i: integer;
StrLst: TStringList;
// Timing vars...
Freq, StartCount, StopCount: Int64;
TimingSeconds: real;
begin
vst1.Clear;
Panel2.Caption := 'Generating Passwords...';
Application.ProcessMessages;
// Start Performance Timer...
QueryPerformanceFrequency(Freq);
QueryPerformanceCounter(StartCount);
StrLst := TStringList.Create;
try
for i := 1 to PassLenEd.Value do
StrLst.Add(Generate(ChkGrpCharSelect.Checked[0],ChkGrpCharSelect.Checked[1],
ChkGrpCharSelect.Checked[2],ChkGrpCharSelect.Checked[3],20));
// Stop Performance Timer...
QueryPerformanceCounter(StopCount);
TimingSeconds := (StopCount - StartCount) / Freq;
// Display Timing... How long it took to generate
Panel2.Caption := 'Generated '+IntToStr(PassLenEd.Value)+' passwords in '+
FloatToStrF(TimingSeconds,ffnumber,1,3)+' seconds';
// Add to VirtualTreeList - here???
finally
StrLst.Free;
end;
end;
I expect that I am doing this completely the wrong way, I have been trying for 2 days now, it would be great if someone could put me straight with how I should go about it.
Chris
I'd probably stick with TListView but turn it into a virtual list view. Like this:
procedure TMyForm.FormCreate;
begin
ListView.OwnerData := True;
ListView.OnData = ListViewData;
ListView.Items.Count := StringList.Count;
end;
procedure TMyForm.ListViewData(Sender: TObject; ListItem: TListItem);
begin
ListItem.Caption := StringList[ListItem.Index];
end;
You can put millions of items in there in an instant.
You better store your stringlist somewhere else in your code to use it "virtually", e.g. in the form's private section. When after populating it, just set:
vst1.Clear;
vst1.RootNodeCount := StrLst.Count;
And on tree's get text event:
procedure TForm1.vst1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType; var CellText: string);
begin
CellText := StrLst[Node.Index];
end;
Or if you really want VirtualTreeView, you can use something like this ...
I'm not sure if this is absolutely clear solution, I'm familiar with records, not only one single variables.
procedure TMain.Button3Click(Sender: TObject);
var i: integer;
p: PString;
Freq, StartCount, StopCount: Int64;
TimingSeconds: real;
begin
Panel2.Caption := 'Generating Passwords...';
Application.ProcessMessages;
QueryPerformanceFrequency(Freq);
QueryPerformanceCounter(StartCount);
vst1.BeginUpdate;
vst1.Clear;
for i := 1 to PassLenEd.Value do
begin
p := VirtualStringTree1.GetNodeData(VirtualStringTree1.AddChild(nil));
p^ := Generate(ChkGrpCharSelect.Checked[0],ChkGrpCharSelect.Checked[1], ChkGrpCharSelect.Checked[2],ChkGrpCharSelect.Checked[3],20);
end;
vst1.EndUpdate;
QueryPerformanceCounter(StopCount);
TimingSeconds := (StopCount - StartCount) / Freq;
Panel2.Caption := 'Generated '+IntToStr(PassLenEd.Value)+' passwords in '+
FloatToStrF(TimingSeconds,ffnumber,1,3)+' seconds';
end;
And you need to implement OnGetNodeDataSize and OnGetText events to initialize node data size and to display the text.
procedure TMain.vst1GetNodeDataSize(
Sender: TBaseVirtualTree; var NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(string);
end;
procedure TMain.vst1GetText(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
var CellText: string);
begin
CellText := PString(VirtualStringTree1.GetNodeData(Node))^;
end;
Edit 1: I've corrected data types UnicodeString -> String