SetParent 函数修改父窗口的误区

SetParent 函数可以更改指定子窗口的父窗口。但是这个函数需要注意它的调用方式,并不是简简单单就拿来用的。

HWND SetParent(
  [in]           HWND hWndChild,
  [in, optional] HWND hWndNewParent
);

[in] hWndChild

类型:HWND

子窗口的句柄。

[in, optional] hWndNewParent

类型:HWND

新父窗口的句柄。 如果此参数为 NULL,桌面窗口将成为新的父窗口。 如果此参数 HWND_MESSAGE,则子窗口将成为 仅消息窗口

如果函数成功,则返回值是上一个父窗口的句柄。

如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。

根据帮助文档的提示,需要注意以下几点:

(1)出于兼容性原因, SetParent 不会修改要更改其父级的窗口的 WS_CHILD 或 WS_POPUP 窗口样式。 因此,如果 hWndNewParent 为 NULL,则还应清除WS_CHILD位,并在调用 SetParent 后设置WS_POPUP样式。 相反,如果 hWndNewParent 不是 NULL,并且窗口以前是桌面的子级,则应在调用 SetParent 之前清除WS_POPUP样式并设置WS_CHILD样式。

(2)更改窗口的父级时,应同步两个窗口的 UISTATE。 有关详细信息,请参阅 WM_CHANGEUISTATE 和 WM_UPDATEUISTATE

(3)如果 hWndNewParent 和 hWndChild 以不同的 DPI 感知模式运行,则可能会出现意外行为或错误。 

所以,在设置父窗口前,一是:如果窗口是 POPUP 窗口,应该去除 WS_POPUP 属性,并手动添加 WS_CHILD 属性;二是,如果窗口线程的 DPI 设置不相同,则应该首先同步 DPI 设置,然后再调用 SetParent;三是,如果窗口控件被键盘击中,需要在调用前同步 UI 状态。

我们应该按照要求来,不然会出现 WS_POPUP 和 WS_CHILDWINDOW 同时存在的情况,窗口的行为会比较奇怪,解决方法是使用 GetWindowLong 和 SetWindowLong 组合来修改窗口属性,代码如下:

// 按照要求修改属性
LONG style = GetWindowLongW(hClientWnd, GWL_STYLE);
style &= ~WS_POPUP;
style |= WS_CHILD;
SetWindowLongW(hClientWnd, GWL_STYLE, style);

// 设置父窗口
SetParent(hClientWnd, hNewParentWnd);

然后,我们可以使用 GetProcessDPIAwareness 检索进程的 DPI 设置,然后使用SetProcessDPIAwareness 同步进程 DPI,或者使用 SetThreadDPIContext 来同步线程 DPI。

强烈建议使用应用程序清单来设置程序的 DPI 设置!!!

那么,如何取消设置父窗口呢,我们发现即使再次调用 SetParent,指定窗口仍然在最找设置的父窗口上。

我们可以通过将 SetParent 的第二个参数设置为 NULL,并在调用前去除 WS_CHILD 属性,在调用后根据记录选择是否恢复 WS_POPUP 属性。

一个恢复窗口独立性的例子如下:

// 取消CHILD属性
style = GetWindowLongW(hClientWnd, GWL_STYLE);
style &= ~WS_CHILD;
SetWindowLongW(hClientWnd, GWL_STYLE, style);
// 父窗口设置为默认(主桌面)窗口
SetParent(hClientWnd, NULL);
// 恢复WS_POPUP属性
style = GetWindowLongW(hClientWnd, GWL_STYLE);
style |= WS_POPUP;
SetWindowLongW(hClientWnd, GWL_STYLE, style);

完整代码如下:

#include <iostream>
#include <Windows.h>
#include <dwmapi.h>
#include <shellscalingapi.h>

#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "Shcore.lib")
using namespace std;

HWND hClientWnd = NULL;

LRESULT CALLBACK __WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	HRESULT hr = S_OK;
	switch (msg) {
	case WM_CLOSE:
		MessageBoxW(NULL, L"WM_CLOSE", L"NOTICE:!!!", NULL);
		break;
	case WM_SYSCOMMAND:
	{
		if (wParam == SC_CLOSE)
		{
			MessageBoxW(NULL, L"SC_CLOSE", L"NOTICE:!!!", NULL);
		}
	}
	break;
	case WM_ACTIVATE:
	{
		// Extend the frame into the client area.
		MARGINS margins{};

		margins.cxLeftWidth = 8;      // 8
		margins.cxRightWidth = 8;    // 8
		margins.cyBottomHeight = 20; // 20
		margins.cyTopHeight = 27;       // 27

		hr = DwmExtendFrameIntoClientArea(hWnd, &margins);

		if (!SUCCEEDED(hr))
		{
			// Handle the error.
		}
	}
	break;
	default:
		break;
	}

	return DefWindowProc(hWnd, msg, wParam, lParam);
}


DWORD CreateTestChildWindow(void* args)
{
	// 窗口属性初始化
	HINSTANCE hIns = GetModuleHandleW(0);
	WNDCLASSEXW wc{};
	wc.cbSize = sizeof(wc);								// 定义结构大小
	wc.style = CS_HREDRAW | CS_VREDRAW;					// 如果改变了客户区域的宽度或高度,则重新绘制整个窗口 
	wc.cbClsExtra = 0;									// 窗口结构的附加字节数
	wc.cbWndExtra = 0;									// 窗口实例的附加字节数
	wc.hInstance = hIns;								// 本模块的实例句柄
	wc.hIcon = NULL;									// 图标的句柄
	wc.hIconSm = NULL;									// 和窗口类关联的小图标的句柄
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;			// 背景画刷的句柄
	wc.hCursor = NULL;									// 光标的句柄
	wc.lpfnWndProc = __WndProc;							// 窗口处理函数的指针
	wc.lpszMenuName = NULL;								// 指向菜单的指针
	wc.lpszClassName = L"TestWndClass";					// 指向类名称的指针

	// 为窗口注册一个窗口类
	if (!RegisterClassExW(&wc)) {
		cout << "RegisterClassEx error : " << GetLastError() << endl;
	}
	const auto cx{ GetSystemMetrics(SM_CXFULLSCREEN) }; // 取显示器屏幕高宽
	const auto cy{ GetSystemMetrics(SM_CYFULLSCREEN) };
	const auto x{ (cx >> 1 ) - 400 };
	const auto y{ (cy >> 1 ) - 300 };

	// 创建窗口
	HWND hWnd = CreateWindowExW(
		WS_EX_APPWINDOW,				// 窗口扩展样式:顶级窗口
		L"TestWndClass",				// 窗口类名
		L"TestWindows",				// 窗口标题
		WS_POPUP| \
		WS_ACTIVECAPTION | \
		WS_VISIBLE | \
		WS_CAPTION | \
		WS_SYSMENU | \
		WS_THICKFRAME | \
		WS_MINIMIZEBOX | \
		WS_MAXIMIZEBOX,		// 窗口样式:重叠窗口
		x,							// 窗口初始x坐标
		y,							// 窗口初始y坐标
		800,						// 窗口宽度
		600,						// 窗口高度
		0,							// 父窗口句柄
		0,							// 菜单句柄 
		hIns,						// 与窗口关联的模块实例的句柄
		0							// 用来传递给窗口WM_CREATE消息
	);

	if (hWnd == 0) {
		cout << "CreateWindowEx error : " << GetLastError() << endl;
		return 0;
	}
	hClientWnd = hWnd;
	UpdateWindow(hWnd);
	ShowWindow(hWnd, SW_SHOW);

	// 消息循环(没有会导致窗口卡死)
	MSG msg = { 0 };
	while (msg.message != WM_QUIT) {
		// 从消息队列中删除一条消息
		if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
			DispatchMessageW(&msg);
		}
	}
	return 0;
}
/**
void CALLBACK Timeproc(HWND, UINT, UINT_PTR, DWORD)
{
	if (hClientWnd)//判断是否为空,因为对话框创建时会调用此函数,而当时控件还未创建 
	{
		RECT rect; //获取控件变化前大小
		GetWindowRect(hClientWnd, &rect);
		//ScreenToClient(hClientWnd, &rect);//将控件大小转换为在对话框中的区域坐标 
		rect.left = rect.left * cx / m_rect.Width();//调整控件大小
			rect.right = rect.right * cx / m_rect.Width();
		rect.top = rect.top * cy / m_rect.Height();
		rect.bottom = rect.bottom * cy / m_rect.Height();
		MoveWindow(hClientWnd, rect);//设置控件大小  伸缩控件
	}
}*/

DWORD BackgroundTaskHost(void* args)
{
	//SetTimer(hClientWnd, 1001, 3000, Timeproc);
	
	return 0;
}

int main()
{
	SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
	CreateThread(NULL, NULL, CreateTestChildWindow, NULL, 0, 0);

	while (!hClientWnd) {
		//Sleep(10);
	}
	if (!hClientWnd) return 0;
	system("pause");
	HWND hDesktop = FindWindowW(L"Notepad", NULL);
	HWND hChildTextWnd = FindWindowExW(hDesktop, NULL, 
		L"NotepadTextBox", NULL);
	printf("%2p %2p\n", hClientWnd, hDesktop);
	if (!hDesktop || !hChildTextWnd)
	{
		system("pause");
		return 0;
	}
	SetFocus(hChildTextWnd);
	RECT prtrc = { 0 },
		 cntrc = { 0 };
	GetWindowRect(hChildTextWnd, &prtrc);
	GetWindowRect(hClientWnd, &cntrc);
	int prtcx = prtrc.right - prtrc.left;
	int prtcy = prtrc.bottom - prtrc.top;
	int cntcx = cntrc.right - cntrc.left;
	int cntcy = cntrc.bottom - cntrc.top;
	int Child_X = (prtcx >> 1) + prtrc.left - (cntcx >> 1);
	int Child_Y = (prtcy >> 1) + prtrc.top - (cntcy >> 1);
	SetWindowPos(
		hClientWnd, NULL, 
		Child_X, Child_Y, 
		0, 0, 
		SWP_NOSIZE | SWP_NOZORDER
	);
	UpdateWindow(hClientWnd);
	LONG style = GetWindowLongW(hClientWnd, GWL_STYLE);
	style &= ~WS_POPUP;
	style |= WS_CHILD;
	SetWindowLongW(hClientWnd, GWL_STYLE, style);
	SetParent(hClientWnd, hDesktop);
	Child_X -= prtrc.left;
	Child_Y -= prtrc.top;
	SetWindowPos(
		hClientWnd, NULL,
		Child_X, Child_Y,
		0, 0,
		SWP_NOSIZE | SWP_NOZORDER
	);
	//CreateThread(NULL, NULL, BackgroundTaskHost, NULL, 0, 0);
	system("pause");
	style = GetWindowLongW(hClientWnd, GWL_STYLE);
	style &= ~WS_CHILD;
	SetWindowLongW(hClientWnd, GWL_STYLE, style);
	SetParent(hClientWnd, NULL);
	style = GetWindowLongW(hClientWnd, GWL_STYLE);
	style |= WS_POPUP;
	SetWindowLongW(hClientWnd, GWL_STYLE, style);
	system("pause");
	return 0;
}

测试截图:

更新于:2023.10.12

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值