注冊鼠標鉤子
// 注冊鼠標鉤子
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這個方案是否可行,有道詞典就是這么干的。