How to catch WM_DEVICECHANGE in a control other than TForm?

2.8k views Asked by At

Until today I was using the following code to catch WM_DEVICECHANGE message in application main form and it worked pefectly. But if I try to use this in my custom control I don't get notifyed on device insert or remove. What is happening ?

  TDriveBar = class(TCustomPanel)
  private
    procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;  
  end;

implementation

procedure TDriveBar.WMDeviceChange(var Msg: TMessage);
const DBT_DEVICEARRIVAL = $8000;
      DBT_DEVICEREMOVECOMPLETE = $8004;
      DBT_DEVTYP_VOLUME = 2;

type PDEV_BROADCAST_HDR = ^DEV_BROADCAST_HDR;
     DEV_BROADCAST_HDR = record
      dbch_size: dword;
      dbch_devicetype: dword;
      dbch_reserved: dword;
     end;

begin
 case Msg.WParam of

  DBT_DEVICEREMOVECOMPLETE:
   if PDEV_BROADCAST_HDR(Msg.LParam)^.dbch_devicetype = DBT_DEVTYP_VOLUME then UpdateDrives;

  DBT_DEVICEARRIVAL:
   if PDEV_BROADCAST_HDR(Msg.LParam)^.dbch_devicetype = DBT_DEVTYP_VOLUME then UpdateDrives;

 end;
end;
1

There are 1 answers

4
Rob Kennedy On BEST ANSWER

The OS sends wm_DeviceChange messages to all top-level windows. The application's main form is a top-level window, but your control is not, which is why the form receives the messages and your control does not.

For arbitrary device types, you have two alternatives:

  1. Use AllocateHWnd to create a message-only top-level window that will respond to messages by calling a function associated with your control. This will give you the same basic information as the main form receives.

    Write a method for your control that matches the signature for TWndMethod, which is what AllocateHWnd requires. It might look like this:

    procedure TDriveBar.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
                UpdateDrives;
          end;
        end;
      end;
      Message.Result := DefWindowProc(FDeviceWnd, Message.Msg, Message.WParam, Message.LParam);
    end;
    

    Then use that method when you create the message window:

    FDeviceWnd := AllocateHWnd(DeviceWindowProc);
    
  2. Call RegisterDeviceNotification to tell the OS that your control's window wants to receive notifications, too. (Make sure you handle your control's CreateWnd and DestroyWnd methods so that if your control is re-created, you renew the notification registration with the control's new window handle.) This will give you more detailed information than the default wm_DeviceChange message provides, but only for the kinds of devices you specify when you register your window handle.

However, you're interested in changes to volumes. The remarks for RegisterDeviceNotification have something to say about that (emphasis added):

The DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE events are automatically broadcast to all top-level windows for port devices. Therefore, it is not necessary to call RegisterDeviceNotification for ports, and the function fails if the dbch_devicetype member is DBT_DEVTYP_PORT. Volume notifications are also broadcast to top-level windows, so the function fails if dbch_devicetype is DBT_DEVTYP_VOLUME.

That eliminates that notification registration as an option for you, so the only solution in your case is to use AllocateHWnd.