Simulate VB's Sendkeys

I'm rewriting a VB program in Delphi. The VB Program uses a SendKeys 'Alt-FOTEST.TXT-Enter' to open a File Open Dialog, and to introduce the Filename 'TEST.TXT', followed by a ENTER.
I'm using PostMessage Functions to get the FIle Open Dialog, but if i then Use PostMessage, with the handle of the currently active window (the file open dialog), the messages (characters) are not sent to the active Edit Box of this window. I can, however, via GetNextWindow get hold of the handle of this edit box, and then send these Postmessage's. This works fine, but it makes my function not general. How does VB know what the handle is of the active object on the active window, or what technique does VB use to send these keystrokes to the active object of the active window ?
jvh042097Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

sperlingCommented:
If you need to know how VB does it, this ain't the right group...

If you need similar functionality in Delphi, I could give it a try. But I don't know how VB implements this, and therefore I cannot guarantee that I'll do it the same way. Please leave a comment if you'd like me to try writing some generic sendkeys function.

Regards,

Erik.
0
jvh042097Author Commented:
In fact, i'm not really interested how VB does it. The only thing i need is a generic solution for the sendkeys problem.

My problem is that using Postmessage to the File Open Dialog box, doesn't send the keys right to the active edit box. I can - in this particular case - get hold of it's handle and send the keys i want, but that's becuase i know it's specific order on this form. What if i didn't. Please give it a try !
txs
0
sperlingCommented:
Just a questions on VB Sendkeys

Can you specify a window to receive the keys, or does the foreground window receive them?

I've written a function for sending keys to the foreground window. I'll have to add parsing for ALT, CTRL and so on before posting it.

Regards,

Erik.
0
CompTIA Network+

Prepare for the CompTIA Network+ exam by learning how to troubleshoot, configure, and manage both wired and wireless networks.

sperlingCommented:
Here's a unit...

Use e.g.

SendString ('[ALT-F]OTEST.TXT[ENTER]');

SendString ('[ALT-F4][ENTER]');


I have tested it, but not very thoroughly. Leave me a comment if you find any bugs...


Regards,

Erik.

---
unit Sendkeys;

interface
uses
  Windows;

type
  TSpecialSequence =
    record
      Name    : STRING[10];
      Code    : INTEGER;
    end;

const
  SpecialSequences : ARRAY [0..20] of TSpecialSequence =
  (
    (Name : 'TAB';           Code : VK_TAB),
    (Name : 'ENTER';         Code : VK_RETURN),
    (Name : 'ESC';           Code : VK_ESCAPE),
    (Name : 'LEFT';          Code : VK_LEFT),
    (Name : 'RIGHT';         Code : VK_RIGHT),
    (Name : 'UP';            Code : VK_UP),
    (Name : 'DOWN';          Code : VK_DOWN),
    (Name : 'INSERT';        Code : VK_INSERT),
    (Name : 'DELETE';        Code : VK_DELETE),
    (Name : 'PAGEUP';        Code : VK_PRIOR),
    (Name : 'PAGEDOWN';      Code : VK_NEXT),
    (Name : 'F1';            Code : VK_F1),
    (Name : 'F2';            Code : VK_F2),
    (Name : 'F3';            Code : VK_F3),
    (Name : 'F4';            Code : VK_F4),
    (Name : 'F5';            Code : VK_F5),
    (Name : 'F6';            Code : VK_F6),
    (Name : 'F7';            Code : VK_F7),
    (Name : 'F8';            Code : VK_F8),
    (Name : 'F9';            Code : VK_F9),
    (Name : 'F10';           Code : VK_F10)
  );

  SpecialSequenceStart     = '[';
  SpecialSequenceEnd       = ']';
  SpecialSequenceSeparator = '-';

  KeyNameAlt               = 'ALT';
  KeyNameCtrl              = 'CTRL';
  KeyNameShift             = 'SHIFT';

function SendString (s : STRING) : BOOLEAN;

implementation
uses
  SysUtils, Messages, Forms, Classes;

type
  PEventMsgLink = ^TEventMsgLink;
  TEventMsgLink =
    record
      EventMsg  : TEventMsg;
      Next      : PEventMsgLink;
    end;


var
  PlaybackHook : INTEGER;
  ShouldSleep  : BOOLEAN;
  FirstListEntry,
  CurrentListEntry      : PEventMsgLink;

function ParseString (s : STRING; IgnoreCase : BOOLEAN) : BOOLEAN; forward;


function PlaybackProc (code : INTEGER; wParam : WORD; eMsg : PEventMsg) : INTEGER; stdcall;
begin
  case code of
    HC_GETNEXT :
      begin
        Move (CurrentListEntry^.EventMsg, eMsg^, SizeOf(TEventMsg));
        Result := 0;
        if ShouldSleep then begin
          Result := 10;
          ShouldSleep := FALSE
        end;
      end;
    HC_NOREMOVE :
      begin
        Result := 0;
      end;
    HC_SKIP :
      begin
        CurrentListEntry := CurrentListEntry^.Next;
        Result := 0;
        ShouldSleep := TRUE;
      end;
    HC_SYSMODALON :
      begin
        Result := 0;
      end;
    HC_SYSMODALOFF :
      begin
        Result := 0;
      end;
  else
    if code<0 then Result := CallnextHookEx(PlaybackHook, code, wParam, INTEGER(eMsg));
  end;
  if CurrentListEntry=nil then begin
    UnhookWindowsHookEx(PlaybackHook);
    PlaybackHook := 0;
  end;
end;

function AddEventMsg (ACode : INTEGER; AMsg : INTEGER) : BOOLEAN;
var
  pLink   : PEventMsgLink;
begin
  if FirstListEntry = nil then begin
    new (FirstListEntry);
    pLink := FirstListEntry;
  end else begin
    pLink := FirstListEntry;
    while pLink^.Next<>nil do pLink := pLink^.Next;
    new (pLink^.next);
    pLink := pLink^.Next;
  end;
  FillChar (pLink^, SizeOf(TEventMsgLink), 0);
  pLink^.EventMsg.message := AMsg;
  pLink^.EventMsg.ParamH := MapVirtualKey(ACode, 0);
  pLink^.EventMsg.ParamL := ACode + $100 * MapVirtualKey(ACode, 0);
  Result := TRUE;
end;

function AddChar (Source : STRING; var SourcePos : INTEGER; IgnoreCase : BOOLEAN) : BOOLEAN;
var
  AChar  : CHAR;
  IsChar : BOOLEAN;
begin
  AChar := Source[SourcePos];
  Result := TRUE;
  IsChar := ANSILowerCase(AChar) <> ANSIUpperCase(AChar);
  if (AChar=UpCase(AChar)) and IsChar and (not IgnoreCase) then Result := Result AND AddEventMsg (VK_SHIFT, WM_KEYDOWN);
  Result := Result AND AddEventMsg (ORD(UpCase(AChar)), WM_KEYDOWN);
  Result := Result AND AddEventMsg (ORD(UpCase(AChar)), WM_KEYUP);
  if (AChar=UpCase(AChar)) and IsChar and (not IgnoreCase) then Result := Result AND AddEventMsg (VK_SHIFT, WM_KEYUP);
  if Result then inc (SourcePos);
end;

function AddSpecial (Source : STRING; var SourcePos : INTEGER) : BOOLEAN;
var
  ndx     : INTEGER;
  Shift,
  Special : STRING;
  ShiftState : TShiftState;
begin
  Result := FALSE;
  Special := Copy (Source, SourcePos, Length(Source));
  ndx := 2;
  while (ndx<=Length(Special)) and (Special[ndx]<>SpecialSequenceEnd) do inc(ndx);
  if ndx>Length(Special) then exit;
  Delete(Special, ndx+1, Length(Special));
  INC(SourcePos, Length(Special));
  Delete(Special, 1, 1);
  Delete(Special, Length(Special), 1);

  // Extract wanted shift keys
  ShiftState := [];
  while Pos(SpecialSequenceSeparator, Special)>0 do begin
    Shift := Copy(Special, 1, Pos(SpecialSequenceSeparator, Special)-1);
    Delete (Special, 1, Pos(SpecialSequenceSeparator, Special));
    if UpperCase(Shift)=KeyNameAlt then Include (ShiftState, ssAlt)
    else if UpperCase(Shift)=KeyNameCtrl then Include (ShiftState, ssCtrl)
    else if UpperCase(Shift)=KeyNameShift then Include (ShiftState, ssShift)
    else exit;
  end;

  if ssAlt in ShiftState then begin
    AddEventMsg (VK_MENU, WM_SYSKEYDOWN);
    if ssCtrl in ShiftState then AddEventMsg (VK_CONTROL, WM_SYSKEYDOWN);
    if ssShift in ShiftState then AddEventMsg (VK_SHIFT, WM_SYSKEYDOWN);
  end else begin
    if ssCtrl in ShiftState then AddEventMsg (VK_CONTROL, WM_KEYDOWN);
    if ssShift in ShiftState then AddEventMsg (VK_SHIFT, WM_KEYDOWN);
  end;
  if Length(Special)=1 then begin
    if ssAlt in ShiftState then begin
      AddEventMsg (ORD(Special[1]), WM_SYSKEYDOWN);
      AddEventMsg (ORD(Special[1]), WM_SYSKEYUP);
    end else begin
      AddEventMsg (ORD(Special[1]), WM_KEYDOWN);
      AddEventMsg (ORD(Special[1]), WM_KEYUP);
    end;
    Result := TRUE;
  end else begin
    for ndx := LOW (SpecialSequences) to HIGH (SpecialSequences) do begin
      if CompareText (SpecialSequences[ndx].Name, Special) = 0 then begin
        if ssAlt in ShiftState then begin
          AddEventMsg (SpecialSequences[ndx].Code, WM_SYSKEYDOWN);
          AddEventMsg (SpecialSequences[ndx].Code, WM_SYSKEYUP);
        end else begin
          AddEventMsg (SpecialSequences[ndx].Code, WM_KEYDOWN);
          AddEventMsg (SpecialSequences[ndx].Code, WM_KEYUP);
        end;
        Result := TRUE;
        break;
      end;
    end;
  end;

  if not Result then exit;

  if ssAlt in ShiftState then begin
    if ssShift in ShiftState then AddEventMsg (VK_SHIFT, WM_SYSKEYUP);
    if ssCtrl in ShiftState then AddEventMsg (VK_CONTROL, WM_SYSKEYUP);
    AddEventMsg (VK_MENU, WM_SYSKEYUP);
  end else begin
    if ssShift in ShiftState then AddEventMsg (VK_SHIFT, WM_KEYUP);
    if ssCtrl in ShiftState then AddEventMsg (VK_CONTROL, WM_KEYUP);
  end;
end;

function ParseString (s : STRING; IgnoreCase : BOOLEAN) : BOOLEAN;
var
  ndx     : INTEGER;
begin
  Result := FALSE;
  ndx := 1;
  while ndx<=Length(s) do begin
    if s[ndx]=SpecialSequenceStart then begin
      if ndx=Length(s) then exit;
      if s[ndx+1]=SpecialSequenceStart then begin
        INC(ndx);
        if not AddChar (s, ndx, IgnoreCase) then exit;
      end else begin
        if not AddSpecial (s, ndx) then exit;
      end;
    end else begin
      if not AddChar (s, ndx, IgnoreCase) then exit;
    end;
  end;
  Result := TRUE;
end;


function SendString (s : STRING) : BOOLEAN;
begin
  Result := ParseString(s, FALSE);

  if Result then begin
    ShouldSleep := TRUE;
    CurrentListEntry := FirstListEntry;
    PlaybackHook := SetWindowsHookEx (WH_JOURNALPLAYBACK, @PlaybackProc, hInstance, 0);
    while PlaybackHook <> 0 do Application.ProcessMessages;
  end;

  while FirstListEntry <> nil do begin
    CurrentListEntry := FirstListEntry;
    FirstListEntry := CurrentListEntry^.Next;
    Dispose(CurrentListEntry);
  end;
  CurrentListEntry := nil;
end;

initialization
  CurrentListEntry := nil;
  FirstListEntry := nil;
end.



0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
jvh042097Author Commented:
Your program seems to work fine, except for the '.' : This character is sent as VK_Delete, rather than the '.' character. I have temporary added a filter which remaps those VK_delete into the right '.'

TXS !
0
sperlingCommented:

Result := Result AND AddEventMsg (ORD(UpCase(AChar)),WM_KEYDOWN);
Result := Result AND AddEventMsg (ORD(UpCase(AChar)),WM_KEYUP);


Oops...

The ORD(UpCase(AChar)) here only gives correct values for A..Z and 0..9

You'll probably have to check the char here, and then map to the correct virtual key code.

Regards,

Erik.
 
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.