Murphy的记事本

若教眼底无别离,不信人间有白头
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

TSpeedButton在DLL中无法响应CM_MOUSEENTER,CM_MOUSELEAVE事件详解

 

  使用DELPHI进行DLL开发时,如果在DLL的Form窗体内使用了TSpeedButton控件,便能发现,在运行时TSpeedButton无法响应CM_MOUSEENTER,CM_MOUSELEAVE事件!

首先,跟踪一下VCL源码,发现这两个VCL自定义消息源于TApplication.DoMouseIdle:

function TApplication.DoMouseIdle: TControl;
var
  CaptureControl: TControl;
  P: TPoint;
begin
  GetCursorPos(P);                                                           //获取当前鼠标所在位置
  Result := FindDragTarget(P, True);                                  //获取当前位置座标的控件
  CaptureControl := GetCaptureControl;
  if FMouseControl <> Result then                                     //判断以前记录的鼠标所指向控件和当前位置的控件是否相同
  begin
    if ((FMouseControl <> nil) and (CaptureControl = nil)) or
      ((CaptureControl <> nil) and (FMouseControl = CaptureControl)) then
      FMouseControl.Perform(CM_MOUSELEAVE, 0, 0);          //发送鼠标离开消息
    FMouseControl := Result;
    if ((FMouseControl <> nil) and (CaptureControl = nil)) or
      ((CaptureControl <> nil) and (FMouseControl = CaptureControl)) then
      FMouseControl.Perform(CM_MOUSEENTER, 0, 0);          //发送鼠标进入消息
  end;
end;

  DoMouseIdle方法完成了上述消息的发送过程,而该方法由TApplication.Idle调用,Idle是TApplication.HandleMessage的消息处理循环的一部分。最终HandleMessage消息处理循环由TApplication.Run启动;

  再来看看TApplication这个类,这个最常用最基础的类在Forms单元定义,但却是在Controls单元中的InitControls方法中被创建,也就是说,只要在DLL中使用Form,TApplication就会被创建!但是这个TApplication却和主程序中的TApplication是两个东西,同时DLL中创建的TApplication对象也不会调用Run方法;

  看来,只需要在DLL中构建一个消息循环处理的流程,就可以很好的解决这个问题。

  在网络上常用的方法是直接将主程序的TApplication的Handle替换DLL中的Handle,虽然这样一来DLL中的消息可以响应了,但可想而知,被替换后主程序已经无法响应消息。因为主程序接收消息的句柄已经被替换成DLL中的Application.Handle了;

  分析其中的原因后,得出结论是:只需要在主程序的消息循环中,加上处理上述鼠标消息的机制,就能很完美的解决上述问题;

  具体的实现为:响应主程序的TApplication.OnMessage事件,在该事件中完成DLL的TApplication.DoMouseIdle处理即可,DoMouseIdle方法为私有方法,所以,在实现的示例代码中,拷贝了该方法

   示例代码点击这里: 下载