Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi DLL-Fenster erhält nicht alle Keyboard-Events?! (https://www.delphipraxis.net/34067-dll-fenster-erhaelt-nicht-alle-keyboard-events.html)

chrschn 16. Nov 2004 23:18


DLL-Fenster erhält nicht alle Keyboard-Events?!
 
Hi all.

Ich habe folgendes Problem: Ich habe eine Anwendung geschrieben, die sozusagen Plugin-DLLs verwendet. Diese DLLs haben auch einen Konfigurationsform. Da ich aber mit meiner Anwendung nicht die Borland-MM-DLL weitergeben möchte, verwende ich nur einfache Datentypen, Pointer, PChar usw.

Damit das Konfigurationsform im Haupt-Konfigurationsform der EXE angezeigt wird, zeige ich das jeweilige DLL-Fenster in einem TPanel auf meinem Hauptfenster an. Die DLLs exportieren alle in etwa folgende Funktion:

Delphi-Quellcode:
procedure ShowConfigDlg(const ID: Integer; const ParentWindow: THandle;
  const Left: Integer; const Top: Integer; const Width: Integer;
  const Height: Integer);
begin
  if not Assigned(ConfigForm) then
    begin
      ConfigForm := TConfigForm.Create(NIL);
      ConfigForm.ParentWindow := ParentWindow;
    end;
   
  ConfigForm.Left := Left;
  ConfigForm.Top := Top;
  ConfigForm.Width := Width;
  ConfigForm.Height := Height;
  ConfigForm.Visible := true;
end;
Die DLLs werden dann in einem TTreeView im Hauptfenster hierarchisch dargestellt. Im Hauptfenster rufe ich die Funktionen dann wie folgt auf:

Delphi-Quellcode:
type
  TShowConfigDlgProc = procedure(const ID: Integer; const ParentWindow: THandle;
                         const Left: Integer; const Top: Integer;
                         const Width: Integer; const Height: Integer);
var
  ShowCfgDlg: TShowConfigDlgProc;
  Handle: THandle;
begin
  Handle := LoadLibrary(PChar(FileName));
  @ShowCfgDlg := GetProcAddress(fHandle, 'ShowConfigDlg');
  ShowConfigDlg(ConfigPanel.Handle, 0, 0, ConfigPanel.ClientWidth, ConfigPanel.ClientHeight)
end;
Das klappt auch alles ganz gut. Allerdings erhalten die Komponenten wie TEdit in dem DLL-Fenster nicht alle Tastatur-Ereignisse. Genauer: Ich habe den Eindruck, sie erhalten die Ereignisse, aber so, als ob die ALT-Taste dabei gedrückt würde. Ich kann in die Edit-Felder Zahlen und die meisten Buchstaben eingeben (nicht alle!), allerdings kann ich mich nicht mit den Pfeiltasten links oder rechts im Text bewegen. :gruebel:

Wenn ich 'a' tippe, passiert gar nix, wenn ich 'c' tippe, wird das Haupt-Konfigurationsfenster der EXE geschlossen, als ob ich ALT+C gedrückt hätte. Wenn ich für die besagten Edit-Felder im DLL-Fenster eine OnKeyDown oder OnKeyPress-Routine schreibe, dann wird tatsächlich nur für die Tasten ein Event ausgelöst, die ich tippen kann, also z. B. nicht für 'a'.

Ich habe es dann mal mit einem TApplicationEvents versucht, den ich auf dem Haupt-Konfigurationsfenster platziert habe. Für diesen habe ich dann folgenden OnMessage-Handler geschrieben:

Delphi-Quellcode:
procedure TConfigForm.ApplicationKeyboardEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
const
  SYS_KEYS: set of Byte = [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_TAB, VK_RETURN];
var
  Handle: THandle;
begin
  if (Msg.message = WM_KEYDOWN) or (Msg.message = WM_KEYUP) then
    if (Msg.wParam in SYS_KEYS) then
      begin
        Handle := GetFocus;
        SendMessage(Handle, Msg.message, Msg.wParam, Msg.lParam);
        Handled := True;
      end;
end;
Damit kann ich jetzt immerhin die Pfeiltasten in den DLL-Edits verwenden. Die "normalen" Buchstaben bekomme ich trotzdem nicht alle. Wenn ich die Zeile "if (Msg.wParam in SYS_KEYS)" auskommentiere, habe ich überhaupt keine Buchstaben mehr im DLL-Fenster, nur noch Pfeiltasten.

Ich glaube, ich könnte das ganze wohl über globale Tastatur-Hooks lösen, ich habe auch Assabard's Tutorial gelesen. Aber ich glaube, dass das für meinen Zweck absoluter Overkill ist, denn
1. muss dann jede DLL (und es sind ein paar) diese Hooks setzen,
2. muss jede die Ereignisse dann auch noch verarbeiten und
3. muss das doch auch irgendwie einfacher gehen ;-)

Hat jemand einen schlauen Rat?

Danke im Voraus!

Christian

chrschn 18. Nov 2004 17:20

DLL-Fenster erhält nicht alle Keyboard-Events?! GEHT JETZT!
 
Ich habe es jetzt nach einigen Experimenten hinbekommen. Die Lösung baut auf dem schon geposteten Versuch auf, die Keyboard-Messages über ein TApplicationEvents-Objekt abzufangen und an das Control mit dem Focus weiterzuleiten, also sowohl an die eigenen als auch an die DLL.

Allerdings müssen dazu nicht nur die WM_KEYDOWN und WM_KEYUP-Messages weitergeleitet werden. Darüber hinaus müssen die WM_KEYDOWN-Messages noch mit TranslateMessage in eine WM_CHAR oder WM_DEADCHAR-Message umgewandelt werden, welche dann auch noch weitergeleitet werden müssen. Außerdem dürfen die Tabulator oder Enter-Taste nicht weitergeleitet werden, sonst verlieren sie ihre spezielle Funktion. Keine Idee, woran das liegen könnte.

Was jetzt funktioniert:
  • Sämtliche Zeichen können erzeugt werden, auch Akzente (Dead-Chars), [ALT GR]-Zeichen usw.
  • Pfeiltasten, Home, End, PgUp, PgDn usw. können erzeugt werden
  • Die Hostanwendung funktioniert nach meiner Beobachtung genauso wie vorher ([Tab]: Focuswechsel, Return: Default-Button, [ALT]+MenuAccel: Tastenkürzel, ...)
Was allerdings (noch) nicht funktioniert:
  • [TAB] innerhalb des DLL-Fensters wechselt nicht den Focus innerhalb desselben, sondern springt zum nächsten Control der Hostanwendung
  • Der Focuswechsel durch [TAB] in der Hostanwendung überspringt das DLL-Fenster
  • MenuAccels im DLL-Fenster funktionieren nicht, also z. B. Button-Captions wie '&OK' können nicht mit [ALT]+[O] aktiviert werden
Mit diesen Einschränkunten kann ich allerdings erstmal leben :thumb: .

Um die Tabulator-Geschichte hinzubiegen, kam mir schon folgende Idee: Man könnte das Panel in den Focuswechsel mit einbeziehen (TPanel.TabStop := True) und sich das Handle des aktuell angezeigten DLL-Fensters von selbiger erfragen. Dann kann man im TPanel.OnEnter-Event-Handler das Fenster per Windows-API aktivieren, mit SetFocus(HandleOfCurrentDLL). Aber für weitere Experimente habe ich immo leider keine Zeit. Falls es jemand ausprobiert, bitte (Miss-)Erfolgsbereichte hier posten 8)

Hier nun der Code für das TApplicationEvents.OnMessage in der Hostanwendung:
Delphi-Quellcode:
procedure TConfigForm.ApplicationKeyboardEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
const
  DONT_FORWARD_KEYS: set of Byte = [VK_RETURN, VK_TAB];

var
  Handle: THandle;
  VKey: Byte;
begin
  // This event handler forwards the keyboard events to the DLL window.
  // Without it some keys would not be sent to the DLL window. This seems
  // to be an issue of either Delphi or the Windows message handling.
  case Msg.message of
    WM_KEYDOWN:
      begin
        // Translate the virutal key into a character key (0..255)
        VKey := Lo(MapVirtualKey(Msg.wParam, 2));
        // If no translation has to be made, MapVirtualKey returns 0.
        if (VKey = 0) then VKey := Lo(Msg.wParam);
        // Don't handle the DONT_FORWARD_KEYS keys as they have special
        // functions and must be handled by the application itself.
        if not (VKey in DONT_FORWARD_KEYS) then
          begin
            // Get current focused control
            Handle := GetFocus;
            // Forward the message to it
            SendMessage(Handle, Msg.message, Msg.wParam, Msg.lParam);
            // Process message by translating the WM_KEYUP message into a
            // WM_CHAR message.
            Handled := TranslateMessage(Msg);
          end;
      end;

    WM_KEYUP:
      begin
        // Translate the virutal key into a character key (0..255)
        VKey := Lo(MapVirtualKey(Msg.wParam, 2));
        // If no translation has to be made, MapVirtualKey returns 0.
        if (VKey = 0) then VKey := Lo(Msg.wParam);
        // Don't handle the DONT_FORWARD_KEYS keys as they have special
        // functions and must be handled by the application itself.
        if not (VKey in DONT_FORWARD_KEYS) then
          begin
            // Get current focused control
            Handle := GetFocus;
            // Forward the message to it
            SendMessage(Handle, Msg.message, Msg.wParam, Msg.lParam);
            Handled := True;
          end;
      end;

    WM_DEADCHAR,
    WM_CHAR:
      begin
        VKey := Msg.wParam;
        // Don't handle the DONT_FORWARD_KEYS keys as they have special
        // functions and must be handled by the application itself.
        if not (VKey in DONT_FORWARD_KEYS) then
          begin
            // Get current focused control
            Handle := GetFocus;
            // Forward the message to it
            SendMessage(Handle, Msg.message, Msg.wParam, Msg.lParam);
            Handled := True;
          end;
      end;
  end;
end;
Gruß,
Christian

chrschn 18. Nov 2004 23:21

Re: DLL-Fenster erhält nicht alle Keyboard-Events?! GEHT JET
 
Zitat:

Zitat von chrschn
Was allerdings (noch) nicht funktioniert:
  • [TAB] innerhalb des DLL-Fensters wechselt nicht den Focus innerhalb desselben, sondern springt zum nächsten Control der Hostanwendung
  • Der Focuswechsel durch [TAB] in der Hostanwendung überspringt das DLL-Fenster
  • MenuAccels im DLL-Fenster funktionieren nicht, also z. B. Button-Captions wie '&OK' können nicht mit [ALT]+[O] aktiviert werden

Eher zufällig bin ich noch auf eine Lösung für das letzte der genannten Probleme gestoßen, nämlich die Menu-Accelerators. Ich habe meine Prozedur zum anzeigen des DLL-Fensters um einen var-Parameter erweitert. Dieser liefert der Host-Anwendung das Handle des DLL-Fensters zurück. In der DLL sieht das so aus:
Delphi-Quellcode:
procedure ShowConfigDlg(const ID: Integer; const ParentWindow: THandle;
  const Left: Integer; const Top: Integer; const Width: Integer;
  const Height: Integer; var DlgHandle: THandle);
begin
  if not Assigned(ConfigForm) then
    begin
      ConfigForm := TConfigForm.Create(NIL);
      ConfigForm.ParentWindow := ParentWindow;
    end;

  DlgHandle := ConfigForm.Handle;
  ConfigForm.Left := Left;
  ConfigForm.Top := Top;
  ConfigForm.Width := Width;
  ConfigForm.Height := Height;
  ConfigForm.Visible := true;
end;

exports ShowConfigDlg;
Wenn das DLL-Fenster angezeigt wird, dann weise ich in der Host-Anwendung die Eigenschaft Application.DialogHandle zu (siehe Delphi-Hilfe von TApplication.DialogHandle). Der Code der Host-Anwendung:
Delphi-Quellcode:
type
  TShowConfigDlgProc = procedure(const ID: Integer; const ParentWindow: THandle;
                        const Left: Integer; const Top: Integer;
                        const Width: Integer; const Height: Integer;
                        var DlgHandle: THandle);
var
  ShowCfgDlg: TShowConfigDlgProc;
  DlgHandle, Handle: THandle;
begin
  Handle := LoadLibrary(PChar(FileName));
  @ShowCfgDlg := GetProcAddress(fHandle, 'ShowConfigDlg');
  ShowConfigDlg(ConfigPanel.Handle, 0, 0, ConfigPanel.ClientWidth,
                ConfigPanel.ClientHeight, DlgHandle);
  Application.DialogHandle := DlgHandle;
end;
Und siehe da -- die MenuAccels funktionieren :cyclops: . Das TApplicationEvents-Objekt auf dem Form der Host-Anwendung ist aber immer noch nötig, wie schon zuvor beschrieben. Wenn das DLL-Fenster wieder ausgeblendet wird, muss Application.DialogHandle := 0 gesetzt werden.

Den Umweg über das hin- und her mit dem Handle muss man leider gehen. Wenn in der DLL die Unit "Forms" eingebunden wird und dort Application.DialogHandle gesetzt wird, funktioniert es scheinbar nicht. Ich vermute mal, weil die höhere Instanz (in dem Fall die Host-Anwendung) die Message-Queue für die DLL entsprechend anpassen muss, und nicht umgekehrt.

Schönen Gruß,

Christian

chrschn 18. Nov 2004 23:50

Re: DLL-Fenster erhält nicht alle Keyboard-Events?! GEHT JET
 
Zitat:

Zitat von chrschn
Zitat:

Zitat von chrschn
Was allerdings (noch) nicht funktioniert:
  • [TAB] innerhalb des DLL-Fensters wechselt nicht den Focus innerhalb desselben, sondern springt zum nächsten Control der Hostanwendung
  • Der Focuswechsel durch [TAB] in der Hostanwendung überspringt das DLL-Fenster
  • MenuAccels im DLL-Fenster funktionieren nicht, also z. B. Button-Captions wie '&OK' können nicht mit [ALT]+[O] aktiviert werden

Eher zufällig bin ich noch auf eine Lösung für das letzte der genannten Probleme gestoßen, nämlich die Menu-Accelerators. Ich habe meine Prozedur zum anzeigen des DLL-Fensters um einen var-Parameter erweitert. Dieser liefert der Host-Anwendung das Handle des DLL-Fensters zurück.

Da ich jetzt schonmal das Handle des DLL-Fensters hatte, lag es nahe, auch gleich den Focus-Wechsel wie oben beschrieben mit einzubauen. Dazu habe ich das Panel, in dem die DLL-Fenster jeweils angezeigt werden, fokusierbar gemacht (Panel.TabStop := True). Dann habe ich für das Panel folgenden OnEnter-Event-Handler geschrieben:
Delphi-Quellcode:
var
  DllDlgHandle: THandle

[...]

procedure TConfigForm.ConfigPanelEnter(Sender: TObject);
begin
  if (DllDlgHandle > 0) then
     Windows.SetFocus(DllDlgHandle);
end;
Das aktiviert das DLL-Fenster und auch dessen Komponente, falls vorher schon eine ausgewählt war. Nun kann ich mit [TAB] in der Host-Anwendung auch in das DLL-Fenster springen. Allerdings springt das nächste [TAB] zum nächsten Control der Host-Anwendung, und nicht zum nächsten Control des DLL-Fensters. Naja, man kann wohl nicht alles haben ;-)

Schönen Gruß,

Christian


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:17 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz