TVirtualStringTree: Check if node in rendered area [duplicate] - delphi

It's easy to check that node is visible. But I don't know how to rightly define that node is presented on screen. I can find out only so:
BottomNode := Tree.BottomNode;
Node := Tree.TopNode;
IdBottomNode := Tree.AbsoluteIndex(BottomNode);
while Tree.AbsoluteIndex(Node) <> IdBottomNode do
begin
Node := Node.NextSibling;
if not Assigned(Node) then
Break;
end;
(code without checking)
But I think it is rather rough way. May be is there more accurate way?

You may write a function like follows. The Tree parameter there specifies the virtual tree, the Node is the node for which you want to check if it's visible, and the Column optional parameter is the index of a column if you would need to determine whether the node and even column is visible in client rect:
function IsNodeVisibleInClientRect(Tree: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex = NoColumn): Boolean;
begin
Result := Tree.IsVisible[Node] and
Tree.GetDisplayRect(Node, Column, False).IntersectsWith(Tree.ClientRect);
end;
But maybe there's a more straightforward way...

Related

How to get Id values for the adjacent records in TcxGrid (TcxGridDBTableView)

I have DevExpress cxGrid and I would like to get the Ids of the adjacent records. I can use them in the following use case: user deletes focused record and I position the (grid view) cursor on one of the adjacent records (as determined but the current sorting and grouping order of the grid view). Otherwise the position of the grid cursor is poorly determined after removal of the record and after refreshing the grid view.
I have made the following attempts but they are not working - the Id values are junk:
procedure GetAdjacentRecordIds(AView: TcxGridDBTableView; ACdFieldName: string; var APrevId, AId, ANextId: Integer);
var Item: TcxCustomGridTableItem;
RecIdx: Integer;
i, RecCount: Integer;
begin
APrevId:=-1;
AId:=-1;
ANextId:=-1;
if Trim(AIdFieldName)='' then Exit;
if not Assigned(AView) then Exit;
if not Assigned(AView.DataController) then Exit;
Item:=AView.DataController.GetItemByFieldName(UpperCase(AIdFieldName));
if not Assigned(Item) then Exit;
{//First attempt, didn't work, AId was the right one, but APrevId and ANextId were junk
RecIdx:=AView.DataController.FocusedRecordIndex;
AId:=AView.DataController.Values[RecIdx, Item.Index];
APrevId:=AView.DataController.Values[RecIdx-1, Item.Index];
ANextId:=AView.DataController.Values[RecIdx+1, Item.Index];}
//Second attempt, doesn't work, all three Ids are junk
RecIdx:=-1;
RecCount:=AView.ViewData.RecordCount;
for i:=0 to RecCount-1 do begin
if AView.ViewData.Records[i].Focused then begin
RecIdx:=1;
Break;
end;
end;
if RecIdx<0 then Exit;
AId:=AView.ViewData.Records[RecIdx].Values[Item.Index];
if RecIdx>0 then
APrevId:=AView.ViewData.Records[RecIdx-1].Values[Item.Index];
if RecIdx<RecCount then
ANextId:=AView.ViewData.Records[RecIdx+1].Values[Item.Index];
end;
How can I correct this code to get the field values for adjacent records. Or maybe I should use Grid navigator and do prev/next on it, but I would like to find the values in invisible. And DataSet.DisableControls may stop the Grid navigator?
I used the DevExpress knowledgebase to find this answer. You are referencing the internal storage of the grid incorrectly. You can also use dataset.previous and dataset.next to position the record pointer in a bound grid, and simply use dataset.fieldbyname(AidFieldName).AsInteger to retrieve key values.
get-cell-value-by-column-name
procedure TForm1.Button1Click(Sender: TObject);
var
AColumn: TcxGridDBColumn;
I: Integer;
AView: TcxGridDBTableView;
v: Variant;
begin
AView := cxGrid1DBTableView1;
AColumn := TcxGridDBColumn( AView.FindItemByName('cxGrid1DBTableView1Capital'));
if Assigned(AColumn) then
for I := 0 to AView.ViewData.RowCount - 1 do
v := AView.DataController.Values[AView.ViewData.Rows[0].RecordIndex, AColumn.Index];
end;
Use of the navigation functions of DataSet is not the solution, because CxGrid can have completely different sorting order. But DataController navigation is good solution and there is no visual flickering as well. So, the solution is:
RecIdx:=AView.DataController.FocusedRecordIndex;
AId:=AView.DataController.Values[RecIdx, Item.Index];
if not AView.DataController.IsBOF then begin
AView.DataController.GotoPrev;
RecIdx:=AView.DataController.FocusedRecordIndex;
APrevId:=AView.DataController.Values[RecIdx, Item.Index];
AView.DataController.GotoNext;
end;
if not AView.DataController.IsEOF then begin
AView.DataController.GotoNext;
RecIdx:=AView.DataController.FocusedRecordIndex;
ANextId:=AView.DataController.Values[RecIdx, Item.Index];
AView.DataController.GotoPrev;
end;

how to expand only to the second level of the treeview

Ex.:
- link 1
-- link 1.1
--- link 1.1.1(only)
---- link 1.1.1.1 (not expand)
-- link 1.2
--- link 1.2.1 (only)
---- link 1.2.1.1 (not expand)
I can expand only link 1.1, link 1.2... how?
There is no build-in functionality for expanding multiple items or items on a specific level, so there is no other way then traversing through the items. Call the Expand method on all second level items. As well on all first level items, otherwise the second level items will not be shown. Its Recurse parameter should be False in order not to expand a possibly third or deeper level.
There are two ways to traverse through a TreeView's items: by Item index and by Node. Normally, operations on a TreeView's items are preferably done by Node, because the Items property's getter traverses through all Items to find a single Item with a specific index. However, TTreeNodes caches the last retrieved item, thus by incrementing the loop Index with 1, harm will be minimized.
The easy solution then becomes:
procedure ExpandTreeNodes(Nodes: TTreeNodes; Level: Integer);
var
I: Integer;
begin
Nodes.BeginUpdate;
try
for I := 0 to Nodes.Count - 1 do
if Nodes[I].Level < Level then
Nodes[I].Expand(False);
finally
Nodes.EndUpdate;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ExpandTreeNodes(TreeView1.Items, 2);
end;
Note that the Items property's getter is still called twice. Despite the caching mechanism, I think this still should be avoided:
procedure ExpandTreeNodes(Nodes: TTreeNodes; Level: Integer);
var
I: Integer;
Node: TTreeNode;
begin
Nodes.BeginUpdate;
try
for I := 0 to Nodes.Count - 1 do
begin
Node := Nodes[I];
if Node.Level < Level then
Node.Expand(False);
end;
finally
Nodes.EndUpdate;
end;
end;
But then you might as well use:
procedure ExpandTreeNodes(Nodes: TTreeNodes; Level: Integer);
var
Node: TTreeNode;
begin
Nodes.BeginUpdate;
try
Node := Nodes.GetFirstNode;
while Node <> nil do
begin
if Node.Level < Level then
Node.Expand(False);
Node := Node.GetNext;
end;
finally
Nodes.EndUpdate;
end;
end;
This still traverses áll Items. Ok, just once. But if you have a really big and/or deep tree, and you want maximum efficiency, and you do not care about readability, or you just want to experiment with a TreeView's Nodes for fun, then use:
procedure ExpandTreeNodes(Nodes: TTreeNodes; Level: Integer);
var
Node: TTreeNode;
Next: TTreeNode;
begin
if Level < 1 then
Exit;
Nodes.BeginUpdate;
try
Node := Nodes.GetFirstNode;
while Node <> nil do
begin
Node.Expand(False);
if (Node.Level < Level - 1) and Node.HasChildren then
Node := Node.GetFirstChild
else
begin
Next := Node.GetNextSibling;
if Next <> nil then
Node := Next
else
if Node.Level > 0 then
Node := Node.Parent.GetNextSibling
else
Node := Node.GetNextSibling;
end;
end;
finally
Nodes.EndUpdate;
end;
end;

How not to add duplicate into TVirtualStringTree?

I have two VirtualStringTrees, first VST has been populated with data and I want to check the second VST and add nodes that are not already in the first VST. Or I want to add those nodes from second VST that are not duplicates of first VST.
procedure Tcreatevtform.copy2tosimvt(vt: Tvirtualstringtree);
var
data: PMyRec;
simvtdata: PMyRectF;
rootnode, simvtnode: PVirtualNode;
ty: string;
begin
rootnode := vt.GetFirst; //vt is second virtualstringtree
while Assigned(rootnode) do
begin
data := vt.GetNodeData(rootnode);
ty := data^.caption;
if checksimduplicate(ty)=false then
begin
simvtnode := similarvt.AddChild(nil); //similarvt is the first virtualstringtree
simvtdata := similarvt.GetNodeData(simvtnode);
simvtdata^.caption := data^.caption;
end;
rootnode := vt.GetNext(rootnode,false);
end;
end;
function Tcreatevtform.checksimduplicate(t: string): boolean;
var
data: PMyRectf;
rootnode: PVirtualNode;
typew: string;
begin
Result := False;
rootnode := similarvt.GetFirst;
while Assigned(rootnode) do
begin
data := similarvt.GetNodeData(rootnode);
typew := data^.caption; // problem here, typew is always a constant or it is always the first
if t=typew then
begin
// node's caption of vt (letter 'a' is the first node's caption in my
// app. So this function is always false.
Result := True;
Break;
end;
similarvt.GetNext(rootnode, False);
end;
end;
I am using D7.
Finding all items that are in one list but not a second can be done easily with a list comparison algorithm. Here's the basic idea:
Sort both lists
Take 2 index variables, one for each list, and start at the start of each list
Compare the two indexed items from each list according to the sort order.
If they're equal, then the same item is in both lists. Increment both indexes.
If the first item is less than the second one, then it's not in the second list. Increment the first index.
If the second item is less than the first one, then it's not in the first list. Increment the second index.
Repeat until you reach the end of one of the lists. All remaining items in the other list are not in the first list.
You can add actions to the equals case or either the first-unique or second-unique case to determine what to do in these cases. In your specific case, you'll want to make the second-unique case add an item to the VST. It might get more complicated if you have to preserve tree structure, but that's the basic idea.

How should I implement GetLastNode for TTreeNodes?

When I need to find the first node in a TTreeView, I call TTreeNodes.GetFirstNode. However, I sometimes need to locate the last node in a tree and there is no corresponding TTreeNodes.GetLastNode function.
I don't want to use Items[Count-1] since that results in the entire tree being walked with Result := Result.GetNext. Naturally this only matters if the tree views have a lot of nodes. I fully appreciate the virtues of virtual container controls but I am not going to switch to Virtual TreeView just yet.
So far I have come up with the following:
function TTreeNodes.GetLastNode: TTreeNode;
var
Node: TTreeNode;
begin
Result := GetFirstNode;
if not Assigned(Result) then begin
exit;
end;
while True do begin
Node := Result.GetNextSibling;
if not Assigned(Node) then begin
Node := Result.GetFirstChild;
if not Assigned(Node) then begin
exit;
end;
end;
Result := Node;
end;
end;
Can anyone:
Find a flaw in my logic?
Suggest improvements?
Edit 1
I'm reluctant to keep my own cache of the nodes. I have been doing just that until recently but have discovered some hard to track very intermittent AVs which I believe must be due to my cache getting out of synch. Clearly one solution would be to get my cache synchronisation code to work correctly but I have an aversion to caches because of the hard to track bugs that arise when you get it wrong.
Although I am not a non-Exit purist, I think that when it is doable without Exit while keeping readability intact, one might prefer that option.
So here is exactly the same code, for I don't think you can get any other way (faster) to the end node, but without Exit and slightly more compact:
function TTreeNodes.GetLastNode: TTreeNode;
var
Node: TTreeNode;
begin
Node := GetFirstNode;
Result := Node;
if Result <> nil then
repeat
Result := Node;
if Node <> nil then
Node := Result.GetNextSibling;
if Node = nil then
Node := Result.GetFirstChild;
until Node = nil;
end;
The method I've used before is the maintain a TList using List.Add on the OnAddition event and List.Remove on the OnDeletion event (OnRemove?). You've access to List.Count-1 (or whatever you need) pretty much instantly then.
Post edit - I have to say that although this worked fine, I then grew up and moved to Virtual Tree View :-)
If I was to implement it, this would probably be my first draft.
function TTreeNodes.GetLastNode: TTreeNode;
var
Node: TTreeNode;
function GetLastSibling(aNode : TTreeNode) : TTreeNode;
begin
if not Assigned(aNode) then
EXIT(nil);
repeat
Result := aNode;
aNode := Result.GetNextSibling;
until not Assigned(aNode) ;
end;
begin
Node := GetFirstNode;
if not Assigned(Node) then begin
exit;
end;
repeat
Result := GetLastSibling(Node);
Node := Result.GetFirstChild;
until not Assigned(Node);
end;
I find this slightly more readable. It might be slightly slower though.
I'm unsure about whether or not this approach would be faster than items[Count-1], in some cases, it could be slower, as the TTreeNodes actually caches the last node accessed through the items property.

Delphi - Virtual String Tree Slow GetText Method At Large Amount Of Nodes

I am not yet very experienced with the TVirtualStringTree component, therefore maybe I overlooked something trivial.
My app gathers File Information into a record (FileName, Path, Size) and displays the data in a Virtual String Tree.
Now when there are lots of Nodes (200K+) I experience a heavy slow down, the whole Tree basically lags. I am aware that the memory footprint is quite large with just the record data alone, but I found out that the lag is caused by the OnGetText method of the VST.
Hereby it doesn't matter if the method reads actual data or sets the CellText to an static string (e.g. CellText := 'Test';) the slow down is significant.
If I exit OnGetText without setting CellText, it works fine - even with as much as 1,000,000 Nodes in my Tree.
Also, If I collapse the Tree (FullCollapse) hiding this way 90% of my Nodes, OnGetText behaves ok as well or at least much better.
As far as I understand it, the OnGetText is only called for actually visible On Screen Nodes, therefore I don't get why this is such an issue with large amounts of Nodes in the Tree.
Anybody has any hints for me to point me in a direction?
EDIT:
Delphi Version: D2010
VST Version: 4.8.6
My code in its simplest test form is basically as follows:
var
SkipGetText : boolean;
procedure TXForm.VSTGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
if SkipGetText then exit;
CellText := 'TEST';
// actual code commented out to reduce complications
end;
If I set CellText, it lags, if I exit, it doesn't.
Strange enough, it gets worse the further I scroll down.
Here's what's assigned as NodeData:
type
PVSData = ^Fi;
Fi = Packed Record
Name, Dir, Ext: String;
Size: Int64;
end;
procedure TXForm.AddFile( const RootFolder:string; const SR: TSearchRec );
var
FileInfo: PVSData;
FileSize: Int64;
Node: PVirtualNode;
begin
Node := VST.AddChild(nil);
INC(AllFiles);
FileInfo := VST.GetNodeData(Node);
FileInfo^.Name := SR.Name;
FileInfo^.Dir := RootFolder;
Int64Rec(FileSize).Hi := SR.FindData.nFileSizeHigh;
Int64Rec(FileSize).Lo := SR.FindData.nFileSizeLow;
FileInfo^.Size := FileSize;
end;
procedure TXForm.VSTPaintText(Sender: TBaseVirtualTree;
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
begin
if SkipPaintText then exit;
case ListView.GetNodeLevel(Node) of
0: TargetCanvas.Font.Color := Color1;
else TargetCanvas.Font.Color := Color2;
end;
end;
procedure TXForm.VSTBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
begin
case ListView.GetNodeLevel(Node) of
0: TargetCanvas.Font.Color := Color1;
else TargetCanvas.Font.Color := Color2;
end;
end;
I noticed, that expanding / collapsing and re-expanding somehow seems to improve the situation, but it's beyond me to tell why this could actually have any impact.
If any of your columns are auto-sized, then the control needs to know the widths of all the nodes' values in order to determine the maximum.
Strange, I thought it was the whole design of VST to only load cellnodes for the nodes in the active view, not the entire tree. Are you sure it isn't some other factor in the code that you don't show, like doing a fileexists or so for every node?
Problem solved. It turns out, there might have been a complication while deleting nodes. Instead of deleting all children of a parent node, only the parent node has been removed. I expected the childnodes to be removed automatically as well, but when I changed the code to first delete children then the parent node, the lagging vanished. Now I can load a million file names to the tree without lag.
You did not say which version of Delphi you are using. In versions prior to D2009, TVirtualTreeView uses the WideString string type, which is inherently slow in general as it does not have the reference-counting, copy-on-write semantics that AnsiString has, so try to minimize your string operations as much as possible. In D2009 and later, TVirtualTreeView uses the newer UnicodeString string type instead of WideString.

Resources