【问题标题】:Synchronized Scrolling Components Delphi同步滚动组件 Delphi
【发布时间】:2010-07-21 18:12:27
【问题描述】:

我正在尝试在 VCL Forms 应用程序中同步两个 TDBGrid 组件的滚动,我在拦截每个网格组件的 WndProc 时遇到了困难,而没有一些堆栈问题。我曾尝试在滚动事件下发送 WM_VSCROLL 消息,但这仍然会导致操作不正确。它需要用于单击滚动条、突出显示单元格或向上或向下鼠标按钮。整个想法是让两个彼此相邻的网格显示一种匹配的对话框。

尝试过

SendMessage( gridX.Handle, WM_VSCROLL, SB_LINEDOWN, 0 );

还有

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
Msg.Result := CallWindowProc( POldWndProc, gridX.Handle, Msg.Msg, Msg.wParam, Msg.lParam );

   if ( Msg.Msg = WM_VSCROLL ) then 
   begin
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
   end;
end;

还有

procedure TForm1.GridxCustomWndProc( var Msg: TMessage );
begin
   if ( Msg.Msg = WM_VSCROLL ) then 
   begin
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
   end;
   inherited WndProc( Msg );
end;

第一个只是临时解决方案,第二个导致无效的内存读取,第三个导致堆栈溢出。所以这些解决方案似乎都不适合我。我很想就如何完成这项任务提供一些意见!提前致谢。

更新:解决方案

  private
    [...]
    GridXWndProc, GridXSaveWndProc: Pointer;
    GridYWndProc, GridYSaveWndProc: Pointer;
    procedure GridXCustomWndProc( var Msg: TMessage );
    procedure GridYCustomWndProc( var Msg: TMessage );

procedure TForm1.FormCreate(Sender: TObject);
begin
  GridXWndProc := classes.MakeObjectInstance( GridXCustomWndProc );
  GridXSaveWndProc := Pointer( GetWindowLong( GridX.Handle, GWL_WNDPROC ) );
  SetWindowLong( GridX.Handle, GWL_WNDPROC, LongInt( GridXWndProc ) );

  GridYWndProc := classes.MakeObjectInstance( GridYCustomWndProc );
  GridYSaveWndProc := Pointer( GetWindowLong( GridY.Handle, GWL_WNDPROC ) );
  SetWindowLong( GridY.Handle, GWL_WNDPROC, LongInt( GridYWndProc ) );
end;

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
   Msg.Result := CallWindowProc( GridXSaveWndProc, GridX.Handle, Msg.Msg, Msg.WParam, Msg.LParam );
   case Msg.Msg of
      WM_KEYDOWN:
      begin
         case TWMKey( Msg ).CharCode of VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
         end;
      end;
      WM_VSCROLL:
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_HSCROLL:
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_MOUSEWHEEL:
      begin
         ActiveControl := GridY;
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      end;
      WM_DESTROY:
      begin
         SetWindowLong( GridX.Handle, GWL_WNDPROC, Longint( GridXSaveWndProc ) );
         Classes.FreeObjectInstance( GridXWndProc );
      end;
  end;
end;

procedure TForm1.GridXMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
   GridY.SetActiveRow( GridX.GetActiveRow );
end;

procedure TForm1.GridYCustomWndProc( var Msg: TMessage );
begin
   Msg.Result := CallWindowProc( GridYSaveWndProc, GridY.Handle, Msg.Msg, Msg.WParam, Msg.LParam );
   case Msg.Msg of
      WM_KEYDOWN:
      begin
         case TWMKey( Msg ).CharCode of VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
         end;
      end;
      WM_VSCROLL:
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_HSCROLL:
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_MOUSEWHEEL:
      begin
         ActiveControl := GridX;
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      end;
      WM_DESTROY:
      begin
         SetWindowLong( GridY.Handle, GWL_WNDPROC, Longint( GridYSaveWndProc ) );
         Classes.FreeObjectInstance( GridYWndProc );
      end;
   end;
end;

procedure TForm1.GridYMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
   GridX.SetActiveRow( GridY.GetActiveRow );
end;

感谢 - Sertac Akyuz 提供的解决方案。当使用网格集成到 VCL 表单应用程序中时,它们将在滚动和突出显示所选记录时相互模仿。

【问题讨论】:

    标签: delphi scroll windows-controls


    【解决方案1】:

    您可能正在为两个网格实现消息覆盖。 GridX 滚动 GridY,后者又滚动 GridX,而后者又滚动...所以。您可以通过用标志包围块来保护表面的滚动代码。

    type
      TForm1 = class(TForm)
        [..] 
      private
        FNoScrollGridX, FNoScrollGridY: Boolean;
        [..]
    
    procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
    begin
      Msg.Result := CallWindowProc(POldWndProc, gridX.Handle, Msg.Msg, Msg.wParam, Msg.lParam );
    
      if ( Msg.Msg = WM_VSCROLL ) then 
      begin
        if not FNoScrollGridX then
        begin
          FNoScrollGridX := True
          gridY.SetActiveRow( gridX.GetActiveRow );
          gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
    //      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
        end;
        FNoScrollGridX := False;
      end;
    end;
    

    GridY 的类似代码。顺便说一句,您不需要 SetScrollPos。


    编辑:
    TForm1 = class(TForm)
      [..]
    private
      GridXWndProc, GridXSaveWndProc: Pointer;
      GridYWndProc, GridYSaveWndProc: Pointer;
      procedure GridXCustomWndProc(var Msg: TMessage);
      procedure GridYCustomWndProc(var Msg: TMessage);
      [..]
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      [..]
    
      GridXWndProc := classes.MakeObjectInstance(GridXCustomWndProc);
      GridXSaveWndProc := Pointer(GetWindowLong(GridX.Handle, GWL_WNDPROC));
      SetWindowLong(GridX.Handle, GWL_WNDPROC, LongInt(GridXWndProc));
    
      GridYWndProc := classes.MakeObjectInstance(GridYCustomWndProc);
      GridYSaveWndProc := Pointer(GetWindowLong(GridY.Handle, GWL_WNDPROC));
      SetWindowLong(GridY.Handle, GWL_WNDPROC, LongInt(GridYWndProc));
    end;
    
    procedure TForm1.GridXCustomWndProc(var Msg: TMessage);
    begin
      Msg.Result := CallWindowProc(GridXSaveWndProc, GridX.Handle,
          Msg.Msg, Msg.WParam, Msg.LParam);
    
      case Msg.Msg of
        WM_KEYDOWN:
          begin
            case TWMKey(Msg).CharCode of
              VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
                GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
            end;
          end;
        WM_VSCROLL: GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
        WM_MOUSEWHEEL:
          begin
            ActiveControl := GridY;
            GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
          end;
        WM_DESTROY:
          begin
            SetWindowLong(GridX.Handle, GWL_WNDPROC, Longint(GridXSaveWndProc));
            Classes.FreeObjectInstance(GridXWndProc);
          end;
      end;
    end;
    
    procedure TForm1.GridYCustomWndProc(var Msg: TMessage);
    begin
      Msg.Result := CallWindowProc(GridYSaveWndProc, GridY.Handle,
          Msg.Msg, Msg.WParam, Msg.LParam);
    
      case Msg.Msg of
        WM_KEYDOWN:
          begin
            case TWMKey(Msg).CharCode of
              VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
                GridX.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
            end;
          end;
        WM_VSCROLL: GridX.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
        WM_MOUSEWHEEL:
          begin
            ActiveControl := GridX;
            GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
          end;
        WM_DESTROY:
          begin
            SetWindowLong(GridY.Handle, GWL_WNDPROC, Longint(GridYSaveWndProc));
            Classes.FreeObjectInstance(GridYWndProc);
          end;
      end;
    end;
    

    【讨论】:

    • 我在尝试使用inherited WndProc( Msg ) 时遇到堆栈溢出错误,而且当我有您显示的代码时,我会收到无效的内存读取和其他运行时错误。不确定这是否是完成任务的唯一方法?
    • 什么inherited WndProc?,您提供了一个替换网格的WindowProc 的代码部分。你在哪里覆盖'WndProc'?请编辑问题以显示相关代码。
    • @wfoster - 如果你忽略了SetActiveRow,会发生什么?由于它不是标准的网格方法,我不知道它是如何设置活动行的..
    • @Sertac 设置活动行仅突出显示可见行部分中的特定行。我正在使用 TwwDBGrid 与 TkbmMemTable 配对使用这两个表的 .RecNo 属性取得了不同程度的成功。
    • @wfoster - 我在答案的更新部分测试了 2 个标准 DBGrids 的代码,网格是否是 TwwDBGrids 并不重要,但首先通过省略来测试所有其他消息处理/窗口过程的东西。还要忽略SetActiveRow 用于测试目的,它可能会导致额外的滚动。如果此测试成功,您将能够确定是哪个调用导致了问题,同时将其余功能一一列出。
    【解决方案2】:

    我得到了一个部分但现在完整的解决方案(至少对于两个 TMemo)...

    我的意思是部分,因为它只监听一个 TMemo 上的变化,而不是另一个...

    我的意思是完全工作,因为它不依赖于所做的事情......

    这就像在一个备忘录上设置相同的水平滚动值一样简单...

    这与消息无关,但由于我试图通过捕获消息 WM_HSCROLL 等来获得一个可行的解决方案......我留下了代码,因为它可以工作......我稍后会尝试改进它......仅捕获 WM_PAINT 或以其他方式捕获的示例...但是现在,我将其按原样放置,因为它可以工作...而且我没有找到更好的东西...

    这是有效的代码:

    // On private section of TForm1
    Memo_OldWndProc:TWndMethod; // Just to save and call original handler
    procedure Memo_NewWndProc(var TheMessage:TMessage); // New handler
    
    // On implementation section of TForm1    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
         Memo_OldWndProc:=Memo1.WindowProc; // Save the handler
         Memo1.WindowProc:=Memo_NewWndProc; // Put the new handler, so we can do extra things
    end;
    
    procedure TForm1.Memo_NewWndProc(var TheMessage:TMessage);
    begin
         Memo_OldWndProc(TheMessage); // Let the scrollbar to move to final position
         Memo2.Perform(WM_HSCROLL
                      ,SB_THUMBPOSITION+65536*GetScrollPos(Memo1.Handle,SB_HORZ)
                      ,0
                      ); // Put the horizontal scroll of Memo2 at same position as Memo1
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
         Memo1.WindowProc:=Memo_OldWndProc; // Restore the old handler
    end;
    

    它适用于所有使滚动更改的方式...

    注意事项:

    • 我知道捕获所有消息很糟糕,但至少可以工作...
    • 这是我第一次成功尝试同步两个 TMemo 水平滚动条...
    • 所以,如果有人可以稍微改进一下(不要捕获所有消息),请 做并发布。
    • 它只使 Memo1 与 Memo2 条水平同步,但不 备忘录 2 与备忘录 1 同步
    • 向上、向下、向左、向右、鼠标滚轮等按键... 想在 Memo2 上看到它的实际效果

    我将尝试通过以下方式改进它:在 Memo2 上执行某些操作时,Memo1 滚动仍然同步...

    我认为它适用于几乎所有具有 ScrollBar 的控件,而不仅仅是 TMemo...

    【讨论】:

      【解决方案3】:

      如我所说...

      在效率、简洁代码和双向方面,这是一个更好的解决方案(不是最终解决方案)...对任何一个进行更改都会影响另一个...

      请阅读 cmets on code 以了解每个句子的含义...这很棘手...但主要思想与以前相同...将另一个 TMemo 水平滚动条设置为TMemo 用户正在操作的地方...无论用户做什么,移动鼠标并选择文本,按左、右、home、end 键,使用鼠标水平滚轮(不是所有的都有),拖动滚动条,按任意部分水平滚动条等...

      主要思想是……对象需要重新绘制,所以再把另一个对象的水平滚动条和这个相同……

      这第一部分只是为 TMemo 类添加东西,它只是创建一个新的派生类,但具有相同的类名,但仅限于声明中的单元。

      将此添加到接口部分,在您的 TForm 声明之前,这样您的 TForm 将看到这个新的 TMemo 类,而不是普通的:

      type
          TMemo=class(StdCtrls.TMemo) // Just to add things to TMemo class only for this unit
          private
             BusyUpdating:Boolean; // To avoid circular stack overflow
             SyncMemo:TMemo; // To remember the TMemo to be sync
             Old_WindowProc:TWndMethod; // To remember old handler
             procedure New_WindowProc(var Mensaje:TMessage); // The new handler
          public
             constructor Create(AOwner:TComponent);override; // The new constructor
             destructor Destroy;override; // The new destructor
          end;
      

      下一部分是该新 TMemo 类先前声明的实现。

      将此添加到您喜欢的任何地方的实施部分:

      constructor TMemo.Create(AOwner:TComponent); // The new constructor
      begin
           inherited Create(AOwner); // Call real constructor
           BusyUpdating:=False; // Initialize as not being in use, to let enter
           Old_WindowProc:=WindowProc; // Remember old handler
           WindowProc:=New_WindowProc; // Replace handler with new one
      end;
      
      destructor TMemo.Destroy; // The new destructor
      begin
           WindowProc:=Old_WindowProc; // Restore the original handler
           inherited Destroy; // Call the real destructor
      end;
      
      procedure TMemo.New_WindowProc(var Mensaje:TMessage);
      begin
           Old_WindowProc(Mensaje); // Call the real handle before doing anything
           if  BusyUpdating // To avoid circular stack overflow
             or
               (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
             or
               (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
           then Exit; // Do no more and exit the procedure
           BusyUpdating:=True; // Set that object is busy in our special action
           SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
           BusyUpdating:=False; // Set that the object is no more busy in our special action
      end;
      

      现在是最后一部分,告诉每个 TMemo 必须同步的另一个 Memo 是什么。

      在您的实施部分,为 Form1 Create 事件添加如下内容:

      procedure TForm1.FormCreate(Sender: TObject);
      begin
           Memo1.SyncMemo:=Memo2; // Tell Memo1 what TMemo must sync (Memo2)
           Memo2.SyncMemo:=Memo1; // Tell Memo2 what TMemo must sync (Memo1)
      end;
      

      请记住,我们已将 SyncMemo 成员添加到我们特殊的新 TMemo 类中,它就是为此而存在的,告诉彼此一个是另一个。

      现在对两个 TMemo jsut 进行一些配置,以使其完美运行:

      • 让两个 TMemo 滚动条都可见
      • 让两个 Tmemo 上的 WordWrap 为 false
      • 放很多文本(两者都一样)、长行和很多行

      运行它,看看两个水平滚动条是如何始终同步的......

      • 如果移动一个水平滚动条,另一个水平滚动条 移动...
      • 如果您将文本向右或向左移动,行首或行尾, 等等...,无论 SelStart 在哪里...水平 文本滚动正在同步。

      这不是最终版本的问题在于:

      • 滚动条(在我的例子中是水平的)无法隐藏...因为如果隐藏了一个,则在调用 GetScrollPos 时它会返回零,因此不会同步。

      如果有人知道如何模拟 hidden 或让 GetScrollPos 不返回零,请发表评论,这是我唯一需要为最终版本修复的问题。

      注意事项:

      • 显然,垂直滚动条也可以做到这一点...只需更改 WM_HSCROLL 到 WM_VSCROLL 和 SB_HORZ 到 SB_VERT
      • 显然可以同时对两者执行相同操作...只需复制 SyncMemo.Perform 行两次,一次复制 WM_HSCROLL 和 SB_HORZ,另一次复制 WM_VSCROLL 和 SB_VERT

      这是一个同时同步两个滚动条的 New_WindowProc 过程的示例,可能适合懒惰的人,也可能适合喜欢复制和粘贴的人:

      procedure TMemo.New_WindowProc(var Mensaje:TMessage);
      begin
           Old_WindowProc(Mensaje); // Call the real handle before doing anything
           if  BusyUpdating // To avoid circular stack overflow
             or
               (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
             or
               (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
           then Exit; // Do no more and exit the procedure
           BusyUpdating:=True; // Set that object is busy in our special action
           SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
           SyncMemo.Perform(WM_VSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_VERT),0); // Send to the other TMemo a message to set its vertical scroll as it is on this TMemo
           BusyUpdating:=False; // Set that the object is no more busy in our special action
      end;
      

      希望有人能解决隐藏一个滚动条和GetScrollPos返回零的问题!!!

      【讨论】:

        【解决方案4】:

        我找到了一个解决方案……我知道这很棘手……但至少它功能齐全……

        而不是试图隐藏水平滚动条......我让它显示在可见区域之外,所以用户看不到它......

        棘手的部分:

        • 在 TMemo 所在的位置放置一个 TPanel,并将 TMemo 放入 TPanel 中
        • 隐藏 TPanel 边框,将 BorderWith 设为 0,所有 Bevel 为 bvNone/bkNone
        • 配置 TMemo 对齐到 alTop,而不是 alClient 等...
        • 处理 TPanel.OnResize 以使 TMemo.Height 大于 TPanel.Height 与水平滚动条高度一样多(此时我使用 20 像素的常量值,但我想知道如何获得实际值)

        就是这样……完成了!!!水平滚动条不在可见区域...您可以将 TPanel 放在您想要的位置,给它您想要的大小...该水平滚动条不会被用户看到并且它不会被隐藏,因此 GetScrollPos 将正常工作... 我知道这很棘手,但功能齐全。

        这是归档的完整代码:

        在接口部分,在您的 TForm 声明之前,因此您的 TForm 将看到这个新的 TMemo 类,而不是普通的:

        type
            TMemo=class(StdCtrls.TMemo) // Just to add things to TMemo class only for this unit
            private
               BusyUpdating:Boolean; // To avoid circular stack overflow
               SyncMemo:TMemo; // To remember the TMemo to be sync
               Old_WindowProc:TWndMethod; // To remember old handler
               procedure New_WindowProc(var Mensaje:TMessage); // The new handler
            public
               constructor Create(AOwner:TComponent);override; // The new constructor
               destructor Destroy;override; // The new destructor
            end;
        

        在您喜欢的任何地方的实施部分:

        constructor TMemo.Create(AOwner:TComponent); // The new constructor
        begin
             inherited Create(AOwner); // Call real constructor
             BusyUpdating:=False; // Initialize as not being in use, to let enter
             Old_WindowProc:=WindowProc; // Remember old handler
             WindowProc:=New_WindowProc; // Replace handler with new one
        end;
        
        destructor TMemo.Destroy; // The new destructor
        begin
             WindowProc:=Old_WindowProc; // Restore the original handler
             inherited Destroy; // Call the real destructor
        end;
        
        procedure TMemo.New_WindowProc(var Mensaje:TMessage);
        begin
             Old_WindowProc(Mensaje); // Call the real handle before doing anything
             if  (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
               or
                 BusyUpdating // To avoid circular stack overflow
               or
                 (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
             then Exit; // Do no more and exit the procedure
             BusyUpdating:=True; // Set that object is busy in our special action
             SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
             BusyUpdating:=False; // Set that the object is no more busy in our special action
        end;
        

        在你喜欢的任何地方的实施部分:

        procedure TForm1.FormCreate(Sender: TObject);
        begin
             Memo1.SyncMemo:=Memo2; // Tell Memo1 what TMemo must sync (Memo2)
             Memo2.SyncMemo:=Memo1; // Tell Memo2 what TMemo must sync (Memo1)
        end;
        
        procedure TForm1.pnlMemo2Resize(Sender: TObject);
        begin
             Memo2.Height:=pnlMemo2.Height+20; // Make height enough big to cause horizontal scroll bar be out of TPanel visible area, so it will not be seen by the user
        end;
        

        就是这样,伙计们!我知道这很棘手,但功能齐全。

        请注意,我在 New_WindowProc 上更改了评估 OR 条件的顺序......这只是为了提高所有其他消息的速度,因此尽可能少地延迟所有消息处理。

        希望有一天我会知道如何用真实的(计算或读取的)TMemo 水平滚动条高度替换这样的 20。

        【讨论】:

        • 您可以使用GetSystemMetrics 获得默认滚动条高度,使用SM_CYHSCROLL 作为nIndex.. 而不是将此问题用作您的问题的便签本,你为什么不问一个新问题?
        【解决方案5】:

        感谢GetSystemMetricsSM_CYHSCROLL,但这还不够……只需要多 3 个像素……

        所以我只使用:GetSystemMetrics(SM_CYHSCROLL)+3

        注意:其中两个这样的像素可能是因为父面板的BevelWidth 的值为1,但我有BevelInnerBevelOuter 的值为bvNone,所以可能不会;但是额外的像素我不知道为什么。

        非常感谢。

        如果您愿意,可以将它们加入一个大帖子,但我认为最好不要混合它们。

        回答“Sertac Akyuz”(很抱歉在这里,但我不知道如何在您的问题旁边发布它们):

        • 我把找到的解决方案放在这里...我的意图是 不要将其用作便笺簿...我在写帖子前几秒钟就发现了解决方案
        • 我觉得还是看老帖子好,比编辑多 时间只是同一个帖子......它也不会让其他人知道确切的解决方案, 也会让他们知道如何达成这样的解决方案。
        • 我更喜欢以“教如何钓鱼,而不是给 鱼”。
        • 我没有打开一个新问题只是因为这个问题的标题正是我想要做的事情

        重要:我发现无法通过消息捕获来完美解决,因为有一种情况会导致滚动但没有消息WM_VSCROLLWM_HSCROLL(仅WM_PAINT)。 ..它与用鼠标选择文本有关...让我解释一下我是如何看待它的...从最后一条视线的末端开始,将鼠标向下移动一点,然后停止鼠标移动并让鼠标按钮按下...没有做任何事情(鼠标不移动,没有按键,没有按键,没有鼠标按钮更改等......) TMemo 正在向下滚动直到到达文本的末尾......水平滚动时也会发生同样的情况鼠标在视线的右端附近并向右移动......在相反的方向上也一样......这样的滚动不会通过消息WM_VSCROLLWM_HSCROLL,只有WM_PAINT(至少在我的电脑上)......同样的情况也发生在网格上。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多