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