delphic++buildervcltframekeypreview

Is there a way to have a KeyPreview-like functionality when working with Frames?


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

Is there a way to do this? I haven't found a property similar to KeyPreview in TFrame.

I'm using version XE5 of RAD Studio, altough I mostly work with C++Builder.


Solution

  • Thanks to my recent "When does a ShortCut fire"-investigation, I have worked out a stand alone solution for your Frame.

    In short: all key messages enter in TWinControl.CNKeyDwon of the active control. That method calls TWinControl.IsMenuKey which traverses all parents while determining whether the message is a ShortCut. Is does so by calling its GetPopupMenu.IsShortCut method. I have overridden the Frame's GetPopupMenu method by creating one if it is not present. Note that at all time you still can add a PopupMenu to the Frame yourself. By subclassing TPopupMenu and overriding the IsShortCut method, the Frame's KeyDown method is called, which serves as the KeyPreview functionality you require. (I could also have assigned the 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.