TVirtualStringTree: Data changed between OnMeasureItem event and OnGetText event
Asked Answered
V

1

7

I have an array holds data will be represented on TVirtualStringTree. This array is thread-safe and lockable. And grown by another thread.

My problem is that, when VST executes OnMeasureItem event to measure height of the node, data used for measurement can change when it come to the point of printing data with OnGetText event.

I have checked the execution order of events and it is not good for my design. First It fires OnMeasureItem event for all nodes which is not initialized then it starts calling OnGetText events. I mean, suppose we have 3 nodes, events will be fired in that order:

OnMeasureItem for node 1
OnMeasureItem for node 2
OnMeasureItem for node 3
OnGetText for node 1
OnGetText for node 2
OnGetText for node 3

But I need something like this so that I can lock:

OnMeasureItem for node 1
OnGetText for node 1

OnMeasureItem for node 2
OnGetText for node 2

OnMeasureItem for node 3
OnGetText for node 3

What is the best way to maintain synchronization of data obtained between OnMeasureItem and OnGetText events?

I don't want to lock my array during all OnMeasureItem() and OnGetText() events.

Thank you.

Added onTimer:

procedure TMainForm.SyncHexLog;
begin
  HexLog.BeginUpdate;
  Try
    if (HexLog.RootNodeCount <> FirpList.ComOperationCountLagged) then
      begin
          HexLog.RootNodeCount := FirpList.ComOperationCountLagged;

          // measure for fast scrolling
          HexLog.ReInitNode(HexLog.GetLastNoInit(), True);    

          if FAutoScroll then
          begin
            HexLog.ScrollIntoView(HexLog.GetLast, False, False);
          end;
      end;
  Finally
    HexLog.EndUpdate;
  End;
end;
Venality answered 15/4, 2012 at 7:53 Comment(5)
I've deleted my last comment since I forgot you are using toVariableNodeHeight. Just one additional (maybe irrelevant) question. What version of VirtualTreeView are you using ?Beater
Latest I guess. I checked out from SVN. // Version 5.0.0Venality
Ok; now back to your problem. It will be quite hard to resolve since both methods are part of the drawing cycle of the tree. But maybe I would lock the thread safe list in OnGetText and try to fake the item height measurement by removing vsHeightMeasured from node states and call MeasureItemHeight directly from there. But this will trigger the OnMeasureItem again, so it's not so good. However MeasureItemHeight do in fact only two things. It includes the vsHeightMeasured flag to the node state, trigger the OnMeasureItem and call private SetNodeHeight which set the node's height.Beater
Well, I will add one flag into my data structure indicates that data has been changed since last measurement. I'll poll this flag inside OnGetText event, if it's true, I remove vsHeightMeasured from node states like your suggestion. Do you think it will re-trigger OnMeasureItem? If so this should work.Venality
It should, I'll post you an example of how I think to do.Beater
B
6

I would try to force the item measurement manually by removing the vsHeightMeasured from node's states with subsequent calling of the MeasureItemHeight method. It will trigger the OnMeasureItem again. The problem is the again here, because you shouldn't measure the item more than once the text of the node is changed, but still have to handle the OnMeasureItem because of that scrolling stuff.

So as you mentioned in you comment you may include your own NodeMeasured flag into your data structure and reset it when the text of the node will change (when some data in your log item are changed) and set it after you pass the OnGetText event with the forced node height measurement. Here is a pseudocode:

procedure TForm1.VirtualStringTreeGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
begin
  ThreadList.LockList;
  try
    // check if the own flag which indicates that the text is new, that
    // the data has changed since the last time you were here in OnGetText
    // is False and if so, force the node measurement to set current node
    // height and set this flag to True to remember we already did this
    if not ThreadList.Items[Node.Index].NodeMeasured then
    begin
      // fake the node measurement, remove the measured flag
      Exclude(Node.States, vsHeightMeasured);
      // this will trigger the OnMeasureItem again because of removed
      // vsHeightMeasured flag from the node's state
      VirtualStringTree.MeasureItemHeight(VirtualStringTree.Canvas, Node);
      // set the NodeMeasured flag to remember we've measured the item
      ThreadList.Items[Node.Index].NodeMeasured := True;
    end;
    // here set the node's text and unlock your thread safe list
    CellText := ThreadList[Node.Index].SomeText;
  finally
    ThreadList.UnlockList;
  end;
end;

And in your thread when the data gets changed, you have to set this NodeMeasured flag to False.

if LogHasChanged then
begin
  ThreadList.LockList;
  try
    ThreadList.Items[X].NodeMeasured := False;
    ThreadList.Items[X].SomeText := 'Something new';
  finally
    ThreadList.UnlockList;
  end;
end;
Beater answered 15/4, 2012 at 10:16 Comment(2)
I guess "Exclude(Node.States, vsHeightMeasured)" is enough. It is not required to call MeasureItemHeight(), right? It seems that my problem disappeared after removing vsHeightMeasured.Venality
If you only remove the vsHeightMeasured flag from node state, the OnMeasureItem has to be fired to setup the current node's height. This happens whenever the tree is painted though, but as you said the data (the text) of the node may change in between the OnMeasureItem and OnGetText, so you should trigger it manually when your thread list is locked to be 100% sure you are measuring the height for the same text you display.Beater

© 2022 - 2024 — McMap. All rights reserved.