注册鼠标钩子
// 注册鼠标钩子
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这个方案是否可行,有道词典就是这么干的。