// PreviewTipWnd.h // 双击在线主机时弹出的浮窗:上方 JPEG 缩略图,下方主机信息文本。 // 无图片时显示"加载预览…"占位,供 OnListClick 即时弹窗、收到响应后再 SetImage 重画。 #pragma once #include #include #include namespace Gdiplus { class Bitmap; } class CPreviewTipWnd : public CWnd { public: CPreviewTipWnd(); virtual ~CPreviewTipWnd(); // 创建浮窗。 // anchor 屏幕坐标,浮窗左上角 // text 下方显示的主机详情文本(宽字符,确保跨语言系统正确渲染) // imageReserveW 上方图像区域预留宽度(即将到来的预览最大宽度,仅作初始布局) // 为 0 表示不预留 — 与老 STATIC 路径行为一致(仅文本) // loopMode true: 真正的可拖拽/缩放/最小化/最大化的独立窗口("播放快照"循环), // 带标题栏 + 任务栏图标,关闭按钮走系统菜单; // false: 单发 tooltip(双击主机的预览),无 chrome、不激活、置顶。 // windowTitle loopMode=true 时显示在标题栏与任务栏的文本;false 时忽略。 BOOL Create(CWnd* pParent, CPoint anchor, const CStringW& text, int imageReserveW, bool loopMode = false, const CStringW& windowTitle = L""); // 收到 JPEG 后调用:解码并重画。线程安全前提是只在主 UI 线程调用。 void SetImageFromJpeg(const BYTE* data, size_t bytes); // 标记预览不可用(请求超时 / 客户端报错)。 void MarkPreviewUnavailable(); WORD GetReqId() const { return m_reqId; } void SetReqId(WORD id) { m_reqId = id; } // 循环快照模式:开启后窗口销毁时会向 ownerHwnd 发送 msgId(LPARAM 是堆上的 // uint64_t* 携带 clientId,接收方负责 delete),且 PostNcDestroy 会自动 delete this。 // 调用方需在自行销毁前用 SetLoopOwner(NULL, 0, 0) 解除回调,避免重复通知。 void SetLoopOwner(HWND ownerHwnd, UINT msgId, uint64_t clientId); uint64_t GetClientID() const { return m_clientId; } bool IsLoopMode() const { return m_loopMode; } protected: afx_msg void OnPaint(); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnDestroy(); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); virtual void PostNcDestroy(); DECLARE_MESSAGE_MAP() private: void RecalcLayoutAndResize(); void DrawImageArea(CDC& dc, const CRect& rc); void DrawTextArea(CDC& dc, const CRect& rc); // 循环模式:基于当前 client rect 重新分配图像区/文本区 void LayoutForLoopMode(const CRect& client, CRect& outImg, CRect& outText); CStringW m_text; int m_imageReserveW = 0; // 预留图像宽度(图像未到达时占位) int m_imageReserveH = 0; // 预留图像高度(按 16:9) int m_imageDrawW = 0; // 实际图像绘制宽度 int m_imageDrawH = 0; // 实际图像绘制高度 int m_textW = 0; int m_textH = 0; bool m_hasImage = false; bool m_unavailable = false; std::unique_ptr m_image; CFont m_font; WORD m_reqId = 0; // 循环快照模式相关 bool m_loopMode = false; HWND m_loopOwner = nullptr; UINT m_loopMsg = 0; uint64_t m_clientId = 0; // 标题栏 / 任务栏图标。WM_SETICON 不转移所有权(MSDN:originator 必须自己释放), // 析构时 DestroyIcon;不能在 OnDestroy 里销毁,因为系统在 WM_NCDESTROY 之前还在用它。 HICON m_hIconSmall = nullptr; HICON m_hIconBig = nullptr; };