关于delphi:如何在编辑框中接收TAB键?

How to receive TAB key press in edit box?

我希望在用户按下Tab键时接收OnKeyPress事件。

1
2
3
4
5
6
7
8
9
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消息:

1
2
3
4
5
6
7
8
procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   WM_GETDLGCODE: Message.Result := DLGC_WANTTAB;
   else
      FOldAccountNumberWindowProc(Message);
   end;
end;

现在我收到Tab KeyPress事件(如我希望的那样),但是现在按下LeftRight光标键会导致焦点转移到制表符顺序中的上一个或下一个控件。

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

奖励阅读

我尝试执行MSDN文档所说的内容:

wParam
The virtual key, pressed by the user, that prompted Windows to
issue this notification. The handler must selectively handle these
keys. For instance, the handler might accept and process VK_RETURN but
delegate VK_TAB to the owner window. For a list of values, see
Virtual-Key Codes.

lParam A pointer to an MSG structure (or NULL if
the system is performing a query).

wParamwParam均为零。

更新二

我意识到我和这个答案有相同的错误:

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

当我实际上应该使用相同答案中其他位置列出的正确代码中的概念时:

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

这有助于解释为什么我的原始代码是错误的。将Message.Result设置为DLGC_WANTTAB是错误的:

1
2
3
4
5
6
7
8
procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   WM_GETDLGCODE: Message.Result := DLGC_WANTTAB;
   else
      FOldAccountNumberWindowProc(Message);
   end;
end;

尝试将bitwise or标志DLGC_WANTTAB插入Message.Result也是错误的,因为Message.Result尚无值:

1
2
3
4
5
6
7
8
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的Edit控件设置正确的Message.Result值。然后我可以按位组合DLGC_WANTTAB

1
2
3
4
5
6
7
8
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博客条目,并根据需要添加重点:

After asking the original control what behavior it thinks it wants, we turn on the DLGC_WANTTAB flag

所以这更好。光标键继续在Edit控件中导航文本(而不是移动焦点),并且我收到Tab键的OnKeyPress(以及OnKeyDownOnKeyUp)事件。

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

我试图开始着手手动黑客,以改变自己的注意力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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六年前指出的那样,代码已损坏:

There are many things wrong with this approach. You can spend quite a lot of time nitpicking the little details, how this code fails to set focus in a dialog box properly, how it fails to take nested dialogs into account, how it fails to handle the Shift+Tab navigation key

在我的情况下,我破坏了Shift Tab。还有谁知道呢。

所以,我的问题是:

How to receive TAB key press in edit box?

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

额外的ter不休

  • 雷蒙德·陈(Raymond Chen)提供了解决方案,但没有解释为什么它不起作用
  • MSDN:WM_GETDLGCODE消息


您可以处理CN_KEYDOWN消息:

1
2
3
4
5
6
7
8
9
procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
   case Message.Msg of
   CN_KEYDOWN:
      if TWMKey(Message).CharCode = VK_TAB then
         ....
   end;
   FOldAccountNumberWindowProc(Message);
end;

还可以在窗体级别检测按键消息,而无需将编辑归类:

1
2
3
4
5
6
7
procedure TfrmEnableVIPMode.CMDialogKey(var Message: TCMDialogKey);
begin
  if (Message.CharCode = VK_TAB) and (ActiveControl = edAccountNumber) then
    ...

  inherited;
end;

您需要首先调用上一个WndProc,以便Message.Result获取TEdit本机需要的键代码的默认值,然后将DLGC_WANTTAB标志附加到该结果,例如:

1
2
3
4
5
6
procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage);
begin
  FOldAccountNumberWindowProc(Message);
  if Message.Msg = WM_GETDLGCODE then
    Message.Result := Message.Result or DLGC_WANTTAB;
end;