It is doable, if you are willing to change VCL code.
KeyPreview is handled in TWinControl.DoKeyDown
method. As it can be seen from the code control that has focus will lookup its parent form and invoke its DoKeyDown
method if KeyPreview
is turned on.
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 that behavior, you would need to either change TWinControl.DoKeyDown
code to scan for frames too or intercept WM_KEYDOWN
and WM_SYSKEYDOWN
for every TWinControl
descendant you want to use, and finally add KeyPreview
field to base Frame class.
Probably best option would be to declare IKeyPreview
interface and when scanning for parent forms/frames test if parent implements that interface. If none found, you can fall back to original code. That would contain VCL code changes only to TWinControl.DoKeyDown
method, and you can easily implement interface in Frames where needed.
Note: On Windows control that has focus receives key events. So above modifications would be able to find frame only if some of its controls has focus. Whether or not mouse would be over the frame or not would not have any influence on functionality.
More detailed code would look like this:
Interface definition that Frame would have to implement:
IKeyPreview = interface
['{D7318B16-04FF-43BE-8E99-6BE8663827EE}']
function GetKeyPreview: boolean;
property KeyPreview: boolean read GetKeyPreview;
end;
Function for finding parent frame that implements IKeyPreview
interface, should be put somewhere in Vcl.Controls
implementation 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 (insert in above original code):
var
PreviewParent: IKeyPreview;
PreviewParent := GetParentKeyPreview(Self);
if PreviewParent <> nil then
begin
if PreviewParent.KeyPreview and TWinControl(PreviewParent).DoKeyDown(Message) then
Exit;
end;