I would like to select either all root nodes or all child nodes (not all nodes in a VirtualTreeView).
I've tried to use this code to select all root nodes:
procedure SelectAllRoots;
var
Node: PVirtualNode;
begin
Form1.VirtualStringTree1.BeginUpdate;
Node := Form1.VirtualStringTree1.GetFirst;
while True do
begin
if Node = nil then
Break;
if not (vsSelected in Node.States) then
Node.States := Node.States + [vsSelected];
Node := Form1.VirtualStringTree1.GetNext(Node);
end;
Form1.VirtualStringTree1.EndUpdate;
end;
I can tell there's a small glitch.
The selection is either incomplete or gets stuck. What am I doing wrong ?
Edit:
I use MultiSelection.
1. Select all root nodes:
To select all root nodes, you can use the following procedure:
procedure SelectRootNodes(AVirtualTree: TBaseVirtualTree);
var
Node: PVirtualNode;
begin
AVirtualTree.BeginUpdate;
try
Node := AVirtualTree.GetFirst;
while Assigned(Node) do
begin
AVirtualTree.Selected[Node] := True;
Node := AVirtualTree.GetNextSibling(Node);
end;
finally
AVirtualTree.EndUpdate;
end;
end;
2. Select all child nodes:
To select all child nodes, level independent, you need to use recursive function like this:
procedure SelectChildNodes(AVirtualTree: TBaseVirtualTree);
var
Node: PVirtualNode;
procedure SelectSubNodes(ANode: PVirtualNode);
var
SubNode: PVirtualNode;
begin
SubNode := AVirtualTree.GetFirstChild(ANode);
while Assigned(SubNode) do
begin
SelectSubNodes(SubNode);
AVirtualTree.Selected[SubNode] := True;
SubNode := AVirtualTree.GetNextSibling(SubNode);
end;
end;
begin
AVirtualTree.BeginUpdate;
try
Node := AVirtualTree.GetFirst;
while Assigned(Node) do
begin
SelectSubNodes(Node);
Node := AVirtualTree.GetNextSibling(Node);
end;
finally
AVirtualTree.EndUpdate;
end;
end;
Related
I want information from TADOQuery to be loaded into a TTreeView. For example, I want it to be loaded as Field1->Add in Table1 and as Field2->AddChild with buttonClick. But when I run the code, I am getting an error:
Access violation at adress 0043616B in module "TRV2.exe"
I'm making a mistake or something is missing. Can you guide me?
procedure TForm1.AddButtonClick(Sender: TObject);
var
t: Integer;
MyNode, Node : TTreeNode;
begin
MyNode := Node;
t := Node.AbsoluteIndex;
TreeView1.Items.Add(MyNode, ADOQuery1.FieldByName('CODE_NAME').AsString);
end;
procedure TForm1.AddChildButtonClick(Sender: TObject);
var
t: Integer;
MyNode, Node: TTreeNode;
begin
MyNode := Node;
t := Node.AbsoluteIndex;
TreeView1.Items.Add(MyNode, ADOQuery1.FieldByName('CODE_CHILD').AsString);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
t: Integer;
MyNode, Node: TTreeNode;
begin
MyNode := Node;
t := Node.AbsoluteIndex;
ADOQuery1.Open;
end;
UPDATE: I want to get the whole table and update the TTreeView when I add new Add and Child to the database. With these codes (AddButtonClick and AddChildButtonClick) I can only import the first values into the TTreeView. I wonder if a loop is needed?
MyNode and Node are both local variables that you are not initializing to anything. Your AV is because you are trying to access an object that doesn't exist.
Try using a class member instead, where you initialize it with one button click, and then use it with the other button click, eg:
private
MyNode: TTreeNode;
...
procedure TForm1.AddButtonClick(Sender: TObject);
begin
MyNode := TreeView1.Items.Add(nil, ADOQuery1.FieldByName('CODE_NAME').AsString);
end;
procedure TForm1.AddChildButtonClick(Sender: TObject);
begin
if MyNode <> nil then
TreeView1.Items.AddChild(MyNode, ADOQuery1.FieldByName('CODE_CHILD').AsString);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ADOQuery1.Open;
end;
UPDATE: to iterate through multiple records in the query result, you need to call TADOQuery.Next() in a loop until TADOQuery.Eof is true.
The database was taken into treview with the following codes. There is something missing. Because Field1=Add and Field2=Child. Same Fields repeating.
procedure TForm1.AddButtonClick(Sender: TObject);
var
CurrentDeptID, RecordDeptID: Integer; RootNode, DeptNode: TTreeNode;
begin
CurrentDeptID := 0;
TreeView1.Items.Clear;
RootNode := TreeView1.Items.Add(DeptNode, 'CODE_NAME');
DeptNode := nil;
ADOQuery1.SQL.Text := 'Select * from Tablo1 where CODE_NAME= CODE_NAME';
ADOQuery1.Open;
try
ADOQuery1.First;
while not ADOQuery1.Eof do
begin
RecordDeptID := ADOQuery1.FieldByName('ID').AsInteger;
if ( DeptNode = nil) or (RecordDeptID <> CurrentDeptID) then
begin
DeptNode := TreeView1.Items.AddChild(RootNode,
ADOQuery1.FieldByName('CODE_NAME').AsString); //
CurrentDeptID := RecordDeptID;
end;
TreeView1.Items.AddChild(DeptNode,
ADOQuery1.FieldByName('CODE_CHILD').AsString);
ADOQuery1.Next;
end;
finally
ADOQuery1.close;
end;
end;
[https://i.stack.imgur.com/kNojV.jpg]
Blockquote
I'm using a TVirtualStringTree to store pointers to records.
Originally there is a TList that contains the list of records.
I'm using the OnInitNode event to iterate through the TList and assign each records' data to the tree's nodes.
However, when retrieving the data associated with a node in the OnNewText event handler, the pointer returned has a different address than the one originally stored in the tree.
Further, I can see through debugging that the pointer (to the record data) retrieved from the node is not the same as the one originally stored in the node. I need to save changed data to a database and need to reference the record with the changed data. It should be simple as referencing the pointer, but the problem is that the pointer is not the same.
I'm not sure what I'm doing wrong and hope someone can help me fix this.
Thanks in advance.
Here is my code:
Data structure and declarations:
TTherapData = record
TherapID: Integer;
TherapName: String[120];
TherapInstr: String[120];
Selected_DB: Byte;
Selected: Byte;
end;
PTherapData = ^TTherapData;
FTherapDataList: TList<PTherapData>;
FTherapDataListAsg_Iter: Integer;
vstRxList_Asg: TVirtualStringTree;
Loading the data into the TList, and then into the tree:
procedure TfmPatient_Conslt.LoadTherapList(const ADBLoad: Boolean = False);
var
TherapData: PTherapData;
d, x: Integer;
begin
datamod.uspLKTHERAP_S.First;
while not datamod.uspLKTHERAP_S.Eof do
begin
New(TherapData);
TherapData^.TherapID := datamod.uspLKTHERAP_SROW_ID.AsInteger;
TherapData^.TherapName := datamod.uspLKTHERAP_SIMPRTHERAP.AsString;
TherapData^.TherapInstr := EmptyStr;
TherapData^.Selected := 0;
TherapData^.Selected_DB := 0;
FTherapDataList.Add(TherapData);
datamod.uspLKTHERAP_S.Next;
end;
datamod.uspCONSLT_RX_S.First;
while not datamod.uspCONSLT_RX_S.Eof do
begin
d := datamod.uspCONSLT_RX_SRX_ID.AsInteger;
TherapData := FTherapDataList[TherapDataList_GetIndexOfID(d)];
TherapData^.TherapInstr := datamod.uspCONSLT_RX_SRX_INSTRUCTION.AsString;
TherapData^.Selected := 1;
TherapData^.Selected_DB := 1;
datamod.uspCONSLT_RX_S.Next;
end;
x := TherapDataList_CountSelectedItems;
FTherapDataListAsg_Iter := 0;
vstRxList_Asg.NodeDataSize := SizeOf(TTherapData);
vstRxList_Asg.RootNodeCount := 0;
vstRxList_Asg.RootNodeCount := x;
end;
procedure TfmPatient_Conslt.vstRxList_AsgInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
var InitialStates: TVirtualNodeInitStates);
var
TherapData: PTherapData;
begin
TherapData := Sender.GetNodeData(Node);
while (FTherapDataList[FTherapDataListAsg_Iter]^.Selected <> 1) do
Inc(FTherapDataListAsg_Iter);
TherapData^.TherapID := FTherapDataList[FTherapDataListAsg_Iter]^.TherapID;
TherapData^.TherapName := FTherapDataList[FTherapDataListAsg_Iter]^.TherapName;
TherapData^.TherapInstr := FTherapDataList[FTherapDataListAsg_Iter]^.TherapInstr;
{ TherapData := FTherapDataList[FTherapDataListAsg_Iter]; } //
{ TherapData^ := FTherapDataList[FTherapDataListAsg_Iter]^; } //
Inc(FTherapDataListAsg_Iter);
end;
procedure TfmPatient_Conslt.vstRxList_AsgGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType; var CellText: string);
var
TherapData: PTherapData;
begin
TherapData := Sender.GetNodeData(Node);
if Assigned(TherapData) then
if (Column = 0) then
CellText := TherapData^.TherapName
else if (Column = 1) then
CellText := TherapData^.TherapInstr;
end;
procedure TfmPatient_Conslt.vstRxList_AsgEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
var Allowed: Boolean);
begin
Allowed := (Column = 1);
end;
Retrieving the data. I noticed the problem here:
procedure TfmPatient_Conslt.vstRxList_AsgNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
NewText: string);
var
TherapData: PTherapData;
begin
if (Column = 1) then
begin
TherapData := Sender.GetNodeData(Node);
if Assigned(TherapData) then // <---- There is a debug breakpoint here
// and the watch window screen-shot
// is taken here
TherapData^.TherapInstr := NewText;
// Showmessage(Format('%p', [TherapData])); // <---- The pointer value is not the same
// as that originally stored !
end;
end;
This is where I save the list data to database, and the reason that I need the tree to change the original data and not a copy:
procedure TfmPatient_Conslt.SaveRxListToDB;
var
TherapData: PTherapData;
begin
for TherapData in FTherapDataList do
begin
if (TherapData^.Selected = 1) and (TherapData^.Selected_DB = 0) then
begin
// Add new entries to DB
// :ROW_ID, :CONSLT_ID, :RX_ID, :RX_INSTRUCTION
datamod.uspCONSLT_RX_I.ParamByName('ROW_ID').AsInteger := 0;
datamod.uspCONSLT_RX_I.ParamByName('CONSLT_ID').AsInteger := FConsultationID;
datamod.uspCONSLT_RX_I.ParamByName('RX_ID').AsInteger := TherapData^.TherapID;
datamod.uspCONSLT_RX_I.ParamByName('RX_INSTRUCTION').AsString := TherapData^.TherapInstr;
datamod.uspCONSLT_RX_I.PrepareSQL(False);
datamod.uspCONSLT_RX_I.ExecProc;
TherapData^.Selected_DB := 1;
end
else if (TherapData^.Selected = 1) and (TherapData^.Selected_DB = 1) then
begin
// Update existing DB entries
// :CONSLT_ID, :RX_ID, :RX_INSTRUCTION
datamod.uspCONSLT_RX_U.ParamByName('CONSLT_ID').AsInteger := FConsultationID;
datamod.uspCONSLT_RX_U.ParamByName('RX_ID').AsInteger := TherapData^.TherapID;
datamod.uspCONSLT_RX_U.ParamByName('RX_INSTRUCTION').AsString := TherapData^.TherapInstr;
datamod.uspCONSLT_RX_U.PrepareSQL(False);
datamod.uspCONSLT_RX_U.ExecProc;
end
else if (TherapData^.Selected = 0) and (TherapData^.Selected_DB = 1) then
begin
// Delete removed entries from DB
// :CONSLT_ID, :RX_ID
datamod.uspCONSLT_RX_D.ParamByName('CONSLT_ID').AsInteger := FConsultationID;
datamod.uspCONSLT_RX_D.ParamByName('RX_ID').AsInteger := TherapData^.TherapID;
datamod.uspCONSLT_RX_D.PrepareSQL(False);
datamod.uspCONSLT_RX_D.ExecProc;
TherapData^.Selected_DB := 0;
end;
end;
end;
Here's a screenshot of the from the Debug->Watch List window:
To answer to the question in the title, I would say yes, your coding approach is wrong.
The mistake is that you don't save a pointer to your data record to the VT's node, you're allocating whole (separate) TTherapData record for each node! So the "mistake" is the line
vstRxList_Asg.NodeDataSize := SizeOf(TTherapData);
in the TfmPatient_Conslt.LoadTherapList method. What you probably want is an additional record which holds pointer to the data record:
type
TVTNodeData = record
TherapData: PTherapData;
end;
PVTNodeData = ^TVTNodeData;
and use that record as node data record:
vstRxList_Asg.NodeDataSize := SizeOf(TVTNodeData);
and node init becomes something like
procedure TfmPatient_Conslt.vstRxList_AsgInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
NodeData: PVTNodeData;
begin
NodeData := Sender.GetNodeData(Node);
while (FTherapDataList[FTherapDataListAsg_Iter]^.Selected <> 1) do
Inc(FTherapDataListAsg_Iter);
NodeData^.TherapData := FTherapDataList[FTherapDataListAsg_Iter];
Inc(FTherapDataListAsg_Iter);
end;
and using the data in other tree events like
procedure TfmPatient_Conslt.vstRxList_AsgGetText(Sender: TBaseVirtualTree; Node:PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
NodeData: PVTNodeData;
TherapData: PTherapData;
begin
NodeData := Sender.GetNodeData(Node);
if Assigned(NodeData) and Assigned(NodeData.TherapData) then begin
TherapData := NodeData.TherapData;
if (Column = 0) then
CellText := TherapData^.TherapName
else if (Column = 1) then
CellText := TherapData^.TherapInstr;
end;
end;
I have a frame with an TLMDDockPanel component as the parent, on the frame there is a TTreeView component:
unit devices;
...
Tmaster = class(TObject)
...
devTreeNode : ttreenode;
...
end;
...
end.
unit deviceTree;
...
TfrmDevTree = class(TFrame)
JvTreeView1: TTreeView;
...
end;
procedure TfrmDevTree.GetSlavesOnSelectedClick(Sender: TObject);
var
Node: TTreeNode;
begin
...
Node := self.JvTreeView1.Selected;
...
end;
...
end.
unit mainForm;
...
TfrmMain = class(TForm)
...
LMDDockSite1: TLMDDockSite;
LMDDockPanel_DevTree: TLMDDockPanel;
...
var
frmDevTree : TfrmDevTree;
...
procedure TfrmMain.FormCreate(Sender: TObject);
begin
...
frmDevTree := TfrmDevTree.Create(self);
frmDevTree.Parent := LMDDockPanel_DevTree;
...
end;
...
end.
At application start, i fill the 'Data' fields for all the nodes of JvTreeView1:
master := Tmaster.create;
Node.Data := master;
master.devtreenode := node; //I also save the treenode that is representing the master in JvTreeView1 into a master field.
The LMDDockPanel_DevTree dock panel is docked at the left of the docksite by default and there is no any problem while the dock panel sits there, but after undocking it, the obj. references for the treenodes are changing so the references stored in the masters (master.devtreenode) are no longer valid.
Can someone please explain why are the treenode references changing? How to avoid this? Should i refresh all the references stored in the masters every time i dock/undock the dock panel?
Thank You.
The reason it happens is because docking/undocking destroys and recreates the TreeView's HWND, which in turn destroys and recreates its node objects. A TreeView is designed to cache and restore the TTreeNode.Data values automatically during this recreation process, but it knows nothing about TMaster.DevTreeNode. As such, you need to detect when the nodes have been recreated so you can manually update their DevTreeNode values with the new TTreeNode pointers.
A TreeView has OnAddition and OnDeletion events that one would think would be ideal for this task. However, they are inconveniently NOT triggered during HWND recreation!
So you have two choices:
subclass the TreeView's WindowProc property to catch the recreation messages.
private
{ Private declarations }
DefTreeViewWndProc: TWndMethod;
procedure TreeViewWndProc(var Message: TMessage);
procedure TfrmDevTree.FormCreate(Sender: TObject);
begin
DefTreeViewWndProc := JvTreeView1.WindowProc;
JvTreeView1.WindowProc := TreeViewWndProc;
end;
procedure UpdateMasterDevNode(Node: TTreeNode; Destroying: Boolean);
var
Child: TTreeNode;
begin
if Node.Data <> nil then
begin
if Destroying then
TMaster(Node.Data).DevTreeNode := nil
else
TMaster(Node.Data).DevTreeNode := Node;
end;
Child := Node.getFirstChild;
while Child <> nil do
begin
UpdateMasterDevNode(Child, Destroying);
Child := Child.getNextSibling;
end;
end;
procedure UpdateMasterDevNodes(Nodes: TTreeNodes; Destroying: Boolean);
var
Node: TTreeNode;
begin
Node := Nodes.GetFirstNode;
while Node <> nil do
begin
UpdateMasterDevNode(Node, Destroying);
Node := Node.getNextSibling;
end;
end;
procedure TfrmDevTree.TreeViewWndProc(var Message: TMessage);
const
WM_UPDATEMASTERDEVNODES = WM_APP + 1;
begin
if Message.Msg = CM_RECREATEWND then
UpdateMasterDevNodes(JvTreeView1.Items, True);
DefTreeViewWndProc(Message);
if Message.Msg = WM_CREATE then
begin
// the cached nodes have not been recreated yet, so delay the DevTreeNode updates
PostMessage(TreeView1.Handle, WM_UPDATEMASTERDEVNODES, 0, 0)
end
else if Message.Msg = WM_UPDATEMASTERDEVNODES then
UpdateMasterDevNodes(JvTreeView1.Items, False);
end;
use an interceptor class to override the virtual CreateWnd() and DestroyWnd() methods.
type
TJvTreeView = class(JVCL.ListsAndTrees.Trees.TJvTreeView)
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
end;
TfrmDevTree = class(TForm)
JvTreeView1: TJvTreeView;
...
end;
procedure UpdateMasterDevNode(Node: TTreeNode; Destroying: Boolean);
var
Child: TTreeNode;
begin
if Node.Data <> nil then
begin
if Destroying then
TMaster(Node.Data).DevTreeNode := nil
else
TMaster(Node.Data).DevTreeNode := Node;
end;
Child := Node.getFirstChild;
while Child <> nil do
begin
UpdateMasterDevNode(Child, Destroying);
Child := Child.getNextSibling;
end;
end;
procedure UpdateMasterDevNodes(Nodes: TTreeNodes; Destroying: Boolean);
var
Node: TTreeNode;
begin
Node := Nodes.GetFirstNode;
while Node <> nil do
begin
UpdateMasterDevNode(Node, Destroying);
Node := Node.getNextSibling;
end;
end;
procedure TJvTreeView.CreateWnd;
begin
inherited;
UpdateMasterDevNodes(Items, False);
end;
procedure TTreeView.DestroyWnd;
begin
if csRecreating in ControlState then
UpdateMasterDevNodes(Items, True);
inherited;
end;
Either way, be sure that any code which uses TMaster.DevTreeNode checks for nil first before using the TTreeNode.
I have a heirarchy of data that is displayed in a VirtualStringTree. I use this heirarchy multiple times in my application with slight modifications to the way the tree is drawn/displayed. My method currently utilizes the AddChild() procedure for adding nodes an d as such i have multiple copies of the data when the application is run.
I would now like to consolidate these trees and have a 'master' tree which points to the actual data, but then have the 'slave' trees point to the SAME data.
I am a little unsure of if/how this can be acheived. I would think i could simply load the master tree and populate its NodeData with pointers to where the data i being held, and then for all the slave trees, simply store the same pointer in their nodedata.
However im not having much luck.
My current code looks like:
//Initialize the NodeDataSize
procedure TForm1.FormCreate(Sender: TObject);
begin
TreeMasterComponents.NodeDataSize := SizeOf(rMasterComponent);
VST.NodeDataSize := SizeOf(Pointer);
end;
Procedure to copy the master tree to slave trees
procedure TForm1.LoadSlaveTree(ATree: TVirtualStringTree);
var Node : PVirtualNode;
procedure RecursiveCopy(SrcPNode,SrcTNode : PVirtualNode; ATree : TVirtualStringTree);
var SrcNode, TargetNode : PVirtualNode;
SrcData : PMasterComponent;
begin
SrcNode := TreeMasterComponents.GetFirstChild(SrcPNode);
while Assigned(SrcNode) do
begin
SrcData := TreeMasterComponents.GetNodeData(SrcNode);
TargetNode := ATree.AddChild(SrcTNode,SrcData);
RecursiveCopy(SrcNode,TargetNode,ATree);
SrcNode := SrcNode.NextSibling;
end;
end;
begin
ATree.BeginUpdate;
ATree.Clear;
Node := TreeMasterComponents.GetFirst(true);
while Assigned(Node) do
begin
RecursiveCopy(Node,nil,ATree);
Node := Node.NextSibling;
end;
ATree.EndUpdate;
end;
Procedure for slave tree to getCellText
procedure TForm1.SlaveGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
var Data : PMasterComponent;
begin
Data := Sender.GetNodeData(Node);
Case column of
0:CellText := Data^.ComponentCode;
1:CellText := Data^.FullLocation;
end;
end;
At the moment, the nodes are added in the correct heirarchy, however there is no text appearing for the slave trees. Any help would be appreciated.
I don't know why no text appears in your slave trees but I'd like to advise the following.
Probably easier would be to create a frame with the tree and code, and reuse the frame on your forms. Each instance of the tree would simply load the same data (no copying of nodes necessary).
The slight modifications can be achieved by visual form inheritance by writing new event handlers for the specific instance of the frame/tree. You can also inherit from the frame, creating a new class, if needed.
Ok so i beleive i have found a solution:
The trick was to create a new record to store the Pointer to the original data:
type PRefMasterComponent = ^RefMasterComponent;
RefMasterComponent = packed record
PMCData : PMasterComponent;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TreeMasterComponents.NodeDataSize := SizeOf(rMasterComponent);
VST.NodeDataSize := SizeOf(RefMasterComponent);
end;
Now the Copy code looks like:
procedure TForm1.LoadTree(ATree: TVirtualStringTree);
var Node,TargetNode : PVirtualNode;
SrcData : PMasterComponent;
Data : PRefMasterComponent;
procedure RecursiveCopy(SrcPNode, TargetPNode : PVirtualNode; ATree : TVirtualStringTree);
var Node, TargetNode : PVirtualNode;
SrcData : PMasterComponent;
Data : PRefMasterComponent;
begin
Node := TreeMasterComponents.GetFirstChild(SrcPNode);
while Assigned(Node) do
begin
SrcData := TreeMasterComponents.GetNodeData(Node);
TargetNode := ATree.AddChild(TargetPNode);
Data := ATree.GetNodeData(TargetNode);
Data.PMCData := SrcData;
RecursiveCopy(Node,TargetNode,ATree);
Node := Node.NextSibling;
end;
end;
begin
ATree.BeginUpdate;
ATree.Clear;
Node := TreeMasterComponents.GetFirst(true);
while Assigned(Node) do
begin
SrcData := TreeMasterComponents.GetNodeData(Node);
TargetNode := ATree.AddChild(nil);
Data := ATree.GetNodeData(TargetNode);
Data.PMCData := SrcData;
RecursiveCopy(Node,TargetNode,ATree);
Node := Node.NextSibling;
end;
ATree.EndUpdate;
end;
and the OnGetText:
procedure TForm1.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
var Data : PRefMasterComponent;
RefData : PMasterComponent;
begin
Data := Sender.GetNodeData(Node);
RefData := Data.PMCData;
Case column of
0:CellText := RefData.ComponentCode;
1:CellText := RefData.FullLocation;
end;
end;
Now if i change data in one tree all i have to do is call VST.Invalidate and the changes are reflected in the other tree.
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