伴随窗口的实现

伴随窗口主要功能点:

  1. 保持和目标窗口相同的Z序
  2. 随伴随窗口移动、关闭、隐藏

保持Z序

cpp 复制代码
//SetOwner 设置所属关系,防止窗口可以单独激活, requires administrator privileges
SetWindowLongPtr(m_hWnd, GWLP_HWNDPARENT, (LONG_PTR)hCompanitionWnd);

随窗口移动、关闭、隐藏

cpp 复制代码
SetWinEventHook 抓取窗口事件,并且处理事件

具体实现

cpp 复制代码
#pragma once
#include <windows.h>
#include <string>
#include "WebWnd.h"
class CVALCompanitionWnd 
{
public:
    static CVALCompanitionWnd& Instance();
    void Attach(const std::wstring& wurl,HWND hCompanitionWnd);
    void Detach();
private:
    LRESULT HandleMessage(CWebWnd* pWeb, UINT uMsg, WPARAM wParam, LPARAM lParam);
    static void CALLBACK WinEventProc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime);
    void UpdatePos();
    void UpdateWorkRect();
private:
    CVALCompanitionWnd();
    HWINEVENTHOOK m_hook;
    HWND m_hCompanitionWnd;
    HWND m_hWnd;
    BOOL m_isLoadOK;
    BOOL m_isCompanition;//是否伴随
    BOOL m_isUserMoved;//是否用户主动移动
    RECT m_rtVWrok;//工作区域
};
cpp 复制代码
#include "stdafx.h"
#include "VALCompanitionWnd.h"
#include "WebWnd.h"


CVALCompanitionWnd& CVALCompanitionWnd::Instance()
{
	static CVALCompanitionWnd sInstance;
	return sInstance;
}

CVALCompanitionWnd::CVALCompanitionWnd()
{
	m_hook = nullptr;
	m_hWnd = nullptr;
	m_hCompanitionWnd = nullptr;
	m_isUserMoved = FALSE;
	m_isCompanition = TRUE;
	m_isLoadOK = FALSE;
}

void CVALCompanitionWnd::Attach(const std::wstring& wurl, HWND hCompanitionWnd)
{
	XLOGI("CVALCompanitionWnd::Attach url:%s,hCompanitionWnd:%I64d", W2ACSTR(wurl), hCompanitionWnd);
	m_hCompanitionWnd = hCompanitionWnd;
	if (!CWebWnd::WindowFromUrl(wurl))
	{
		UpdateWorkRect();
		CWebWnd* pWebWnd = CWebWnd::Open(_T("VAL伴随"), wurl, L"",0, 0, true, true, 0,
			std::bind(&CVALCompanitionWnd::HandleMessage,this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
		
		//默认隐藏,加载成功之后显示
		pWebWnd->SetRoundCorner(0,0);
		pWebWnd->ShowWindow(false, false);
		m_hWnd = *pWebWnd;
	}
	
	if(m_isLoadOK)
		::ShowWindow(m_hWnd,SW_SHOWNOACTIVATE);
	
	//SetOwner 设置所属关系,防止窗口可以单独激活,需要管理员权限
	SetWindowLongPtr(m_hWnd, GWLP_HWNDPARENT, (LONG_PTR)hCompanitionWnd);
	//更新位置
	UpdatePos();

	if (m_hook)
		UnhookWinEvent(m_hook);
	m_hook = nullptr;
	DWORD dwProcessId = 0;
	DWORD dwThreadId = GetWindowThreadProcessId(hCompanitionWnd, &dwProcessId);
	m_hook = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, &CVALCompanitionWnd::WinEventProc, dwProcessId, dwThreadId, WINEVENT_OUTOFCONTEXT);

	XLOGI("CVALCompanitionWnd::SetWinEventHook Hook:%I64d", m_hook);
}

void CVALCompanitionWnd::Detach()
{
	XLOGI("CVALCompanitionWnd::Detach");
	m_hCompanitionWnd = nullptr;
	m_isCompanition = TRUE;
	m_isUserMoved = FALSE;
	if (m_hook)
		UnhookWinEvent(m_hook);
	m_hook = nullptr;

	if(::IsWindow(m_hWnd))
		::ShowWindow(m_hWnd, SW_HIDE);
}


void CVALCompanitionWnd::UpdatePos()
{
	static const int W = 264;
	RECT rtWindow;

	//如果不伴随则不调整位置
	if (!m_isCompanition || !GetWindowRect(m_hCompanitionWnd, &rtWindow)) return;

	XLOGI("CVALCompanitionWnd::Attach HWND:%I64d rt{%d,%d,%d,%d}\r\n", m_hCompanitionWnd, rtWindow.left, rtWindow.top, rtWindow.right, rtWindow.bottom);
	rtWindow.left = rtWindow.right;
	rtWindow.right = rtWindow.left + (W*GetDpi()*1.0)/100.0;
	if (rtWindow.right > m_rtVWrok.right)
	{
		rtWindow.right = m_rtVWrok.right;
		rtWindow.left = rtWindow.right - (W * GetDpi() * 1.0) / 100.0;
	}
	SetWindowPos(m_hWnd, NULL, rtWindow.left, rtWindow.top, rtWindow.right - rtWindow.left, rtWindow.bottom - rtWindow.top, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW);

}

void CVALCompanitionWnd::UpdateWorkRect()
{
	int totalWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); // 总宽度
	int totalHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); // 总高度
	int startX = GetSystemMetrics(SM_XVIRTUALSCREEN); // 虚拟桌面左上角X坐标(可能为负)
	int startY = GetSystemMetrics(SM_YVIRTUALSCREEN); // 虚拟桌面左上角Y坐标

	m_rtVWrok.left = startX;
	m_rtVWrok.top = startY;
	m_rtVWrok.right = m_rtVWrok.left + totalWidth;
	m_rtVWrok.bottom = m_rtVWrok.top + totalHeight;

	XLOGI("CVALCompanitionWnd UpdateWorkRect[l:%d,t:%d,r:%d,b:%d]",
		m_rtVWrok.left, m_rtVWrok.top, m_rtVWrok.right, m_rtVWrok.bottom);
}

LRESULT CVALCompanitionWnd::HandleMessage(CWebWnd* pWeb, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg == UM_UPDATEWEB_LOADSTATUS)
	{
		m_isLoadOK = BOOL(lParam);
		XLOGI("CVALCompanitionWnd UM_UPDATEWEB_LOADSTATUS web load:%s", m_isLoadOK ? "successed" : "failed");
		if (m_isLoadOK && IsWindow(m_hCompanitionWnd) && !::IsIconic(m_hCompanitionWnd))
			ShowWindow(*pWeb, SW_SHOWNOACTIVATE);
	}
	else if (uMsg == WM_DISPLAYCHANGE || uMsg == WM_DPICHANGED)
	{
		UpdateWorkRect();
		UpdatePos();
	}
	else if (uMsg == WM_SHOWWINDOW)
	{
		if (pWeb)
			pWeb->NotifyWindowActive(BOOL(wParam), ::IsZoomed(*pWeb));
	}
	else if (uMsg == WM_NCLBUTTONDOWN)
	{
		m_isUserMoved = TRUE;
	}
	else if (uMsg == WM_NCLBUTTONUP)
	{
		m_isUserMoved = FALSE;
	}
	else if(uMsg == WM_MOVE)
	{
		if (m_isUserMoved)
		{
			m_isUserMoved = FALSE;
			RECT rtCompanition;
			RECT rtWnd;
			//如果区域超过80则认为脱离伴随
			if (GetWindowRect(m_hCompanitionWnd, &rtCompanition) &&
				GetWindowRect(*pWeb, &rtWnd))
			{
				int offset_x = abs(rtWnd.left - rtCompanition.right);
				int offset_y = abs(rtWnd.top - rtCompanition.top);
				if (offset_x >= 50 ||
					offset_y >= 50)
				{
					m_isCompanition = FALSE;
				}
				else
				{
					m_isCompanition = TRUE;
					//如果误差大于20则重新定位
					if (offset_y >= 10 || offset_x >= 10)
						UpdatePos();
				}
			}
		}
	}
	else if (uMsg == WM_DESTROY)
	{
		Instance().Detach();
	}
	return FALSE;
}

void CALLBACK CVALCompanitionWnd::WinEventProc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime)
{
	DWORD dwCurThreadid = GetCurrentThreadId();
	if (event == EVENT_OBJECT_LOCATIONCHANGE) {
		//如果窗口移动
		if (idObject == OBJID_WINDOW && idChild == CHILDID_SELF)
		{
			if (hWnd == Instance().m_hCompanitionWnd)
			{
				if (::IsIconic(hWnd))
					ShowWindow(Instance().m_hWnd, SW_HIDE);
				else
				{
					static bool bfisrt = true;
					if (Instance().m_isLoadOK && !IsWindowVisible(Instance().m_hWnd))
					{
						if (bfisrt)
						{
							bfisrt = false;
							SetWindowPos(Instance().m_hWnd, NULL, 0, 0, 1, 1, SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);
						}
						else
						{
							SetWindowPos(Instance().m_hWnd, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW);
						}
					}
					Instance().m_isUserMoved = FALSE;
					Instance().UpdatePos();
				}
			}
		}
	}
	else if (event == EVENT_OBJECT_HIDE)
	{
		XLOGI("WinEventProc EVENT_OBJECT_HIDE idObject:%I64d,idChild:%I64d", idObject, idChild);
		if (idObject == OBJID_WINDOW && idChild == CHILDID_SELF)
		{
			if (hWnd == Instance().m_hCompanitionWnd)
				::ShowWindow(Instance().m_hWnd, SW_HIDE);
		}
	}
	else if (event == EVENT_OBJECT_SHOW)
	{
		XLOGI("WinEventProc EVENT_OBJECT_SHOW idObject:%I64d,idChild:%I64d", idObject, idChild);
		if (idObject == OBJID_WINDOW && idChild == CHILDID_SELF)
		{
			if (hWnd == Instance().m_hCompanitionWnd)
				::ShowWindow(Instance().m_hWnd, SW_SHOWNORMAL);
		}
		
	}
	else if (event == EVENT_OBJECT_DESTROY)
	{
		if (idObject == OBJID_WINDOW && idChild == CHILDID_SELF)
		{
			//建议定时器通过IsWindow来检查窗口是否存在,如果不存在则UnhookWinEvent
			if (hWnd == Instance().m_hCompanitionWnd)
			{
				Instance().Detach();
			}
		}
	}
	//窗口移动
	else if (event == EVENT_SYSTEM_MOVESIZEEND)
	{
		int a = 0;
	}
}