如何使用本地Win32 API从聚焦窗口中获取所选文本?

22
我的应用程序将在系统托盘监控热键; 当用户在任何窗口中选择一些文本并按下热键时,我如何获取所选文本,在我收到WM_HOTKEY消息时?
为了将文本捕获到剪贴板上,我尝试使用keybd_event()和SendInput()发送Ctrl + C到活动窗口(GetActiveWindow())和前景窗口(GetForegroundWindow()),以及它们之间的组合;但都失败了。我能否使用普通的Win32系统API在Windows中获取焦点窗口的所选文本?

1
你确定获取了正确的窗口句柄吗?你可以用Spy++来测试。另外,你尝试过老旧的WM_GETTEXT吗? - Luke
@Aaron:适用于Windows XP及更高版本,包括32位和64位;基本上我正在将我的应用程序(http://artha.sourceforge.net/)移植到Windows平台,我需要这个功能才能继续进行。 - legends2k
@Luke:谢谢!虽然 WM_GETTEXT 只能获取类名,但使用 Spy++ 的提示很有帮助 :) - legends2k
3个回答

23
TL;DR:是的,可以使用纯 Win32 系统 API 来实现,但正确实现难度很大。
WM_COPY 和 WM_GETTEXT 可能有效,但不是所有情况都适用。它们取决于接收窗口正确处理请求 - 在许多情况下它不会。让我介绍一种可能的方法。它可能没有你希望的那么简单,但在 Win32 编程的冒险世界中却是一项挑战。准备好了吗?我们开始吧。
首先,我们需要获取目标窗口的 HWND ID。有很多方法可以做到这一点。其中一种方法是你上面提到的:获取前景窗口然后获取具有焦点的窗口等等。然而,有一个巨大的陷阱很多人忘记了。在获取前景窗口之后,你必须使用 AttachThreadInput 来获取具有焦点的窗口。否则,GetFocus() 将简单地返回 NULL。
有一种更简单的方法。只需(错误)使用 GUITREATINFO 函数。它更安全,因为它避免了将您的输入线程附加到另一个程序时出现的所有隐藏危险。
LPGUITHREADINFO lpgui = NULL;
HWND target_window = NULL;

if( GetGUIThreadInfo( NULL, lpgui ) )
    target_window = lpgui->hwndFocus;
else
{
    // You can get more information on why the function failed by calling
    // the win32 function, GetLastError().
}

发送复制文本的按键需要更多操作...

我们将使用SendInput而不是keybd_event,因为它更快,并且最重要的是,无法被并发用户输入或其他模拟按键的程序弄乱。

这意味着程序需要在Windows XP或更高版本上运行,如果你在运行98,很抱歉!

// We're sending two keys CONTROL and 'V'. Since keydown and keyup are two
// seperate messages, we multiply that number by two.
int key_count = 4;

INPUT* input = new INPUT[key_count];
for( int i = 0; i < key_count; i++ )
{
    input[i].dwFlags = 0;
    input[i].type = INPUT_KEYBOARD;
}

input[0].wVK = VK_CONTROL;
input[0].wScan = MapVirtualKey( VK_CONTROL, MAPVK_VK_TO_VSC );
input[1].wVK = 0x56 // Virtual key code for 'v'
input[1].wScan = MapVirtualKey( 0x56, MAPVK_VK_TO_VSC );
input[2].dwFlags = KEYEVENTF_KEYUP;
input[2].wVK = input[0].wVK;
input[2].wScan = input[0].wScan;
input[3].dwFlags = KEYEVENTF_KEYUP;
input[3].wVK = input[1].wVK;
input[3].wScan = input[1].wScan;

if( !SendInput( key_count, (LPINPUT)input, sizeof(INPUT) ) )
{
    // You can get more information on why this function failed by calling
    // the win32 function, GetLastError().
}

好的,这不算难,对吧?

现在我们只需来看一下剪贴板中有什么。这并不像你最初想的那样简单。“剪贴板”实际上可以容纳同一内容的多个表示形式。当将内容复制到剪贴板时处于活动状态的应用程序会控制要将什么放入剪贴板中。

例如,当你从 Microsoft Office 复制文本时,它会将 RTF 数据与相同文本的纯文本表示形式一起放入剪贴板中。这样,你就可以将其粘贴到 WordPad 和记事本中。WordPad 将使用 RTF 格式,而记事本将使用纯文本格式。

不过,对于这个简单的例子,让我们假设我们只对纯文本感兴趣。

if( OpenClipboard(NULL) )
{
    // Optionally you may want to change CF_TEXT below to CF_UNICODE.
    // Play around with it, and check out all the standard formats at:
    // http://msdn.microsoft.com/en-us/library/ms649013(VS.85).aspx
    HGLOBAL hglb = GetClipboardData( CF_TEXT );
    LPSTR lpstr = GlobalLock(hglb);

    // Copy lpstr, then do whatever you want with the copy.

    GlobalUnlock(hglb);
    CloseClipboard();
}
else
{
    // You know the drill by now. Check GetLastError() to find out what
    // went wrong. :)
}

好的,就这样了!只需确保将lpstr复制到您想要使用的某个变量中,不要直接使用lpstr,因为我们必须在关闭剪贴板之前放弃对其内容的控制。

一开始,Win32编程可能会让人感到非常令人生畏,但是过一段时间后...仍然很令人生畏。

干杯!


2
成功了!哦,谢谢,在很长时间之后,它终于成功了!之前当我尝试使用SendInput时,我做的是一样的,但问题在于我注册的热键。我注册了一个 Ctrl + Alt + S,而在_WN_HOTKEY_中,我调用了 Ctrl + C 的SendInput。但是当用户按下 Ctrl + Alt + S 时,当我虚拟传递 Ctrl + C 时,Alt 还会处于按下状态;当我将热键更改为 Windows + S 时,它完美运行了。在组合键中带有 Alt 的热键中,当我虚拟松开 Alt(KEYEVENTF_KEYUP) 时,也可以正常运行。 - legends2k
1
有没有一种方法可以虚拟“取消”所有按下的键,以便仅将Ctrl + C(复制)传递给操作系统?我想要这个的原因是,当我传递Ctrl + C时,像ShiftAlt这样的键会干扰复制操作。 - legends2k
1
我不明白的是,如果目标窗口 HWND 不会在后面使用,为什么要获取它。除非我漏掉了什么。 - Oliver Weichhold
2
@OliverWeichhold - 我相信(那是很久以前的事了)我只是在解释“发送WM_COPY”不起作用,除非你**A)使用attachThreadInput,和b)**获取具有键盘焦点的窗口。然后我继续解释无关的SendInput方法,它不使用任何HWND ids。这两个想法没有像它们应该分开得那么清晰。 - kurige
你也应该编写AttachThread代码。 - tmighty
显示剩余4条评论

3
尝试在每个SendInput()后添加一个Sleep()。一些应用程序不太能快速捕获键盘输入。

这个与kurige的答案结合起来使它工作了!谢谢 :) - legends2k
是的,例如在我的进程读取文本之前,Chrome无法将所选文本放入剪贴板。 - Jorge González Lorenzo
4
也许更好的做法是将您的窗口注册到剪贴板链中,并在应用程序的WndProc中添加WM_DRAWCLIPBOARD。这样,当接收输入的窗口修改剪贴板后,您将收到该消息。 - Jorge González Lorenzo

0
尝试使用 SendMessage(WM_COPY, 等等)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接