2014-12-13 6 views
5

Ich hätte gerne eine KeyPreview Funktionalität innerhalb von Frames, ich meine, dass wenn die Eingabe (sagen wir, eines der Steuerelemente des Frames ausgewählt ist, oder die Maus drin ist) in einem Frame ist (das hätte mehrere Panels und andere Steuerelemente) werden dann die vom Benutzer gedrückten Tasten zuerst von dem Rahmen verarbeitet.Gibt es eine Möglichkeit, eine KeyPreview-ähnliche Funktionalität beim Arbeiten mit Frames zu haben?

Gibt es eine Möglichkeit, dies zu tun? Ich habe keine ähnliche Eigenschaft wie KeyPreview in TFrame gefunden.

Ich benutze Version XE5 von RAD Studio, obwohl ich meistens mit C++ Builder arbeite.

+0

@TLama gibt es kein Ereignis KeyDown für das TFrame –

+0

ich zwingende 'KeyDown' Methode gemeint, aber das scheint nicht damit zu arbeiten ich meinen Kommentar gelöscht haben. – TLama

+0

Lassen Sie es mich wissen, wenn Sie es herausfinden! http://stackoverflow.com/questions/27116331/handle-gestures-when-initiated-from-a-frame (etwas anderes Thema, aber immer noch darüber, wie Frames "Benutzerereignisse" schlucken) –

Antwort

5

Dank meiner aktuellen "When does a ShortCut fire" -Untersuchung habe ich eine Standalone-Lösung für Ihren Frame ausgearbeitet.

Kurz gesagt: Alle wichtigen Meldungen geben Sie in TWinControl.CNKeyDwon der aktiven Steuerung ein. Diese Methode ruft TWinControl.IsMenuKey auf, die alle Eltern durchquert, während ermittelt wird, ob die Nachricht ein ShortCut ist. Dies geschieht durch Aufruf seiner GetPopupMenu.IsShortCut Methode. Ich habe die GetPopupMenu Methode des Rahmens außer Kraft gesetzt, indem ich eins erstelle, wenn es nicht anwesend ist. Beachten Sie, dass Sie immer noch selbst ein PopupMenu zum Frame hinzufügen können. Durch Unterklassen TPopupMenu und Überschreiben der Methode IsShortCut wird die Methode KeyDown des Frames aufgerufen, die als erforderliche KeyPreview-Funktion dient. (Ich hätte auch den Ereignishandler OnKeyDdown zuweisen können).

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

Es gibt einen Nachteil: 'TranslateMessage' wird noch nicht aufgerufen, daher sind alle Zeichen groß. Wie repariere ich das? – NGLN

+0

Das sieht gut aus! Ich werde es morgen versuchen und dich wissen lassen. Danke für die Idee! –

+0

Das löst mein Bedürfnis ganz gut, danke! Ich habe kein Problem damit, dass die Charaktere Kapital sind, da ich nur spezielle Schlüssel behandle. –

0

Es ist machbar, wenn Sie VCL-Code ändern möchten.

KeyPreview wird in TWinControl.DoKeyDown Methode behandelt. Wie aus der Codekontrolle hervorgeht, die den Fokus hat, sucht sie nach ihrer übergeordneten Form und ruft ihre Methode DoKeyDown auf, wenn aktiviert ist.

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; 

Um dieses Verhalten zu ändern, würden Sie müssen entweder TWinControl.DoKeyDown Code ändern für Rahmen zu oder abfangen WM_KEYDOWN und WM_SYSKEYDOWN für jeden TWinControl Nachkomme Sie verwenden möchten, scannen und schließlich KeyPreview Feld Basis Frame-Klasse hinzufügen.

Wahrscheinlich beste Option wäre, IKeyPreview Schnittstelle zu deklarieren und beim Scannen für übergeordnete Formulare/Frames testen, ob Eltern diese Schnittstelle implementiert. Wenn keiner gefunden wird, können Sie auf den ursprünglichen Code zurückgreifen. Das würde VCL-Code-Änderungen nur in TWinControl.DoKeyDown Methode enthalten, und Sie können einfach Schnittstelle in Frames wo erforderlich implementieren.

Hinweis: Auf Windows-Steuerelement, das Fokus hat, erhält wichtige Ereignisse. Daher können die obigen Änderungen nur dann Frames finden, wenn einige ihrer Steuerelemente den Fokus haben. Ob die Maus über dem Frame ist oder nicht, hätte keinen Einfluss auf die Funktionalität.

Detailliertere Code würde wie folgt aussehen:

Schnittstellendefinition, dass Rahmen würde umsetzen müssen:

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

Funktion für geordneten Rahmen zu finden, die IKeyPreview-Schnittstelle implementiert, sollte irgendwo in Vcl.Controls Implementationsabschnitt setzen :

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 Änderung (in den obigen ursprünglichen Code einfügen) :

var 
    PreviewParent: IKeyPreview; 

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

Danke für die Idee und den detaillierten Code, DAlija, aber ich fürchte, es ist in diesem Fall keine Option, die VCL-Bibliothek zu modifizieren. Wenn ich in Delphi zweimal darüber nachdenke, bekomme ich mit C++ Builder nur Gänsehaut. Ich wundere mich, warum Embarcadero keine Möglichkeit hinzugefügt hat, dies out-of-the-box zu tun, denn was ich finden kann, ist ein etwas wiederkehrendes Problem. Ich verstehe die Probleme, die es verursachen könnte, aber auch andere Dinge, die Sie tun können. –

+0

Da Modifikationen nur in Umsetzung sind, sollte das nicht so problematisch sein, aber ich verstehe Ihre Zurückhaltung. Ich bin auch kein großer Fan von VCL-Code zu ändern. –

+2

Ja, genug Probleme habe ich mit einem nicht funktionierenden Debugger und Absturz IDE und müssen Änderungen an mehreren Drittanbieter-Bibliotheken für sie mit C++ Builder zu arbeiten, um sich über etwas so niedrigen Niveau zu brechen zu halten:/Danke trotzdem! ! Ich denke, ich werde den hässlichen Weg gehen, einen Key-Handler zum Frame hinzuzufügen und ihn von den Formularen aus aufzurufen, in denen der Frame verwendet wird. –

2

Wenn Sie nur an der Zeit einen Rahmen auf dem Formular haben Sie die Verwendung von Formen KeyPreview Fähigkeit und leiten die notwendigen Informationen an den Rahmen machen könnten.

Wenn Sie nur die Informationen weiterleiten, die Sie nicht benötigen, um den ursprünglichen VCL-Code zu ändern, nehmen Sie einfach eine modifizierte TFrame-Klasse vor. Es gibt also kein Wort, dass Sie die ganze VCL kaputt machen könnten.

Hier ist ein kurzes Codebeispiel:

Maincode:

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 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. 

simillar Ansatz Sie andere wichtige Ereignisse weiterleiten können.

+0

Ja, ich habe etwas ähnliches an Ort und Stelle, aber war auf der Suche nach etwas eleganter :) –

+0

Eleganter? Wie meinst du das eleganter? – SilverWarior

+2

Nun, etwas, das nicht die einzelnen Formen, in denen die Frames verwendet werden, geändert hat. –

Verwandte Themen