首页 文章

在边缘上方/附近拖动时滚动TTreeView

提问于
浏览
8

我有一个可以有很多节点的TTreeView,当很多节点被扩展时,树会占用大量的屏幕空间 .

现在假设我想将TreeView底部附近的节点拖到顶部,我无法在物理上看到TreeView的顶部,因为我选择的节点位于底部 . 当将节点拖动到TreeView的顶部时,我希望TreeView在拖动时自动滚动,默认情况下这似乎不会发生 .

在Windows资源管理器中可以看到此行为的完美示例 . 如果您尝试拖动文件或文件夹,当您将拖动的项目(节点)悬停时,它会根据光标位置自动向上或向下滚动 .

希望有道理 .

PS,我已经知道如何拖动节点了,如果在TreeView的顶部或底部附近徘徊,我希望TreeView在拖动时与我一起滚动 .

谢谢 .

2 回答

  • 11

    这是我使用的代码 . 它适用于任何 TWinControl 后代:列表框,树视图,列表视图等 .

    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;
    

    然后使用它为控件添加 OnStartDrag 事件处理程序并按如下方式实现:

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

    这是一个替代方案,基于所选节点始终在视图中自动滚动的事实 .

    type
      TForm1 = class(TForm)
        TreeView1: TTreeView;
        TreeView2: TTreeView;
        procedure TreeViewDragOver(Sender, Source: TObject; X, Y: Integer;
          State: TDragState; var Accept: Boolean);
        procedure TreeViewEndDrag(Sender, Target: TObject; X, Y: Integer);
        procedure TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
      private
        FDragNode: TTreeNode;
        FNodeHeight: Integer;
      end;
    
    ...
    
    procedure TForm1.TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    begin
      with TTreeView(Sender) do
      begin
        FDragNode := GetNodeAt(X, Y);
        if FDragNode <> nil then
        begin
          Selected := FDragNode;
          with FDragNode.DisplayRect(False) do
            FNodeHeight := Bottom - Top;
          BeginDrag(False, Mouse.DragThreshold);
        end;
      end;
    end;
    
    procedure TForm1.TreeViewDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    var
      Pt: TPoint;
      DropNode: TTreeNode;
    begin
      Accept := Source is TTreeView;
      if Accept then
        with TTreeView(Source) do
        begin
          if Sender <> Source then
            Pt := ScreenToClient(Mouse.CursorPos)
          else
            Pt := Point(X, Y);
          if Pt.Y < FNodeHeight then
            DropNode := Selected.GetPrevVisible
          else if Pt.Y > (ClientHeight - FNodeHeight) then
            DropNode := Selected.GetNextVisible
          else
            DropNode := GetNodeAt(Pt.X, Pt.Y);
          if DropNode <> nil then
            Selected := DropNode;
        end;
    end;
    
    procedure TForm1.TreeViewEndDrag(Sender, Target: TObject; X, Y: Integer);
    var
      DropNode: TTreeNode;
    begin
      with TTreeView(Sender) do
        if Target <> nil then
        begin
          DropNode := Selected;
          DropNode := Items.Insert(DropNode, '');
          DropNode.Assign(FDragNode);
          Selected := DropNode;
          Items.Delete(FDragNode);
        end
        else
          Selected := FDragNode;
    end;
    

    您可能还希望将OnDragOver事件处理程序链接到TreeView的父级,这会导致在鼠标位于TreeView外部时滚动或丢弃 . 如果你想要滚动,但是当鼠标在TreeView之外时不要掉线,那么在OnEndDrag事件处理程序中检查 if Target = Sender .

相关问题