2

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.

AbyxDev
  • 1,363
  • 16
  • 30

2 Answers2

5

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

  • Thanks for the answer! Could you show me how to get the selected text of the currently focused window (the actual question), rather than a particular window by name? That would help out a lot. – AbyxDev Aug 01 '18 at 11:27
  • Hm, this method works for Notepad but when I try to use the selected text in Chrome (even a textarea) the `targetDocument` turns out to be `null` and I get a NullReferenceException where it says `targetDocument.GetCurrentPattern`. What am I missing? – AbyxDev Aug 01 '18 at 13:11
  • @KenHilton that’s a new question. You need to enumerate the elements to find the appropriate control. This answer gives you more than enough to start –  Aug 01 '18 at 16:42
  • I see. I'll accept this answer in that case, thanks for your help! – AbyxDev Aug 01 '18 at 16:51
-1

We can also simulate copy shortcut (press Ctrl + C) And after that read clipboard

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 01 '22 at 22:59