?
Solved

Moving the cursor, clicking, pressing keys in another window.

Posted on 2003-03-05
12
Medium Priority
?
229 Views
Last Modified: 2010-04-04
Hello, I'm trying to create a program that will press buttons and move the mouse in another program. This program (Warcraft 3) runs maximised, not really in another window.

Is it possible to get Delphi to move the mouse and press buttons, and if so how would it be done?

If you could, please add in some code references for me to follow. Thanks a lot,

Mandieb.
0
Comment
Question by:Mandieb
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
12 Comments
 
LVL 21

Expert Comment

by:ziolko
ID: 8070668
take a look at: keybd_event(), mouse_event()
ziolko.
0
 
LVL 34

Expert Comment

by:Slick812
ID: 8075166
SetCursorPos(40,300);
mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN, Form1.Left+50, Form1.Top+6, 0, 0);
mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, Form1.Left+50, Form1.Top+6, 0, 0);


- - - - - - - - - - - - - - - - - - - - - -  --
the mouse_event( ) is independent of the Cursor Position


ask questions if you need more info
0
 

Author Comment

by:Mandieb
ID: 8076230
Thanks for the information on moving the mouse, but how do I click keys too?
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 34

Accepted Solution

by:
Slick812 earned 280 total points
ID: 8077098
????  Click Keys? you mean mouse buttons?? I do not follow you?
if you mean mouse button clicks, , , what do you think this does -

mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN, Form1.Left+50, Form1.Top+6, 0, 0);
mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, Form1.Left+50, Form1.Top+6, 0, 0);

a  MOUSEEVENTF_LEFTDOWN  is for left mouse button down
0
 
LVL 1

Expert Comment

by:User137
ID: 8102692
I became curious about this topic and tried to make this program, but there were problems when recording events. DirectInput could record left mouse button, but it had to be in timer (which may skip events).

Is there some way to get mouseClick and keyboard down and up events?
With WindowFromPoint you could get the handle to window at Cursor, but how to trap messages? Some other way?
0
 
LVL 34

Expert Comment

by:Slick812
ID: 8104601
Can you give a little more information about what you are trying to do? I have gotten system wide mouse messages with a WH_JOURNALRECORD  hook, if you need to change or block mouse input then a DLL using the WH_MOUSE  hook, might be useful, , , but I have not ever tried these with a Direct X.
0
 
LVL 1

Expert Comment

by:User137
ID: 8105102
The task is to add information to my array of MyMouseEvents every time the user presses mouse buttons or keyboard.

In the end, with this program you could press "record" and do some stuff, like start a game from start-menu, press alt+tab, and press stop recording. Then you press the "play" button, which does automatically what i just did with mouse and keyboard (started a game).

I don't know how to trap these messages, but is it like this:
Create a new TWinControl and set its handle to the handle of then window you want to trap messages from?
It's ReadOnly, so it didn't work...
0
 
LVL 34

Expert Comment

by:Slick812
ID: 8108687
Please Look at the API help for  WH_JOURNALRECORD  and  WH_JOURNALPLAYBACK  these were made for the operation you describe, you can even save the array of events to file and play it back later
0
 
LVL 34

Expert Comment

by:Slick812
ID: 8115711
here is some code for a Journal hook the t records and plays back mouse and keyboard -



  private
    { Private declarations }
    Num: Integer;
    EventMsgArray: Array of TEventMsg;


var
  Form1: TForm1;
  JHook: THandle;
  TempEvMsg: TEventMsg;
  Track, SysModal, GotMove, DoDelay: Boolean;
  StartTime, FirstGo: Cardinal;
  NumMsgs, PlayNum: Integer;
  WARect: TRect;
  hJHook: THandle;
  Journaling: Boolean = False;


procedure TForm1.FormCreate(Sender: TObject);
begin
NumMsgs := 0;
JHook := 0;
PlayNum := 0;
Track := False;
SysModal := False;
SetLength(EventMsgArray,1);
end;

procedure TForm1.DrawDesk(DisText: String; Red: Boolean);
var
DeskDC, hBrush: Integer;
begin
if not SystemParametersInfo(SPI_GETWORKAREA,0,@WARect,0) then
WARect := Rect(0,0,Screen.Width,Screen.Height - 32);
WARect.TopLeft := Point(WARect.Right-158,WARect.Bottom-60);
DeskDC := GetDC(0);
hBrush := 0;
if Red then
  begin
  hBrush := CreateSolidBrush($000000FF);
  SelectObject(DeskDC,hBrush);
  SetBkColor(DeskDC,$000000FF);
  end;
Rectangle(DeskDC,WARect.Left,WARect.Top,WARect.Right,WARect.Bottom);
InflateRect(WARect,-3,-2);
DrawText(DeskDC,PChar(DisText),Length(DisText),WARect, DT_LEFT or DT_WORDBREAK);
DeleteObject(hBrush);
ReleaseDC(GetDesktopWindow,DeskDC);
TimerStr := DisText;
Timer1.Tag := 0;
Timer1.Interval := 700;
Timer1.Enabled := True;
end;


function JHookProc(Code:integer; wParam: Longint; var EventStrut: TEVENTMSG): Longint; stdcall;
begin
{this is the JournalRecordProc}
Result := CallNextHookEx( JHook, Code, wParam, Longint(@EventStrut) );
if Code < 0 then Exit;

{you should cancel operation if you get HC_SYSMODALON}
if Code = HC_SYSMODALON then SysModal := True
else if Code = HC_SYSMODALOFF then SysModal := False;

if Code = HC_ACTION then
  begin
  if SysModal then Exit;
  if EventStrut.message = WM_KEYDOWN then
  begin
  if (EventStrut.paramL = 17923) {17923 is for Ctrl+Break}
  or (Chr(LOBYTE(LOWORD(EventStrut.paramL)))='Q') and (GetKeyState(VK_CONTROL) < 0) then
    Form1.sbut_EndJourClick(Form1)  {Ctrl+Q and Ctrl+Break will End the Journal record}
  end  else if (Chr(LOBYTE(LOWORD(EventStrut.paramL)))='S') and (GetKeyState(VK_CONTROL) < 0) then
    begin
    {Ctrl+S will start the journal record}
    if Track = False then
      begin
      StartTime := GetTickCount;
      {It is nessarry to subtract the TickCount to get the amount of ticks between events
      I do not use the Event Time in my playback Proc, but you might want to}
      FirstGo := 0;
      {FirstGo is used to remove the Ctrl and S key up events}
      Form1.DrawDesk('JOURNAL NOW RECORDING'#10'Press Ctrl+q to Stop', False);
      end;
    Track := True;
    end;
If Track then
    begin
    if NumMsgs < 1024 then
    {I have limited the event log to 1024 for NO reason
    except for those who forget or don't know how to stop
    the the journaling}
      begin
      if EventStrut.message = WM_MOUSEMOVE then
        begin
        {I found the 1000's of mouse move messages to be
        totally useless, so I skip all the Mouse Move messages
        except the Last one before another type of event}
        TempEvMsg := EventStrut;
        GotMove := True;
        end else
        begin
        if GotMove then
          begin
          GotMove := False;
          Form1.EventMsgArray[NumMsgs] := TempEvMsg;
          Dec(Form1.EventMsgArray[NumMsgs].time,StartTime);
          Inc(NumMsgs);
          SetLength(Form1.EventMsgArray, NumMsgs+1);
          end;
        if FirstGo < 2 then
        if (EventStrut.message = WM_KEYUP) then
          begin
          {this prevents the Ctrl and s key up from being recorded}
          if EventStrut.paramL = 7441 then Inc(FirstGo);
          {7441 is Ctrl key}
          if Chr(LOBYTE(LOWORD(EventStrut.paramL))) = 'S' then Inc(FirstGo);
          Exit
          end else FirstGo := 2;
        Form1.EventMsgArray[NumMsgs] := EventStrut;
        Dec(Form1.EventMsgArray[NumMsgs].time,StartTime);
        Inc(NumMsgs);
        SetLength(Form1.EventMsgArray, NumMsgs+1);
        end;
      end;
    end;
  end;
end;

procedure TForm1.sbut_StartJourClick(Sender: TObject);
var
Rect1: TRect;
begin
{this button click starts the Journal Record hook}
if Track then
  begin
  Messagebox(0,'Mouse tracking has already been started', 'No can Restart', MB_OK or MB_ICONQUESTION);
  Exit;
  end;

GotMove := False;
FirstGo := 0;
JHook := SetWindowsHookEx(WH_JOURNALRECORD , @JHookProc, 0{hInstance}, 0);

if JHook > 0 then
  begin
  NumMsgs := 1;
{set NumMsgs to 1 because the first EventMsgArray is used for
file verification and NOT for events}
  Track := False;
  SysModal := False;
  SetLength(EventMsgArray,2);

  {If the user has changed screen Resolution OR moved the task bar
  then the Position of mouse clicks will NOT be correct, So get the
  Work Area and record it, so you can compare it later for playback}
  if not SystemParametersInfo(SPI_GETWORKAREA,0,@Rect1,0) then
  Rect1 := Rect(0,0,Screen.Width,Screen.Height - 32);
  EventMsgArray[0].message := Rect1.Left;
  EventMsgArray[0].paramL := Rect1.Top;
  EventMsgArray[0].paramH := Rect1.Right;
  EventMsgArray[0].time := Rect1.Bottom;
  EventMsgArray[0].hwnd := 65432;
  Perform(WM_SYSCOMMAND,SC_MINIMIZE or SC_ICON,0);
  DrawDesk('JOURNAL RECORD READY'#10'Press Ctrl+s to Start', False);
  end else
  ShowMessage('Journal Hook could not be set');
end;

procedure TForm1.sbut_EndJourClick(Sender: TObject);
var
i: Integer;
begin
{This is an Emergency Stop Journaling button click}
Track := False;
if JHook < 1 then
NumMsgs := 0 else
UnhookWindowsHookEx(JHook);
Timer1.Enabled := False;
RedrawWindow(0,@WARect,0, RDW_INVALIDATE or RDW_ERASE or RDW_ALLCHILDREN or RDW_ERASENOW);
SetForegroundWindow(Handle);
Perform(WM_SYSCOMMAND,SC_RESTORE,0);
Application.ProcessMessages;

Sleep(200);
JHook := 0;
if NumMsgs >1 then
  begin
  ListBox1.Clear;
    {start the for loop with 1 because the 0 if for Work Area verification}
  for i := 1 to NumMsgs-1 do
    begin
    case EventMsgArray[i].message of
      WM_LBUTTONDOWN: ListBox1.Items.Add('L Down '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
      WM_LBUTTONUP: ListBox1.Items.Add('L UP '+IntToStr(LOWORD(EventMsgArray[i].paramL))+'  '+IntToStr(HIWORD(EventMsgArray[i].paramL)));
      WM_MOUSEMOVE: ListBox1.Items.Add('Move '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
      WM_RBUTTONDOWN: ListBox1.Items.Add('R Down '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
      WM_RBUTTONUP: ListBox1.Items.Add('R UP '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
      WM_KEYDOWN: ListBox1.Items.Add('Key Down '+IntToStr(LOBYTE(LOWORD(EventMsgArray[i].paramL)))+'  '+IntToStr(EventMsgArray[i].paramH));
      WM_KEYUP: ListBox1.Items.Add('Key UP '+IntToStr(LOBYTE(LOWORD(EventMsgArray[i].paramL)))+'  '+IntToStr(EventMsgArray[i].paramH));
      else ListBox1.Items.Add('Other '+IntToStr(EventMsgArray[i].message)+'  '+IntToStr(EventMsgArray[i].paramH));
      end;
    end;

      while EventMsgArray[NumMsgs-1].paramL = 7441 do
      begin
      {This removes the Ctrl key mouse down repeat messages}
      Dec(NumMsgs);
      if NumMsgs < 3 then Break;
      end;

  end else
  ShowMessage('No Journal Messages are recorded');

end;

procedure TForm1.sbut_SaveJFileClick(Sender: TObject);
var
  SaveFile: File of TEventMsg;
  i: Integer;
  FileName: String;
begin
if NumMsgs > 1 then
  begin
  FileName := 'C:\Stuff\Play Journal1.pbj';
  AssignFile(SaveFile, FileName);
  Rewrite(SaveFile);
  for i := 0 to NumMsgs-1 do
  Write(SaveFile,EventMsgArray[i]);
  CloseFile(SaveFile);
  end;

end;

procedure TForm1.sbut_LoadJFileClick(Sender: TObject);
var
  FileName: String;
  LoadFile: File of TEventMsg;
  i: Integer;
  Rect1: TRect;
begin
FileName := 'C:\Stuff\Play Journal1.pbj';
if FileExists(FileName) then
  begin
  AssignFile(LoadFile, FileName);
  Reset(LoadFile);
  NumMsgs := FileSize(LoadFile);
  if NumMsgs > 1 then
    begin
    SetLength(EventMsgArray, NumMsgs);
    for i := 0 to NumMsgs-1 do
    Read(LoadFile,EventMsgArray[i]);
    CloseFile(LoadFile);

    {this is to check the file to see if it is a valid .ppj file for this
    version AND to see if the DeskTop work Area matches the file's work area}
    if not SystemParametersInfo(SPI_GETWORKAREA,0,@Rect1,0) then
    Rect1 := Rect(0,0,Screen.Width,Screen.Height - 32);
    if EventMsgArray[0].hwnd <> 65432 then
      begin
      {the 65432 is a number just to verify that this is a valid .pbj file}
      SetLength(EventMsgArray, 0);
      ShowMessage('the file "'+FileName+'" is NOT an Event File compatible with this version. No Playback is availible');
      end else
    {check to see if the Desktop work area matches}
      if (abs(EventMsgArray[0].message) <> Rect1.Left) or
      (abs(EventMsgArray[0].paramL) <> Rect1.Top) or
      (abs(EventMsgArray[0].paramH) <> Rect1.Right) or
      (abs(EventMsgArray[0].time) <> Rect1.Bottom) then
        begin
        SetLength(EventMsgArray, 0);
        ShowMessage('the DeskTop Dimentions are NOT compatible with this Playback File. No Playback is availible');
        end else
        begin
        ListBox1.Clear;
        for i := 1 to NumMsgs-1 do
          begin
          {this adds the messages to a memo, just to check in a practice version}
            case EventMsgArray[i].message of
              WM_LBUTTONDOWN: ListBox1.Items.Add('L Down '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
              WM_LBUTTONUP: ListBox1.Items.Add('L UP '+IntToStr(LOWORD(EventMsgArray[i].paramL))+'  '+IntToStr(HIWORD(EventMsgArray[i].paramL)));
              WM_MOUSEMOVE: ListBox1.Items.Add('Move '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
              WM_RBUTTONDOWN: ListBox1.Items.Add('R Down '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
              WM_RBUTTONUP: ListBox1.Items.Add('R UP '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
              WM_KEYDOWN: ListBox1.Items.Add('Key Down '+IntToStr(LOBYTE(LOWORD(EventMsgArray[i].paramL)))+'  '+IntToStr(EventMsgArray[i].paramH));
              WM_KEYUP: ListBox1.Items.Add('Key UP '+IntToStr(EventMsgArray[i].paramL)+'  '+IntToStr(EventMsgArray[i].paramH));
              else ListBox1.Items.Add('Other '+IntToStr(EventMsgArray[i].message)+'  '+IntToStr(EventMsgArray[i].paramH));
            end;
          end;
        end;
    end;
  end;
end;


procedure TForm1.Timer1Timer(Sender: TObject);
begin
If Timer1.Tag = 0 then
  begin
  DrawDesk(TimerStr, True);
  Timer1.Tag := 1;
  Timer1.Interval := 300;
  end else
  begin
  DrawDesk(TimerStr, False);
  Timer1.Tag := 0;
  Timer1.Interval := 800;
  end;
end;

function PlaybackProc(Code:integer; wParam: Integer; var EventStrut: TEVENTMSG): Integer; stdcall;
begin
{this journal playback function is very difficult to understand.
Each event in the EventMsgArray is called here 2 or 3 times or more,
first with a HC_GETNEXT code to get a delay Result, if there is a Delay
Result larger than 0,then the event is NOT Fired, and a HC_GETNEXT is
called again after the delay period. If the Delay Result is 0 then the event
is fired and the HC_SKIP code is called, which advances the PlayNum to the
next EventMsgArray and then it does the HC_GETNEXT to start the loop again.}
Result := 0;

if Code < 0 then
  begin
  {if code is less than 0 pass it}
  Result := CallNextHookEx( JHook, Code, wParam, Integer(@EventStrut) );
  Exit;
  end;

if Code = HC_SKIP then
  begin
  if SysModal then Exit;
  Inc(PlayNum);
  DoDelay := True;
  {set this to true to get a Delay in the HC_GETNEXT}
  if PlayNum >= NumMsgs-1 then
    begin
    {this ends the playback}
    UnhookWindowsHookEx(JHook);
    Form1.Timer1.Enabled := False;
    Sleep(700);
    InvalidateRect(0,@WARect, True);
    SetForegroundWindow(Form1.Handle);
    Form1.Perform(WM_SYSCOMMAND,SC_RESTORE,0);
    end;
  Exit;
  end;

if Code = HC_GETNEXT then
  begin
  if SysModal then Exit;
  Move(Form1.EventMsgArray[PlayNum], EventStrut, SizeOf(TEVENTMSG));
  if DoDelay then
    begin
    Result := 200;
    {I tried lots of different combinations of setting the
    delay Result, but for playback using a constant between 200 and 500
    seemed best. This eliminates user slow typing and slow clicking}

    //Result := Form1.EventMsgArray[PlayNum].time - GetTickCount;
    {Setting the Result to time of EventStrut minus TickCount will simulate the
    event timing, more or less}
    DoDelay := False;
    end; {else
  EventStrut.time := 300{EventStrut.time+ StartTime};
  {I tried setting this time to different values, but it seems to
  have little or No effect}

  {unlike many other windows system Proc, this uses the memory value
  in the Proc parameter EventStrut (lParam), when this function returns.
  Notice that it is declared as a var. You do NOT need to to use
  CallNextHookEx, because this is not used like other Hooks, to get messages,
  this fires mouse and keyboard events outside of a "hook Chain"}
  Exit;
  end else
if Code = HC_SYSMODALON then  SysModal := True
else if Code = HC_SYSMODALOFF then
  begin
  SysModal := False;
  end;

CallNextHookEx( JHook, Code, wParam, Integer(@EventStrut));
{you don't need this CallNextHookEx here but it is standard for hook proc
the Journal hook is different is not in a hook Chain}
end;

procedure TForm1.sbut_PlayJourClick(Sender: TObject);
begin
ShowMessage('NumMsgs '+IntToStr(NumMsgs));
if NumMsgs > 0 then
  begin
  PlayNum := 1;
  DoDelay := False;
  ReleaseCapture;
  SysModal := False;
  Form1.Perform(WM_SYSCOMMAND,SC_MINIMIZE or SC_ICON,0);
  DrawDesk('JOURNAL PLAYBACK'#10'Please Wait', False);
  StartTime := GetTickCount;
  JHook := SetWindowsHookEx(WH_JOURNALPLAYBACK , @PlaybackProc, 0, 0);
  end;

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
UnhookWindowsHookEx(JHook);
end;
0
 

Expert Comment

by:CleanupPing
ID: 9316936
Mandieb:
This old question needs to be finalized -- accept an answer, split points, or get a refund.  For information on your options, please click here-> http:/help/closing.jsp#1 
EXPERTS:
Post your closing recommendations!  No comment means you don't care.
0
 
LVL 5

Expert Comment

by:snehanshu
ID: 10033489
Hi!
No comment has been added lately and this question is therefore classified abandoned.

If asker wishes to close the question, then refer to
http://www.experts-exchange.com/help/closing.jsp

Otherwise, I will leave a recommendation in the Cleanup topic area that this question is:

Answered by: Slick812

Please leave any comments here within the next seven days. It is assumed that any participant not responding to this request is no longer interested in its final disposition.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!

...Snehanshu
EE Cleanup Volunteer
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
NetCrunch network monitor is a highly extensive platform for network monitoring and alert generation. In this video you'll see a live demo of NetCrunch with most notable features explained in a walk-through manner. You'll also get to know the philos…
Michael from AdRem Software explains how to view the most utilized and worst performing nodes in your network, by accessing the Top Charts view in NetCrunch network monitor (https://www.adremsoft.com/). Top Charts is a view in which you can set seve…
Suggested Courses

764 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question