Есть ли способ иметь функциональность, подобную 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;