14

背景:

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

问题:

如果用户持有或修饰符CTRL,我将使用(假设)聚焦元素执行不同的操作。如果用户悬停元素并按住例如键,我想要更改鼠标光标。很简单,你只需覆盖and方法并检查它们的参数是否等于. 在这样的代码中:ALTSHIFTCTRLKeyDownKeyUpKeyVK_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

4 回答 4

8

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

于 2013-10-24T17:03:22.953 回答
5

我会编写一个消息处理程序WM_SETCURSOR来调用消息GetKeyboardState以获取键盘状态(在 Delphi 中,您可以只调用KeyboardStateToShiftState)并基于该(和命中测试)调用的结果,SetCursor使用适当的光标。

对于处理WM_SETCURSOR,VCL 中有一个示例:TCustomGrid.WMSetCursorGrids单元中。

于 2013-10-24T18:59:05.150 回答
3

雷米的答案可能是您的解决方案,但如果您尝试在不受将其封装到控件中的限制的情况下执行此操作并发现自己在这里:

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

这里的关键是:

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

我用一个按钮来说明这个过程。请务必将您的表单设置KeyPreviewTrue.

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;
于 2013-10-24T16:44:57.437 回答
3

我不知道它是否会比使用钩子更小,但一种选择是使用“原始输入”。如果您相应地注册您的控件,它也会在不活动时接收输入。示例实现来决定..:

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;
于 2013-10-27T02:57:04.727 回答