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.