我试图停止使用TMemo键的TRichEdit(以及Escape)控件。

如果用户将焦点放在TEdit上,则按Escape会触发表单执行用户按转义时表单所做的操作。如果用户将注意力集中在TMemo上,则TMemo将按下逃逸键。

我当然可以做hack:

procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
    if Key = #27 then
    begin
       //figure out how to send a key to the form
    end;
end;


但这并不理想(我必须处理转义键,而不是让表单处理它)。

我当然可以做hack:

Form1.KeyPreview := True;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
   if Key = #27 then
   begin
      //Figure out how to invoke what the form was going to do when the user presses escape
   end;
end;


但这并不理想(我必须处理转义键,而不是让表单处理它)。

因此,我们将回答问题而不是问题

相反,我们将借此机会学习一些东西。 TMemo没有收到TEdit的keyPress事件怎么办?

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
    if Key = #27 then
    begin
        //never happens
    end;
end;


TEditTMemo是相同的Windows the escape key

为什么转义绕过表单的KeyPreview

如果我打开表单的EDIT,并且用户将焦点放在KeyPreview框中的同时按下Escape,并且设置了按钮的TEdit属性,则表单将关闭并:


Cancel事件未触发
Edit1.KeyPress事件未触发


如果创建了一个动作,其Form1.KeyPressShortcut,则无论用户关注什么控件,都不会引发Esc事件。

tl; dr:KeyPress属性在哪里?

最佳答案

您观察到的行为是由WM_GETDLGCODE消息的处理控制的。对于这样的备忘录:

procedure TCustomMemo.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
  inherited;
  if FWantTabs then Message.Result := Message.Result or DLGC_WANTTAB
  else Message.Result := Message.Result and not DLGC_WANTTAB;
  if not FWantReturns then
    Message.Result := Message.Result and not DLGC_WANTALLKEYS;
end;


对于编辑控件,VCL不会对WM_GETDLGCODE实施特殊处理,而基础Windows编辑控件会对其进行处理。

在标准Win32应用程序中,Windows对话框管理器发送WM_GETDLGCODE消息。但是Delphi并不是建立在对话框管理器之上的,因此VCL负责发送WM_GETDLGCODE。它在CN_KEYDOWN处理程序中执行此操作。代码如下:

Mask := 0;
case CharCode of
  VK_TAB:
    Mask := DLGC_WANTTAB;
  VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN:
    Mask := DLGC_WANTARROWS;
  VK_RETURN, VK_EXECUTE, VK_ESCAPE, VK_CANCEL:
    Mask := DLGC_WANTALLKEYS;
end;
if (Mask <> 0) and
  (Perform(CM_WANTSPECIALKEY, CharCode, 0) = 0) and
  (Perform(WM_GETDLGCODE, 0, 0) and Mask = 0) and
  (GetParentForm(Self).Perform(CM_DIALOGKEY,
  CharCode, KeyData) <> 0) then Exit;


注意,VK_RETURNVK_EXECUTEVK_ESCAPEVK_CANCEL都集中在一起。这意味着VCL控件必须决定是自己处理这些键,还是让表单在其CM_DIALOGKEY处理程序中对其进行处理。

TCustomMemo.WMGetDlgCode可以看到,您可以通过WantReturns属性影响该选择。因此,您可以通过简单地将备忘录上的WantReturns设置为False来说服VCL让表单处理ESC。但这也使ENTER键停止进入备忘录,并使备忘录的用户输入新行变得非常棘手。他们必须使用CTRL + ENTER来完成。

实际上,WantReturns应该确实已被命名为WantReturnsAndEscapesAndExecutesAndCtrlBreaks。 VCL设计者本可以实现WantEscapes属性,但是该属性不存在。

因此,您只能自己以一种或另一种方式来处理它。就个人而言,我使用自己的派生备忘录控件来执行此操作。它覆盖KeyDown方法并执行以下操作:

procedure TMyMemo.KeyDown(var Key: Word; Shift: TShiftState);
var
  Form: TCustomForm;
  Message: TCMDialogKey;
begin
  inherited;
  if (Key=VK_ESCAPE) and (Shift*[ssShift..ssCtrl])=[]) then begin
    Form := GetParentForm(Self);
    if Assigned(Form) then begin
      // we need to dispatch this key press to the form so that it can 'press'
      // any buttons with Cancel=True
      Message.Msg := CM_DIALOGKEY;
      Message.CharCode := VK_ESCAPE;
      Message.KeyData := 0;
      Message.Result := 0;
      Form.Dispatch(Message);
    end;
  end;
end;


实现此目的的另一种方法是处理CM_WANTSPECIALKEYWM_GETDLGCODE。这是说明该技术的粗略插入器:

type
  TMemo = class(StdCtrls.TMemo)
  protected
    procedure CMWantSpecialKey(var Msg: TCMWantSpecialKey); message CM_WANTSPECIALKEY;
    procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE;
  end;

procedure TMemo.CMWantSpecialKey(var Msg: TCMWantSpecialKey);
begin
  case Msg.CharCode of
  VK_ESCAPE:
    Msg.Result := 0;
  VK_RETURN, VK_EXECUTE, VK_CANCEL:
    Msg.Result := 1;
  else
    inherited;
  end;
end;

procedure TMemo.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
  inherited;
  Msg.Result := Msg.Result and not DLGC_WANTALLKEYS;
end;

08-16 10:42