为什么我在使用 "message" 指令时没有在我的控件中收到消息?

Why I don't receive messages in my control when I use "message" directive?

创建控件后,我必须使用 DeviceWnd:=AllocateHWnd(DeviceWindowProc); 来接收 WM_DEVICECHANGE 消息。然后...

procedure TFileList.DeviceWindowProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_DEVICECHANGE: begin
      case Message.WParam of
        DBT_DEVICEREMOVECOMPLETE, DBT_DEVICEARRIVAL:
          if PDEV_BROADCAST_HDR(Message.LParam).dbch_devicetype = DBT_DEVTYP_VOLUME then
           OnDeviceChange;
      end;
    end;
  end; 
  Message.Result:=DefWindowProc(DeviceWnd, Message.Msg, Message.WParam, Message.LParam);
end;

这很好用,但为什么我在这样做时没有收到该消息? :

TFileList = class(TCustomControl)
private
  procedure DeviceChage(var AMessage:TMessage); message WM_DEVICECHANGE;
end;

procedure TFileList.DeviceWindowProc(var Message: TMessage);
begin
  Message.Result:=DefWindowProc(DeviceWnd, Message.Msg, Message.WParam, Message.LParam);
end;

procedure TFileList.DeviceChage(var AMessage:TMessage);
begin
 case AMessage.WParam of
   DBT_DEVICEREMOVECOMPLETE, DBT_DEVICEARRIVAL:
     if PDEV_BROADCAST_HDR(AMessage.LParam).dbch_devicetype = DBT_DEVTYP_VOLUME then
       OnDeviceChange;
 end;
end;

来自 RegisterDeviceNotification 的文档:

Applications send event notifications using the BroadcastSystemMessage function. Any application with a top-level window can receive basic notifications by processing the WM_DEVICECHANGE message. Applications can use the RegisterDeviceNotification function to register to receive device notifications.

您使用 AllocateHWnd 创建的 window 是顶级 window。因此它接收广播消息。

您的自定义控件不是顶级 window。如果你想让它接收消息,你必须调用 RegisterDeviceNotification 传递它的 window 句柄。如果这样做,请务必通过在 CreateWnd 中注册并在 DestroyWnd 中注销来处理 VCL window 重新创建。

根据一般经验,AllocateHwnd 是侦听通知的首选方式。那是因为它不受 VCL window 重新创建的约束,因此不会错过通知。重新创建 VCL window 时,有 window 发送通知的机会,但您的应用程序没有 window 准备好接收。

这肯定会成为您的问题,因此您应该使用 AllocateHwnd。您可以安排您使用 AllocateHwnd 创建的 window 归您的自定义控件所有,然后您可以将通知路由到该控件的代码。

WM_DEVICECHANGE 广播到顶层 windows。您的自定义控件 window 不是顶级 window,它是控件 Parent window 的子 window。这就是为什么你的 message 处理程序没有被调用的原因 - 消息永远不会到达你的控件的 WndProc() 因此它可以被分派到 message 处理程序。

对于大多数设备通知,您可以使用 RegisterDeviceNotification()WM_DEVICECHANGE 消息发送到特定的 HWND。但是,在您的示例中,volume 更改消息不能像这样注册:

the function fails if dbch_devicetype is DBT_DEVTYP_VOLUME.

因此,要让您的自定义控件接收 WM_DEVICECHANGE 消息,它必须分配自己的顶级 window,例如使用 AllocateHwnd() 函数:

type
  // TCustomControl is meant to be used for developing **visual**
  // controls. If your custom control is not visual, you should
  // derive from `TComponent` instead...
  TFileList = class(TCustomControl)
  private
    DeviceWnd: HWND;
    procedure DeviceWindowProc(var Message: TMessage);
  public
    constructor Create(Owner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TFileList.Create(Owner: TComponent);
begin
  inherited Create(Owner);
  if not (csDesigning in ComponentState) then
    DeviceWnd := AllocateHwnd(DeviceWindowProc);
end;

destructor TFileList.Destroy;
begin
  if DeviceWnd <> 0 then
    DeallocateHwnd(DeviceWnd);
  inherited Destroy;
end;

procedure TFileList.DeviceWindowProc(var Message: TMessage);
begin
  if Message.Msg = WM_DEVICECHANGE then
  begin
    case Message.WParam of
      DBT_DEVICEREMOVECOMPLETE, DBT_DEVICEARRIVAL:
        if PDEV_BROADCAST_HDR(Message.LParam).dbch_devicetype = DBT_DEVTYP_VOLUME then
          OnDeviceChange;
    end;
  end;
  Message.Result := DefWindowProc(DeviceWnd, Message.Msg, Message.WParam, Message.LParam);
end;