2014-12-13 7 views
5

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

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

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

+0

@TLama нет KeyDown событие для TFrame –

+0

я означало переопределение 'метод KeyDown', но это, кажется, не значит, работать я удалил свой комментарий. – TLama

+0

Сообщите мне, когда узнаете! http://stackoverflow.com/questions/27116331/handle-gestures-when-initiated-from-a-frame (немного другой предмет, но все же о том, как кадры «проглатывают» пользовательские события) –

ответ

5

Благодаря моему недавнему исследованию "When does a ShortCut fire", я разработал автономное решение для вашего фрейма.

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

Существует недостаток: 'TranslateMessage' еще не вызывается, поэтому все символы являются капиталом. Как это исправить? – NGLN

+0

Это выглядит красиво! Я попробую завтра и дам вам знать. Спасибо за идею! –

+0

Это решает мою потребность просто отлично, спасибо! У меня нет проблем с тем, что персонажи являются капиталом, поскольку я обрабатываю только специальные клавиши. –

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

Вероятно, лучшим вариантом было бы объявить интерфейс IKeyPreview и при сканировании родительских форм/фреймов, если родитель реализует этот интерфейс. Если их не найти, вы можете вернуться к исходному коду. Это будет содержать изменения кода VCL только до TWinControl.DoKeyDown, и вы можете легко реализовать интерфейс в Frames, где это необходимо.

Примечание: В ОС Windows, в фокусе которого присутствуют ключевые события. Поэтому выше модификаций можно было бы найти фрейм, только если некоторые из его элементов управления имеют фокус. Независимо от того, будет ли мышь над кадром или нет, это не повлияет на функциональность.

Более подробный код будет выглядеть следующим образом:

определение интерфейса, что рамка будет иметь для реализации:

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 модификация (insert in выше оригинальный код) :

var 
    PreviewParent: IKeyPreview; 

    PreviewParent := GetParentKeyPreview(Self); 
    if PreviewParent <> nil then 
    begin 
     if PreviewParent.KeyPreview and TWinControl(PreviewParent).DoKeyDown(Message) then 
     Exit; 
    end; 
+1

Спасибо за идею и подробный код, DAlija, но я боюсь, что в этом случае это не вариант для изменения библиотеки VCL. Если в Delphi я подумал бы дважды, прежде чем это сделать, с C++ Builder я просто получаю дрожь. Интересно, почему Embarcadero не добавил способ сделать это из коробки, поскольку, что я могу найти, это несколько повторяющаяся проблема. Я понимаю проблемы, которые это может вызвать, но так же можно сделать несколько других вещей. –

+0

Поскольку изменения только в части реализации, это не должно быть так проблематично, но я понимаю ваше нежелание. Я не большой поклонник изменения кода VCL. –

+2

Да, достаточно проблем, которые у меня есть с нерабочим отладчиком и сбойной IDE, а также для того, чтобы использовать несколько сторонних библиотек для работы с C++ Builder, чтобы беспокоиться о нарушении чего-то такого низкого уровня:/Спасибо в любом случае! ! Я думаю, что я уйду с уродливым путем добавления обработчика Key в Frame и вызывая его из форм, в которых используется фрейм. –

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. 

Используя подход вы предложения этого типа можно пересылать другие ключевые события.

+0

Да, у меня что-то похожее на месте, но искал что-то более элегантное :) –

+0

Более элегантный? Как вы относитесь к более элегантным? – SilverWarior

+2

Ну, что-то, что не связано с изменением каждой из форм, где используются кадры, в основном. –