无窗口的CWnd类 可以接收消息吗? 解决方法:动态创建窗口

不能,因为无窗口的CWnd类其m_hWnd为0x000000。没有句柄,其他类也就无法使用::SendMessage(hwnd, …)向其发送消息。
解决方法:
为CWnd类动态创建一个透明窗口。

动态创建MFC窗口和控件

前言:
那么如何动态创建窗口和控件呢?

本质:都是利用CreateWindowEx,CreateWindow

MFC接口:

Create --里面封装了CreateWindow
CreateEx --里面封装了CreateWindowEx

win32接口:

CreateWindow --里面封装了CreateWindowEx,第一个参数是0,即WS_EX_LEFT
CreateWindowEx --最原始接口

MFC 接口创建窗口

MFC–所有的子控件、窗口,本质都是继承自CWnd,所以创建函数都是一样的,都是需要先注册窗口类,然后创建窗口:

头文件:

CMyWnd *m_pMyWnd;

实现文件:

m_pMyWnd = new CMyWnd;        
//m_pMyWnd->InitParam();    // 初始化
m_pMyWnd->CreateMyWindow(this->m_hWnd);

其中CreateMyWindow方法

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;
}

MFC窗口创建流程

  • 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设置

API创建窗口

1动态创建窗口: --CreateWindowEx

创建窗口:

    const WCHAR mywnd_class_name[] = L"Sample Window Class";
    WNDCLASS wc= {0};				// 必须 初始化 
    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.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

总结:
[
最底层的接口是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也这样写的(个人观点)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值