如何使我的TComponent拦截ESC键并处理它?

8
在我的 TComponent 中,有一个地方我想监听按键事件并拦截 ESC 键,在我的组件中处理它,消耗/“吃掉”这个按键,这样例如所有者窗体就不会在那个阶段处理它。就像在 TDragObject 中,当你开始拖动并通过按下 ESC 键取消它时一样。

问题是 TDragObject 有一个叫做 AllocateHWnd 的方法,由其所有者窗体通过 CN_KEYDOWN 进行通知。但没有人通知我的组件。

我需要替换表单的 WindowProc 吗?如果是,那么如何正确地进行“按照书本上说”的操作呢?


为了100%的清晰:

TMyComponent = class(TComponent)

我做了一个小测试,似乎它可以工作:

TMyComponent = class(TComponent)
  private
    FOldWindowProc: TWndMethod;
    FParentForm: TCustomForm;
    procedure FormWindowProc(var Message: TMessage);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;    
end;

...

constructor TMyComponent.Create(AOwner: TComponent);
begin
  if not (AOwner is TWinControl) then
    raise Exception.Create('TMyComponent.Create: Owner must be a TWinControl');
  inherited Create(AOwner);
  // hook parent form
  FParentForm := GetParentForm(TWinControl(Owner));
  if Assigned(FParentForm) then
  begin
    FOldWindowProc := FParentForm.WindowProc;
    FParentForm.WindowProc := FormWindowProc;
  end;
end;

destructor TMyComponent.Destroy;
begin
  // unhook parent form
  if Assigned(FParentForm) then
    FParentForm.WindowProc := FOldWindowProc;
  inherited;
end;

procedure TMyComponent.FormWindowProc(var Message: TMessage);
begin
  FOldWindowProc(Message);
  if Message.Msg = CM_CHILDKEY then // CM_CHILDKEY -> CM_DIALOGKEY -> CM_DIALOGCHAR
  begin
    OutputDebugString('CM_CHILDKEY');
    if Message.WParam = VK_ESCAPE then
    begin
      Beep;
      // do my stuff...
      Message.Result := 1; // consume keystroke
    end;
  end;
end; 

我在想这是否是正确/唯一的方法。

2
@DavidHeffernan,OP说“就像TDragObject一样”,这只是一个例子,我认为OP只想要ESC键,没有更多,也没有更少。ESC是对话框键。我现在只是没有1分钟的时间去查找代码/API/Windows消息。 - Cosmin Prund
@DavidHeffernan,我在考虑使用WM_GETDLGCODE处理程序和DLGC_WANTALLKEYS;没时间测试。 - Cosmin Prund
@Cosmin 好的。假设控件需要具有输入焦点。 - David Heffernan
1
如果“有一个时间点我想要监听键盘事件”仅意味着特定的持续时间,那么您不必为组件的整个生命周期替换窗口过程。顺便说一下,还有其他方法,比如安装消息钩子(SetWindowsHookEx),或者拥有自己的消息循环...讨论“正确”的方法,我想,需要了解确切的场景。 - Sertac Akyuz
1
检查是否收到WM_CANCELMODE消息。这是Windows告诉您Esc键(或其他取消捕获/拖动的事件)必须结束的常规方式。 - mj2008
显示剩余7条评论
1个回答

4

一种方法可能是在您的组件内创建一个TApplicationEvents对象,然后使用它的OnMessage事件来查看主线程消息队列中的消息,例如按键,在VCL处理它们之前。


1
组件能实现这个吗?如果应用程序分配了OnMessage,那不会冲突吗? - David Heffernan
3
TApplicationEvents 是一个多播类,多个实例会接收相同的事件。换句话说,当队列中有一条消息时,所有分配的 TApplicationEvents.OnMessage 处理程序都可以使用它,如果每个人都使用 TApplicationEvents。但是,如果有人直接使用 TApplication.OnMessage,那么 TApplicationEvents.OnMessage 可能会失效,反之亦然。这不是一个完美的系统,但总比没有好。 - Remy Lebeau
1
多个TApplicationEvents组件的OnMessage事件将按照这些组件创建的相反顺序触发。 - NGLN
你认为这是比我之前挂钩WindowProc更好的解决方案吗? - Vlad
1
另一种选择是使用SetWindowsHookEx()来创建一个特定于线程的键盘钩子或getmessage/wndproc钩子。这比尝试挂接VCL事件更加隔离。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接