如何检测没有焦点的控件中的修改键更改?

时间:2013-10-24 15:59:11

标签: delphi winapi delphi-xe3

背景:

我正在研究从TCustomControl类派生的控件,它可以获得焦点,并且内部有一些内部元素。如果用户使用光标悬停它们,则可以突出显示这些内部元素,您可以选择它们,移动它们等等。现在问题......

问题:

如果用户持有 CTRL ALT SHIFT 修饰符,我正在使用(比如说)聚焦元素执行不同的操作。我想要的是,如果用户悬停元素并保持例如 CTRL 键,则更改鼠标光标。非常简单,您只需覆盖KeyDownKeyUp方法,并检查其Key参数是否等于VK_CONTROL。在这样的代码中:

procedure TMyCustomControl.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited;
  if Key = VK_CONTROL then
    Screen.Cursor := crSizeAll;
end;

procedure TMyCustomControl.KeyUp(var Key: Word; Shift: TShiftState);
begin
  inherited;
  if Key = VK_CONTROL then
    Screen.Cursor := crDefault;
end;

即使这不是检查 CTRL 键是否被按下并释放的最佳方法(例如由于现有的Shift状态参数),它仍然按预期工作控件有焦点,甚至可以得到,但是......

我的目标是在用户悬停控件时(或者确切地说,是其中的某个元素)更改鼠标光标并保持例如即使我的控件没有焦点, CTRL 键也是如此。可以说,所以只需覆盖MouseMove方法并在那里询问修饰符状态。这将是方式,但是......

如果用户将鼠标停留在我的控件上并按下并释放 CTRL 键,该怎么办?对于我的控制,这不会产生任何鼠标移动或按键事件,或者我错了?那么,我的问题很明显......

问题:

如果控件没有焦点且用户没有使用鼠标移动,如何检测修改键更改?我在想这两个选项,但我希望有一些我错过的东西:

  • 键盘钩 - 可靠,但看起来对我来说太过分了
  • 使用计时器定期检查修改器状态 - 我无法忍受延迟

那么,您如何检测当前未聚焦的控件的修改键更改?

4 个答案:

答案 0 :(得分:8)

如果您的控件没有聚焦,则不会触发其自己的键事件。但是,你可以做的是让你的控件在内部实例化一个私有的TApplicationEvents组件,并使用它的OnMessage事件来检测从主消息队列中检索的关键事件,然后再将它们分派到任何控件。处理。然后,您可以检查鼠标是否在您的控制之上(最好使用GetMessagePos()而不是GetCursorPos()Screen.CursorPos,以便在生成消息时获得鼠标坐标,以防万一它们被延迟了)并根据需要更新控件自己的Cursor属性(而不是Screen.Cursor属性)。

答案 1 :(得分:5)

我会为WM_SETCURSOR消息写一个消息处理程序来调用GetKeyboardState来获取键盘状态(在Delphi中你可以调用KeyboardStateToShiftState)并根据它的结果(和命中测试)用适当的光标调用SetCursor

为了处理WM_SETCURSOR,VCL中有一个示例:TCustomGrid.WMSetCursor单元中的Grids

答案 2 :(得分:3)

Remy的答案很可能就是你的解决方案,但是如果你试图这样做而没有将它封装到控件中的限制并发现自己在这里:

您可以通过三个步骤处理此问题,如下所示。

这里的关键是:

  1. 设置控件的光标,而不是屏幕的光标
  2. 使用表单的KeyPreview属性
  3. 在光标
  4. 下找到控件

    我用一个按钮来说明这个过程。请务必将表单KeyPreview设置为True

    procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    var
      myControl: TControl;
    begin
      // If they pressed CTRL while over the control
      if ssCtrl in Shift then
      begin
        myControl := ControlAtPos(ScreenToClient(Mouse.CursorPos), False, True);
        // is handles nil just fine
        if (myControl is TButton) then
        begin
          myControl.Cursor := crSizeAll;
        end;
      end;
    end;
    
    procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    var
      myControl: TControl;
    begin
      // If they released CTRL while over the control
      if not(ssCtrl in Shift) then
      begin
        myControl := ControlAtPos(ScreenToClient(Mouse.CursorPos), False, True);
        if (myControl is TButton) then
        begin
          myControl.Cursor := crDefault;
        end;
      end;
    end;
    
    procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    begin
      // If they move over the button, consider current CTRL key state
      if ssCtrl in Shift then
      begin
        Button1.Cursor := crSizeAll;
      end
      else
      begin
        Button1.Cursor := crDefault;
      end;
    end;
    

答案 3 :(得分:3)

我无法判断它是否比使用钩子更难以解决,但一种选择是使用“raw input”。如果您相应地注册了控件,它将在未激活时接收输入。样本实现决定..:

type
  TMyCustomControl = class(TCustomControl)
    ..
  protected
    ..
    procedure CreateWindowHandle(const Params: TCreateParams); override;
    procedure WMInput(var Message: TMessage); message WM_INPUT;
  ..
  end;

uses
  types;

type
  tagRAWINPUTDEVICE = record
    usUsagePage: USHORT;
    usUsage: USHORT;
    dwFlags: DWORD;
    hwndTarget: HWND;
  end;
  RAWINPUTDEVICE = tagRAWINPUTDEVICE;
  TRawInputDevice = RAWINPUTDEVICE;
  PRawInputDevice = ^TRawInputDevice;
  LPRAWINPUTDEVICE = PRawInputDevice;
  PCRAWINPUTDEVICE = PRawInputDevice;

function RegisterRawInputDevices(
  pRawInputDevices: PCRAWINPUTDEVICE;
  uiNumDevices: UINT;
  cbSize: UINT): BOOL; stdcall; external user32;

const
  GenericDesktopControls: USHORT = 01;
  Keyboard: USHORT = 06;
  RIDEV_INPUTSINK = $00000100;

procedure TMyCustomControl.CreateWindowHandle(const Params: TCreateParams);
var
  RID: TRawInputDevice;
begin
  inherited;

  RID.usUsagePage := GenericDesktopControls;
  RID.usUsage := Keyboard;
  RID.dwFlags := RIDEV_INPUTSINK;
  RID.hwndTarget := Handle;
  Win32Check(RegisterRawInputDevices(@RID, 1, SizeOf(RID)));
end;

type
  HRAWINPUT = THandle;

function GetRawInputData(
  hRawInput: HRAWINPUT;
  uiCommand: UINT;
  pData: LPVOID;
  var pcbSize: UINT;
  cbSizeHeader: UINT): UINT; stdcall; external user32;

type
  tagRAWINPUTHEADER = record
    dwType: DWORD;
    dwSize: DWORD;
    hDevice: THandle;
    wParam: WPARAM;
  end;
  RAWINPUTHEADER = tagRAWINPUTHEADER;
  TRawInputHeader = RAWINPUTHEADER;
  PRawInputHeader = ^TRawInputHeader;

  tagRAWKEYBOARD = record
    MakeCode: USHORT;
    Flags: USHORT;
    Reserved: USHORT;
    VKey: USHORT;
    Message: UINT;
    ExtraInformation: ULONG;
  end;
  RAWKEYBOARD = tagRAWKEYBOARD;
  TRawKeyboard = RAWKEYBOARD;
  PRawKeyboard = ^TRawKeyboard;
  LPRAWKEYBOARD = PRawKeyboard;

//- !!! bogus declaration below, see winuser.h for the correct one
  tagRAWINPUT = record
    header: TRawInputHeader;
    keyboard: TRawKeyboard;
  end;
//-
  RAWINPUT = tagRAWINPUT;
  TRawInput = RAWINPUT;
  PRawInput = ^TRawInput;
  LPRAWINPUT = PRawInput;

const
  RIM_INPUT = 0;
  RIM_INPUTSINK = 1;
  RID_INPUT = $10000003;
  RIM_TYPEKEYBOARD = 1;
  RI_KEY_MAKE = 0;
  RI_KEY_BREAK = 1;

procedure TMyCustomControl.WMInput(var Message: TMessage);
var
  Size: UINT;
  Data: array of Byte;
  RawKeyboard: TRawKeyboard;
begin
  if (Message.WParam and $FF) in [RIM_INPUT, RIM_INPUTSINK] then
    inherited;

  if not Focused and
      (WindowFromPoint(SmallPointToPoint(SmallPoint(GetMessagePos))) = Handle) and
      (GetRawInputData(Message.LParam, RID_INPUT, nil, Size,
      SizeOf(TRawInputHeader)) = 0) then begin
    SetLength(Data, Size);
    if (GetRawInputData(Message.LParam, RID_INPUT, Data, Size,
        SizeOf(TRawInputHeader)) <> UINT(-1)) and
        (PRawInput(Data)^.header.dwType = RIM_TYPEKEYBOARD) then begin
      RawKeyboard := PRawInput(Data)^.keyboard;

      if (RawKeyboard.VKey = VK_CONTROL) then begin
        if RawKeyboard.Flags and RI_KEY_BREAK = RI_KEY_BREAK then
          Cursor := crDefault
        else
          Cursor := crSizeAll; // will call continously until key is released
      end;
      // might opt to reset the cursor regardless of pointer position...


      if (RawKeyboard.VKey = VK_MENU) then begin
        ....
      end;

    end;

  end;
end;