实验过程
win32的每个控件都可以叫做一个“窗口”,下面这个代码不陌生了,创建一个edit控件:
CreateWindow(L"edit", L"Edit at here.",
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
130, 50, 200, 20,
hwnd, (HMENU)IDC_EDIT, hInstance, NULL);
然而,我发现参数style可以是WS_CAPTION
于是……
WS_CAPTION 万 恶 之 源
当你在一个控件的style中加入WS_CAPTION时……(此处用static控件举例)
#define IDC_BTN 101
#define IDC_BOX 102
// 创建static控件的“窗口”
HWND hBox = CreateWindow(L"static", L"xx",
WS_CHILD | WS_VISIBLE | WS_CAPTION,
10, 10, 300, 300,
hwnd, (HMENU)IDC_BOX, hInstance, NULL);
// 在窗口内创建一个子控件
HWND hBtn = CreateWindow(L"button", L"Click Me!",
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
50, 50, 100, 20, hBox, (HMENU)IDC_BTN, hInstance, NULL);
它活了。
它拥有了一个窗口标题栏,而且还有一个子控件。于是,一个static控件顺理成章地变成了交互窗口,而且,这个窗口只能在主窗口中使用。
值得一提的是,static控件创造的窗口不能够拖动,但是窗口内的子控件可以正常使用。
把控件类型从static换成button后,窗口的整体是一个button,它的子控件仍可以正常使用,如果你点击了大的button,小的button会因为重绘而被覆盖。
注意!不是所有的控件都可以这么玩的,比如:
下拉框COMBOBOX,用它的话窗口标题栏是出不来的,而且窗口会被锁住,子控件也无法使用;
滚动条SCROLLBAR,使用的时候会出现鬼畜情况,体验极差。
还有其它控件,没有试。
那么,可以把这样的“窗口”的子控件也变成“窗口”吗?当然可以。这样的窗口可以无限套娃!
下面的代码进行了套娃:
#define IDC_EDIT 100
#define IDC_BTN 101
#define IDC_BOX 102
HWND hEdit;
HWND hBtn;
HWND hBox;
hEdit = CreateWindow(L"edit", L"Edit at here.",
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
130, 50, 200, 20,
hwnd, (HMENU)IDC_EDIT, hInstance, NULL);
hBox = CreateWindow(L"message", L"first",
WS_CHILD | WS_VISIBLE | WS_CAPTION | CS_VREDRAW | CS_HREDRAW,
330, 50, 400, 400,
hwnd, (HMENU)IDC_BOX, hInstance, NULL);
hBtn = CreateWindow(L"button", L"Click Me!",
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
50, 50, 100, 20,
CreateWindow(L"message", L"third",
WS_CHILD | WS_VISIBLE | WS_CAPTION,
10, 10, 200, 200,
CreateWindow(L"message", L"second",
WS_CHILD | WS_VISIBLE | WS_CAPTION,
10, 10, 300, 300,
hBox, (HMENU)IDC_BOX+1, hInstance, NULL)
, (HMENU)IDC_BOX+2, hInstance, NULL)
, (HMENU)IDC_BTN, hInstance, NULL);
效果:
根据上图,可以知道:
1. 主窗口的子窗口不能拥有Aero效果。
2. 子窗口的子窗口不能拥有Basic效果,只能拥有经典效果。
3. 其实子窗口中的子窗口在拖动时会出现重绘残留的情况,图中擦除了重绘残留,故看不出来。
还有更奇葩的一点是:上面的程序放到win10里运行,会卡出win7界面!
可以看出,主窗口是win10,子窗口开始变成win7窗口了!!
这一波,有图有真相,有码有真相,可以自己试试,不过不晓得微软啥时候就把它修复了。
窗体也可以够拥有关闭按钮,放大按钮,最小化按钮,只需要将WS_CAPTION改为WS_OVERLAPPEDWINDOW即可。
这样,做出来的效果和windows的MDI也差不多了:
实用建议
要利用win32这一特性,现对上述实验结果做一个总结:
1. 要使用子窗口,可以在控件的style参数中加入WS_CAPTION
2. 选择怎样的控件类作为子窗口也是很重要的一点
2.1 如果希望窗口不能够移动,推荐使用static,但是它和其它基础控件有个共同缺点是:窗口标题和控件标题是相同的,设置窗口标题会影响到控件文本。
2.2 请不要选择如下拉框(COMBOBOX),滚动条(SCROLLBAR)等钉子控件,用不了的。
2.3 如果希望窗口可以移动,类名请设置为message,它所创造出的窗口的客户区的全白的,设置它的窗口标题不会影响到窗口背景内容。
3. 多次套娃,建议不要。上述实验可以总结出经验:套娃到第三次时,重绘有残留,而且窗口样式变成了经典。
4. 非常重要:此操作不能取代win32原生MDI,因为经过测试,该操作无法获取子窗口内的控件消息,所以该操作仅适用于无需人机交互的情况。或者可以进行自绘控件,然后响应。
完整可运行代码:
VS2019
// WindowsProject1.cpp : 定义应用程序的入口点。
//
#include "framework.h"
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
#define IDC_EDIT 100
#define IDC_BTN 101
#define IDC_BOX 102
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此处放置代码。
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目标: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目标: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
HWND hEdit;
HWND hBtn;
HWND hBox;
hEdit = CreateWindow(L"edit", L"Edit at here.",
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
130, 50, 200, 20,
hWnd, (HMENU)IDC_EDIT, hInst, NULL);
hBox = CreateWindow(L"message", L"first",
WS_CHILD | WS_VISIBLE | WS_CAPTION | CS_VREDRAW | CS_HREDRAW,
330, 50, 400, 400,
hWnd, (HMENU)IDC_BOX, hInst, NULL);
hBtn = CreateWindow(L"button", L"Click Me!",
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
50, 50, 100, 20,
CreateWindow(L"message", L"third",
WS_CHILD | WS_VISIBLE | WS_CAPTION,
10, 10, 200, 200,
CreateWindow(L"message", L"second",
WS_CHILD | WS_VISIBLE | WS_CAPTION,
10, 10, 300, 300,
hBox, (HMENU)IDC_BOX + 1, hInst, NULL)
, (HMENU)IDC_BOX + 2, hInst, NULL)
, (HMENU)IDC_BTN, hInst, NULL);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}