If you have ever had a TMemo on a modal form with an OK and Cancel button, where the latter has its Cancel property set to true, you might have found that Esc does not cancel the dialog while the memo has got the focus.
According to David Heffernan that’s because the VCL tells it to use all keys but it then doesn’t handle Esc. David also provides a fix for this via an interposer class. While this works it means that you have to add this interposer to every form in your application.
If you are already using Andras Hausladen’s excellent VCL Fix Pack there is another option: Add David’s fix to the hook installed by InitContextMenuFix (works only for Delphi 6-2007) or add a special hook for the fix only (for later Delphi versions where the context menu bug has been fixed).
So far I have only done the first. Look for the code between the two “TMemo Esc Fix” comments
procedure TContextMenuFixWinControl.DefaultHandler(var Message); type TDefHandler = procedure(Self: TControl; var Message); begin if HandleAllocated then begin with TMessage(Message) do begin { Here was the WM_CONTEXTMENU Code that is not necessary because DefWndProc will send this message to the parent control. } { Keep the odd bahavior for grids because everybody seems to be used to it. } if (Msg = WM_CONTEXTMENU) and (Parent <> nil) and (Parent is TCustomGrid) then begin Result := Parent.Perform(Msg, WParam, LParam); if Result <> 0 then Exit; end; // Begin - TMemo Esc Fix if (Msg = WM_GETDLGCODE) and (Parent <> nil) and Self.InheritsFrom(TCustomMemo) then begin //inherited DefaultHandler(Message); TDefHandler(@TControl.DefaultHandler)(Self, Message); Result := Result and not DLGC_WANTALLKEYS; Exit; end; if (Msg = CM_WANTSPECIALKEY) and (Parent <> nil) and Self.InheritsFrom(TCustomMemo) then begin case TCMWantSpecialKey(Message).CharCode of VK_ESCAPE: begin Result := 0; Exit; end; VK_RETURN, VK_EXECUTE, VK_CANCEL: begin Result :=1; Exit; end; end; end; // End - TMemo Esc Fix case Msg of WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC: Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam); CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC: begin SetTextColor(WParam, ColorToRGB(Font.Color)); SetBkColor(WParam, ColorToRGB(Brush.Color)); Result := Brush.Handle; end; else if Msg = RM_GetObjectInstance then Result := LRESULT(Self) else Result := CallWindowProc(DefWndProc, Handle, Msg, WParam, LParam); end; if Msg = WM_SETTEXT then SendDockNotification(Msg, WParam, LParam); end; end else //inherited DefaultHandler(Message); TDefHandler(@TControl.DefaultHandler)(Self, Message); end;
What it does is basically the same as David’s interposer class:
- It handles WM_GETDLGCODE by calling the inherited message handler (in this case: The original WindowProc) and removing the DLGC_WANTALLKEYS bit from the result.
- It handles CM_WANTSPECIALKEY, checks for VK_ESCAPE for which it sets Result to 0, meaning “I’m not interested in this key.”, and VK_RETURN, VK_EXECUTE, VK_CANCEL setting Result to 1 meaning “I’m interested in these keys.”.
Andy’s code hooks TWinControl.DefaultHandler so the code above gets called for all TWinControls, but we don’t want to meddle with the Esc key handling of other controls. There was a small problem with checking whether the control is actually a Memo. “Self is TMemo” did not work because TContextMenuFixWinControl.DefaultHandler is a class method, so the compiler thinks that Self is a class rather than a class instance and didn’t want to compile this code. Changing the condition to Self.InheritsFrom(TCustomMemo) did the trick.