6

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

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

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

4

3 回答 3

6

感谢我最近的“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.
于 2014-12-14T11:23:09.900 回答
2

如果您当时在表单上只有一个框架,您可以利用表单 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.

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

于 2014-12-13T22:08:39.540 回答
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_SYSKEYDOWN添加到基 Frame 类。 TWinControlKeyPreview

可能最好的选择是声明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;
于 2014-12-13T20:01:55.983 回答