Search code examples
c#focusselection

How to get selected text of currently focused window?


So, I'm trying to make an application that does the following:

  1. Listens for a keyboard shortcut (using this library)
  2. When the shortcut is hit, retrieves the contents of the currently selected text, and
  3. Processes the text

I've used the method shared by the latest edit of this answer (this method) to attach my application to the focused control, but the GetText function in that method doesn't do what I need.

I've seen this answer as well, but that only gives detailed steps as to how to get the focused window on double click, which is not what I need. It did link to this question which led me to try the WM_KEYDOWN method (shown below), but that didn't work either.

So far I've tried these GetText methods (all within the context of that MSDN post):

string GetText(IntPtr handle)
{
    // works in Notepad, but not Chrome
    SendMessageW(handle, WM_COPY, 0, 0);
    string w = Clipboard.GetText();
    return w;

    // works in every app, but in Notepad gets the complete contents
    // and in Chrome gets the window title
    int maxLength = 160;
    IntPtr buffer = Marshal.AllocHGlobal((maxLength + 1) * 2);
    SendMessageW(handle, WM_GETTEXT, maxLength, buffer);
    string w = Marshal.PtrToStringUni(buffer);
    Marshal.FreeHGlobal(buffer);
    return w;

    // I would have thought these would work, but
    // they don't do anything for some reason. They
    // all simulate a Ctrl+C.

    SendKeys.SendWait("^c");
    // or
    // this is from the same library that listens for the keyboard shortcut
    KeyboardSimulator.SimulateStandardShortcut(StandardShortcut.Copy);
    // or
    SendMessageW(handle, WM_KEYDOWN, (ushort)Keys.LControlKey, 0);
    SendMessageW(handle, WM_KEYDOWN, (ushort)Keys.C, 0);
    SendMessageW(handle, WM_KEYUP, (ushort)Keys.C, 0);
    SendMessageW(handle, WM_KEYUP, (ushort)Keys.LControlKey, 0);
    // after any of those
    string w = Clipboard.GetText();
    return w;
}

(I don't care about preserving the clipboard yet.)

How can I consistently get the selected text of the currently focused application? Bonus points for not tampering with the clipboard, but using it is OK too.


Solution

  • Ever since Vista, apps should refrain from using p-invoke or WM_GETTEXT to snoop on other apps due to potential blocks from Windows Elevated Processes. Instead consider using Microsoft UI Automation. Though arguably a testing framework, it is also useful as a means to remotely interact with another GUI application.

    MSDN:

    Microsoft UI Automation is the new accessibility framework for Microsoft Windows. It addresses the needs of assistive technology products and automated test frameworks by providing programmatic access to information about the user interface (UI). In addition, UI Automation enables control and application developers to make their products accessible.

    The following code will look for the running process Notepad and grab any text selection. Ensure you run notepad beforehand, enter in some text and select a word or two.

    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows.Automation;
    
    namespace UiaTest
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                var p = Process.GetProcessesByName("notepad").FirstOrDefault();       
                var root = AutomationElement.FromHandle(p.MainWindowHandle);
    
                var documentControl = new                                 
                        PropertyCondition(AutomationElement.ControlTypeProperty, 
                                          ControlType.Document);
    
                var textPatternAvailable = new PropertyCondition(AutomationElement.IsTextPatternAvailableProperty, true);
    
                var findControl = new AndCondition(documentControl, textPatternAvailable);
    
                var targetDocument = root.FindFirst(TreeScope.Descendants, findControl);    
                var textPattern = targetDocument.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
    
                foreach (var selection in textPattern.GetSelection())
                {
                    Console.WriteLine($"Selection: \"{selection.GetText(255)}\"");
                }    
            }
        }
    }
    

    EDIT:

    How can I consistently get the selected text of the currently focused application?

    Now in your case to work from focused window, instead of:

    var p = Process.GetProcessesByName("notepad").FirstOrDefault();     
    

    ...perform a:

    IntPtr handle = GetForegroundWindow();
    var root = AutomationElement.FromHandle(handle);
    

    ...where GetForegroundWindow is defined as:

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();
    

    See also