Is there a way to use the KeyPreview function when working with frames?

I would like to have KeyPreview functionality in Frames, I mean that when the input (for example, one of the frame controls is selected or the mouse inside) is in the frame (which will have several panels and other controls), then the keys pressed by the user are first processed by frame.

Is there any way to do this? I did not find a property similar to KeyPreview in TFrame.

I am using the XE5 version of RAD Studio, although I mainly work with C ++ Builder.

+5
source share
3 answers

Thanks to my recent “When the ShortCut Fire Occurs” , I have developed a standalone solution for your frame.

In short: all key messages are entered into the TWinControl.CNKeyDwon active control. This method calls TWinControl.IsMenuKey , which traverses all parents when determining whether the message is ShortCut. It does this by calling its GetPopupMenu.IsShortCut method. I overridden the GetPopupMenu Frame GetPopupMenu , creating it if it isn't. Note that you can still add PopupMenu to Frame yourself. TPopupMenu subclassing TPopupMenu and overriding the IsShortCut method, the Frame KeyDown method is called, which serves as the required KeyPreview function. (I could also assign an OnKeyDdown event handler).

 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. 
+5
source

If you have only one frame in the form, you could use KeyPreview forms and send the necessary information to the frame.

If you are only sending information that you do not need in order to make any changes to the VCL source code, just make a modified TFrame class. Therefore, there is no doubt that you can break the whole VCL.

Here is a quick code example:

MainForm Code:

 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. 

Modified Frame Code:

 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. 

Using the simillar approach, you can forward other key events.

+2
source

This is doable if you want to change the VCL code.

KeyPreview is handled by the TWinControl.DoKeyDown method. As you can see from the code that has focus, it will look for its parent form and call its DoKeyDown method if KeyPreview enabled.

 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; 

To change this behavior, you need to either change the TWinControl.DoKeyDown code to scan frames, or intercept WM_KEYDOWN and WM_SYSKEYDOWN for each TWinControl descendant you want to use, and finally add the KeyPreview field to the base class.

Probably the best option would be to declare the IKeyPreview interface when scanning for parent forms / frames, if the parent implements this interface. If you do not find them, you can return to the source code. This will contain VCL code changes only for the TWinControl.DoKeyDown method, and you can easily implement the interface in frames where necessary.

Note. A Windows control that focuses on key events. Therefore, a frame could be found above the modifications only if some of its controls have focus. Regardless of whether the mouse is above the frame or not, this will not affect the functionality.

A more detailed code would look like this:

The definition of the interface that Frame should have implemented:

  IKeyPreview = interface ['{D7318B16-04FF-43BE-8E99-6BE8663827EE}'] function GetKeyPreview: boolean; property KeyPreview: boolean read GetKeyPreview; end; 

The search function for the parent frame that implements the IKeyPreview interface should be placed somewhere in the Vcl.Controls section:

 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 modification (paste into source code):

 var PreviewParent: IKeyPreview; PreviewParent := GetParentKeyPreview(Self); if PreviewParent <> nil then begin if PreviewParent.KeyPreview and TWinControl(PreviewParent).DoKeyDown(Message) then Exit; end; 
0
source

Source: https://habr.com/ru/post/1208961/


All Articles