【问题标题】:How do I get the selected text from the focused window using native Win32 API?如何使用本机 Win32 API 从焦点窗口中获取选定的文本?
【发布时间】:2011-01-16 03:10:56
【问题描述】:

我的应用程序。将在系统上运行尝试监视热键;当用户在任何窗口中选择一些文本并按下热键时,当我收到 WM_HOTKEY 消息时,如何获取所选文本?

为了将文本捕获到剪贴板上,我尝试使用 keybd_event()SendInput()Ctrl + C 发送到活动窗口 (GetActiveWindow()) 和前景窗口 (GetForegroundWindow()) ;尝试其中的组合;一切都是徒劳。我可以使用普通的 Win32 系统 API 在 Windows 中获取焦点窗口的选定文本吗?

【问题讨论】:

  • 你确定你得到了正确的窗口句柄吗?您可以使用 Spy++ 进行测试。另外,您尝试过旧的 WM_GETTEXT 吗?
  • @Aaron:Windows XP 及更高版本; 32 位和 64 位;基本上是在将我的应用程序 (artha.sourceforge.net) 移植到 Windows,我需要此功能才能继续。
  • @Luke:谢谢!虽然 WM_GETTEXT 只能让我获得类名,但使用 Spy++ 的提示有所帮助:)

标签: c windows winapi clipboard selection


【解决方案1】:

TL;DR:是的,有一种方法可以使用普通的 win32 系统 API 来做到这一点,但很难正确实现。

WM_COPY 和 WM_GETTEXT 可能有效,但并非在所有情况下都有效。它们依赖于正确处理请求的接收窗口——在许多情况下它不会。让我介绍一种可能的方法。它可能不像您希望的那么简单,但是在充满冒险的 win32 编程世界中是什么?准备好?好的。我们走吧。

首先我们需要获取目标窗口的 HWND id。有很多方法可以做到这一点。您上面提到的一种方法是:获取前景窗口,然后获取焦点窗口等。但是,有一个很多人忘记的巨大陷阱。获得前景窗口后,您必须 AttachThreadInput 才能获得具有焦点的窗口。否则GetFocus() 将简单地返回NULL

有一个更简单的方法。只需(错过)使用 GUITREADINFO 函数。它更安全,因为它避免了与将输入线程附加到另一个程序相关的所有隐患。

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 数据与相同文本的纯文本表示一起放入剪贴板。这样您就可以将其粘贴到写字板和记事本中。写字板将使用富文本格式,而记事本将使用纯文本格式。

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

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 编程一开始可能会让人望而生畏,但过了一段时间......它仍然令人望而生畏。

干杯!

【讨论】:

  • 首先,非常感谢您花时间给出如此详细的答案,解释其中的细微差别!我会尝试并回复你。
  • 成功了!哦,谢谢,找了好久,终于成功了!早些时候,当我尝试使用 SendInput 时,我也在做同样的事情,但问题是我注册的热键。我注册了Ctrl + Alt + S,在WM_HOTKEY 中,我用Ctrl + C 调用了SendInput。但是当用户按下Ctrl + Alt + S时,当我虚拟通过Ctrl + CAlt仍然会被关闭;当我将热键更改为Windows + S 时,它运行良好。在带有Alt 组合的热键中,当我几乎松开Alt (KEYEVENTF_KEYUP) 时,它也可以工作。
  • 有没有办法虚拟地un-press所有按下的键,以便只有Ctrl + C(复制)被传递给操作系统?我想要它的原因是,当我通过Ctrl + C 时,复制操作受到ShiftAlt 等键的阻碍。
  • 什么我不明白。 target_window HWND 如果以后不使用,为什么要获取它。除非我错过了什么。
  • @OliverWeichhold - 我相信(那是很久以前的事了)我只是在解释“发送 WM_COPY”是行不通的,除非你 A) 使用 attachThreadInput,和 b) 获取具有键盘焦点的窗口。然后我继续解释不使用任何 HWND id 的不相关的 SendInput 方法。这两个想法并没有像应有的那样完全分开。
【解决方案2】:

尝试在每个 SendInput() 之后添加 Sleep()。有些应用在捕捉键盘输入方面并没有那么快。

【讨论】:

  • 这与 kurige 的回答相结合使它起作用了!谢谢:)
  • 是的,例如 Chrome 无法在我的阅读过程中将所选文本放入剪贴板。
  • 但是如果您在剪贴板链中注册您的窗口,并将 WM_DRAWCLIPBOARD 添加到您的应用程序 WndProc 中,可能会更好。这样你会在接收输入的窗口修改剪贴板后收到消息。
【解决方案3】:

试试 SendMessage(WM_COPY 等)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    相关资源
    最近更新 更多