问题 当TEdit没有时,TMemo如何吃逃生钥匙?


我试图阻止一个 TMemo (并且 TRichEdit控制饮食 Escape 键。

如果用户专注于 TEdit,按 Escape 当用户按下escape时,将触发表单执行表单所做的操作。如果用户专注于 TMemo,按下逃跑被吃掉了 TMemo

当然我可以做黑客:

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;

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

当然我可以做黑客:

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 甚至 接收 与之关联的keyPress事件 逃生钥匙, 当一个 TEdit 不会:

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

TEdit 和 TMemo 是相同的Windows EDIT 共同控制

为什么逃避绕过表格 的KeyPreview

如果我打开表格 KeyPreview,用户按下 Escape 而专注于一个 TEdit 框和一个按钮 Cancel 属性设置,表单关闭,并:

  • Edit1.KeyPress 事件未触发
  • Form1.KeyPress 事件未触发

如果创建了一个Action,那么 Shortcut 是 Esc, 那就不要 KeyPress 无论用户关注什么控件,都会引发事件。

TL;博士: 哪儿是 TMemo.WantEscape 属性?


5158
2018-03-21 15:10


起源

您始终可以捕获WM_KeyDown(或WM_SysKeyDown,不记得)消息。这是触发事件的第一个地方,您可以设置 Handled := True消费它。老实说,我找到了VCL OnKeyPress 事件有一个非常奇怪的行为(特别是如果你使用表单的 KeyPreview)。 - GabrielF


答案:


您观察到的行为由处理控制 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_ESCAPE 和 VK_CANCEL 都集中在一起。这意味着VCL控件必须决定是否自己处理这些键,或让表单在其中处理它们 CM_DIALOGKEY 处理程序。

如你所见 TCustomMemo.WMGetDlgCode 你可以影响那个选择 WantReturns 属性。所以,你可以说服VCL让表单处理 退出 只需设置即可 WantReturns 在备忘录上 False。但这也阻止了 输入 密钥到达备忘录并使备忘录的用户输入新行变得相当棘手。他们必须这样做 CTRL + 输入

事实上 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_WANTSPECIALKEY 和 WM_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;

13
2018-03-21 15:46



所以它真的是深刻的东西 EDIT 控制自己处理它们? - Ian Boyd
@Ian其实可能不是。让我更新。 - David Heffernan
@Ian我认为现在好多了。 - David Heffernan