Click here to Skip to main content
Click here to Skip to main content

Getting Caret Position Inside Any Application

By , 30 Mar 2009
 

Introduction 

Sometimes we need to know about the caret position inside another application for various purposes like showing Tooltip or message window along with caret. (Here the other application is an entirely different process and not connected in any way with our application.) This article describes how to show a tooltip window next to the caret in any application. The source code, attached here, is fully illustrative and easy to understand.

Background

Though there are many ways to get the caret position inside an application, the problem is that those methods are associated with the client region and non client regions and provide/use wrong handles. This association leads to wrong information about caret position inside Microsoft Office 2007 like products where the entire window results as the client area. Facing the same client and non client region problem, I spent some time looking for the possible solution and arrived at this approach. The utility is ready to use. You can enhance the UI or add some more information.

Using the Code 

To test the caret postion, just run the application and follow the process below:

  1. Open a Notepad and type some text. It will show tooltip next to caret. While typing, Tooltip moves along with caret.
  2. Now move the Notepad window and observe the continuously changing caret position inside tooltip.
  3. When Notepad window reaches the edge of screen, Tooltip changes its position (By default, it gets displayed at the bottom right to the caret).
  4. Now click on Desktop, Tooltip disappears.
  5. Now open Microsoft Word and see the Tooltip re-appear. Tooltip can be moved by pressing the left mouse button and moving the cursor onto it.

Explanation

GetCaretPosition() method populates GUI Thread information into guiInfo object. guiInfo is a structure variable of type GUIThreadInfo that is required by GetGUIThreadInfo() method of user32.dll

  public void GetCaretPosition()
  {
       guiInfo = new GUITHREADINFO();
       guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);

       // Get GuiThreadInfo into guiInfo
       GetGUIThreadInfo(0, out guiInfo);
  }   

guiInfo variable is a global variable that is further processed by EvaluateCaretPosition() method. This method fetches caret information and handle of associated window from guiInfo object and converts it to the screen coordinates.

  private void EvaluateCaretPosition()
   {
        caretPosition = new Point();                 

        // Fetch GUITHREADINFO
        GetCaretPosition();

        caretPosition.X = (int)guiInfo.rcCaret.Left + 25;
        caretPosition.Y = (int)guiInfo.rcCaret.Bottom + 25;

        ClientToScreen(guiInfo.hwndCaret, out caretPosition);

        txtCaretX.Text = (caretPosition.X).ToString();
        txtCaretY.Text = caretPosition.Y.ToString();
   }

Further the tooltip should be visible only for GUI applications and not for Windows Explorer likewise renaming any file on desktop. For this current active process is evaluated by GetActiveProcess() method. This method returns the name of currently active process to the timer event.

 private string GetActiveProcess()
  {
        const int nChars = 256;
        int handle = 0;
        StringBuilder Buff = new StringBuilder(nChars);
        handle = (int)GetForegroundWindow();

        // If Active window has some title info
        if (GetWindowText(handle, Buff, nChars) > 0)
         {
               uint lpdwProcessId;
               uint dwCaretID = GetWindowThreadProcessId(handle, out lpdwProcessId);
               uint dwCurrentID = (uint)Thread.CurrentThread.ManagedThreadId;
               return Process.GetProcessById((int)lpdwProcessId).ProcessName;
         }

         // Otherwise either error or non client region
         return String.Empty;
  }	

In the timer event, just check whether the tooltip is foreground window (just because user might have clicked on that), if not then check whether Windows Explorer is an active process(just because of user click on desktop/ opening Wiindows Explorer), if so then hide tooltip else evaluate caret position and adjust tooltip position by AdjustUI() method.

  private void timer1_Tick(object sender, EventArgs e)
           {
               // If Tooltip window is active window (Suppose user clicks on the
               //  Tooltip Window)
               if (GetForegroundWindow() == this.Handle)
               {
                   // then do no processing
                   return;
               }

               // Get Current active Process
               string activeProcess = GetActiveProcess();

               // If window explorer is active window (eg. user has opened any drive)
               // Or for any failure when activeProcess is nothing               
               if ((activeProcess.ToLower().Contains("explorer") |
 			(activeProcess == string.Empty)))
               {
                   // Dissappear Tooltip
                   this.Visible = false;
               }
               else
               {
                   // Otherwise Calculate Caret position
                   EvaluateCaretPosition();

                   // Adjust ToolTip according to the Caret
                   AdjustUI();

                   // Display current active Process on Tooltip
                   lblCurrentApp.Text = " You are Currently inside : " + activeProcess;
                   this.Visible = true;
               }               
           }

The AdjustUI method is responsible to adjust UI of tooltip to keep it inside user screen.

 private void AdjustUI()
           {
               // Get Current Screen Resolution
               Rectangle workingArea = SystemInformation.WorkingArea;

               // If current caret position throws Tooltip outside of screen area
               // then do some UI adjustment.
               if (caretPosition.X + this.Width > workingArea.Width)
               {
                   caretPosition.X = caretPosition.X - this.Width - 50;
               }

               if (caretPosition.Y + this.Height > workingArea.Height)
               {
                   caretPosition.Y = caretPosition.Y - this.Height - 50;
               }

               this.Left = caretPosition.X;
               this.Top = caretPosition.Y;
           }

Points of Interest

After spending some time on Google to get some clue about it, I found some people saying that it is impossible for Microsoft Office 2007 products because it has its own paint logic. Then I tried to resolve this problem and reached the solution shared here that works for any application (Including Microsoft Office 2007 & Visual Studio).

History

  • 30th March, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Saurabh Singh Gangwar
Software Developer (Senior)
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: Using this with AutoHotkeymemberGilbert Premo24 Sep '09 - 7:50 
Could you please post the AutoHotKey script you wrote?
Thank you.
GeneralRe: Using this with AutoHotkeymemberIvo Roper24 Sep '09 - 14:41 
This script isn't as generalized as I'd like, but it should at least give you a decent example:
 
   CoordMode, Mouse, Screen
   CoordMode, Caret, Screen
   CoordMode, ToolTip, Screen
   
   SendEvent {Left} ; moving the caret helped on my machine, try removing this and the last line
 
   ; base for code at http://www.codeproject.com/KB/dialog/CaretPosition.aspx
   NumPut(VarSetCapacity(gi,48,0),gi)     ; set cbSize var in structure
   hWnd:= WinGet, 
   tid := DllCall("GetWindowThreadProcessId", "Uint", hWnd, "Uint", 0)
   DllCall("GetGUIThreadInfo", "Uint", tid, "Uint", &gi)
 
   VarSetCapacity( xyPair, 8, 0 )
   caretX := NumGet(gi,40) + 6
   caretY := NumGet(gi,44) - 9
   if( 20 > caretX or 20 > caretY )  ; top left corner if not found
   {
      ToolTip, cannot find text caret
      SetTimer, ToolTipTimeOut, -2000
      return
   }
   NumPut( caretX, xyPair )    ; get right and bottom coords of caret rect
   NumPut( caretY, xyPair, 4 )
   DllCall("ClientToScreen", "Uint", NumGet(gi, 28), "Uint", &xyPair)
   caretX := NumGet(xyPair)
   caretY := NumGet(xyPair, 4)
 
   MouseMove, % NumGet(xyPair), % NumGet(xyPair, 4), 1
   ToolTip, !, caretX, caretY, 2  ; make the new location more visible
 
   SendEvent {Right}  ; moving the cursor back to it's original position
 
Have fun, and let me know if you find bugs.
GeneralRe: Using this with AutoHotkey [modified]memberGilbert Premo16 Jul '10 - 12:22 
Thank you!
It works, if I take out all reference to ToolTip, i,e. CoordMode, ToolTip, {.. ToolTip... } and ToolTip,! parts. Otherwise, it generated an error message that the "Target Label Does Not Exist."
I don't need the message, just to move the mouse to the cursor, which this does.

modified on Friday, July 16, 2010 6:32 PM

GeneralRe: Using this with AutoHotkeymemberIvo Roper16 Jul '10 - 12:48 
*nods* My apologies about that, I simply forgot to take out my debugging code before I posted it. Would you be so kind as to upload your corrected version? If it's not convenient I can get to it soon-ish. Smile | :)
GeneralRe: Using this with AutoHotkeymemberGilbert Premo18 Jul '10 - 11:33 
CoordMode, Mouse, Screen
CoordMode, Caret, Screen

SendEvent {Left} ; moving the caret helped on my machine, try removing this and the last line
 
NumPut(VarSetCapacity(gi,48,0),gi) ; set cbSize var in structure
hWnd:= WinGet,
tid := DllCall("GetWindowThreadProcessId", "Uint", hWnd, "Uint", 0)
DllCall("GetGUIThreadInfo", "Uint", tid, "Uint", &gi)
 
VarSetCapacity( xyPair, 8, 0 )
caretX := NumGet(gi,40) + 6
caretY := NumGet(gi,44) - 9
;if( 20 > caretX or 20 > caretY ) ; top left corner if not found

NumPut( caretX, xyPair ) ; get right and bottom coords of caret rect
NumPut( caretY, xyPair, 4 )
DllCall("ClientToScreen", "Uint", NumGet(gi, 28), "Uint", &xyPair)
caretX := NumGet(xyPair)
caretY := NumGet(xyPair, 4)
 
MouseMove, % NumGet(xyPair), % NumGet(xyPair, 4), 1

SendEvent {Right} ; moving the cursor back to it's original position
 
==EndCode==
I notice that even thought I commented out the line "if( 20 > ....", or if that line is deleted,
the mouse will move to the top left corner when the macro fails. Apparently, some programs don't have a "cursor" or "caret", even though there is an indicator in the programs as to what it is focused on. For example, PaperPort 11 can have a focus on a particular folder, and Quattro Pro on a particular cell, but the above macro has no effect but to jump the mouse to the top left corner of the screen.
 
Seems it would be better when the macro failed to leave the mouse where it is. How to do that?
GeneralRe: Using this with AutoHotkeymemberIvo Roper18 Jul '10 - 13:03 
Thanks for posting your version.
 
Regarding the mouse jumping into the corner, I've run into the same problems since I originally posted, and it doesn't <i>seem</i> to be a bug in the script. I've discontinued use of the function until I have time to figure out why that's happening and implement a fix... And that's probably not soon.
 
I suspect that the affected programs aren't using the operating system's text cursor, so the function that the script calls has no idea what the program in question is doing. I don't see any universal way around that, although under some circumstances you could find the control that currently has focus and center the mouse on that.
 
Sorry I couldn't help more, and I'd love to hear if you work out either of those issues.
GeneralRe: Using this with AutoHotkeymemberGilbert Premo18 Jul '10 - 14:49 
We can add Firefox as a program that doesn't seem to allow script to see the text cursor, even though the cursor is visible while I write this email.
Couldn't one tell the script not to move the mouse if no cursor is found? It currently jumps to 6, 0 if no cursor is found.
GeneralRe: Using this with AutoHotkeymemberIvo Roper18 Jul '10 - 15:18 
It not working in Firefox is exactly why I haven't been using it. *sigh*
 
I think the only easy way around the mouse jumping issue is to save the pointer's previous coordinates and move it back after it misbehaves. Kick its ass.   ^_^
GeneralThanks a lot Saurabh for this articlememberkkbajwa31 Mar '09 - 3:51 
Thanks Saurabh,
 
This article really helped me a lot. I was fed up after searching for caret position inside MSWord 2007. And atlast thought that this is not possible programmaticaly but your article has really solved my problem. I'm very thankful to you.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 30 Mar 2009
Article Copyright 2009 by Saurabh Singh Gangwar
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid