How to receive TAB key press in edit box?

accessibility, delphi, delphi-5, subclassing

Solution

You need to call the previous WndProc first so the Message.Result gets a default value for the key codes that `TEdit` natively wants, then append your `DLGC_WANTTAB` flag to that result, eg:

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

Problem

i want to receive `OnKeyPress` events when the user presses the Tab key. ``` procedure TForm1.Edit1(Sender: TObject; var Key: Char); begin case Key of #09: begin //Snip - Stuff i want to do end; end; end; ``` i try subclassing the `Edit` box, and handle the `WM_GETDLGCODE` message: ``` procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage); begin case Message.Msg of WM_GETDLGCODE: Message.Result := DLGC_WANTTAB; else FOldAccountNumberWindowProc(Message); end; end; ``` And i now receive Tab KeyPress events (as i hoped), but now pressing the Left or Right cursor keys causes focus to shift to the previous, or next, control in the tab order. What is the correct way to recieve Tab Key Press events? Bonus Reading i tried doing what the MSDN documentation says: 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). but `wParam` and `wParam` are both zero. Update Two i realized i have the same bug as this answer: ``` if Message.Msg = WM_GETDLGCODE then Message.Result:= Message.Result or DLGC_WANTTAB else if Assigned(FOldWndProc) then FOldWndProc(Message); ``` when i should actually use concepts from the correct code listed elsewhere in the same answer: ``` if Assigned(FOldWndProc) then FOldWndProc(Message); if Message.Msg = WM_GETDLGCODE then Message.Result:= Message.Result or DLGC_WANTTAB; ``` That helps to explain why my original code is wrong. Setting `Message.Result` to `DLGC_WANTTAB` is wrong: ``` procedure TfrmEnableVIPMode.AccountNumberWindowProc(var Message: TMessage); begin case Message.Msg of WM_GETDLGCODE: Message.Result := DLGC_WANTTAB; else FOldAccountNumberWindowProc(Message); end; end; ``` it is also wrong to try to `bitwise or` the flag `DLGC_WANTTAB` into `Message.Result`, because `Message.Result` doesn't have a value yet: ``` 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; ``` i must first call the original window procedure, to get Windows' `EDIT` control set correct values of `Message.Result`. Then i can bitwise combine `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; ``` To paraphrase the Raymond Chen blog entry, and adding emphasis as required: After asking the original control what behavior it thinks it wants, we turn on the DLGC_WANTTAB flag So this is better. Cursor keys continue to navigate text in the Edit control (rather than shifting focus), and i receive `OnKeyPress` (and `OnKeyDown` and `OnKeyUp`) events for the Tab key. The remaining problem is that the user pressing Tab no longer shifts focus. i tried to start to start manually hacking in focus changing myself: ``` 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; ``` The above code works - if the user pressed the Tab key. But the code is broken, as Raymond Chen notes six year ago: 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 In my case, i broke Shift+Tab. And who knows what else. So, my question: How to receive TAB key press in edit box? i don't want to eat them, i just want to know that the user pressed the Tab key. Bonus Chatter - Raymond Chen gives a solution, but doesn't explain why it doesn't work - MSDN: WM_GETDLGCODE message

Original source

Related problems