前言:
静态创建窗口比较简单,都是资源文件,拖动控件,或支持xml配置的
那么如何动态创建窗口和控件呢?
【
本质:都是利用CreateWindowEx,CreateWindow
下面几点知道就差不多了:
[
1.CreateWindowEx动态创建窗口
CreateWindow动态创建控件
2.注册窗口类使用方法
3.窗口类型填什么(窗口和子窗口)
4.动态创建的窗口或控件响应
5.动态创建的窗口是非阻塞的,所以外边需要加消息循环
]
】
1动态创建窗口: --CreateWindowEx
[
创建窗口:
const WCHAR mywnd_class_name[] = L"Sample Window Class";
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)::DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = theApp.m_hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = L"";
wc.lpszClassName = mywnd_class_name;
RegisterClass(&wc);
m_hMywnd = ::CreateWindowExW(WS_EX_TOOLWINDOW, mywnd_class_name, L"mywnd", WS_POPUP | WS_OVERLAPPEDWINDOW, 0, 0, 800, 500, this->m_hWnd, nullptr, theApp.m_hInstance, nullptr);
显示:
CWnd::FromHandle(m_hMywnd)->CenterWindow(this);
::ShowWindow(m_hMywnd, SW_SHOW);
说明:
1不注册类名,会导致创建失败;"WNDCLASS wc"后要初始化或者全都赋值,否则创建失败,初始化方法:WNDCLASS wc = {0};或者memset(&wc, 0, sizeof(WNDCLASS));
2使用默认响应使用::DefWindowProc,想自定义,就自己自定义一个,使用SetWindowLong,下面有
3.如果不需要标题等,上面参数直接填WS_POPUP就行了,其他可以自己绘制实现
4.CS_HREDRAW | CS_VREDRAW --水平重绘,竖直重绘,不是水平,竖直
5.扩展类型
WS_EX_LEFT --CreateWindow是CreateWindowEx第一个参数为0的封装,0就是WS_EX_LEFT, 指定窗口具有左对齐属性。这是缺省值
WS_EX_APPWINDOW --窗口有任务栏图标
WS_EX_TOOLWINDOW --工具窗口不出现在任务条或用户按下ALT+TAB时出现的窗口中,一般弹出窗口设置这个
WS_EX_TOPMOST --顶层
WS_EX_LAYERED --分层或透明窗口,该样式可使用混合特效
WS_EX_TRANSPARENT --透明的,这意味着,在这个窗口下面的任何窗口都不会被这个窗口挡住。用这个风格创建的窗口只有当它下面的窗口都更新过以后才接收WM_PAINT消息
其他不常用
所以,对一个默认的窗口,如果已经有父窗口时,如果不知道设置什么属性就设置WS_EX_LEFT 属性就行了,这是缺省属性,
也可以设置WS_EX_TOOLWINDOW
]
2动态创建子控件或子窗口: --CreateWindow
[
响应函数:
static HWND g_hWndBtn;
static WNDPROC g_wndOrignProcBtn;
LRESULT APIENTRY BtnProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_LBUTTONDOWN)
{
::MessageBox(NULL, 0, 0, MB_OK);
return TRUE;
}
return CallWindowProc(g_wndOrignProcBtn, hwnd, uMsg, wParam, lParam);
}
创建:
g_hWndBtn = ::CreateWindowW(L"BUTTON", L"mybtn", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS , 0, 0, 0, 0, this->m_hWnd, nullptr, theApp.m_hInstance, nullptr);
if (g_hWndBtn)
{
g_wndOrignProcBtn = (WNDPROC)SetWindowLong(g_hWndBtn,GWL_WNDPROC, (LONG)BtnProc);
}
位置和大小:在onsize中设置
说明:
1自定义的子窗口第一个参数类名填空,会导致创建窗口失败,所以要先注册窗口类名,然后再创建;如果想创建已经支持的控件,查看下面msdn网址
2变量用全局的, 原因是SetWindowLong(g_hWndBtn,GWL_WNDPROC, (LONG)BtnProc);的第三个参数只能设置为全局函数,设置为成员函数会导致编译不过,此时全局变量可以直接用
3响应方式不能使用窗口的Lbuttondown判断区域的方法,因为控件是实际窗口,鼠标被控件响应了
]
说明:
1所有控件、窗口,创建流程都是一样的,都是先注册类,然后创建,只不过有的接口封装了看不到而已,比如MFC的CWnd,CDialog
2窗口、控件都有类名,已经实现的控件的类名如下:
msdn参考:(含已有控件的类名)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowa
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
1.MFC--所有的子控件、窗口,本质都是继承自CWnd,所以创建函数都是一样的,都是需要先注册窗口类,然后创建
创建窗口:
[
头文件:
CMyWnd *m_pMyWnd;
实现文件:
m_pMyWnd = new CMyWnd;
//m_pMyWnd->InitParam();
m_pMyWnd->CreateMyWindow(this->m_hWnd);
其中
bool CMyWnd::CreateMyWindow(HWND hWnd)
{
m_hWndParent = hWnd;
CString strWndClass = AfxRegisterWndClass(CS_VREDRAW | CS_HREDRAW, AfxGetApp()->LoadStandardCursor(IDC_ARROW), (HBRUSH)(COLOR_3DFACE + 1));
CreateEx(WS_EX_TOOLWINDOW, strWndClass, NULL, WS_POPUP, CRect(0, 0, 0, 0), FromHandle(hWnd), 0);
return true;
}
流程:
AfxRegisterWndClass注册窗口类
CreateEx创建
子控件的位置:在onsize中处理
]
---
创建子控件:
[
头文件:
CMyList *m_pWndList;
实现文件:
//创建列表
if (nullptr == m_pWndList)
{
m_pWndList = new CMyList(this);
//m_pWndList->InitParam();
//m_pWndList->SetFont(m_fontText);
//m_pWndList->SetBkColor(m_clrBackground);
//m_pWndList->SetTextColor(m_clrText);
//m_pWndList->SetTextHotColor(m_clrHotText);
m_pWndList->Create(NULL, _T(""), WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_CHILDWINDOW | WS_VISIBLE | WS_EX_TOPMOST, CRect(0,0,0,0), this, 8000);
m_pWndList->ShowWindow(SW_SHOW);
}
位置和大小:在onsize设置
]
----------
2.如何通过msdn找到创建窗口和子控件的代码?
bing搜索 "msdn createwindow"
->“Creating a Window - Win32 apps | Microsoft Docs” --这是创建窗口
->“CreateWindowA macro (winuser.h) - Win32 apps” -- CreateWindow使用
->“CreateWindowExA function (winuser.h) - Win32 apps” --CreateWindowEx使用
【
1.Create,CreateEx的本质
[
关系:
[
#define CreateWindowA(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\
CreateWindowExA(0L, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)
#define CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\
CreateWindowExW(0L, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)
#ifdef UNICODE
#define CreateWindow CreateWindowW
#else
#define CreateWindow CreateWindowA
#endif
msdn网址:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowa
---
BOOL Create(
PCWSTR lpWindowName,
DWORD dwStyle,
DWORD dwExStyle = 0,
int x = CW_USEDEFAULT,
int y = CW_USEDEFAULT,
int nWidth = CW_USEDEFAULT,
int nHeight = CW_USEDEFAULT,
HWND hWndParent = 0,
HMENU hMenu = 0
)
{
WNDCLASS wc = {0};
wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = ClassName();
RegisterClass(&wc);
m_hwnd = CreateWindowEx(
dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
);
return (m_hwnd ? TRUE : FALSE);
}
参考msdn网址:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
]
=>
[
MFC接口:
Create --里面封装了CreateWindow
CreateEx --里面封装了CreateWindowEx
win32接口:
CreateWindow --里面封装了CreateWindowEx,第一个参数是0,即WS_EX_LEFT
CreateWindowEx --最原始接口
]
总结:
[
最底层的接口是CreateWindowEx,
CreateWindow是对CreateWindowEx的封装,
MFC是对win32的封装,其中
Create是对CreateWindow的封装
CreateEx是对CreateWindowEx的封装
]
]
---
2.使用方式
动态创建窗口用CreateWindowEx
动态创建控件用CreateWindow
(有人可能不服,就要让两个都支持,寻找两个使用的方法,没必要,因为msdn有例子,按例子来就好了)
---
3.使用CreateWindowEx动态创建一个窗口:
msdn创建窗口的例子是用CreateWindowEx:
【
[
代码:
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
网址:https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window
]
---
上面的WindowProc这里给出:
代码:
[
LRESULT CALLBACK MainWndProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter
{
switch (uMsg)
{
case WM_CREATE:
// Initialize the window.
return 0;
case WM_PAINT:
// Paint the window's client area.
return 0;
case WM_SIZE:
// Set the size and position of the window.
return 0;
case WM_DESTROY:
// Clean up window-specific data objects.
return 0;
//
// Process other messages.
//
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
int APIENTRY WinMain(
HINSTANCE hinstance, // handle to current instance
HINSTANCE hinstPrev, // handle to previous instance
LPSTR lpCmdLine, // address of command-line string
int nCmdShow) // show-window type
{
WNDCLASS wc;
// Register the main window class.
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWindowClass";
if (!RegisterClass(&wc))
return FALSE;
//
// Process other messages.
//
}
网址:https://docs.microsoft.com/en-us/windows/win32/winmsg/using-window-procedures
]
这两个结合,就能写出一个具体的代码
注意:
偷工取巧的方法是新建一个win32工程,就能看到自动生成的代码了,工程及消息循环代码都可以直接参考
说明:CreateWindow,CreateWindowEx动态创建的窗口,不会阻塞,所以外边需要添加消息循环
---
4.使用CreateWindow动态创建一个按控件
msdn创建控件介绍的例子:
[
原理:利用CreateWindow里的lpClassName
如创建按钮:
[
cpp文件添加:
static HWND g_hWndBtn;
static WNDPROC g_wndOrignProcBtn;
LRESULT APIENTRY BtnProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_LBUTTONDOWN)
{
::MessageBox(NULL, 0, 0, MB_OK);
return TRUE;
}
return CallWindowProc(g_wndOrignProcBtn, hwnd, uMsg, wParam, lParam);
}
创建: --L"BUTTON"是控件类名,默认支持的在msdn能找到,下面有网址
g_hWndBtn = ::CreateWindowW(L"BUTTON", L"mybtn", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS , 0, 0, 0, 0, this->m_hWnd, nullptr, theApp.m_hInstance, nullptr);
if (g_hWndBtn)
{
g_wndOrignProcBtn = (WNDPROC)SetWindowLong(g_hWndBtn,GWL_WNDPROC, (LONG)BtnProc);//设置响应
CRect rcClient;
GetClientRect(rcClient);
::MoveWindow(g_hWndBtn, rcClient.left, rcClient.top, 80, 50, TRUE);
}
说明:
(1)变量用全局的, 原因是SetWindowLong(g_hWndBtn,GWL_WNDPROC, (LONG)BtnProc);的第三个参数只能设置为全局函数,设置为成员函数会导致编译不过,此时全局变量可以直接用
(2)响应方式不能使用窗口的Lbuttondown判断区域的方法,因为控件是实际窗口,鼠标被控件响应了
]
msdn参考:(含已有控件的类名)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowa
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
]
]
---
5.窗口类型dwStyle,dwExStyle怎么填?
[
::CreateWindowExW(WS_EX_TOOLWINDOW, mywnd_class_name, L"mywnd", WS_POPUP, 0, 0, 800, 500, this->m_hWnd, nullptr, theApp.m_hInstance, nullptr);
::CreateWindowW(L"BUTTON", L"mybtn", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS , 0, 0, 0, 0, this->m_hWnd, nullptr, theApp.m_hInstance, nullptr);
一般情况下窗口:--CreateWindowEx
[
dwExStyle -- 默认填WS_EX_LEFT,就是0,就是窗口具有左对齐属性;一般工程自动生成的都是WS_EX_APPWINDOW带有任务栏图标的;
自定义窗口填WS_EX_LEFT或WS_EX_TOOLWINDOW都行,有时会用到WS_EX_TOPMOST,但是WS_EX_TOPMOST类型的
注意父窗口隐藏或者按alt+tab后被其他窗口遮挡主窗口后,此窗口会始终显示在最顶层,所以要注意下这个问题,解决办法
是换成WS_EX_LEFT或WS_EX_TOOLWINDOW,不要用WS_EX_TOPMOST
dwStyle --WS_POPUP就行,其他根据情况添加
]
子窗口或控件:--CreateWindowW
[
dwStyle --填“WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ”
]
msdn参考:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowa
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
]
】
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
总结:
【
1.CreateWindowEx动态创建窗口
CreateWindow动态创建控件
2.注册窗口类使用方法
3.窗口类型填什么(窗口和子窗口)
4.动态创建的窗口或控件响应
5.动态创建的窗口是非阻塞的,所以外边需要加消息循环
】
【
说明:
1.因为SetWindowLong(g_hWndBtn,GWL_WNDPROC, (LONG)BtnProc);的第三个参数只能设置为全局函数或静态全局的,所以变量
设置为全局比较方便调用
2设置成员变量可以利用SetWindowLong(m_hWndBtn, GWL_USERDATA, (LONG)this);,然后获取时取指针用即可
但是调用此函数是在窗口创建后才能调用,所以响应的消息里面WM_CREATE是先响应的,此时还无法获取到指针
所以非要使用成员变量,就需要根据需要合理调整调用函数位置,导致显得繁琐,而直接设置全局变量方便,也不用考虑调用位置
个人感觉:用全局的方式最简单,肯定不会出问题,因为官方api给的接口就这样,msdn也这样写的(个人观点)
】
Demo地址:Demo-动态创建窗口