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

746 views Asked by At

When my control is created I must use DeviceWnd:=AllocateHWnd(DeviceWindowProc); to receive WM_DEVICECHANGE message. And then...

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;

This works well, but why I don't reveive that message when I do like this ? :

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;
2

There are 2 answers

8
David Heffernan On BEST ANSWER

From the documentation of 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.

The window that you create with AllocateHWnd is a top-level window. Hence it receives the broadcast messages.

Your custom control is not a top level window. If you want it to receive messages you will have to call RegisterDeviceNotification passing its window handle. If you do that, be sure to deal with VCL window recreation by registering in CreateWnd and unregistering in DestroyWnd.

As a general rule of thumb, AllocateHwnd is the preferred way to listen for notifications. That's because it is not subject to VCL window recreation, and so cannot miss notifications. When a VCL window is being recreated then there is a window of opportunity for notifications to be sent, but your application not having a window ready to receive.

That's certainly going to be an issue in your case and so you should use AllocateHwnd. You can arrange that the window you create with AllocateHwnd is owned by your custom control, and then you can route the notification to that control's code.

3
Remy Lebeau On

WM_DEVICECHANGE is broadcasted to top-level windows. Your custom control's window is not a top-level window, it is a child window of the control's Parent window. That is why your message handler is not being called - the message is never arriving in your control's WndProc() so it can be dispatched to a message handler.

For most device notifications, you can use RegisterDeviceNotification() to have WM_DEVICECHANGE messages be sent to a specific HWND. However, in your example, volume change messages cannot be registered like that:

the function fails if dbch_devicetype is DBT_DEVTYP_VOLUME.

So, for your custom control to receive WM_DEVICECHANGE messages, it MUST allocate its own top-level window, such as with the AllocateHwnd() function:

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;