我试图阻止一个 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
属性?
您观察到的行为由处理控制 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_RETURN
, VK_EXECUTE
, VK_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;