Есть ли способ иметь функциональность, подобную KeyPreview, при работе с фреймами?

Я хотел бы иметь функциональность KeyPreview внутри фреймов, я имею в виду, что когда вход (скажем, выбран один из элементов управления фрейма или мышь находится внутри) находится в фрейме (который будет иметь несколько панелей и других элементов управления) затем клавиши, нажимаемые пользователем, сначала обрабатываются фреймом.

Есть ли способ сделать это? Я не нашел свойства, похожего на KeyPreview в TFrame.

Я использую версию RAD Studio XE5, хотя в основном я работаю с C++Builder.

3 ответа

Решение

Благодаря моему недавнему расследованию "Когда срабатывает шорткут", я разработал отдельное решение для вашего фрейма.

Короче говоря: все ключевые сообщения вводятся в TWinControl.CNKeyDwon активного контроля. Этот метод вызывает TWinControl.IsMenuKey который пересекает всех родителей при определении, является ли сообщение ShortCut. Это делает, называя его GetPopupMenu.IsShortCut метод. Я переопределил рамки GetPopupMenu метод путем создания одного, если его нет. Обратите внимание, что вы все равно можете добавлять PopupMenu в Frame самостоятельно. Подклассами TPopupMenu и переопределяя IsShortCut метод фрейма 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, делая это.

Вот быстрый пример кода:

Код 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.

Модифицированный код кадра:

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 для каждого TWinControl потомок, который вы хотите использовать, и, наконец, добавить KeyPreview поле в базовый класс кадров.

Вероятно, лучшим вариантом будет объявить 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;
Другие вопросы по тегам