Link to home
Start Free TrialLog in
Avatar of evansj
evansj

asked on

Remembering and sending keystrokes to a spawned app

This is a two part question. How do you spawn another app from within a delphi app and send keystrokes to it? Example, spawn a paint program and send it the key strokes to convert a 24bit picture to an 8bit picture.
Avatar of wmckie
wmckie

Is the example of changing the number of bits per pixcel what your actualy wanting to do, if so would it not be better to load the picture (e.g. with an instance of TBitmap) change its bits/pixel and save it again?

Walter McKie
Generally, spawning another application and control it then from within the starter application is very unreliable and therefor only recommendable for old software which doesn't know about COM or at least DDE. I recommend to use automation to control another application if it is necessary or (in the case your true problem is really only to convert a picture color resolution) to use Delphi's features.

Ciao, Mike
If you still want to record and play back keystrokes you can experiment with the JournalRecordProc and JournalPlaybackProc API calls.

Alternatively you can use something the keybd_event function to send keystrokes directly (you'll need the handle of the window to send them to). If you specify your problem in more detail I'll be happy to give you a code example of how to use keybd_event.

runebj
If you want to send keystrokes to an app, try this lot:

PROCEDURE SendKeys(sText : STRING);
VAR
  iCount    : INTEGER;
  bShift    : BOOLEAN;
  wVK       : WORD;
  wScancode : WORD;
  ch        : CHAR;
  byC       : BYTE;
  byS       : BYTE;
CONST
  vk_keys : ARRAY[0..9] OF BYTE = (VK_HOME, VK_END, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_PRIOR, VK_NEXT, VK_INSERT, VK_DELETE);
  vk_shft : ARRAY[0..2] OF BYTE = (VK_SHIFT, VK_CONTROL, VK_MENU);
  flags   : ARRAY[FALSE..TRUE] OF INTEGER = (KEYEVENTF_KEYUP, 0);
BEGIN
  bShift := FALSE;
  FOR iCount := 1 TO LENGTH(sText)
  DO
  BEGIN
    ch := sText[iCount];
    IF ch >= #250
    THEN
    BEGIN
      byS := ORD(ch) - 250;
      bShift := NOT(ODD(byS));
      byC := vk_shft[byS SHR 1];
      wScancode := MapVirtualKey(byC, 0);
      Keybd_Event(byC, wScancode, flags[bShift], 0);
    END
    ELSE
    BEGIN
      wVK := 0;
      IF ch >= #240
      THEN
        byC := vk_keys[ORD(ch) - 240]
      ELSE
      BEGIN
        IF ch >= #228
        THEN
          byC := ORD(ch) - 116
        ELSE
        BEGIN
          IF ch < #32
          THEN
            byC := ORD(ch)
          ELSE
          BEGIN
            wVK := VkKeyScan(ch);
            byC := LoByte(wVK);
          END;
        END;
      END;

      wScancode := MapVirtualKey(byC, 0);
      IF NOT(bShift) AND (Hi(wVK) > 0)
      THEN
        Keybd_Event(VK_SHIFT, $2A, 0, 0);
      Keybd_Event(byC, wScancode, 0, 0);
      Keybd_Event(byC, wScancode, KEYEVENTF_KEYUP, 0);
      IF NOT(bShift) AND (Hi(wVK) > 0)
      THEN
        Keybd_Event(VK_SHIFT, $2A, KEYEVENTF_KEYUP, 0);
    END;
    Application.ProcessMessages;
  END;
END;

Just make sure that Word (or whatever) is the focused window and you'll have no problem! (Use char-codes to send system keys such as tab, enter, arrow keys etc.).

To actually get your app running, use ShellExecute or whatever. e.g.

ShellExecute(Form1.Handle, 'open', 'Notepad.exe', '', '', SW_SHOWNORMAL);

This should get notepad up and running (maybe) and make it the application in focus, so you can send your keystrokes directly to it

The Neil
The TJPEGImage (on delphi cd)component is very good at color reducing a full color image down to 256 colors.
see the example below and instead of deleting the tempfile delete the opendialog filename ,and you can always assign the new jpg image back to a bitmap if wanted..

procedure TForm1.Button1Click(Sender: TObject);
var
  JP : TJPEGImage;
  IM : TImage;
  TempFileName : string;
begin
{Pop up a Open Dialog}
  OpenDialog1.Options := [ofNoChangeDir, ofFileMustExist];
  OpenDialog1.Filter := 'Bitmap Files (*.bmp)|*.bmp';
  if OpenDialog1.Execute then begin
  {Create a temporary TImage}
    IM := TImage.Create(nil);
  {Load the bitmap file}
    IM.Picture.LoadFromFile(OpenDialog1.FileName);
  {Create a temporary TJPEGImage}
    JP := TJPEGImage.Create;
  {Priorty on quality}
    JP.Performance := jpBestQuality;
  {Assign the bitmap to the JPEG}
    JP.Assign(IM.Picture.Graphic);
  {Free the temp image}
    IM.Free;
  {Make a temp file name with the extension of .jpg}
    TempFileName := 'test.jpg';
  {Save the JPEG to a temp file}
    JP.SaveToFile(TempFileName);
  {Free the JPEG}
    JP.Free;
  {Load the temp file to an image on the form}
    Image1.Picture.LoadFromFile(TempFileName);
  {Delete the temp file}
    DeleteFile(TempFileName);
  end;
end;
 
or see here:
http://www.efg2.com/lab/Graphics/Colors/CombinePf4bit.htm

or if want to use the keystroke stuff instead i suggest looking at:

http://community.borland.com/article/0,1410,10323,00.html 

Regards Barry
Avatar of evansj

ASKER

I need to change a 24bit TGA file to an 8bit BMP file with gamma correction. If I can do it in D3 or D4 that's fine. If there are some graphic controls to do this, OK also.
Hi
take a look at tga on this page:

http://www.efg2.com/lab/library/delphi/graphics/FileFormatsAndConversion.htm#Conversions

there are some units there that will help.
Avatar of evansj

ASKER

What header file contains the ShellExecute Method?
shellapi is the one your after.
For reading TGA files you can download my GraphicEx unit from www.lischke-online.de. It will read in a TGA file into a TBitmap (and makes so the conversion you are looking for) but does no gamma correction.

Ciao, Mike
Avatar of evansj

ASKER

What header file contains the ShellExecute Method?
Avatar of evansj

ASKER

What header file contains the ShellExecute Method?
As inthe said, you need the ShellApi file (or is this a wonderful internet/microsoft repeat problem - grrrr)

The Neil =:)
Avatar of evansj

ASKER

Wonderful repeat problem...
Wonderful repeat problem...
Wonderful repeat problem...
Wonderful repeat problem...
Wonderful repeat problem...
Avatar of evansj

ASKER

To TheNeil: give me an example of sending say ALT-F, Alt-0, then a file name then Close.
Avatar of evansj

ASKER

To Lischke:
How would you load a TGA using your control?
Avatar of evansj

ASKER

To Lischke:
How would you load a TGA using your control?
ASKER CERTIFIED SOLUTION
Avatar of Lischke
Lischke

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Here's a demo for Notepad. What it'll do is add some text ('Some text'), save it to a new file (newfile.txt), and then close the application

SendKeys('Some Text');
SendKeys(CHR(VK_Menu) + 'F');
SendKeys('S');
SendKeys('NewFile.txt');
SendKeys(CHR(VK_TAB) + CHR(VK_TAB) + CHR(VK_RETURN));
SendKeys(CHR(VK_MENU) + 'F');
SendKeys('X');

The Neil
Here's a demo for Notepad. What it'll do is add some text ('Some text'), save it to a new file (newfile.txt), and then close the application

SendKeys('Some Text');
SendKeys(CHR(VK_Menu) + 'F');
SendKeys('S');
SendKeys('NewFile.txt');
SendKeys(CHR(VK_TAB) + CHR(VK_TAB) + CHR(VK_RETURN));
SendKeys(CHR(VK_MENU) + 'F');
SendKeys('X');

The Neil
Avatar of evansj

ASKER

To Lischke:
How would you load a TGA using your control?
Avatar of evansj

ASKER

To TheNeil: I tried sending the following, and all I got in the notepad windows was this FONewFile.txtO:

SendKeys(CHR(VK_Menu) + 'F');
SendKeys('O'); //open file
SendKeys('NewFile.txt'); //file name
SendKeys(CHR(VK_Menu) + 'O');//open file


Avatar of evansj

ASKER

To TheNeil: I tried sending the following, and all I got in the notepad windows was this FONewFile.txtO:

SendKeys(CHR(VK_Menu) + 'F');
SendKeys('O'); //open file
SendKeys('NewFile.txt'); //file name
SendKeys(CHR(VK_Menu) + 'O');//open file


Avatar of evansj

ASKER

To Lischke:
I put the reference to GraphicEx in the uses clause. I get an error when I try to load the TGA. It tells me unknown file extention (D3).
Err...it works for me. What I've done in my sample app is ShellExecute Notepad, set a timer running (for 1 second) then when the timer fires do your SendKeys calls.

By the way, you'll need to add the following line after your SendKeys('NewFile.txt');

SendKeys(CHR(VK_RETURN));

Otherwise it won't 'click' the Ok button on the open dialog

The Neil
Avatar of evansj

ASKER

To TheNeil: I tried sending to Notepad ok. However, when I tried to send keystrokes to other apps (Paintshop Pro, Photoshop,etc) nothing happens. What about sending mouse clicks instead?
Avatar of evansj

ASKER

Adjusted points to 300
Avatar of evansj

ASKER

Common you gurus, help me out (if you can).
Avatar of evansj

ASKER

Ok runebj: What's your solution?
Your first problem is to get the handle of the window that you want to send keystrokes to. You can get this by using a few different API calls.

The FindWindow API call is probably your best bet. I haven't used it too much so you'll need to do some experimenting with it. You have to know the windows class name or caption to use this funtion.

If you know the location of the window you can use WindowFromPoint.

Lastly you can use GetForegroundWindow to get the handle of the current window with focus.

Assuming that you have the window handle, this code (taken from my "virtual keyboard") will send keystrokes to any app:

type
  TKeypress   = record
                  CharCode: word;
                  Shift   : boolean;
                  Ctrl    : boolean;
                  Alt     : boolean;
                end;

procedure SendKey(pKeys: array of TKeypress);
var
  i          : integer;
  lThisThread: THandle;
  lThread    : THandle;
  lProcessID : cardinal;
begin
  //fForeground is the handle of the window you want to send keystrokes to
  if fForeground <> 0 then
  begin
    //fWinHandle is the handle of this window (sending from)
    if fWinHandle = 0 then
      fWinHandle := Parent.Handle;
    lThisThread := GetWindowThreadProcessID( fWinHandle, @lProcessID );
    lThread := GetWindowThreadProcessID( fForeground, @lProcessID );
    if AttachThreadInput( lThisThread, lThread, True ) then
    begin
      for i := 0 to Length( pKeys ) - 1 do
      begin
        if pKeys[ i ].Shift then Keybd_Event( VK_Shift, MapVirtualKey( VK_Shift, 0), 0, 0 );
        if pKeys[ i ].Ctrl then Keybd_Event( VK_Control, MapVirtualKey( VK_Control, 0), 0, 0 );
        if pKeys[ i ].Alt then Keybd_Event( VK_Menu, MapVirtualKey( VK_Menu, 0), 0, 0 );
        Keybd_Event( pKeys[ i ].CharCode, MapVirtualKey( pKeys[ i ].CharCode, 0 ), 0, 0 );
        Keybd_Event( pKeys[ i ].CharCode, MapVirtualKey( pKeys[ i ].CharCode, 0 ), KeyEventF_KeyUp, 0 );
        if pKeys[ i ].Alt then Keybd_Event( VK_Menu, MapVirtualKey( VK_Menu, 0), KeyEventF_KeyUp, 0 );
        if pKeys[ i ].Ctrl then Keybd_Event( VK_Control, MapVirtualKey( VK_Control, 0), KeyEventF_KeyUp, 0 );
        if pKeys[ i ].Shift then Keybd_Event( VK_Shift, MapVirtualKey( VK_Shift, 0), KeyEventF_KeyUp, 0 );
      end;
      AttachThreadInput( lThisThread, lThread, False );
    end;
  end;
end;

Before you call this function make sure to use "SetForegroundWindow( Handle )" in the calling procedure. This creates enough delay to let the foreground window get focus before you send keystrokes. If you have problems with the first keystrokes, then you need to add a delay of a few milliseconds before sending keystrokes.

The KeyPress structure contains the character code (virtual keykode) plus three booleans for shift, ctrl and alt. If you want to send Alt+F set keycode to "F" and the Alt flag to true.

Good luck
runebj
Avatar of evansj

ASKER

To Lischke:
I sucessfully loaded the GraphicEx library. I can load a TGA, descrease the color depth, and save it. How would you resize it to 640x480 and save it as a bitmap using your library?
Avatar of evansj

ASKER

To Lischke:
Never mind, I got it working. But does anyone know how to set the gamma correction on a bitmap?
Thank you, evansj, for the points. I've seldom seen such a respondible questioneer like you. Often the experts are discussion without ever getting a comment from the one who asked the question :-)

For image processing I still recommend Earl F. Glynns graphics page (http://www.efg2.com/lab/). I have not found code for gamma correction there, though, and I have to admit that I'm not so familiar with this as I am with other graphics stuff. After a short discussion with another programmer here in my company I think I got the basic idea.

Actually, gamma correction is a mapping from an input color value to a new output color value. As the range is always 0..255 you can easily use a looup table. Fill it with values from a curve (e.g. created by the user) and then iterate through all pixels and color parts and replace them accordingly. The usual way, though, is to use:

  O := pow(I, 1/gamma)

(with I and O in 0..1) to construct the LUT from. With this formula 0 maps always to 0 and 1 maps always to 1 (as so often seen with this little nifty square graph in various screen settings dialogs or Photoshop).

Ciao, Mike
Have a look at www.jps.net/gfody (I found this link from Earl F Glynn's reference lists) there are a couple of components and demos all with source, the Almost Photoshop demo has a gamma correction ability. This may not be exactly what you need but there may be some clues in the source.

Cheers - Walter McKie
Avatar of evansj

ASKER

To Lischke :
How would you optimize a 256 color reduction of a 24bit TGA? I can reduced the pixelformat to pf8bit, but the color optimization is windows standard 20 colors. I need some code snippets for using the GraphicEx to color optimize the palette before I save the reduced color bitmap (IYKWIM).