AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Simulierten Hotkey per WMHotKey abfangen?
Thema durchsuchen
Ansicht
Themen-Optionen

Simulierten Hotkey per WMHotKey abfangen?

Ein Thema von schwa226 · begonnen am 16. Apr 2008 · letzter Beitrag vom 23. Apr 2008
Antwort Antwort
Seite 1 von 2  1 2      
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#1

Simulierten Hotkey per WMHotKey abfangen?

  Alt 16. Apr 2008, 17:39
Hi,

ich habe mir einen Keyboard-Hook mit WMHotKey geschrieben (Anleitung ist ja hier zu finden).

Das klappt auch super mit der Tastatur! Alle Registrierten Hooks werden erkannt!

Jedoch möchte ich jetzt noch Simulierte Tastendrücke abfangen!

Und zwar habe ich das Programm "Girder 4" (IR-Empfänger Programm für PC: http://www.promixis.com/products.php).

Dies wertet die IR-Signale aus und gibt sie per z.B. SHIFT-B, Escape usw. weiter.

Nun reagiert aber WMHotKey auf diese Befehle überhaupt nicht!

Kann man das irgendwie lösen?
  Mit Zitat antworten Zitat
Benutzerbild von toms
toms
(CodeLib-Manager)

Registriert seit: 10. Jun 2002
4.648 Beiträge
 
Delphi XE Professional
 
#2

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 16. Apr 2008, 18:24
Zitat von schwa226:
Hi,

ich habe mir einen Keyboard-Hook mit WMHotKey geschrieben (Anleitung ist ja hier zu finden).
Hallo, meinst du einen systemweiten Hotkey (RegisterHotKey) oder einen systemweiten Hook (SetWindowsHookEx) ?
Thomas
  Mit Zitat antworten Zitat
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#3

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 17. Apr 2008, 07:43
Mit Globalen Hotkey per RegisterHotKey per diesen sample Code:

Code:
{
  The following example demonstrates registering hotkeys with the
  system to globally trap keys.

  Das Folgende Beispiel zeigt, wie man Hotkeys registrieren und
  darauf reagieren kann, wenn sie gedrückt werden. (systemweit)
}

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    id1, id2, id3, id4: Integer;
    procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// Trap Hotkey Messages
procedure TForm1.WMHotKey(var Msg: TWMHotKey);
begin
  if Msg.HotKey = id1 then
    ShowMessage('Ctrl + A pressed !');
  if Msg.HotKey = id2 then
    ShowMessage('Ctrl + Alt + R pressed !');
  if Msg.HotKey = id3 then
    ShowMessage('Win + F4 pressed !');
  if Msg.HotKey = id4 then
    ShowMessage('Print Screen pressed !');
end;

procedure TForm1.FormCreate(Sender: TObject);
  // Different Constants from Windows.pas
const
  MOD_ALT = 1;
  MOD_CONTROL = 2;
  MOD_SHIFT = 4;
  MOD_WIN = 8;
  VK_A = $41;
  VK_R = $52;
  VK_F4 = $73;
begin
  // Register Hotkey Ctrl + A
  id1 := GlobalAddAtom('Hotkey1');
  RegisterHotKey(Handle, id1, MOD_CONTROL, VK_A);

  // Register Hotkey Ctrl + Alt + R
  id2 := GlobalAddAtom('Hotkey2');
  RegisterHotKey(Handle, id2, MOD_CONTROL + MOD_Alt, VK_R);

  // Register Hotkey Win + F4
  id3 := GlobalAddAtom('Hotkey3');
  RegisterHotKey(Handle, id3, MOD_WIN, VK_F4);

  // Globally trap the Windows system key "PrintScreen"
  id4 := GlobalAddAtom('Hotkey4');
  RegisterHotKey(Handle, id4, 0, VK_SNAPSHOT);
end;

// Unregister the Hotkeys
procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnRegisterHotKey(Handle, id1);
  GlobalDeleteAtom(id1);
  UnRegisterHotKey(Handle, id2);
  GlobalDeleteAtom(id2);
  UnRegisterHotKey(Handle, id3);
  GlobalDeleteAtom(id3);
  UnRegisterHotKey(Handle, id4);
  GlobalDeleteAtom(id4);
end;

end.

{
  RegisterHotKey fails if the keystrokes specified for the hot key have
  already been registered by another hot key.

  Windows NT4 and Windows 2000/XP: The F12 key is reserved for use by the
  debugger at all times, so it should not be registered as a hot key. Even
  when you are not debugging an application, F12 is reserved in case a
  kernel-mode debugger or a just-in-time debugger is resident.
}
  Mit Zitat antworten Zitat
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#4

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 18. Apr 2008, 21:52
Hi,

hab jetzt eine Hook mit DLL getestet!

Dieser fängt die Simulierten Befehle von Girder ab.

Jedoch habe ich zu wenig Erfahrung mit dem ganzen und deswegen habe ich noch ein Frage:

Wie kann ich jetzt Kombinationen wie SHIFT+D oder CTRL+D abfangen?

Einzelne Tasten wie VK_Escape kann ich ohne Probleme abfangen.

Kann mir da noch jemand helfen?

Code:
unit WHookInt;

interface

uses
  Windows, Messages, SysUtils, msxml, Forms, unit2;


function SetHook(WinHandle: HWND; MsgToSend: Integer): Boolean; stdcall; export;
function FreeHook: Boolean; stdcall; export;
function MsgFilterFunc(Code: Integer; wParam, lParam: Longint): Longint stdcall; export;

implementation


// Memory map file stuff

{
  The CreateFileMapping function creates unnamed file-mapping object
  for the specified file.
}

function CreateMMF(Name: string; Size: Integer): THandle;
begin
  Result := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, Size, PChar(Name));
  if Result <> 0 then
  begin
    if GetLastError = ERROR_ALREADY_EXISTS then
    begin
      CloseHandle(Result);
      Result := 0;
    end;
  end;
end;

{ The OpenFileMapping function opens a named file-mapping object. }

function OpenMMF(Name: string): THandle;
begin
  Result := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(Name));
  // The return value is an open handle to the specified file-mapping object.
end;

{
 The MapViewOfFile function maps a view of a file into
 the address space of the calling process.
}

function MapMMF(MMFHandle: THandle): Pointer;
begin
  Result := MapViewOfFile(MMFHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
end;

{
  The UnmapViewOfFile function unmaps a mapped view of a file
  from the calling process's address space.
}

function UnMapMMF(P: Pointer): Boolean;
begin
  Result := UnmapViewOfFile(P);
end;

function CloseMMF(MMFHandle: THandle): Boolean;
begin
  Result := CloseHandle(MMFHandle);
end;

// Actual hook stuff

type
  TPMsg = ^TMsg;



const
  VK_D = $44;
  VK_E = $45;
  VK_F = $46;
  VK_M = $4D;
  VK_R = $52;

  MMFName = 'MsgFilterHookDemo';

type
  PMMFData = ^TMMFData;
  TMMFData = record
    NextHook: HHOOK;
    WinHandle: HWND;
    MsgToSend: Integer;
  end;

  // global variables, only valid in the process which installs the hook.
var
  MMFHandle: THandle;
  MMFData: PMMFData;

function UnMapAndCloseMMF: Boolean;
begin
  Result := False;
  if UnMapMMF(MMFData) then
  begin
    MMFData := nil;
    if CloseMMF(MMFHandle) then
    begin
      MMFHandle := 0;
      Result := True;
    end;
  end;
end;

{
  The SetWindowsHookEx function installs an application-defined
  hook procedure into a hook chain.

  WH_GETMESSAGE Installs a hook procedure that monitors messages
  posted to a message queue.
  For more information, see the GetMsgProc hook procedure.
}

function SetHook(WinHandle: HWND; MsgToSend: Integer): Boolean; stdcall;

var
 test : boolean;
begin



  if (MMFData = nil) and (MMFHandle = 0) then
  begin
    MMFHandle := CreateMMF(MMFName, SizeOf(TMMFData));
    if MMFHandle <> 0 then
    begin
      MMFData := MapMMF(MMFHandle);
      if MMFData <> nil then
      begin
        MMFData.WinHandle := WinHandle;
        MMFData.MsgToSend := MsgToSend;
        MMFData.NextHook := SetWindowsHookEx(WH_GETMESSAGE, MsgFilterFunc, HInstance, 0);

        if MMFData.NextHook = 0 then
          UnMapAndCloseMMF
        else
          Result := True;
      end
      else
      begin
        CloseMMF(MMFHandle);
        MMFHandle := 0;
      end;
    end;
  end;
end;


{
  The UnhookWindowsHookEx function removes the hook procedure installed
  in a hook chain by the SetWindowsHookEx function.
}

function FreeHook: Boolean; stdcall;
begin
  Result := False;
  if (MMFData <> nil) and (MMFHandle <> 0) then
    if UnHookWindowsHookEx(MMFData^.NextHook) then
      Result := UnMapAndCloseMMF;
end;



(*
    GetMsgProc(
    nCode: Integer; {the hook code}
    wParam: WPARAM; {message removal flag}
    lParam: LPARAM {a pointer to a TMsg structure}
    ): LRESULT; {this function should always return zero}

    { See help on ==> GetMsgProc}
*)

function MsgFilterFunc(Code: Integer; wParam, lParam: Longint): Longint;
var
  MMFHandle: THandle;
  MMFData: PMMFData;
  Kill: boolean;
  F: TForm2;

begin
  Result := 0;
  MMFHandle := OpenMMF(MMFName);

  if MMFHandle <> 0 then
  begin
    MMFData := MapMMF(MMFHandle);
    if MMFData <> nil then
    begin
      if (Code < 0) or (wParam = PM_NOREMOVE) then
        {
          The CallNextHookEx function passes the hook information to the
          next hook procedure in the current hook chain.
        }
        Result := CallNextHookEx(MMFData.NextHook, Code, wParam, lParam)
      else
      begin
        Kill := False;

        { Examples }
        with TMsg(Pointer(lParam)^) do
        begin
          // Kill Numbers
          if (wParam >= 48) and (wParam <= 57) then Kill := True;
          // Kill Tabulator
          if (wParam = VK_TAB) then Kill := True;
        end;

        if TPMsg(lParam)^.wParam = VK_Escape then begin

        Kill := True;

        end;


        //MessageBox(0,TPMsg(lParam)^.message,'testme',mb_Ok);
        { Example to disable all the start-Key combinations }
        // F.Label1.Caption := 'Test';//inttostr(2);
        //F.Label1.Caption := inttostr(TPMsg(lParam)^.wParam );
        case TPMsg(lParam)^.message of
        //  WM_SYSCOMMAND: // The Win Start Key (or Ctrl+ESC)
         //   if TPMsg(lParam)^.wParam = SC_TASKLIST then Kill := True;

          WM_HOTKEY:
            case ((TPMsg(lParam)^.lParam and $00FF0000) shr 16) of
              VK_D,     // Win+D       ==> Desktop
              VK_E,     // Win+E       ==> Explorer
              VK_F,     // Win+F+(Ctrl) ==> Find:All (and Find: Computer)
              VK_M,     // Win+M       ==> Minimize all
              VK_R,     // Win+R       ==> Run program.
              VK_F1,    // Win+F1       ==> Windows Help
              VK_PAUSE: // Win+Pause   ==> Windows system properties
                Kill := True;
            end;
        end;
       
        if Kill then TPMsg(lParam)^.message := WM_NULL;
        Result := CallNextHookEx(MMFData.NextHook, Code, wParam, lParam)
      end;
      UnMapMMF(MMFData);
    end;
    CloseMMF(MMFHandle);
  end;
end;


initialization
  begin
    MMFHandle := 0;
    MMFData  := nil;
  end;

finalization
  FreeHook;
end.
  Mit Zitat antworten Zitat
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#5

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 19. Apr 2008, 22:00
Also ich habe jetzt folgendes Probiert:

Code:
        if (TPMsg(lParam)^.message) = WM_KEYDOWN then begin

          if TPMsg(lParam)^.wParam = VK_CONTROL then begin
               CTRLPressed := True;
          end;

        end;
        if (TPMsg(lParam)^.message) = WM_KEYUP then begin

          if TPMsg(lParam)^.wParam = VK_CONTROL then begin
               CTRLPressed := False;
          end;

        end;

        if (TPMsg(lParam)^.message) = WM_KEYDOWN then begin

          if TPMsg(lParam)^.wParam = VK_S and CTRLPressed then begin
               Kill := True;
          end;

        end;
Sinn der Sache ist ein Test um STRG+S per Hook Abfangen zu können. Es geht aber nicht!

Kann mir keiner Tipps geben wie das mit Hooks + Tasenkombinationen zu machen ist!
  Mit Zitat antworten Zitat
Benutzerbild von toms
toms
(CodeLib-Manager)

Registriert seit: 10. Jun 2002
4.648 Beiträge
 
Delphi XE Professional
 
#6

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 20. Apr 2008, 06:53
Delphi-Quellcode:
if (GetKeyState(VK_CONTROL)< 0) // CTRL gedrückt then
begin
  if TPMsg(lParam)^.wParam = vkKeyScan('s') // S-Taste then
  begin
    if TPMsg(lParam)^.message = WM_KEYDOWN then// Taste unten
    begin
      Messagebox(0, 'Strg + s', 'H O O K', 0);
      Kill := True;
    end;
  end;
end;
Thomas
  Mit Zitat antworten Zitat
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#7

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 21. Apr 2008, 12:05
Danke! geht super!

Nun möchte ich das ganze noch dynamisch machen:

Code:
  TempRecord = record
    str1:     string;
    str2:     string;
    car1:     cardinal;
    car2:     cardinal;
    car3:     cardinal;
Code:
HandleHook : Array[1..2] of TempRecord =
    (
      (str1:'Test1'; str2:'test'; car1:VK_CONTROL; car2:VK_S; car3:0),
      (str1:'Test2'; str2:'test'; car1:VK_SHIFT; car2:VK_D; car3:0)
    );
Code:
    for i := 0 to High(HandleHook) - 1 do begin

      if (GetKeyState(HandleHook[i].car1)< 0) then // Cardinal 1
      begin
        if TPMsg(lParam)^.wParam = HandleHook[i].car2 then // Cardinal 2
        begin
            if TPMsg(lParam)^.message = WM_KEYDOWN then// Taste unten
            begin
              Messagebox(0, 'Example', 'H O O K', 0);
              Kill := True;
            end;
        end;
      end;
end;
Nun spricht aber der Hook nur auf den ersten Eintrag an, aber nicht auf den zweiten!?

Ich will einen Hook für Speziele Tastenkombinationen machen, die eben per z.B. Girder geschickt werden.

Dazu muss ich halt CTRL+E, CTRL+P, SHIFT+E usw extra abfangen können.

EDIT:

habe das "Problem" gefunden...

wenn i bei 0 zu zählen anfängt aber das Array erst bei 1 losgeht...
  Mit Zitat antworten Zitat
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#8

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 21. Apr 2008, 16:34
Hab jetzt alles dynamisch gelöst:

Code:
    //ein Tasten Shorcut
     if (TPMsg(lParam)^.wParam = HandleHook[i].car3) and (HandleHook[i].car4 = 0) and (HandleHook[i].car5 = 0) then // 1. Cardinal
        begin
            if TPMsg(lParam)^.message = WM_KEYDOWN then// Taste unten
            begin
              SetForeGroundWIndow(WindowHandle);

              keybd_event(HandleHook[i].car1, 0, 0, 0);

                if not (HandleHook[i].car2 = 0) then begin
                  keybd_event(HandleHook[i].car2, 0, 0, 0);
                  keybd_event(HandleHook[i].car1, 0, KEYEVENTF_KEYUP,0);
                  keybd_event(HandleHook[i].car2, 0, KEYEVENTF_KEYUP,0);
                end
                else keybd_event(HandleHook[i].car1, 0, KEYEVENTF_KEYUP,0);

              Messagebox(0, PChar(HandleHook[i].str1), 'H O O K', 0);
              Kill := True;
            end;

     end

     //zwei Tasten Shorcut
     else if (GetKeyState(HandleHook[i].car3)< 0) and (HandleHook[i].car4 <> 0) and (HandleHook[i].car5 = 0) then // 1. Cardinal
      begin
        if TPMsg(lParam)^.wParam = HandleHook[i].car4 then // 2. Cardinal
        begin
            if TPMsg(lParam)^.message = WM_KEYDOWN then// Taste unten
            begin
              SetForeGroundWIndow(WindowHandle);

              keybd_event(HandleHook[i].car1, 0, 0, 0);

                if not (HandleHook[i].car2 = 0) then begin
                  keybd_event(HandleHook[i].car2, 0, 0, 0);
                  keybd_event(HandleHook[i].car1, 0, KEYEVENTF_KEYUP,0);
                  keybd_event(HandleHook[i].car2, 0, KEYEVENTF_KEYUP,0);
                end
                else keybd_event(HandleHook[i].car1, 0, KEYEVENTF_KEYUP,0);
              Messagebox(0, PChar(HandleHook[i].str1), 'H O O K', 0);
              Kill := True;
            end;
        end;
    end
     //drei Tasten Shorcut
    else if (GetKeyState(HandleHook[i].car3)< 0) and (GetKeyState(HandleHook[i].car4)< 0) then // 1. + 2. Cardinal
      begin
        if TPMsg(lParam)^.wParam = HandleHook[i].car5 then // 3. Cardinal
        begin
            if TPMsg(lParam)^.message = WM_KEYDOWN then// Taste unten
            begin
              SetForeGroundWIndow(WindowHandle);

              keybd_event(HandleHook[i].car1, 0, 0, 0);

                if not (HandleHook[i].car2 = 0) then begin
                  keybd_event(HandleHook[i].car2, 0, 0, 0);
                  keybd_event(HandleHook[i].car1, 0, KEYEVENTF_KEYUP,0);
                  keybd_event(HandleHook[i].car2, 0, KEYEVENTF_KEYUP,0);
                end
                else keybd_event(HandleHook[i].car1, 0, KEYEVENTF_KEYUP,0);          
              Messagebox(0, PChar(HandleHook[i].str1), 'H O O K', 0);
              Kill := True;
            end;
        end;
     end;
Hirmit kann ich 1, 2 & 3 Tasten-Codes abfangen.

Nun Fange ich z.B. die Tast "ESC" ab. Geht auch super!

Mein Problem ist nun wenn ich aus:

Abfangen -> Weiterschicken
ESC -> Enter
Enter -> Enter

mache, verfängt sich die Schleife natürlich in eine Endlosschleife wenn ich nun "Esc" drücke!

Sinn der Sache ist:

Ich habe 3 Programme, Prog1 sendet an Prog2 einen Shortcut. Dieser soll jedoch abgefangen werden und an Prog3 geschickt werden.

Muss ich da jetzt jedesmal bevor ich ein keybd_event auslöse den Hook wieder auflösen damit es sich nicht endlos in der Schleife verfängt?

EDIT:

zusätzlich habe ich herausgefunden, dass die Variable "HandleHook[i]", die Global definiert ist meinen Hook auf nurmehr Lokal verändert!

Wie kann ich das noch übergehen, denn die Array HandleHook wird in einer anderen Funktion bei SetHook erst aufgefüllt.
  Mit Zitat antworten Zitat
schwa226

Registriert seit: 4. Apr 2008
400 Beiträge
 
#9

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 22. Apr 2008, 23:43
ManOMan.. bin am verzweifeln!

Das mit den Globalen Variabeln und MMF habe ich hinbekommen, aber nicht das Problem dass die MsgFilterFunc auch auf die eigenen keybd_events reagiert!

Habe versucht den Hook vor dem Keyboard Event aufzulösen. Das bringt aber auch nichts -> Endlosschleife!

z.B. Ich fange vom 1. Programm ein "Up" ab, dass für Programm 2 bestimmt gewesen wäre. Dann sende ich das "Up" an Program 3 das ich zuerst in den Vordergrund geholt habe!

Kann mir da nochmal jemand Helfen!?

Code:
    //ein Tasten Shorcut z.B. Up -> Up, Esc -> Enter, Enter -> Enter,...
     if ((TPMsg(lParam)^.wParam = HandleHook[x].car3)
     and (HandleHook[x].car4 = 0)
     and (HandleHook[x].car5 = 0)) then // 1. Cardinal
        begin
            if TPMsg(lParam)^.message = WM_KEYDOWN then// Taste unten
            begin

              TPMsg(lParam)^.message := WM_NULL;  //Originale Message löschen
              Result := CallNextHookEx(MMFData.NextHook, Code, wParam, lParam);

              ClearTempArray(HandleHook);          //HandleHook[x].car3.. bis car5 auf 0 setzen damit oben nicht mehr auf einen Tastendruck reagiert wird

              SetForeGroundWIndow(MMFData.ProgramHandle);

              keybd_event(HandleHook[x].car1, 0, 0, 0);

                if not (HandleHook[x].car2 = 0) then begin
                  keybd_event(HandleHook[x].car2, 0, 0, 0);
                  keybd_event(HandleHook[x].car1, 0, KEYEVENTF_KEYUP,0);
                  keybd_event(HandleHook[x].car2, 0, KEYEVENTF_KEYUP,0);
                end
                else keybd_event(HandleHook[x].car1, 0, KEYEVENTF_KEYUP,0);

              //Messagebox(0, PChar(HandleHook[x].str1), 'H O O K 1', 0);
              Kill := True;

              CopyBackTempArray();           //HandleHook[x].car3.. bis car5 wieder auf Original Shortcut Kombination zurücksetzen
              Break;
            end;
     end
....
..
.
  Mit Zitat antworten Zitat
Benutzerbild von toms
toms
(CodeLib-Manager)

Registriert seit: 10. Jun 2002
4.648 Beiträge
 
Delphi XE Professional
 
#10

Re: Simulierten Hotkey per WMHotKey abfangen?

  Alt 23. Apr 2008, 05:26
Zitat:
Ich habe 3 Programme, Prog1 sendet an Prog2 einen Shortcut. Dieser soll jedoch abgefangen werden und an Prog3 geschickt werden.
Könntest du mal den Sinn davon erklären? Vielleicht geht's ja auch anders.!?
Thomas
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:18 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