**
1024节日的献礼
**
网上找了很多关于win32非模态对话框的文章,也看了一些培训班的视频,感觉误人者真是不少。这些资料建立一个简单的对话框程序,主程序本身就是一个对话框,,关闭对话框就是关闭主程序,这一切都不是问题。当主程序是一个比较复杂的程序,在运行到某个节点弹出一个非模态对话框的时候(比如登录框、消息提示框),按照他们的代码,关闭对话框,结果连主程序也给关掉了。对于非模态对话框,很多文章资料都说的不清楚。
今天总结一下共享给大家,我在代码中做了精确的注释,方便初学者搞懂问题的关键。
//建立非模态对话框的步骤:
//第一步,建立全局变量:HWND Dlg_Modeless; 弹出对话框(非模态)的窗口句柄
//第二步,在资源文件里建立对话框资源,并且命名、确定ID号、确定风格
//第三步,在菜单栏设立选项,实现点击以后弹出对话框的逻辑
//第四步,弹出对话框的逻辑。在鼠标点击菜单项“非模态对话框”以后,调用函数CreateDialog()创建非模态对话框,
//第五步,调用函数 ShowWindow(Dlg_Modeless, SW_SHOW);显示窗口(下面注释掉了,自己体会效果)
//第六步,定义和声明对话框窗口过程函数(照着程序自带的“关于”对话框去做,复制粘贴,改改名称)
//第七步,非模态对话框的窗口过程函数中使用DestroyWindow(Dlg_Modeless);这个函数来关闭对话框,
//而不是 EndDialog(hDlg, LOWORD(wParam));
废话少说,上代码:
VS2019,VS2017,
工程名称:Dialog_Modeless:
这样的话可复制粘贴代码可以直接运行
// Dialog_Modeless.cpp : 定义应用程序的入口点。
#include "framework.h"
#include "Dialog_Modeless.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
//非模态对话框,第一步:
HWND Dlg_Modeless; //弹出对话框(非模态)的窗口句柄
HWND Dlg_Mode; //弹出对话框(模态)的窗口句柄
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
//第六步:
//声明对话框窗口过程函数了:DlgModeLessProc和DlgModeProc
//我很懒,复制“关于”对话框的窗口过程函数About,改改就可以了(转到最后)
//INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
//就是这个:(在上面呢)
// “模态对话框”的消息处理程序。把About换成DlgModeProc
INT_PTR CALLBACK DlgModeProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
// “非模态对话框”的消息处理程序。把About换成DlgModeLessProc
INT_PTR CALLBACK DlgModeLessProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM 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_DIALOGMODELESS, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DIALOGMODELESS));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
//此处的消息循环必须加上下面的判断内容,
//否则将来关闭非模态对话框调用函数DestroyWindow(Dlg_Modeless)的时候
//不仅仅关闭对话框,连同主程序也给关掉了,网上大批的狗屁例子主程序就是一个简单的对话框,这这这当然没有问题了
//if (Dlg_Modeless == 0 || !IsDialogMessage(Dlg_Modeless, &msg))//如果不是对话框的消息主程序才循环
//{
// TranslateMessage(&msg);
// DispatchMessage(&msg);
//}
//记住了,下面的if判断里必须有:|| Dlg_Modeless == 0 || !IsDialogMessage(Dlg_Modeless, &msg
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)|| Dlg_Modeless == 0 || !IsDialogMessage(Dlg_Modeless, &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_DIALOGMODELESS));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_DIALOGMODELESS);
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;
}
//第二步,在资源文件里建立模态和非模态对话框:
//在.rc文件里建立两个对话框“模态对话框”和“非模态对话框”模板,注意:是对话框!对话框!对话框!
//把他们的菜单项ID设为IDD_MODE 和IDD_MODELESS(为了和下面的代码匹配)
// 函数: 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_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
//第三步,在资源文件的菜单里建立“模态对话框”和“非模态对话框”两个菜单项,注意是菜单项!菜单项!菜单项!
//在.rc文件里建立两个菜单项“模态对话框”和“非模态对话框”,把他们的菜单项ID设为IDM_MODE 和IDM_MODELESS
switch (wmId)
{
case IDM_MODE: //在菜单项中点击了“模态对话框”选项
{
//第四步(1)
//我比较懒:把程序自带的“关于”对话框的创建函数复制过来,改改
//DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
//改成这个样子:
//其中IDD_MODE是我们第二步在资源文件中建立的对话框的ID,DlgModeProc是处理这个对话框的窗口过程函数
DialogBox(hInst, MAKEINTRESOURCE(IDD_MODE), hWnd, DlgModeProc);
//接下来该定义对话框窗口过程函数了:DlgModeProc
}
break;
case IDM_MODELESS: //在菜单项中点击了“模态对话框”选项
{
//第四步(2)
//我比较懒:使用程序自带的“关于”对话框的创建函数:
//DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
//复制过来,改改,改成这个样子:
//其中IDD_MODELESS是我们第二步在资源文件中建立的对话框的ID,DlgModeLessProc是处理这个对话框的窗口过程函数
Dlg_Modeless=CreateDialog(hInst, MAKEINTRESOURCE(IDD_MODELESS), hWnd, DlgModeLessProc);
//全局变量:弹出对话框(非模态)的窗口句柄Dlg_Modeless,现在有了用武之地:
//ShowWindow(Dlg_Modeless, SW_SHOW);
//上面这句注释掉的代码也可以放在非模态对话框的窗口过程函数DlgModeLessProc里case WM_INITDIALOG:消息下面这样写:
//ShowWindow(hDlg, SW_SHOW);
//接下来该第五步定义对话框窗口过程函数了:DlgModeLessProc和DlgModeProc
//我很懒,复制“关于”对话框的窗口过程函数About,改改就可以了(转到最后)
}
break;
case IDM_ABOUT: //IDD_MODELESS IDD_MODE
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;
}
//第五步:
//定义对话框窗口过程函数了:DlgModeLessProc和DlgModeProc
//我很懒,复制“关于”对话框的窗口过程函数About,改改就可以了(转到最后)
//就是这个:(在上面呢)
//INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
// “模态对话框”的消息处理程序。把About换成DlgModeProc
INT_PTR CALLBACK DlgModeProc(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;
}
// “非模态对话框”的消息处理程序。把About换成DlgModeLessProc
INT_PTR CALLBACK DlgModeLessProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
//因为:我们在主窗口的消息循环里加了非模态对话框的判断:
//if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)|| Dlg_Modeless == 0 || !IsDialogMessage(Dlg_Modeless, &msg))
//所以,本程序写不写下面这句代码都没有问题,无关紧要
ShowWindow(hDlg, SW_SHOW);
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
//因为61-66行代码的缘故,所以下面的做法行得通,
//否则关闭对话框,将会连主程序关闭,这就是网上很多资料耍流氓的地方:
//主程序本身仅仅是一个什么都不干的简单对话框,关闭非模态对话框就是关闭主程序,看不出有任何问题
//不能用 EndDialog(Dlg_Modeless, LOWORD(wParam));函数来关闭
//也不能用 EndDialog(hDlg, LOWORD(wParam));
//要用这个函数来关闭
// DestroyWindow(Dlg_Modeless);
//也可以这样写:(自己思考为什么)
DestroyWindow(hDlg);
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
//第六步,回到最前面,把这两个函数声明一下:DlgModeLessProc和DlgModeProc
//第七步:编译成功
//发现“模态对话框”以可像“关于”对话框一样正常显示,而非模态对话框不能正常建立和显示
//因为这两个函数不一样(很关键!很关键!很关键!)
//建立非模态对话框:CreateDialog(hInst, MAKEINTRESOURCE(IDD_MODELESS), hWnd, DlgModeLessProc);
//建立模态对话框: DialogBox(hInst, MAKEINTRESOURCE(IDD_MODE), hWnd, DlgModeProc);
//CreateDialog这个函数后面需要
//把155行代码注释打开:(在第四步(2))
//ShowWindow(Dlg_Modeless, SW_SHOW);
//第八步,现在基本正常了,
//打开非模态对话框之后还能打开模态对话框
//但是打开模态对话框却无法进行其他操作了
//第九步,CreateDialog建立的对话框,(代码241--251行)
//要用 DestroyWindow(Dlg_Modeless);函数来关闭
//不能用 EndDialog(Dlg_Modeless, LOWORD(wParam));函数来关闭