注册鼠标钩子
// 注册鼠标钩子
HHOOK hMouseHook;
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, GetModuleHandle(NULL), 0);// 取消鼠标钩子
UnhookWindowsHookEx(hMouseHook);
hMouseHook = nullptr;
上述代码中MouseProc方法用于处理系统的鼠标消息
处理鼠标消息
LRESULT MouseHook::MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{static QPoint pos;static qint64 lastTriggerTime = 0;if (nCode >= 0) {if (wParam == WM_LBUTTONDOWN) {pos = QCursor::pos(); }else if (wParam == WM_LBUTTONUP) {if (pos != QCursor::pos()) {timer->start(280); //拖拽}qint64 currentTime = QDateTime::currentMSecsSinceEpoch();if (currentTime - lastTriggerTime <= 500) {timer->start(280); //双击}lastTriggerTime = currentTime;}}return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
这段代码中,我们使用一个QTimer来处理双击或拖拽选中文本的操作。
文本选中后的处理方法
void MouseHook::processMouseUp()
{auto textGetter = new TextGetter(this);connect(textGetter, &TextGetter::resultReady, this, &MouseHook::onTextReady, Qt::ConnectionType::QueuedConnection);textGetter->start();
}
TextGetter是一个继承自QThread的类,我们将在一个新线程中去获取用户选中的文本。
获取选中文本的第一种情况
QString TextGetter::getSelectedTextByUIAutomation() {try {auto hr = CoInitialize(NULL);if (FAILED(hr)){CoUninitialize();return "";}CComPtr<IUIAutomation> automation;hr = CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_IUIAutomation,(void**)(&automation));if (FAILED(hr)){CoUninitialize();return "";}CComPtr<IUIAutomationElement> focusedElement;hr = automation->GetFocusedElement(&focusedElement);if (FAILED(hr) || !focusedElement){CoUninitialize();return "";}CComPtr<IUIAutomationTextPattern> textPattern;hr = focusedElement->GetCurrentPatternAs(UIA_TextPatternId, IID_PPV_ARGS(&textPattern));if (FAILED(hr) || !textPattern){CoUninitialize();return "";}CComPtr<IUIAutomationTextRangeArray> selection;hr = textPattern->GetSelection(&selection);if (FAILED(hr) || !selection){CoUninitialize();return "";}CComPtr<IUIAutomationTextRange> range;hr = selection->GetElement(0, &range);if (FAILED(hr) || !range){ CoUninitialize();return "";}CComBSTR text;range->GetText(-1, &text);std::wstring ws(text, SysStringLen(text));CoUninitialize();return QString::fromStdWString(ws);}catch (std::exception& e) {return "";}
}
这段代码使用 Microsoft UI Automation (UIA) API 从当前具有焦点的 UI 元素中获取选中文本的方法。但有的时候这种方法获取不到想要的文本(老式窗口中的文本)
获取选中文本的第二种情况
当第一种情况获取到的文本是空时,就要尝试第二种情况
auto hwnd = getCurrentHwnd();
if (!hwnd) {return "";
}
auto cache = cacheClipboard();
sendCtrlC();
str = getClipboardText();
if (str.isEmpty()) {CloseClipboard();return "";
}
restoreClipboard(cache);
CloseClipboard();
这种情况,先获取系统当前聚焦的窗口,然后缓存当前剪切板,然后发送Ctrl+C复制此窗口选中的文本,然后获取剪切板内的文本,然后把之前缓存的内容存入剪切板。
下面我们看看这些实现代码:
获取系统当前聚焦的窗口
HWND TextGetter::getCurrentHwnd()
{HWND hwnd = GetForegroundWindow();DWORD threadId = GetWindowThreadProcessId(hwnd, NULL);AttachThreadInput(GetCurrentThreadId(), threadId, TRUE);hwnd = GetFocus();AttachThreadInput(GetCurrentThreadId(), threadId, FALSE);POINT pt;GetCursorPos(&pt);RECT rect;GetWindowRect(hwnd, &rect);if (pt.x<rect.left || pt.y<rect.top || pt.x>rect.right || pt.y>rect.bottom) {return nullptr;}return hwnd;
}
如果聚焦的窗口与鼠标所在位置的窗口不是一个窗口,那么我们取消任务。
缓存剪切板的内容
ClipboardData TextGetter::cacheClipboard()
{OpenClipboard(nullptr);ClipboardData cache;UINT format = 0;while ((format = EnumClipboardFormats(format)) != 0) {HANDLE hData = GetClipboardData(format);if (hData) {SIZE_T size = GlobalSize(hData);HGLOBAL hCopy = GlobalAlloc(GMEM_MOVEABLE, size);if (hCopy) {void* pDest = GlobalLock(hCopy);void* pSource = GlobalLock(hData);if (pDest && pSource) {memcpy(pDest, pSource, size);}GlobalUnlock(hData);GlobalUnlock(hCopy);cache.push_back({ format, hCopy });}}}EmptyClipboard();CloseClipboard();return cache;
}
发送Ctrl+C按键消息
void TextGetter::sendCtrlC()
{INPUT inputs[4] = { 0 };inputs[0].type = INPUT_KEYBOARD;inputs[0].ki.wVk = VK_CONTROL;inputs[1].type = INPUT_KEYBOARD;inputs[1].ki.wVk = 'C';inputs[2].type = INPUT_KEYBOARD;inputs[2].ki.wVk = 'C';inputs[2].ki.dwFlags = KEYEVENTF_KEYUP;inputs[3].type = INPUT_KEYBOARD;inputs[3].ki.wVk = VK_CONTROL;inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));QThread::msleep(360);
}
按键发送成功后需要等待360毫秒
获取剪切板的内容
QString TextGetter::getClipboardText()
{OpenClipboard(nullptr);if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) {CloseClipboard();return "";}HANDLE hData = GetClipboardData(CF_UNICODETEXT);if (hData == nullptr) {CloseClipboard();return "";}LPCWSTR pText = static_cast<LPCWSTR>(GlobalLock(hData));if (pText == nullptr) {CloseClipboard();return "";}QString result = QString::fromWCharArray(pText);GlobalUnlock(hData);CloseClipboard();return result;
}
不要怀疑发送按键Ctrl+C这个方案是否可行,有道词典就是这么干的。