SetParent 函数修改父窗口的误区

本文详细解释了如何正确使用SetParent函数改变窗口的父窗口,包括处理WS_CHILD和WS_POPUP样式、同步UISTATE和DPI设置,以及如何取消设置以保持窗口独立性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

<think>嗯,用户现在问的是关于QLabel的move函数。首先,我需要确认用户的问题是什么。他们可能在使用Qt的时候遇到了布局或者位置调整的问题,想知道如何使用move函数来移动QLabel。 首先,我应该回顾一下QLabel的move函数的基本用法。记得move是继承自QWidget的方法,用来设置部件的位置。参数是x和y坐标,或者是QPoint对象。坐标是相对于父部件的,这一点很重要,用户可能容易忽略父容器的影响。 然后,用户可能的疑问包括为什么使用move后控件的位置没变,或者动态调整位置时的注意事项。比如,如果在布局管理器中使用move,可能不会生效,因为布局管理器会自动调整位置。这时候需要提醒用户避免这种情况。 接下来,用户的应用场景可能是什么?比如,他们可能在做一个需要手动调整控件位置的界面,或者在做动画效果,需要移动标签。这时候需要给出不同场景下的建议,比如在绝对定位的情况下使用move,而在有布局的情况下推荐使用布局管理器。 还要考虑用户可能的误区,比如多次调用move是否有效率问题,或者是否需要考虑重绘。这时候可以建议在需要频繁移动时使用setGeometry,或者搭配repaint()使用。 另外,用户可能想知道move和setGeometry的区别,是否需要同时调整大小。这时候需要解释两者的不同,move只改变位置,而setGeometry改变位置和大小。 最后,应该总结注意事项,比如父部件的影响、布局管理器的冲突、坐标系统的问题,以及如何动态调整。给出示例代码帮助用户理解,并提醒常见错误,比如忘记设置父部件或者布局管理器的影响。 总之,回答要覆盖基本用法、注意事项、常见问题,以及示例代码,确保用户能够全面理解如何使用QLabel的move函数,并避免常见错误。</think>在Qt中,`QLabel` 的 `move()` 函数是用于控件**坐标定位**的核心方法。以下是关于该函数的详细解析: --- ### 一、函数定义与基本用法 #### 1. 函数原型 ```cpp // 设置控件左上角在父容器中的坐标 void QWidget::move(int x, int y); // 通过坐标值 void QWidget::move(const QPoint &pos); // 通过QPoint对象 ``` #### 2. 使用示例 ```cpp // 创建标签并设置父窗口 QLabel *label = new QLabel("Hello Qt!", this); // 移动到坐标(100,50)处 label->move(100, 50); // 等价写法 label->move(QPoint(100, 50)); ``` --- ### 二、关键特性 #### 1. 坐标系规则 - **相对坐标**:以父容器的**客户区左上角**为原点 $(0,0)$ - **单位**:像素(px) - **生效时机**:需要父容器`show()`后生效 #### 2. 定位逻辑 ```mermaid graph TD A[父容器] --> B[子控件] B --> C{调用move(x,y)} C --> D[更新几何位置] D --> E[触发重绘事件] ``` --- ### 三、典型应用场景 #### 1. 绝对定位布局 ```cpp // 在无布局管理器时手动定位 label1->move(10, 20); label2->move(10, 60); // 纵向排列 ``` #### 2. 动态动画效果 ```cpp // 实现平移动画 QPropertyAnimation *anim = new QPropertyAnimation(label, "pos"); anim->setDuration(1000); anim->setStartValue(QPoint(0,0)); anim->setEndValue(QPoint(200,150)); anim->start(); ``` #### 3. 响应式调整 ```cpp // 窗口大小变化时重新定位 void MainWindow::resizeEvent(QResizeEvent *event) { label->move(width()-100, height()-50); // 保持右下角相对位置 } ``` --- ### 四、注意事项 #### 1. 布局管理器冲突 - 如果控件已加入布局(`QVBoxLayout`等),`move()` 将失效 - 解决方案: ```cpp // 移除布局管理 layout()->removeWidget(label); label->setParent(this); // 重新设置父对象 label->move(x,y); // 此时move生效 ``` #### 2. 隐藏/显示问题 ```cpp label->hide(); label->move(100, 50); // 移动后需要show()才能看到效果 label->show(); ``` #### 3. 性能优化 - 避免在循环中频繁调用: ```cpp // 错误用法 for(int i=0; i<1000; i++){ label->move(i, i); // 触发多次重绘 } // 正确做法 label->hide(); label->move(targetX, targetY); label->show(); ``` --- ### 五、关联方法对比 | 方法 | 功能差异 | 适用场景 | |--------------------|---------------------------------|---------------------| | `move(x,y)` | 仅改变位置 | 精确定位 | | `setGeometry()` | 同时设置位置和尺寸 | 需要调整大小时 | | `adjustSize()` | 根据内容自动调整大小 | 文本/图片变化后 | | `pos()` | 获取当前坐标 | 位置读取 | --- ### 六、常见错误排查 1. **控件不移动** - ✅ 检查父容器是否设置正确 - ✅ 确认未使用布局管理器 - ✅ 验证坐标是否超出父容器范围 2. **位置偏移异常** ```cpp // 注意坐标系包含窗口边框 QRect frame = frameGeometry(); move(100 + frame.left(), 50 + frame.top()); // 补偿边框偏移 ``` 3. **多显示器问题** ```cpp // 获取屏幕坐标系 QPoint screenPos = label->mapToGlobal(QPoint(0,0)); ``` --- 通过合理使用 `move()` 方法,可以实现精确的控件定位,但建议优先使用Qt的布局系统(Layout System)进行自动排版,以更好地适应不同分辨率和窗口尺寸的变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

涟幽516

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

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

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

打赏作者

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

抵扣说明:

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

余额充值