Link to home
Start Free TrialLog in
Avatar of czechmate
czechmate

asked on

How to access selected text in other applications

Hello,
Example: I am using a dictionary application "WordWebPro".  This app sits in a system tray waiting for me to select a text (typically a word) in any application (except edits on html forms) and to hit a hot key.  It then wakes up with the selected text copied into its own edit.  If the selected text comes from an application like Word, Notepad or an edit component I can make a correction and the corrected text replaces the original text.

So, how does one go about doing the same thing?  
cj
Avatar of Ivanov_G
Ivanov_G
Flag of Bulgaria image

http://www.delphi.about.com/library/weekly/aa060303a.htm

You can find this useful for reading information from other applications.
Avatar of czechmate
czechmate

ASKER

It is an interesting article, but I cannot imagine this would be the solution for accessing text in any editable window.  I guess the way to access text in other apps must be via finding focused control in foreground window.  As I say it is just a guess:)
cj
Use keyboard events:

This example copies the selected text in edit1 to the richeditbox when enter key is pressed (normally your hotkey)

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,Clipbrd, ComCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    RichEdit1: TRichEdit;
    procedure Edit1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
if key=vk_return then
begin
keybd_event(VK_CONTROL,0,0,0);
keybd_event(ord('C'),0,0,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
keybd_event(ord('C'),0,KEYEVENTF_KEYUP,0);
richedit1.Clear;
richedit1.PasteFromClipboard;
end;

end;

end.

Then depending on what you want to do with the collected data you eventually paste something back (modify contents of richedit):

richedit.copytoclipboard;
keybd_event(VK_CONTROL,0,0,0); //paste back into field.
keybd_event(ord('V'),0,0,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
keybd_event(ord('V'),0,KEYEVENTF_KEYUP,0);

You must not lose the focus of the application.

For a hotkey control the Jedi JVCL free component library is good. If you use it the control is located under the JvWin32 tab : http://sourceforge.net/project/showfiles.php?group_id=45786 

Regards,

Hypoviax
Here is a complete working one i coded. It uses a different method for the hotkey rather than a component (reads for a key state). You need 2 richedit controls. :

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    RichEdit1: TRichEdit;
    RichEdit2: TRichEdit;
    procedure Timer1Timer(Sender: TObject);
    procedure RichEdit1Change(Sender: TObject);
  private
    { Private declarations }
  public
    control:string;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if ((GetAsyncKeyState(VK_F12) and 1) = 1) then //if the user presses F12
  begin
    keybd_event(VK_CONTROL,0,0,0);
    keybd_event(ord('C'),0,0,0);
    keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
    keybd_event(ord('C'),0,KEYEVENTF_KEYUP,0);
    richedit1.Clear;
    richedit1.PasteFromClipboard; //load contents
    control:=richedit1.Text;
  end;
end;

procedure TForm1.RichEdit1Change(Sender: TObject);
begin
if richedit2.Text=control then
exit
else
richedit2.Text:='NEW TEXT'; //Process word here
richedit2.SelectAll;
richedit2.CopyToClipboard; //Copy processed data
keybd_event(VK_CONTROL,0,0,0); //paste back into field.
keybd_event(ord('V'),0,0,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
keybd_event(ord('V'),0,KEYEVENTF_KEYUP,0);
end;

end.

One little bug seems to be you need to press F12 twice - probably something to do with the richedit control

Best Regards,

Hypoviax
Get rid of the control variable you don't need it:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if ((GetAsyncKeyState(VK_F12) and 1) = 1) then
  begin
    keybd_event(VK_CONTROL,0,0,0);
    keybd_event(ord('C'),0,0,0);
    keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
    keybd_event(ord('C'),0,KEYEVENTF_KEYUP,0);
    richedit1.Clear;
    richedit1.PasteFromClipboard;

  end;
end;

procedure TForm1.RichEdit1Change(Sender: TObject);
begin
richedit2.Text:='NEW TEXT';
richedit2.SelectAll;
richedit2.CopyToClipboard;
keybd_event(VK_CONTROL,0,0,0); //paste back into field.
keybd_event(ord('V'),0,0,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
keybd_event(ord('V'),0,KEYEVENTF_KEYUP,0);
end;

Hypoviax
ASKER CERTIFIED SOLUTION
Avatar of Molando
Molando

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
Sorry guys I have not come back earlier, down with flu and not enjoying it.

Hypoviax, I thought your solution was about accessing text betweeen two controls in a single application.  Anyway it is almost the same as the Molando's comment.  I based my test on Molando's code.  I think it is the right way to go about it, but I get these bugs with HotKeys.  

If I have a combination of any keys with an Alt key then copy from foreground app does not work at all.  The other combinations copy text OK, that is until I paste the text back to foreground window.  It does it once then the copy and paste stops working altogether.

Here's my code, can you have a look at it?  My system, D5 on Win98se, is a bit shaky right now and I thought maybe that was the reason but when I ran the Dictionary App it works happily with Alt/Ctrl/W key combination.

I also put the RegisterHotKey function into if/then statement, it always registers without problem.

Here's the code anyway:

~~~~~~~~~~~~~~~~~~~~~~~~~~
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs,  StdCtrls, clipbrd, ComCtrls, Buttons, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    SpeedButton1: TSpeedButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private declarations }
     procedure WMHotKey(var Msg : TWMHotKey); message WM_HOTKEY;
  public
    { Public declarations }
  end;


var
  Form1: TForm1;
  FgWND : HWND;
implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
RegisterHotKey(Handle, 101, 0, VK_INSERT);
//works until and including the 1st Paste back thereafter
//the foreground window does not copy.
//-------------
//RegisterHotKey(Handle, 101, 0, ord('E'));
//same as above
//-------------
//RegisterHotKey(Handle, 101, MOD_ALT,     ord('E'))
//does not work - foreground window does not copy
//-------------
//RegisterHotKey(Handle, 101, MOD_CONTROL, ord('E'));
//works until 1st Pasteback....as above
//-------------
//RegisterHotKey(Handle, 101, MOD_SHIFT,   ord('E'));
//works until 1st Pasteback....as above
//-------------
//RegisterHotKey(Handle, 101, MOD_CONTROL or MOD_ALT,   ord('E'))
//does not work - foreground window does not copy
//-------------
//RegisterHotKey(Handle, 101, MOD_CONTROL OR MOD_SHIFT, ord('E'));
//works until 1st Pasteback....as above
//-------------
//RegisterHotKey(Handle, 101, MOD_SHIFT OR MOD_ALT,     ord('E'));
//does not work - foreground window does not copy
//-------------
end;

procedure TForm1.WMHotKey (var Msg : TWMHotKey);
begin
   if Msg.HotKey = 101 then
   begin
      FgWND := GetForegroundWindow;
      if FgWND <> 0 then
      begin //probably no reason to do this, but why not
         keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), 0, 0);
         keybd_event(ord('C'),  MapvirtualKey( ord('C'), 0 ), 0, 0 );
         keybd_event(ord('C'),  MapvirtualKey( ord('C'), 0 ), KEYEVENTF_KEYUP, 0);
         keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), KEYEVENTF_KEYUP, 0);
         Sleep(500); //give windows time to catchup

         Memo1.Clear;
         Memo1.PasteFromClipBoard;
         If IsIconic(Application.Handle) Then
            Application.Restore
         else
            Application.BringToFront;
      end;
   end;
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  UnRegisterHotkey(Handle, 101);
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
   Memo1.SelectAll;
   Memo1.CopyToClipboard;
   Memo1.Clear;
   SetForeGroundWindow(FgWND);
   sleep(250);

   keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), 0, 0);
   keybd_event(ord('V'),  MapvirtualKey( ord('V'), 0 ), 0, 0 );
   keybd_event(ord('V'),  MapvirtualKey( ord('V'), 0 ), KEYEVENTF_KEYUP, 0);
   keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), KEYEVENTF_KEYUP, 0);
end;

end.
No, you misunderstood me. My code:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if ((GetAsyncKeyState(VK_F12) and 1) = 1) then //if the user presses F12
  begin
    keybd_event(VK_CONTROL,0,0,0);
    keybd_event(ord('C'),0,0,0);
    keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
    keybd_event(ord('C'),0,KEYEVENTF_KEYUP,0);
    richedit1.Clear;
    richedit1.PasteFromClipboard; //load contents
    control:=richedit1.Text;
  end;
end;

effectively is another way of detecting a keypress. My code works globally. I tested it with notepad etc.
I'll explain my logic. The hotkey section (Molando's or mine) detects the key press. The data selected is copied and pasted into the richedit1 control. This is the data. The OnChange event starts and you manipulate the data that is in richedit1 and put it in richedit2. The data in richedit2 is copied back to the clipboard and pasted where the selected text is.

Regards,

Hypoviax
I'll have a look at your code

Hypoviax
Ok, Hypoviax I'll try it your way.  One thing though, isn't the GetAsyncKeyState function using lot of resources?  How often does your timer time out?
cj
The problem occurs whether you use the hotkey or my detection method. For some reason you need to press it twice for it to work. The following code illustrates using both methods, the hotkey and the detection.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, ComCtrls,clipbrd, Buttons;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    RichEdit1: TRichEdit;
    RichEdit2: TRichEdit;
    Button1: TButton;
    SpeedButton1: TSpeedButton;
    procedure Timer1Timer(Sender: TObject);
    procedure RichEdit1Change(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
     procedure WMHotKey(var Msg : TWMHotKey); message WM_HOTKEY;
  public
    Fgwnd:hwnd;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.WMHotKey (var Msg : TWMHotKey);   //insert the key
begin
   if Msg.HotKey = 101 then
   begin
      FgWND := GetForegroundWindow;
      if FgWND <> 0 then
      begin //probably no reason to do this, but why not
         keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), 0, 0);
         keybd_event(ord('C'),  MapvirtualKey( ord('C'), 0 ), 0, 0 );
         keybd_event(ord('C'),  MapvirtualKey( ord('C'), 0 ), KEYEVENTF_KEYUP, 0);
         keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), KEYEVENTF_KEYUP, 0);
         Sleep(500); //give windows time to catchup

         richedit1.Clear;
         richedit1.PasteFromClipBoard;
         If IsIconic(Application.Handle) Then
            Application.Restore
         else
            Application.BringToFront;
      end;
   end;
end;


procedure TForm1.Timer1Timer(Sender: TObject); //f12 the key
begin
if ((GetAsyncKeyState(VK_F12) and 1) = 1) then
  begin
fgwnd:=GetForegroundWindow;
    keybd_event(VK_CONTROL,0,0,0);
    keybd_event(ord('C'),0,0,0);
    keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
    keybd_event(ord('C'),0,KEYEVENTF_KEYUP,0);
    richedit1.Clear;
    richedit1.PasteFromClipboard;
    application.BringToFront;
  end;
end;

procedure TForm1.RichEdit1Change(Sender: TObject);
var manipulatedtext:string;
begin
if richedit1.Text='' then
exit
else
 begin
 richedit2.text:=richedit1.Text + 'ADDED';   //modify text
 clipboard.SetTextBuf(pansichar(richedit2.text));
 end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
SetForeGroundWindow(FgWND);
keybd_event(VK_CONTROL,0,0,0); //paste back into field.
keybd_event(ord('V'),0,0,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
keybd_event(ord('V'),0,KEYEVENTF_KEYUP,0);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  UnRegisterHotkey(Handle, 101);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
RegisterHotKey(Handle, 101, 0, VK_INSERT);
end;

end.

This problem would not really be a problem if you did not require the application to setfocus to view the text as you could just press the keys twice. Will look into it.

Regards,

Hypoviax
With the timer i set it to 1ms ;). I'll have a look at the resources.
Doesn't appear to be on my pc , 3364 kb and no movement in the CPU usage. I agree the hotkey is better, but  the problem is not in the hotkey it has something to do with either setting the apps window focus or something to do with the keyboard event. In my original i required to press it twice in order for it to work. I found i needed to do this with Molando's too

Regards,

Hypoviax
Hang on i found this:

Function ReadFromNotepad:string;
var h:hwnd;
    Text:string;
    NumCaracters:integer;
begin
h:=FindWindow('notepad',nil);
h:=FindWindowex(h,0,'edit',nil);
if h<>0 then begin
             NumCaracters:=SendMessage(h,wm_getTextLength,0,0);
             setlength(text,NumCaracters);
             SendMessage(h,wm_getText,NumCaracters+1,Integer(text));
             result:=text;
             end
        else result:='ERROR!';
end;

No copy and pasting involved. I'll see if i can modify it for any window
Try a slight modification of this. I ditched the last post couldn't get it to work.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Memo1: TMemo;
    procedure Timer1Timer(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    procedure WMHotKey(var Msg : TWMHotKey); message WM_HOTKEY;
  public
    fgwnd:hwnd;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function GetActiveChildWindow: HWND;
var
  h, h1: HWND;
begin
  h := GetForegroundWindow();
  if IsWindow(h) then begin
    repeat
      h1 := GetWindow(h, GW_CHILD);
      if IsWindow(h1) then begin
        if h1 <> h then h := h1;
      end else break;
    until h1 <> 0;
  end;
  result := h;
end;

function GetWindowText(h:HWND) : string;
var
 buf : string;
 len : integer;
begin
 try
   len := SendMessage(h, WM_GETTEXTLENGTH,0,0);
   SetLength(buf,len);
   SendMessage(h, WM_GETTEXT, len+1, Integer(buf));
   result := buf;
 except
   result := '';
 end;
end;



procedure TForm1.WMHotKey (var Msg : TWMHotKey);   //insert the key
         var
  s: string;
  h: HWND;
  StartPos, EndPos: integer;
  res: integer;
begin
   if Msg.HotKey = 101 then
   begin
      FgWND := GetForegroundWindow;
      if FgWND <> 0 then
      begin //probably no reason to do this, but why not

begin
  h := GetActiveChildWindow; // get active control
  if IsWindow(h) then begin
    s := GetWindowText(h); // get the whole text
    if Length(s) > 0 then begin
      res := SendMessage(h, EM_GETSEL, 0, 0); // get selected text coordinates
      StartPos := LOWORD(res);
      EndPos := HIWORD(res);
      s := Copy(s, StartPos, EndPos - StartPos); // get the selected text
      memo1.Text:=s;
      if Length(s) > 0 then begin // there is selected text
        s:= 'Hello'; // modify it
        SendMessage(h, EM_REPLACESEL, 0, Integer(PChar(s))); // replace the selection
      end;
    end;
  end;
end;
         If IsIconic(Application.Handle) Then
            Application.Restore
         else
            Application.BringToFront;
      end;
   end;
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 UnRegisterHotkey(Handle, 101);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
RegisterHotKey(Handle, 101, 0, VK_INSERT);
end;

end.
Better stick with the original as i can only get that to work in notepad
I'll look at this tomorrow it's getting kinda late here:)
Thanks
cj
Ok I am back, sorry about the delay I got sidetracked on something else.

Hypoviax you've put a lot in this and I appreciate it.  Unfortunately the latest stuff copies only from Edit based controls, I get nothing from Outlook Express, IE nor from Word.  So I am going to go for Molando solution, it is not perfect in the present form but I will pose other questions later.

However I don't want Hypoviax to walk empty from this, his original suggestion was not far off.  I propose to split the points at 150 each for Hypoviax and Molando.  Let me know guys if your are happy about it?
cj
That's fine by me :)

Regards,

Hypoviax
I have more time to help now i've finished my exams. If you still need more work on it i can try later and post it here

Regards,

Hypoviax
I think I'll shelve it for a moment.  There are confusing variations in behaviour with different Hotkey combinations:

-Insert key on its own works ok.  Ctrl/C copies from all apps, OE, IE, Word, Excel.
-Any key in any combination (well, almost:) with Alt key copies from edit based apps only, eg Notebook.
-Can't figure out all variations with Shift/Ctrl

Clipboard behaviour:
-keybd_event Ctrl/C:
As long as I hit the hotkey only (doing Ctrl/C) everything's OK.     The app pops up with selected text copied every time.

-keybd_event Ctrl/V:
depending on Hotkey definition it may or may not work.  But..! Repeating Ctrl/C event after Ctrl/V does the following:

1) Copies selected text to clipboard, checked with Clipboard Wiever.
2) Getting text from Clipboard causes Delphi error message 'Cannot open Clipboard'.
3) My application pops up empty.
4) If I hide the app and hit the hotkey again everything works normally.

I tried to close and open clipboard in strategic places, tried to find who's the owner and close it, lock it unlock it...to no avail.  I searched the web, nada.  So if you figure out how it works please let me know:)  My email is czechmate@fastmail.fm,  let me know when you have it, I'll pose a new question for you to answer.

Here's the last code:

~~~~~~~~~~~~~~~~~~~~~~~~~~~
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs,  StdCtrls, clipbrd, ComCtrls, Buttons, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    butPasteBack: TSpeedButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure butPasteBackClick(Sender: TObject);
  private
    { Private declarations }
     procedure WMHotKey(var Msg : TWMHotKey); message WM_HOTKEY;
     procedure SendCtrlKey(c:char);
  public
     FgWND : HWND;
    { Public declarations }
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
      RegisterHotKey(Handle, 101, 0, VK_INSERT);
end;

procedure TForm1.WMHotKey (var Msg : TWMHotKey);
var b:boolean;
begin
   if Msg.HotKey = 101 then
   begin
      FgWND := GetForegroundWindow;
      if FgWND <> 0 then
      begin
         
         SendCtrlKey('C');
         
         Sleep(250);

         //Memo1.SetFocus;
         Memo1.clear;
         try
            Memo1.Text := Clipboard.AsText;
         except
               // seems to suppress 'Cannot Open Clipboard'
               // message with Debugger setting
               //'Stop on Delphi Exception' unchecked
         end;

         If IsIconic(Application.Handle) Then
            Application.Restore;
         Application.BringToFront;
      end;
   end;
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  UnRegisterHotkey(Handle, 101);
end;

procedure TForm1.butPasteBackClick(Sender: TObject);
begin
   Memo1.SelectAll;
   Clipboard.AsText := Memo1.Text;
   SetForeGroundWindow(FgWND);//bring FgWND to foreground again
   
   SendCtrlKey('V');
   
   sleep(200);
end;

procedure TForm1.SendCtrlKey(c: char);
begin
   keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), 0, 0);
   keybd_event(ord(c),  MapvirtualKey( ord(c), 0 ), 0, 0 );
   keybd_event(ord(c),  MapvirtualKey( ord(c), 0 ), KEYEVENTF_KEYUP, 0);
   keybd_event(VK_Control,MapVirtualKey(VK_Control, 0), KEYEVENTF_KEYUP, 0);
end;

end.

Hypoviax, sorry, somehow I missed the "Assisted Answer" facility for a point split, so I am posting question for you "Points for Hypoviax", just grab it.

Regards
cj
Thanks,

I'll have a look at your final code to see if i can improve it in anyway ;-)

Best Regards,

Hypoviax
Thanks and regards
cj:)