2

I have variable height nodes. If scrolled node height is more than VST client area, calling "ScrollIntoView(GetLast, False, False)" function first time does the job perfectly and it jumps to the end of last node which is good.

But calling same function again causes that scrolling to the beginning of last node. Is this a kind of feature? I don't want this, how to disable?

I have checked ScrollIntoView function to understand the reason. With the first call R.Top is 0, so it branches to else part which yields expected result. But with the second call it finds that R.Top is negative, and does if part, which causes to scroll to beginning of the last node which is not desired.

Any suggestion?

This is OnTimer event: (500ms)
procedure TMainForm.SyncHexLog;
begin
  Try
    if (HexLog.RootNodeCount <> FirpList.ComOperationCountLagged) then
      begin
        HexLog.RootNodeCount := FirpList.ComOperationCountLagged;

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

        if FAutoScroll then
          begin
            //HexLog.ScrollToTheBottom();
            HexLog.ScrollIntoView(HexLog.GetLast(), False, False);
          end;
      end;
  Finally
  End;
end;

function TBaseVirtualTree.ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean;

// Scrolls the tree so that the given node is in the client area and returns True if the tree really has been
// scrolled (e.g. to avoid further updates) else returns False. If extened focus is enabled then the tree will also
// be horizontally scrolled if needed.
// Note: All collapsed parents of the node are expanded.

var
  R: TRect;
  Run: PVirtualNode;
  UseColumns,
  HScrollBarVisible: Boolean;
  ScrolledVertically,
  ScrolledHorizontally: Boolean;

begin
  ScrolledVertically   := False;
  ScrolledHorizontally := False;

  if Assigned(Node) and (Node <> FRoot) then
  begin
    // Make sure all parents of the node are expanded.
    Run := Node.Parent;
    while Run <> FRoot do
    begin
      if not (vsExpanded in Run.States) then
        ToggleNode(Run);
      Run := Run.Parent;
    end;
    UseColumns := FHeader.UseColumns;
    if UseColumns and FHeader.FColumns.IsValidColumn(FFocusedColumn) then
      R := GetDisplayRect(Node, FFocusedColumn, not (toGridExtensions in FOptions.FMiscOptions))
    else
      R := GetDisplayRect(Node, NoColumn, not (toGridExtensions in FOptions.FMiscOptions));

    // The returned rectangle can never be empty after the expand code above.
    // 1) scroll vertically
    if R.Top < 0 then // <==== what is the purpose of this if, I need always else part
    begin
      if Center then
        SetOffsetY(FOffsetY - R.Top + ClientHeight div 2)
      else
        SetOffsetY(FOffsetY - R.Top);
      ScrolledVertically := True;
    end
    else
      if (R.Bottom > ClientHeight) or Center then
      begin
        HScrollBarVisible := (ScrollBarOptions.ScrollBars in [ssBoth, ssHorizontal]) and
          (ScrollBarOptions.AlwaysVisible or (Integer(FRangeX) > ClientWidth));
        if Center then
          SetOffsetY(FOffsetY - R.Bottom + ClientHeight div 2)
        else
          SetOffsetY(FOffsetY - R.Bottom + ClientHeight);
        // When scrolling up and the horizontal scroll appears because of the operation
        // then we have to move up the node the horizontal scrollbar's height too
        // in order to avoid that the scroll bar hides the node which we wanted to have in view.
        if not UseColumns and not HScrollBarVisible and (Integer(FRangeX) > ClientWidth) then
          SetOffsetY(FOffsetY - GetSystemMetrics(SM_CYHSCROLL));
        ScrolledVertically := True;
      end;

    if Horizontally then
      // 2) scroll horizontally
      ScrolledHorizontally := ScrollIntoView(FFocusedColumn, Center);

  end;

  Result := ScrolledVertically or ScrolledHorizontally;
end;
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Mehmet Fide
  • 1,643
  • 1
  • 20
  • 35
  • Possible duplicate, see http://stackoverflow.com/questions/2839397/how-to-reliably-scroll-virtual-treeview-to-the-bottom – Tony Hopkinson Apr 15 '12 at 12:36
  • The purpose of the "if" branch is obvious: The ScrollIntoView function isn't designed for *only* scrolling to the last node. It's designed for scrolling to *any* node, including nodes that reside above the current viewport. – Rob Kennedy Apr 16 '12 at 14:03
  • Ok but why does it jump to a different place each time when we call it? I think it is a bug for my condition. – Mehmet Fide Apr 18 '12 at 17:47
  • You've only talked about calling it two times. You're overstating it if you say it goes to a different place *each* time you call it when it really just alternates between *two* places no matter how many times you call it. Or have you observed something different without saying so? – Rob Kennedy Apr 18 '12 at 18:01
  • Data coming at real time like a flood, during this, it looks like random jumping which causes flicker on the scroll. After analyzing the problem, I found the issue like I explained on the question. – Mehmet Fide Apr 18 '12 at 18:49
  • It should only jump to something other than the very bottom if the last node is already visible and larger than the client area. For that condition to occur, there cannot have been any additional nodes added to the tree. Why are you scrolling to the bottom when you haven't added any new nodes? – Rob Kennedy Apr 18 '12 at 19:23
  • Actually I'm not calling it more than one if there is no new entry. See SyncHexLog() function above called every half second. If RootNodeCount is different than what it should be then it tries scrolling. But still toggling. – Mehmet Fide Apr 19 '12 at 07:30

1 Answers1

1

I guess time to use new delphi features like class helpers :p I wrote something simple in my main.pas, it seems working but I'm not sure it will cover all cases.

  TBaseVirtualTreeHelper = class helper for TBaseVirtualTree
  public
    Procedure ScrollToTheBottom();
  end;

{ TBaseVirtualTreeHelper }
procedure TBaseVirtualTreeHelper.ScrollToTheBottom;
Var
  Node: PVirtualNode;
  R: TRect;

begin
  Node := Self.GetLast();

  if Assigned(Node) and (Node <> Self.FRoot) then
  begin
    R := GetDisplayRect(Node, NoColumn, True);

    if (R.Bottom > Self.ClientHeight) then
    begin
      Self.SetOffsetY(Self.FOffsetY - R.Bottom + Self.ClientHeight);
    end;
  end;
end;
Mehmet Fide
  • 1,643
  • 1
  • 20
  • 35
  • 2
    Class helpers are irrelevant here. Your function could just as easily be a standalone function. Simply use the `RootNode`, `OffsetX`, and `OffsetY` properties instead of the protected fields and methods. – Rob Kennedy Apr 16 '12 at 14:05
  • I didn't know this. Could you give an example? – Mehmet Fide Apr 18 '12 at 17:44
  • I understood your point. Beginning of the idea was very similar to ScrollIntoView() function, so it looked like a good idea to use class helper because of private fields used. After simplification, it seems all fields and functions are available in public scope which I didn't check previously at all. – Mehmet Fide Apr 18 '12 at 18:41
  • This code works! Thanks :) Without class helper: `R:= VST.GetDisplayRect(Node, -1, False); if (R.Bottom > VT2.ClientHeight) then VST.OffsetY := VST.OffsetY - R.Bottom + VST.ClientHeight;` – SzakiLaci Jul 27 '21 at 13:29