我想在 Frames 中有一个 KeyPreview 功能,我的意思是,当输入(例如,选择了框架的控件之一,或者鼠标在里面)在一个框架中时(它将有几个面板和其他控件) 然后用户按下的键首先由框架处理。
有没有办法做到这一点?我在 TFrame 中没有找到类似 KeyPreview 的属性。
我使用的是 RAD Studio 的 XE5 版本,尽管我主要使用 C++Builder。
我想在 Frames 中有一个 KeyPreview 功能,我的意思是,当输入(例如,选择了框架的控件之一,或者鼠标在里面)在一个框架中时(它将有几个面板和其他控件) 然后用户按下的键首先由框架处理。
有没有办法做到这一点?我在 TFrame 中没有找到类似 KeyPreview 的属性。
我使用的是 RAD Studio 的 XE5 版本,尽管我主要使用 C++Builder。
感谢我最近的“ShortCut 何时起火”的调查,我已经为您的 Frame 制定了一个独立的解决方案。
简而言之:所有关键信息都进入TWinControl.CNKeyDwon
了活动控件。该方法调用TWinControl.IsMenuKey
遍历所有父母,同时确定消息是否是快捷方式。Is 通过调用它的GetPopupMenu.IsShortCut
方法来做到这一点。GetPopupMenu
如果 Frame 不存在,我通过创建一个方法来覆盖 Frame 的方法。请注意,您仍然可以随时将 PopupMenu 添加到 Frame 中。通过继承TPopupMenu
和覆盖该IsShortCut
方法,调用 Frame 的KeyDown
方法,该方法用作您需要的 KeyPreview 功能。(我也可以分配 OnKeyDdown 事件处理程序)。
unit Unit2;
interface
uses
Winapi.Messages, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Menus,
Vcl.StdCtrls;
type
TPopupMenu = class(Vcl.Menus.TPopupMenu)
public
function IsShortCut(var Message: TWMKey): Boolean; override;
end;
TFrame2 = class(TFrame)
Label1: TLabel;
Edit1: TEdit;
private
FPreviewPopup: TPopupMenu;
protected
function GetPopupMenu: Vcl.Menus.TPopupMenu; override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
end;
implementation
{$R *.dfm}
{ TPopupMenu }
function TPopupMenu.IsShortCut(var Message: TWMKey): Boolean;
var
ShiftState: TShiftState;
begin
ShiftState := KeyDataToShiftState(Message.KeyData);
TFrame2(Owner).KeyDown(Message.CharCode, ShiftState);
Result := Message.CharCode = 0;
if not Result then
Result := inherited IsShortCut(Message);
end;
{ TFrame2 }
function TFrame2.GetPopupMenu: Vcl.Menus.TPopupMenu;
begin
Result := inherited GetPopUpMenu;
if Result = nil then
begin
if FPreviewPopup = nil then
FPreviewPopup := TPopupMenu.Create(Self);
Result := FPreviewPopup;
end;
end;
procedure TFrame2.KeyDown(var Key: Word; Shift: TShiftState);
begin
if (Key = Ord('X')) and (ssCtrl in Shift) then
begin
Label1.Caption := 'OH NO, DON''T DO THAT!';
Key := 0;
end;
end;
end.
如果您当时在表单上只有一个框架,您可以利用表单 KeyPreview 功能并将必要的信息转发到框架。
如果您只是转发信息,则不需要对原始 VCL 代码进行任何更改,只需修改 TFrame 类即可。所以不用担心你可能会破坏整个 VCL 这样做。
这是一个快速的代码示例:
主窗体代码:
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Unit3, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TForm2 = class(TForm)
Panel1: TPanel;
ModifiedFrame: TModifiedFrame;
Edit1: TEdit;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.FormCreate(Sender: TObject);
begin
//This is required since I'm asigning frames OnKeyDown event method manually
ModifiedFrame.OnKeyDown := ModifiedFrame.FrameKeyDown;
end;
procedure TForm2.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
//Forward key down information to ModifiedFrame
ModifiedFrame.DoKeyDown(Sender, Key, Shift);
if Key = 0 then
MessageDlg('Key was handled by the modified frame!',mtInformation,[mbOK],0)
else
MessageDlg('Key was not handled!',mtInformation,[mbOK],0);
end;
end.
修改帧代码:
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TModifiedFrame = class(TFrame)
Edit1: TEdit;
//Normally this method would be added by the Delphi IDE when you set the
//OnKeyDown event but here I created this manually in order to avoid crating
//design package with modified frame
procedure FrameKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
FOnKeyDown: TKeyEvent;
public
{ Public declarations }
//This is used to recieve forwarded key down information from the Form
procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
published
//Property to alow setting the OnKeyDown event at design-time
//NOTE: In order for this to work properly you have to put this modified
//frame class into separate unti and register it as new design time component
property OnKeyDown: TKeyEvent read FOnKeyDown write FOnKeyDown;
end;
implementation
{$R *.dfm}
procedure TModifiedFrame.DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
//Check to see if OnKeyDownEvent has been assigned. If it is foward the key down
//information to the event procedure
if Assigned(FOnKeyDown) then FOnKeyDown(Self, Key, Shift);
end;
procedure TModifiedFrame.FrameKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
//Do something
if Key = VK_RETURN then
begin
MessageBeep(0);
Key := 0;
end;
end;
end.
使用类似的方法,您可以转发其他关键事件。
如果您愿意更改 VCL 代码,这是可行的。
KeyPreview 在TWinControl.DoKeyDown
方法中处理。从代码中可以看出,具有焦点的控件将查找其父窗体并在打开时调用其DoKeyDown
方法。KeyPreview
function TWinControl.DoKeyDown(var Message: TWMKey): Boolean;
var
ShiftState: TShiftState;
Form, FormParent: TCustomForm;
LCharCode: Word;
begin
Result := True;
// Insert modification here
{ First give the immediate parent form a try at the Message }
Form := GetParentForm(Self, False);
if (Form <> nil) and (Form <> Self) then
begin
if Form.KeyPreview and TWinControl(Form).DoKeyDown(Message) then
Exit;
{ If that didn't work, see if that Form has a parent (ie: it is docked) }
if Form.Parent <> nil then
begin
FormParent := GetParentForm(Form);
if (FormParent <> nil) and (FormParent <> Form) and
FormParent.KeyPreview and TWinControl(FormParent).DoKeyDown(Message) then
Exit;
end;
end;
with Message do
begin
ShiftState := KeyDataToShiftState(KeyData);
if not (csNoStdEvents in ControlStyle) then
begin
LCharCode := CharCode;
KeyDown(LCharCode, ShiftState);
CharCode := LCharCode;
if LCharCode = 0 then Exit;
end;
end;
Result := False;
end;
要更改该行为,您需要更改TWinControl.DoKeyDown
代码以扫描帧或拦截您想要使用的每个后代,最后将WM_KEYDOWN
字段WM_SYSKEYDOWN
添加到基 Frame 类。 TWinControl
KeyPreview
可能最好的选择是声明IKeyPreview
接口,并在扫描父表单/框架时测试父级是否实现了该接口。如果没有找到,您可以回退到原始代码。这将只包含对TWinControl.DoKeyDown
方法的 VCL 代码更改,并且您可以在需要的地方轻松地在 Frames 中实现接口。
注意:在 Windows 上,具有焦点的控件接收键事件。所以上面的修改只有在它的一些控件有焦点的情况下才能找到框架。鼠标是否在框架上不会对功能产生任何影响。
更详细的代码如下所示:
Frame 必须实现的接口定义:
IKeyPreview = interface
['{D7318B16-04FF-43BE-8E99-6BE8663827EE}']
function GetKeyPreview: boolean;
property KeyPreview: boolean read GetKeyPreview;
end;
查找实现IKeyPreview
接口的父框架的函数,应该放在Vcl.Controls
实现部分的某个地方:
function GetParentKeyPreview(Control: TWinControl): IKeyPreview;
var
Parent: TWinControl;
begin
Result := nil;
Parent := Control.Parent;
while Assigned(Parent) do
begin
if Parent is TCustomForm then Parent := nil
else
if Supports(Parent, IKeyPreview, Result) then Parent := nil
else Parent := Parent.Parent;
end;
end;
TWinControl.DoKeyDown
修改(插入上面的原始代码):
var
PreviewParent: IKeyPreview;
PreviewParent := GetParentKeyPreview(Self);
if PreviewParent <> nil then
begin
if PreviewParent.KeyPreview and TWinControl(PreviewParent).DoKeyDown(Message) then
Exit;
end;