有没有办法在使用Frames时具有类似KeyPreview的功能?

时间:2014-12-13 19:09:03

标签: delphi c++builder vcl tframe keypreview

我想在Frames中有一个KeyPreview功能,我的意思是,当输入(例如,选择了一个框架的控件,或者鼠标在里面)是在框架中(这将有几个面板)和其他控件)然后用户按下的键首先由帧处理。

有办法做到这一点吗?我没有在TFrame中找到类似于KeyPreview的属性。

我正在使用RAD Studio的XE5版本,尽管我主要使用的是C ++ Builder。

3 个答案:

答案 0 :(得分:6)

感谢我最近的"When does a ShortCut fire"调查,我为您的框架制定了一个独立的解决方案。

简而言之:所有关键信息都输入到活动控件的TWinControl.CNKeyDwon中。该方法调用TWinControl.IsMenuKey遍历所有父级,同时确定该消息是否为ShortCut。是通过调用其GetPopupMenu.IsShortCut方法来实现的。我已经覆盖了Frame的GetPopupMenu方法,如果它不存在则创建一个方法。请注意,您始终可以自己向框架添加PopupMenu。通过继承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.

答案 1 :(得分:2)

如果您在表单上只有一个框架,那么您可以使用表单KeyPreview功能并将必要的信息转发到框架。

如果您只是转发信息,则无需对原始VCL代码进行任何更改,只需修改TFrame类即可。所以没有什么可以让你打破整个VCL。

以下是一个快速代码示例:

MainForm代码:

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.

ModifiedFrame代码:

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.

使用simillar方法,您可以转发其他关键事件。

答案 2 :(得分:0)

如果您愿意更改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_SYSKEYDOWNTWinControl ,最后将KeyPreview字段添加到基类Frame类。

可能最好的选择是声明IKeyPreview接口,并在扫描父窗体/帧时测试父级是否实现该接口。如果找不到,您可以回退到原始代码。这将仅包含VCL代码更改为TWinControl.DoKeyDown方法,您可以在需要的框架中轻松实现接口。

注意:在具有焦点的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;