我正在研究从TCustomControl
类派生的控件,它可以获得焦点,并且内部有一些内部元素。如果用户使用光标悬停它们,则可以突出显示这些内部元素,您可以选择它们,移动它们等等。现在问题......
如果用户持有 CTRL , ALT 或 SHIFT 修饰符,我正在使用(比如说)聚焦元素执行不同的操作。我想要的是,如果用户悬停元素并保持例如 CTRL 键,则更改鼠标光标。非常简单,您只需覆盖KeyDown
和KeyUp
方法,并检查其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 键,该怎么办?对于我的控制,这不会产生任何鼠标移动或按键事件,或者我错了?那么,我的问题很明显......
如果控件没有焦点且用户没有使用鼠标移动,如何检测修改键更改?我在想这两个选项,但我希望有一些我错过的东西:
那么,您如何检测当前未聚焦的控件的修改键更改?
答案 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的答案很可能就是你的解决方案,但是如果你试图这样做而没有将它封装到控件中的限制并发现自己在这里:
您可以通过三个步骤处理此问题,如下所示。
这里的关键是:
KeyPreview
属性我用一个按钮来说明这个过程。请务必将表单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;