Search code examples
delphitreeviewscroll

Scroll TTreeView while dragging over/near the edges


I have a TTreeView that can have lots of nodes, when a lot of nodes are expanded the tree uses a lot of screen space.

Now suppose I want to drag a node that is near the bottom of the TreeView to the top, I can't physically see the top part of the TreeView because the node I am selecting is at the bottom. When dragging the node to the top of the TreeView I would like the TreeView to automatically scroll with me when dragging, by default this does not seem to happen.

A perfect example of this behaviour is seen in Windows Explorer. If you try to drag a file or folder, when you hover the dragged item (node) it automatically scrolls up or down depending on cursor position.

Hope that makes sense.

PS, I already know how to drag nodes, I want the TreeView to scroll with me when dragging if hovering near the top or bottom of the TreeView.

Thanks.


Solution

  • This is the code I use. It will work for any TWinControl descendent: list box, tree view, list view etc.

    type
      TAutoScrollTimer = class(TTimer)
      private
        FControl: TWinControl;
        FScrollCount: Integer;
        procedure InitialiseTimer;
        procedure Timer(Sender: TObject);
      public
        constructor Create(Control: TWinControl);
      end;
    
    { TAutoScrollTimer }
    
    constructor TAutoScrollTimer.Create(Control: TWinControl);
    begin
      inherited Create(Control);
      FControl := Control;
      InitialiseTimer;
    end;
    
    procedure TAutoScrollTimer.InitialiseTimer;
    begin
      FScrollCount := 0;
      Interval := 250;
      Enabled := True;
      OnTimer := Timer;
    end;
    
    procedure TAutoScrollTimer.Timer(Sender: TObject);
    
      procedure DoScroll;
      var
        WindowEdgeTolerance: Integer;
        Pos: TPoint;
      begin
        WindowEdgeTolerance := Min(25, FControl.Height div 4);
        GetCursorPos(Pos);
        Pos := FControl.ScreenToClient(Pos);
        if not InRange(Pos.X, 0, FControl.Width) then begin
          exit;
        end;
        if Pos.Y<WindowEdgeTolerance then begin
          SendMessage(FControl.Handle, WM_VSCROLL, SB_LINEUP, 0);
        end else if Pos.Y>FControl.Height-WindowEdgeTolerance then begin
          SendMessage(FControl.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
        end else begin
          InitialiseTimer;
          exit;
        end;
    
        if FScrollCount<50 then begin
          inc(FScrollCount);
          if FScrollCount mod 5=0 then begin
            //speed up the scrolling by reducing the timer interval
            Interval := MulDiv(Interval, 3, 4);
          end;
        end;
    
        if Win32MajorVersion<6 then begin
          //in XP we need to clear up transient "fluff"; results in flickering so only do it in XP where it is needed
          FControl.Invalidate;
        end;
      end;
    
    begin
      if Mouse.IsDragging then begin
        DoScroll;
      end else begin
        Free;
      end;
    end;
    

    Then to use it you add an OnStartDrag event handler for the control and implement it like this:

    procedure TMyForm.SomeControlStartDrag(Sender: TObject; var DragObject: TDragObject);
    begin
      TAutoScrollTimer.Create(Sender as TWinControl);
    end;