windowpreviewwidget.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. #include "windowpreviewwidget.h"
  2. #include <QApplication>
  3. #include <QBoxLayout>
  4. #include <QDebug>
  5. #include <QLabel> // 添加标签用于显示信息
  6. #include <QListView>
  7. #include <QMouseEvent>
  8. #include <QPainter>
  9. #include <QScreen>
  10. #include <QStandardItem>
  11. #include <QStandardItemModel>
  12. #include <QStyledItemDelegate>
  13. #include <QTimer>
  14. #include <QtWin>
  15. #include <psapi.h>
  16. #include <shellapi.h>
  17. #include <windows.h>
  18. class ThumbnailDelegate; // 前置声明
  19. struct WindowInfo
  20. {
  21. HWND hwnd;
  22. std::wstring title;
  23. std::wstring className; // 窗口类名
  24. };
  25. static bool isTooltipWindow(HWND hwnd);
  26. static bool isAltTabWindow(HWND hwnd, const std::wstring &title, const std::wstring &className)
  27. {
  28. HWND shellWindow = GetShellWindow();
  29. if (hwnd == shellWindow) {
  30. return false;
  31. }
  32. if (title.length() == 0 || title == L"NVIDIA GeForce Overlay") {
  33. return false;
  34. }
  35. if (!IsWindowVisible(hwnd)) {
  36. return false;
  37. }
  38. if (GetAncestor(hwnd, GA_ROOT) != hwnd) {
  39. return false;
  40. }
  41. // LONG style = GetWindowLong(hwnd, GWL_STYLE);
  42. // if (!((style & WS_DISABLED) != WS_DISABLED)) {
  43. // return false;
  44. // }
  45. // 跳过工具提示窗口
  46. // if (isTooltipWindow(hwnd)) {
  47. // return false;
  48. // }
  49. // 跳过桌面窗口的特殊类名
  50. if (className == L"WorkerW" || className == L"Progman") {
  51. return false;
  52. }
  53. return true;
  54. return !IsIconic(hwnd);
  55. }
  56. static bool isTooltipWindow(HWND hwnd)
  57. {
  58. wchar_t className[256] = {0};
  59. GetClassNameW(hwnd, className, 256);
  60. // 类名过滤列表
  61. const wchar_t *tooltipClasses[] = {L"tooltips_class32",
  62. L"Qt5QWindowToolSaveBtn",
  63. L"QWindowToolTipDropShadowEffect",
  64. L"QWindowToolTipDropShadows",
  65. L"PopupWindow",
  66. L"Xaml_WindowedPopupClass",
  67. L"Chrome_WidgetWin_2",
  68. L"QtQWindowIcon",
  69. L"QtQWindowOwnDCIcon",
  70. L"QtQWindowPopup",
  71. L"QtQWindowToolTip",
  72. L"Windows.UI.Core.CoreWindow"};
  73. for (const wchar_t *cls : tooltipClasses) {
  74. if (wcscmp(className, cls) == 0) {
  75. return true;
  76. }
  77. }
  78. // 通过窗口标题过滤(如果标题包含工具提示相关关键词)
  79. wchar_t title[256] = {0};
  80. GetWindowTextW(hwnd, title, 256);
  81. std::wstring titleStr(title);
  82. if (titleStr.find(L"Tooltip") != std::wstring::npos
  83. || titleStr.find(L"Tip") != std::wstring::npos
  84. || titleStr.find(L"提示") != std::wstring::npos
  85. || titleStr.find(L"Popup") != std::wstring::npos) {
  86. return true;
  87. }
  88. // // 通过窗口样式过滤
  89. // LONG style = GetWindowLong(hwnd, GWL_STYLE);
  90. // LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
  91. // // 过滤无边框、置顶且透明的弹出窗口
  92. // if ((exStyle & WS_EX_TOOLWINDOW) && (style & WS_POPUP) && !(style & WS_CHILD)
  93. // && (exStyle & WS_EX_LAYERED)) {
  94. // return true;
  95. // }
  96. // 过滤小尺寸窗口(通常为工具提示)
  97. // RECT rect;
  98. // GetWindowRect(hwnd, &rect);
  99. // int width = rect.right - rect.left;
  100. // int height = rect.bottom - rect.top;
  101. // if (width < 100 && height < 50) {
  102. // return true;
  103. // }
  104. return false;
  105. }
  106. BOOL CALLBACK enumWindowsProc(HWND hwnd, LPARAM lParam)
  107. {
  108. // 确保窗口有效
  109. if (!IsWindow(hwnd)) {
  110. return TRUE;
  111. }
  112. // 获取窗口标题
  113. std::wstring title;
  114. wchar_t buffer[256] = {0};
  115. GetWindowTextW(hwnd, buffer, 256);
  116. title = buffer;
  117. // 获取窗口类名
  118. std::wstring className;
  119. wchar_t classBuffer[256] = {0};
  120. GetClassNameW(hwnd, classBuffer, 256);
  121. className = classBuffer;
  122. // 检查是否是Alt+Tab窗口
  123. if (!isAltTabWindow(hwnd, title, className)) {
  124. return TRUE;
  125. }
  126. // 添加到窗口列表
  127. std::vector<WindowInfo> *windows = reinterpret_cast<std::vector<WindowInfo> *>(lParam);
  128. windows->push_back({hwnd, title, className});
  129. return TRUE;
  130. }
  131. class ThumbnailDelegate : public QStyledItemDelegate
  132. {
  133. public:
  134. explicit ThumbnailDelegate(QObject *parent = nullptr)
  135. : QStyledItemDelegate(parent)
  136. {}
  137. void paint(QPainter *painter,
  138. const QStyleOptionViewItem &option,
  139. const QModelIndex &index) const override
  140. {
  141. painter->save();
  142. painter->setRenderHint(QPainter::Antialiasing, true);
  143. // 整个项尺寸(160x120)
  144. const int itemWidth = 160;
  145. const int itemHeight = 120;
  146. // 计算绘制区域在父项中的位置(居中)
  147. const int left = option.rect.left() + (option.rect.width() - itemWidth) / 2;
  148. const int top = option.rect.top() + (option.rect.height() - itemHeight) / 2;
  149. // 主绘制区域(项边界)
  150. const QRect itemRect(left, top, itemWidth, itemHeight);
  151. // 创建圆角背景
  152. painter->setPen(Qt::NoPen);
  153. painter->setBrush(QColor(40, 40, 40, 180));
  154. painter->drawRoundedRect(itemRect, 8, 8);
  155. // 图标位置(左上角)
  156. const QRect iconRect(itemRect.left() + 5, itemRect.top() + 5, 30, 30);
  157. // 图标位置(右上角)
  158. const QRect isMinimizedRect(itemRect.width() - 25, itemRect.top() + 5, 30, 30);
  159. // 边框区域(内缩10像素)
  160. const QRect borderRect = itemRect.adjusted(10, 10, -10, -10);
  161. // 缩略图可用区域(减去底部标题空间)
  162. const QRect thumbArea = borderRect.adjusted(0, 0, 0, -20); // 底部留20像素给标题
  163. // 计算最大正方形区域(1:1比例)
  164. const int thumbSize = qMin(thumbArea.width(), thumbArea.height());
  165. const QRect thumbRect(thumbArea.left() + (thumbArea.width() - thumbSize) / 2,
  166. thumbArea.top() + (thumbArea.height() - thumbSize) / 2,
  167. thumbSize,
  168. thumbSize);
  169. // 标题区域(底部居中)
  170. const QRect titleRect(borderRect.left(), borderRect.bottom() - 20, borderRect.width(), 20);
  171. // 获取数据
  172. const QPixmap thumbnail = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
  173. const QString title = index.data(Qt::DisplayRole).toString();
  174. const QString className = index.data(Qt::UserRole + 5).toString();
  175. const bool isActive = index.data(Qt::UserRole + 1).toBool();
  176. const bool isMinimized = index.data(Qt::UserRole + 2).toBool();
  177. const QIcon icon = qvariant_cast<QIcon>(index.data(Qt::UserRole + 3));
  178. const bool isDesktop = index.data(Qt::UserRole + 4).toBool();
  179. const bool hasFocus = option.state & QStyle::State_HasFocus;
  180. const bool isHovered = option.state & QStyle::State_MouseOver;
  181. // 绘制缩略图背景(边框区域内)
  182. painter->fillRect(borderRect, QColor(30, 30, 30, 150));
  183. // 居中绘制缩略图(保持1:1比例)
  184. if (!thumbnail.isNull()) {
  185. // 创建1:1比例的缩略图
  186. QPixmap scaledThumb = thumbnail.scaled(thumbSize,
  187. thumbSize,
  188. Qt::KeepAspectRatio,
  189. Qt::SmoothTransformation);
  190. // 居中绘制
  191. const int x = thumbRect.left() + (thumbRect.width() - scaledThumb.width()) / 2;
  192. const int y = thumbRect.top() + (thumbRect.height() - scaledThumb.height()) / 2;
  193. painter->drawPixmap(x, y, scaledThumb);
  194. } else {
  195. // 无缩略图时显示占位符
  196. painter->setPen(Qt::lightGray);
  197. painter->drawText(thumbRect, Qt::AlignCenter, "No Preview");
  198. }
  199. // 绘制标题(底部居中,不换行)
  200. painter->setPen(isActive ? Qt::white : Qt::lightGray);
  201. QFont titleFont = painter->font();
  202. titleFont.setBold(true);
  203. titleFont.setPointSize(9);
  204. painter->setFont(titleFont);
  205. // 截短标题以适应空间
  206. QString shortTitle = title;
  207. QFontMetrics titleMetrics(titleFont);
  208. shortTitle = titleMetrics.elidedText(title, Qt::ElideRight, titleRect.width());
  209. painter->drawText(titleRect, Qt::AlignCenter | Qt::TextSingleLine, shortTitle);
  210. // 绘制边框(焦点时变色)
  211. QPen borderPen;
  212. if (isHovered || hasFocus) {
  213. borderPen = QPen(QColor(0, 255, 0), 3);
  214. } else if (isActive) {
  215. borderPen = QPen(QColor(0, 120, 215), 3);
  216. } else {
  217. borderPen = QPen(Qt::gray, 1);
  218. }
  219. painter->setPen(borderPen);
  220. painter->setBrush(Qt::NoBrush);
  221. painter->drawRoundedRect(borderRect, 6, 6);
  222. // 绘制左上角图标
  223. if (!icon.isNull()) {
  224. QPixmap iconPix = icon.pixmap(30, 30);
  225. painter->drawPixmap(iconRect, iconPix);
  226. }
  227. if (isMinimized) {
  228. // 使用红色矩形作为最小化标识
  229. painter->setPen(QPen(Qt::red, 2));
  230. painter->setBrush(Qt::NoBrush);
  231. // 在右上角区域绘制矩形框
  232. const int markerSize = 20;
  233. QRect markerRect(isMinimizedRect.left() + (isMinimizedRect.width() - markerSize) / 2,
  234. isMinimizedRect.top() + (isMinimizedRect.height() - markerSize) / 2,
  235. markerSize,
  236. markerSize);
  237. painter->drawRect(markerRect);
  238. // 在矩形框内绘制减号"-"
  239. painter->setPen(QPen(Qt::red, 3));
  240. painter->drawLine(markerRect.left() + 5,
  241. markerRect.center().y(),
  242. markerRect.right() - 5,
  243. markerRect.center().y());
  244. }
  245. // 如果是桌面项,添加特殊标记
  246. if (isDesktop) {
  247. painter->setPen(QPen(Qt::cyan, 2));
  248. QFont desktopFont("Arial", 8, QFont::Bold);
  249. painter->setFont(desktopFont);
  250. painter->drawText(thumbRect.topLeft() + QPoint(10, 15), "Desktop");
  251. }
  252. painter->restore();
  253. }
  254. QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
  255. {
  256. Q_UNUSED(option);
  257. Q_UNUSED(index);
  258. return QSize(160, 120); // 调整大小以适应新布局
  259. }
  260. };
  261. WindowPreviewWidget::WindowPreviewWidget(QWidget *parent)
  262. : QListView(parent)
  263. {
  264. // 设置模型
  265. model = new QStandardItemModel(this);
  266. setModel(model);
  267. // 设置委托
  268. setItemDelegate(new ThumbnailDelegate(this));
  269. // 设置视图属性
  270. setViewMode(QListView::IconMode);
  271. setResizeMode(QListView::Adjust);
  272. setGridSize(QSize(180, 140)); // 增加网格大小以容纳间距
  273. setSpacing(20); // 增加项间间距到20像素
  274. setDragEnabled(false);
  275. setSelectionMode(QAbstractItemView::SingleSelection);
  276. setEditTriggers(QAbstractItemView::NoEditTriggers);
  277. // 设置焦点策略
  278. setFocusPolicy(Qt::StrongFocus);
  279. // 启用滚动
  280. setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  281. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  282. // 设置定时器
  283. updateTimer = new QTimer(this);
  284. connect(updateTimer, &QTimer::timeout, this, &WindowPreviewWidget::updateThumbnails);
  285. updateTimer->start(1000); // 初始1000ms更新一次
  286. // 初始更新
  287. QTimer::singleShot(100, this, &WindowPreviewWidget::updateThumbnails);
  288. // 启用鼠标跟踪
  289. setMouseTracking(true);
  290. viewport()->setMouseTracking(true);
  291. }
  292. WindowPreviewWidget::~WindowPreviewWidget()
  293. {
  294. // 安全清理资源
  295. for (auto it = thumbnailCache.begin(); it != thumbnailCache.end(); ++it) {
  296. if (it.value()) {
  297. delete it.value();
  298. }
  299. }
  300. thumbnailCache.clear();
  301. }
  302. void WindowPreviewWidget::mousePressEvent(QMouseEvent *event)
  303. {
  304. QListView::mousePressEvent(event);
  305. // if (event->button() == Qt::LeftButton) {
  306. // const QModelIndex index = indexAt(event->pos());
  307. // if (index.isValid()) {
  308. // const quintptr hwndPtr = index.data(Qt::UserRole).value<quintptr>();
  309. // const HWND hwnd = reinterpret_cast<HWND>(hwndPtr);
  310. // const bool isDesktop = index.data(Qt::UserRole + 4).toBool();
  311. // const bool isMinimized = index.data(Qt::UserRole + 2).toBool();
  312. // if (isDesktop) {
  313. // // 切换到桌面
  314. // const HWND desktopHwnd = FindWindow(L"Progman", L"Program Manager");
  315. // if (desktopHwnd) {
  316. // SetForegroundWindow(desktopHwnd);
  317. // }
  318. // } else if (hwnd) {
  319. // // 激活窗口
  320. // if (isMinimized) {
  321. // ShowWindow(hwnd, SW_RESTORE);
  322. // }
  323. // // 尝试多次激活窗口(解决某些情况下激活失败的问题)
  324. // for (int i = 0; i < 3; i++) {
  325. // if (SetForegroundWindow(hwnd)) {
  326. // break;
  327. // }
  328. // Sleep(50);
  329. // }
  330. // }
  331. // // 设置焦点索引
  332. // focusedIndex = index;
  333. // update();
  334. // }
  335. // }
  336. }
  337. // 添加双击事件处理
  338. void WindowPreviewWidget::mouseDoubleClickEvent(QMouseEvent *event)
  339. {
  340. if (event->button() == Qt::LeftButton) {
  341. const QModelIndex index = indexAt(event->pos());
  342. if (index.isValid()) {
  343. const quintptr hwndPtr = index.data(Qt::UserRole).value<quintptr>();
  344. const HWND hwnd = reinterpret_cast<HWND>(hwndPtr);
  345. const bool isDesktop = index.data(Qt::UserRole + 4).toBool();
  346. const bool isMinimized = index.data(Qt::UserRole + 2).toBool();
  347. const QString title = index.data(Qt::DisplayRole).toString();
  348. // if (isDesktop) {
  349. // // 切换到桌面
  350. // const HWND desktopHwnd = FindWindow(L"Progman", L"Program Manager");
  351. // if (desktopHwnd) {
  352. // SetForegroundWindow(desktopHwnd);
  353. // }
  354. // } else if (hwnd) {
  355. // // 激活窗口
  356. // if (isMinimized) {
  357. // ShowWindow(hwnd, SW_RESTORE);
  358. // }
  359. // // 尝试多次激活窗口(解决某些情况下激活失败的问题)
  360. // for (int i = 0; i < 3; i++) {
  361. // if (SetForegroundWindow(hwnd)) {
  362. // break;
  363. // }
  364. // Sleep(50);
  365. // }
  366. // }
  367. // 设置焦点索引
  368. focusedIndex = index;
  369. update();
  370. // 发出双击信号
  371. emit windowDoubleClicked(hwndPtr, title);
  372. }
  373. }
  374. // 调用基类实现以确保正确处理事件
  375. QListView::mouseDoubleClickEvent(event);
  376. }
  377. void WindowPreviewWidget::mouseMoveEvent(QMouseEvent *event)
  378. {
  379. QListView::mouseMoveEvent(event);
  380. // 获取当前鼠标位置下的索引
  381. QModelIndex currentIndex = indexAt(event->pos());
  382. // 更新悬停状态
  383. if (hoveredIndex != currentIndex) {
  384. if (hoveredIndex.isValid()) {
  385. update(hoveredIndex); // 更新旧的悬停项
  386. }
  387. hoveredIndex = currentIndex;
  388. if (hoveredIndex.isValid()) {
  389. update(hoveredIndex); // 更新新的悬停项
  390. }
  391. }
  392. }
  393. void WindowPreviewWidget::leaveEvent(QEvent *event)
  394. {
  395. Q_UNUSED(event);
  396. // 清除悬停状态
  397. if (hoveredIndex.isValid()) {
  398. update(hoveredIndex);
  399. hoveredIndex = QModelIndex();
  400. }
  401. }
  402. void WindowPreviewWidget::enterEvent(QEvent *event)
  403. {
  404. Q_UNUSED(event);
  405. // 不需要额外处理
  406. }
  407. void WindowPreviewWidget::focusInEvent(QFocusEvent *event)
  408. {
  409. QListView::focusInEvent(event);
  410. // 当获得焦点时更新视图
  411. update();
  412. }
  413. void WindowPreviewWidget::focusOutEvent(QFocusEvent *event)
  414. {
  415. QListView::focusOutEvent(event);
  416. // 当失去焦点时更新视图
  417. update();
  418. }
  419. void WindowPreviewWidget::updateThumbnails()
  420. {
  421. // 重置悬停索引
  422. if (hoveredIndex.isValid()) {
  423. update(hoveredIndex);
  424. hoveredIndex = QModelIndex();
  425. }
  426. // 获取活动窗口
  427. const HWND activeWindow = GetForegroundWindow();
  428. // 检查是否需要更新
  429. bool needUpdate = (activeWindow != lastActiveWindow);
  430. lastActiveWindow = activeWindow;
  431. // 获取所有窗口
  432. std::vector<WindowInfo> windows;
  433. EnumWindows(enumWindowsProc, reinterpret_cast<LPARAM>(&windows));
  434. // 清理缓存中的无效窗口
  435. for (auto it = thumbnailCache.begin(); it != thumbnailCache.end();) {
  436. if (!IsWindow(it.key())) {
  437. delete it.value();
  438. it = thumbnailCache.erase(it);
  439. } else {
  440. ++it;
  441. }
  442. }
  443. // 创建当前窗口集合(确保只包含有效窗口)
  444. QSet<HWND> currentWindowSet;
  445. for (const auto &win : windows) {
  446. if (IsWindow(win.hwnd)) {
  447. currentWindowSet.insert(win.hwnd);
  448. }
  449. }
  450. // 创建缓存窗口集合(确保只包含有效窗口)
  451. QSet<HWND> cachedWindowSet;
  452. for (auto it = thumbnailCache.begin(); it != thumbnailCache.end(); ++it) {
  453. if (IsWindow(it.key())) {
  454. cachedWindowSet.insert(it.key());
  455. }
  456. }
  457. // 如果窗口集合不同,需要完全更新
  458. if (currentWindowSet != cachedWindowSet) {
  459. needUpdate = true;
  460. }
  461. // 如果没有变化,不需要更新
  462. if (!needUpdate) {
  463. return;
  464. }
  465. // 清空模型
  466. model->clear();
  467. // 添加桌面预览
  468. addDesktopThumbnail(activeWindow);
  469. // 更新窗口预览
  470. for (const auto &win : windows) {
  471. // 确保窗口有效
  472. if (IsWindow(win.hwnd)) {
  473. addWindowThumbnail(win.hwnd, activeWindow);
  474. }
  475. }
  476. // 动态调整更新频率
  477. updateTimer->setInterval(needFullUpdate() ? 500 : 1500);
  478. }
  479. void WindowPreviewWidget::addWindowThumbnail(HWND hwnd, HWND activeWindow)
  480. {
  481. // 跳过某些系统窗口
  482. if (hwnd == (HWND) effectiveWinId())
  483. return;
  484. // 确保窗口有效
  485. if (!IsWindow(hwnd)) {
  486. return;
  487. }
  488. // 获取窗口标题
  489. std::wstring title;
  490. wchar_t titleBuffer[256] = {0};
  491. GetWindowTextW(hwnd, titleBuffer, 256);
  492. title = titleBuffer;
  493. // 获取窗口类名
  494. std::wstring className;
  495. wchar_t classBuffer[256] = {0};
  496. GetClassNameW(hwnd, classBuffer, 256);
  497. className = classBuffer;
  498. // 检查窗口状态
  499. const bool isMinimized = IsIconic(hwnd);
  500. const bool isActive = (activeWindow == hwnd);
  501. // 获取窗口图标
  502. QIcon icon = getWindowIcon(hwnd);
  503. // 获取窗口预览
  504. QPixmap *thumbnail = nullptr;
  505. if (thumbnailCache.contains(hwnd) && !needFullUpdate()) {
  506. thumbnail = thumbnailCache[hwnd];
  507. } else {
  508. thumbnail = captureWindow(hwnd);
  509. if (thumbnail) {
  510. thumbnailCache[hwnd] = thumbnail;
  511. }
  512. }
  513. // 创建列表项
  514. QStandardItem *item = new QStandardItem;
  515. if (thumbnail) {
  516. item->setData(*thumbnail, Qt::DecorationRole);
  517. }
  518. item->setData(QString::fromWCharArray(title.c_str()), Qt::DisplayRole);
  519. item->setData(reinterpret_cast<quintptr>(hwnd), Qt::UserRole); // 使用quintptr存储句柄
  520. item->setData(isActive, Qt::UserRole + 1);
  521. item->setData(isMinimized, Qt::UserRole + 2);
  522. item->setData(icon, Qt::UserRole + 3);
  523. item->setData(false, Qt::UserRole + 4);
  524. item->setData(QString::fromWCharArray(className.c_str()), Qt::UserRole + 5);
  525. model->appendRow(item);
  526. }
  527. QPixmap *WindowPreviewWidget::captureWindow(HWND hwnd)
  528. {
  529. // 确保窗口有效
  530. if (!IsWindow(hwnd)) {
  531. return nullptr;
  532. }
  533. // 获取窗口大小
  534. RECT rect;
  535. GetWindowRect(hwnd, &rect);
  536. const int width = rect.right - rect.left;
  537. const int height = rect.bottom - rect.top;
  538. if (width <= 0 || height <= 0) {
  539. return nullptr;
  540. }
  541. // 创建内存DC
  542. HDC hdcScreen = GetDC(NULL);
  543. HDC hdcMem = CreateCompatibleDC(hdcScreen);
  544. HBITMAP hbm = CreateCompatibleBitmap(hdcScreen, width, height);
  545. SelectObject(hdcMem, hbm);
  546. // 尝试两种捕获方法
  547. bool captureSuccess = false;
  548. // 处理最小化窗口
  549. bool wasMinimized = IsIconic(hwnd);
  550. if (wasMinimized) {
  551. // 临时恢复窗口位置
  552. // WINDOWPLACEMENT wp;
  553. // wp.length = sizeof(WINDOWPLACEMENT);
  554. // GetWindowPlacement(hwnd, &wp);
  555. // // 移动窗口到屏幕外
  556. // SetWindowPos(hwnd,
  557. // NULL,
  558. // -width - 10,
  559. // -height - 10,
  560. // width,
  561. // height,
  562. // SWP_NOZORDER | SWP_SHOWWINDOW);
  563. // ShowWindow(hwnd, SW_RESTORE);
  564. // 方法2: 使用PrintWindow (适用于最小化窗口)
  565. if (!captureSuccess) {
  566. if (PrintWindow(hwnd, hdcMem, PW_RENDERFULLCONTENT)) {
  567. captureSuccess = true;
  568. }
  569. }
  570. }
  571. if (!captureSuccess) {
  572. // 方法1: 使用BitBlt (最快)
  573. if (BitBlt(hdcMem, 0, 0, width, height, GetWindowDC(hwnd), 0, 0, SRCCOPY)) {
  574. captureSuccess = true;
  575. }
  576. }
  577. // 恢复最小化窗口状态
  578. if (wasMinimized) {
  579. ShowWindow(hwnd, SW_MINIMIZE);
  580. }
  581. // 转换为QPixmap
  582. QImage image(width, height, QImage::Format_ARGB32);
  583. BITMAPINFO bmi = {0};
  584. bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  585. bmi.bmiHeader.biWidth = width;
  586. bmi.bmiHeader.biHeight = -height; // 从上到下
  587. bmi.bmiHeader.biPlanes = 1;
  588. bmi.bmiHeader.biBitCount = 32;
  589. bmi.bmiHeader.biCompression = BI_RGB;
  590. if (captureSuccess) {
  591. GetDIBits(hdcMem, hbm, 0, height, image.bits(), &bmi, DIB_RGB_COLORS);
  592. } else {
  593. // 创建纯色背景作为回退
  594. image.fill(QColor(60, 60, 60).rgb());
  595. QPainter painter(&image);
  596. painter.setPen(Qt::white);
  597. painter.drawText(image.rect(), Qt::AlignCenter, "Capture Failed");
  598. }
  599. QPixmap *pixmap = new QPixmap(QPixmap::fromImage(image));
  600. // 缩放缩略图
  601. *pixmap = pixmap->scaled(thumbnailSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
  602. // 清理资源
  603. DeleteObject(hbm);
  604. DeleteDC(hdcMem);
  605. ReleaseDC(NULL, hdcScreen);
  606. return pixmap;
  607. }
  608. void WindowPreviewWidget::addDesktopThumbnail(HWND activeWindow)
  609. {
  610. // 使用缓存
  611. static QPixmap desktopThumbnail;
  612. if (desktopThumbnail.isNull() || needFullUpdate()) {
  613. // 捕获桌面
  614. QScreen *screen = QApplication::primaryScreen();
  615. desktopThumbnail = screen->grabWindow(0);
  616. }
  617. // 创建桌面项
  618. QStandardItem *item = new QStandardItem;
  619. item->setData(desktopThumbnail.scaled(thumbnailSize,
  620. Qt::KeepAspectRatio,
  621. Qt::SmoothTransformation),
  622. Qt::DecorationRole);
  623. item->setData("Desktop", Qt::DisplayRole);
  624. item->setData(quintptr(0), Qt::UserRole); // 桌面项使用0作为句柄
  625. item->setData((activeWindow == GetDesktopWindow()), Qt::UserRole + 1);
  626. item->setData(false, Qt::UserRole + 2);
  627. item->setData(QIcon(), Qt::UserRole + 3); // 桌面不需要图标
  628. item->setData(true, Qt::UserRole + 4);
  629. item->setData("Desktop", Qt::UserRole + 5);
  630. model->appendRow(item);
  631. }
  632. QIcon WindowPreviewWidget::getWindowIcon(HWND hwnd)
  633. {
  634. // 确保窗口有效
  635. if (!IsWindow(hwnd)) {
  636. return QIcon();
  637. }
  638. HICON hIcon = (HICON) SendMessage(hwnd, WM_GETICON, ICON_BIG, 0);
  639. if (!hIcon) {
  640. hIcon = (HICON) GetClassLongPtr(hwnd, GCLP_HICON);
  641. }
  642. if (!hIcon) {
  643. hIcon = LoadIcon(NULL, IDI_APPLICATION);
  644. }
  645. if (hIcon) {
  646. QIcon icon = QIcon(QtWin::fromHICON(hIcon));
  647. DestroyIcon(hIcon);
  648. return icon;
  649. }
  650. return QIcon();
  651. }
  652. bool WindowPreviewWidget::needFullUpdate() const
  653. {
  654. // 每5次更新做一次完全更新
  655. static int counter = 0;
  656. return (++counter % 5) == 0;
  657. }
  658. // int main(int argc, char *argv[])
  659. // {
  660. // QApplication app(argc, argv);
  661. // QWidget mainWidget;
  662. // QVBoxLayout *layout = new QVBoxLayout(&mainWidget);
  663. // layout->setContentsMargins(0, 0, 0, 0);
  664. // // 设置半透明背景
  665. // mainWidget.setAttribute(Qt::WA_TranslucentBackground);
  666. // // mainWidget.setStyleSheet("background: rgba(0, 0, 0, 150);");
  667. // WindowPreviewWidget *previewWidget = new WindowPreviewWidget;
  668. // layout->addWidget(previewWidget);
  669. // // 添加信息标签
  670. // QLabel *infoLabel = new QLabel("Double-click a window to see its handle");
  671. // infoLabel->setAlignment(Qt::AlignCenter);
  672. // infoLabel->setStyleSheet("color: white; font-weight: bold; background: rgba(0, 0, 0, 100);");
  673. // layout->addWidget(infoLabel);
  674. // // 连接双击信号
  675. // QObject::connect(previewWidget,
  676. // &WindowPreviewWidget::windowDoubleClicked,
  677. // [infoLabel](quintptr hwnd, const QString &title) {
  678. // QString info = QString("Double-clicked window: HWND=0x%1, Title=\"%2\"")
  679. // .arg(hwnd, 0, 16) // 以十六进制显示句柄
  680. // .arg(title);
  681. // infoLabel->setText(info);
  682. // });
  683. // mainWidget.setWindowTitle("Window Preview");
  684. // mainWidget.resize(900, 700);
  685. // mainWidget.show();
  686. // return app.exec();
  687. // }
  688. // #include "main.moc"