6

I have learned how to use TVirtualStringTree, and I've found it excellent. I have one custom non-visual list called PackedList which is filled by another thread. And I want to show all list content in TVirtualStringTree at realtime. So I put one timer on the mainform to update HexLog's (which is TVirtualStringTree) RootNodeCount every 500ms.

All my data appears on the VirtualStringTree, and I don't have any speed problem, very nice. But there is one problem with Vertical Scroll bar. When I press Ctrl+End on the control in order to jump to the end of the list, it goes somewhere on the middle. Similarly when I drag scroll bar to the end, it doesn't go to the end. But HexLog knows the DataCount. Why doesn't it jump to the end? If I press couple of times to Ctrl+END then it reaches to the end.

Inside timer routine, I want to say HexLog to jump to the end of the list by code. How can I do this and How to handle vertical scroll bar properly?

procedure TMainForm.StatusUpdateTimerTimer(Sender: TObject);
begin
   if (FirpList.ComOperationCount > 0) and (PacketList.Items.Count <> FirpList.ComOperationCount) then
    begin
      HexLog.RootNodeCount := PacketList.Items.Count;
    end;
end;

procedure TMainForm.HexLogMeasureItem(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);
begin
  if Sender.MultiLine[Node] then
  begin
    TargetCanvas.Font := Sender.Font;
    NodeHeight := HexLog.ComputeNodeHeight(TargetCanvas, Node, 1, FirpList.ComOperations[Node^.Parent^.Index].DataAsHexString(FAppSettings.HexLogColumnCharWidth) + #13#10);
  end;
end;

Appearance of HexLog

Suggested reply by TLama is not working properly, see the image for explanation: TLama solution is not working

See that link for detailed image explanation: http://i43.tinypic.com/1445thi.png

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Mehmet Fide
  • 1,643
  • 1
  • 20
  • 35

1 Answers1

6

To jump to the end of the tree, call ScrollIntoView(GetLast).

To scroll to a particular node, the control needs to add up the heights of all the prior nodes so it can determine the proper offset.

Your nodes have varying heights. If you're not initializing the actual heights of the nodes somewhere, then the control uses the DefaultNodeHeight property for any uninitialized nodes. It looks like that height is shorter than any actual node height in your tree, so the control ends up calculating an offset that is smaller than expected, and scrolls there instead of where you intended.

Make sure you're handling the OnMeasureItem event, and that you have the toVariableNodeHeight option set in Options.MiscOptions. If you don't, then the control will just use the currently assigned height for each node, and use the default height for any uninitialized node.

You could get the behavior you report here if you manually assign NodeHeight instead of setting toVariableNodeHeight and handling OnMeasureItem.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • Hi Rob, I have handler on MeasureItem to calculate MultiLine child Height. toVariableNodeHeight was not set on MiscOptions. I made it true and now ScrollIntoView() jumps to the end properly. But It slightly scrolls down and takes around 2 seconds to reach to the end which is not acceptable :/ – Mehmet Fide Apr 06 '12 at 14:53
  • The major thing that would take time to calculate the offset of the last node, besides calculating the heights of all the new nodes, would be if the node-position cache weren't valid. Set a breakpoint in `GetDisplayRect` and see whether you're in the `tsUseCache` state when you call `ScrollIntoView`. When the cache is valid, the tree can find the locations of nodes much faster, but changing the number of nodes invalidates the cache. The worker thread needs time to re-validate it. – Rob Kennedy Apr 06 '12 at 15:34
  • (tsUseCache in FStates) condition in GetDisplayRect() function returns false when I call ScrollIntoView(). What should I do now? For this project, I was using RichEdit control, because of speed problems that I faced, I changed all design to use VirtualStringTree. Now again I'm at the same point :/ Actually I'm facing difficulties to understand why I need to calculate height of whole messages to jump to the end of list. I simply want to show last messages to the user every 500ms while data transaction is active. – Mehmet Fide Apr 06 '12 at 16:37
  • You need the height of all the nodes because the control needs to know what offset to display. If it doesn't know what offset it's at, then it can't display the scroll bar correctly or scroll up properly. Use a profiler to figure out which parts of the scrolling operation are taking the most time, and then see whether you can reduce the time spent in those parts. – Rob Kennedy Apr 06 '12 at 16:41
  • I did this. Without OnMeasureItem handler, ScrollIntoView() takes 720ms. With OnMeasureItem handler shown above it takes 1799ms. Is there a way to force VirtualStringTree to calculate node height without scrolling so that all nodes have tsUseCache in their states? – Mehmet Fide Apr 06 '12 at 16:47
  • Good progress. Keep investigating. Even without variable heights, you're still not within your time goal. `tsUseCache` isn't a node state. It's a tree state. It's achieved when the worker thread, triggered by `ValidateCache`, finishes calling `DoValidateCache`. Maybe you could call the latter yourself. Maybe you could make measuring take less time, too. Instead of re-calculating the height, just use the previous value of `NodeHeight`. You only have to recalculate if `NodeHeight <> DefaultNodeHeight` and `HexLogColumnCharWith` (sic; not *width*?) hasn't changed. – Rob Kennedy Apr 06 '12 at 17:06
  • I need a way to jump to the end of the list without setting toVariableNodeHeight in MiscOptions. Without toVariableNodeHeight, it takes only 1ms to jump. But final position is not exact, it jumps somewhere near to the end. Why does it take less time without toVariableNodeHeight? – Mehmet Fide Apr 06 '12 at 17:46
  • I put a very big number like 5000 to DefaultNodeHeight and now it seems it jumps properly to the end without toVariableNodeHeight. What's going on? – Mehmet Fide Apr 06 '12 at 17:55
  • So it jumps properly to the end. But does it scroll properly throughout the middle, too, and is the scroll thumb the right size? Probably not. When variable node heights are disabled, the control can quickly go to any node because it just multiplies `DefaultNodeHeight * ` to get the desired offset. – Rob Kennedy Apr 06 '12 at 18:19
  • This is my point, estimation done using DefaultNodeHeight(which is very big number)*TotalNodeCount is totally invalid but it jumps to the end properly. that means VirtualTree actually doesn't need to know exact position to jump to the the end? But if user want to scroll to somewhere other than the end then it can do all calculations above. I'll try more tomorrow. Thanks. – Mehmet Fide Apr 06 '12 at 18:36
  • It "jumps to the end" by calculating an incorrect offset for the bottom node, and then painting that node at the bottom of the viewport. That incorrect offset is used to calculate the scroll-thumb size, and the scrollbar range is used to calculate relative offsets when you scroll elsewhere in the control. You should see lots of blank spaces or other graphical anomalies as you scroll elsewhere in the control. – Rob Kennedy Apr 06 '12 at 18:43
  • Hi Rob, I have optimized OnMeasureItem event handler a little. Instead of ComputeNodeHeight() function which uses canvas for measurement, I have implemented my own function. It saved 800ms. I have one more question. You know that calling "ScrollIntoView(HexLog.GetLast, False)" function for the first time, it takes time to measure all nodes etc.. But second call is very quick. Is there a way to keep VST as cached without scrolling? Each time when I update RootNodeCount, I want VST to do all calculations required for scrolling, so that user will not wait for first scroll. Is it possible? – Mehmet Fide Apr 08 '12 at 17:08
  • I've tried calling both protected methods, but they didn't help: TVirtualStringTreeHack(HexLog).ValidateCache; TVirtualStringTreeHack(HexLog).DoValidateCache; – Mehmet Fide Apr 09 '12 at 03:54