如何在编辑框中接收到TAB键的按下?

7

当用户按下 Tab 键时,我希望能够接收到 OnKeyPress 事件。

procedure TForm1.Edit1(Sender: TObject; var Key: Char);
begin
   case Key of
   #09:
      begin
         //Snip - Stuff i want to do
      end;
   end;
end;

我尝试对Edit框进行子类化,并处理WM_GETDLGCODE消息:

procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   WM_GETDLGCODE: Message.Result := DLGC_WANTTAB;
   else
      FOldAccountNumberWindowProc(Message);
   end;
end;

现在我可以接收Tab KeyPress事件了(正如我的希望),但按下光标键会导致焦点转移到制表顺序中的前一个或下一个控件。

接收Tab键Press事件的正确方法是什么?

额外阅读

我尝试按照MSDN文档所说的做:

wParam
用户按下的虚拟键,促使Windows发出此通知。 处理程序必须有选择地处理这些键。例如,处理程序可能接受并处理VK_RETURN,但将VK_TAB委派给所有者窗口。有关值列表,请参见虚拟键代码。

lParam指向MSG结构的指针(如果系统正在执行查询,则为NULL)。

wParamwParam都为零。

更新二

我意识到我有与此答案相同的错误:

if Message.Msg = WM_GETDLGCODE then
   Message.Result:= Message.Result or DLGC_WANTTAB
else
   if Assigned(FOldWndProc) then FOldWndProc(Message);

在同一个回答中,我应该什么时候使用正确代码中的概念:

if Assigned(FOldWndProc) then FOldWndProc(Message);
if Message.Msg = WM_GETDLGCODE then
   Message.Result:= Message.Result or DLGC_WANTTAB;

这有助于解释为什么我的原始代码是错误的。将Message.Result设置为DLGC_WANTTAB是错误的:
procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   WM_GETDLGCODE: Message.Result := DLGC_WANTTAB;
   else
      FOldAccountNumberWindowProc(Message);
   end;
end;

试图对标志DLGC_WANTTAB进行位或操作并将其合并到Message.Result中也是错误的,因为Message.Result尚未具有值:

procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   WM_GETDLGCODE: Message.Result := Message.Result or DLGC_WANTTAB;
   else
      FOldAccountNumberWindowProc(Message);
   end;
end;

我必须首先调用原始的窗口过程,以获取Windows的编辑控件设置正确的Message.Result值。 然后我可以按位组合DLGC_WANTTAB

procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
    FOldAccountNumberWindowProc(Message);

    case Message.Msg of
    WM_GETDLGCODE: Message.Result := Message.Result or DLGC_WANTTAB;
    end;
end;

根据Raymond Chen的博客文章,我们需要询问原始控件其所需的行为,并启用DLGC_WANTTAB标志。这样做更好。光标键可继续在编辑控件中导航文本(而不是转移焦点),并且我会收到Tab键的OnKeyPress(以及OnKeyDown和OnKeyUp)事件。

剩余的问题是用户按下Tab键不再转移焦点。

我尝试着手动修改焦点转移,但是遇到了困难。

procedure TfrmEnableVIPMode.edAccountNumberKeyPress(Sender: TObject; var Key: Char);
begin
   case Key of
   #09:
      begin
         //Snip - Stuff i want to do

         { 
            The DLGC_WANTTAB technique broke Windows focus change. 
            Keep throwing in hacks until it's no longer obviously broken
         }
         //Perform(CM_DialogKey, VK_TAB, 0); //doesn't work
         Self.ActiveControl := Self.FindNextControl(edAccountNumber, True, True, False);
      end;
   end;
end;

上述代码是有效的 - 如果用户按下了 Tab 键。但是该代码已经损坏,正如 Raymond Chen 六年前所指出的那样:

这种方法有很多问题。你可以花费相当长的时间来挑剔小细节,比如这个代码无法正确设置对话框中的焦点,无法考虑嵌套对话框,无法处理 Shift+Tab 导航键。

在我的情况下,我破坏了 Shift+Tab。还有谁知道其他什么。
因此,我的问题是:

如何在编辑框中接收 TAB 键按下事件?

我不想 吃掉 它们,我只想 知道 用户按下了 Tab 键。

额外交流


你可能会在这篇帖子中获得灵感。 - TLama
1
首先,你说“我想让编辑控件获取TAB键而不是移动焦点”,然后你抱怨“编辑控件获取了TAB键而不是移动焦点!”你究竟想要什么? - Raymond Chen
@RaymondChen 我没有说那个。你想的是用户1651105在我链接到的问题(https://dev59.com/uUzSa4cB1Zd3GeqPlU3s#2364305)中说的,他说:“如何在我的TEdit控件中捕获VK_TAB键而不让它失去焦点?”相反,我想在我的TEdit控件中捕获VK_TAB键。 - Ian Boyd
好的,现在我明白你只是想知道是否按下了TAB键而不干扰它。为什么不监听焦点丢失呢?这也可以捕获用户通过其他方式离开控件的情况。(对鼠标用户公平。)如果你真的只关心TAB键,那么你需要在模态循环中拦截它。(这并不是对话框设计的真正用法,所以混乱也就不足为奇了。) - Raymond Chen
1
@RaymondChen 这些是 Delphi 表单;不是实际的 Windows *"对话框"*。也许这与意外行为有关。 - Ian Boyd
显示剩余6条评论
2个回答

7
您可以处理 CN_KEYDOWN 消息:
procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   CN_KEYDOWN:
      if TWMKey(Message).CharCode = VK_TAB then
         ....
   end;
   FOldAccountNumberWindowProc(Message);
end;


同时,在不使用子类化编辑控件的情况下,也可以在窗体级别检测按键消息:

procedure TfrmEnableVIPMode.CMDialogKey(var Message: TCMDialogKey);
begin
  if (Message.CharCode = VK_TAB) and (ActiveControl = edAccountNumber) then
    ...

  inherited;
end;

5

您需要先调用前一个 WndProc,以便 Message.Result 获取 TEdit 原生想要的键码的默认值,然后将您的 DLGC_WANTTAB 标志附加到该结果中,例如:

procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
  FOldAccountNumberWindowProc(Message);
  if Message.Msg = WM_GETDLGCODE then
    Message.Result := Message.Result or DLGC_WANTTAB;
end;

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