VC++深入详解(5):MFC对话框(一)

对话框可以分为两大类:模态对话框和非模态对话框。模态对话框是指,当其显示时,程序会暂停执行,直到关闭这个对话框之后,才能继续执行程序中的其他任务。非模态对话框是指,当对话框显示时,允许转而执行程序中的其他任务,而可以不理会这个对话框。
对话框也是一种资源,可以在资源视图中新建一个对话框来实现。新建的对话框已经包含了2个按钮,确定和取消。
在MFC中,对资源的操作都是通过一个与资源相关的类来完成的。与话框通相关的是CDialog类。当我们使用类向导时,编译器自动检测出我们添加了一个资源,提示我们是否需要为他创建一个类,然后编译器会自动将我们的资源,以及基类选择好,我们只需要填写类名就可以了,我们起名为CTestDlg。编译器会自动的帮我们填写对应的源文件和头文件名,如果我们的类名很长,那么文件名也很长,可以改变。
此时,编译器为这个类编写了两个函数:1.构造函数2.DoDataExchange,用来完成数据的交换和校验。
下面我们希望在程序中显示这个对话框,最简单的做法:增加一个菜单项,点击菜单显示对话框。

我们我们在菜单中添加一个菜单项,然后在VIEW类中为其添加消息响应函数:建立一个CTestDlg对象,调用DoModal函数来实现模态对话框:

  1. void CCH_7_DialogView::OnDialog()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.     CTestDlg dlg;  
  5.     dlg.DoModal();  
  6. }  
注意,我们还得自己包含#include "TestDlg.h",因为这句代码类向导并没有帮我们添加。
非模态对话框使用的是Create 创建的,注意,创建完成后必须调用ShowWindow函数来显示对话框。
  1. CTestDlg dlg;  
  2. dlg.Create(IDD_DIALOG1,this);  
  3. dlg.ShowWindow(SW_SHOW  


可是程序编译后依然没有显示,这是为什么呢?因为这里的dlg是一个局部变量,程序执行完以后,就析构了。那为什么前面的模态对话框就可以呢?因为模态对话框创建之后,程序就停在了这里,所以他的生命周期并没有结束。那么怎么解决呢?
1.把这个对话框变量定义为局部变量。
2.定义一个指针,指向堆上的数据:
  1. CTestDlg *pDlg = new CTestDlg;  
  2. pDlg->Create(IDD_DIALOG1,this);  
  3. pDlg->ShowWindow(SW_SHOW);  
虽然这样做可以实现功能,但是并不好,因为pDlg会在程序结束时被释放,所以我们以后就拿不到这个指针了,自然也就无法释放它,会造成内存泄露。这个问题可以通过将指针定义为类的成员变量来解决。
还有一点区别在于:对于模态对话框,点击“确定”后,对话框是被“销毁了”;而对于非模态对话框,则是“隐藏了”,你需要重写虚函数OnOK,并在其中调用DestroyWindow ,基类的OnOK只是调用了EndDialog来隐藏对话框。


下面实现一个功能:单击对话框的按钮时,在对话框中动态的创建一个新的按钮。
这里为了方便,使用模态对话框。我们回到资源视图,打开原来的对话框,从工具箱中托一个按钮到对话框上,修改的它的ID为IDC_BTN_ADD,名字为Add。然后右键点击它,选择类向导,然后为其添加消息响应函数OnBtnAdd:
  1. void CTestDlg::OnBtnAdd()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.     m_btn.Create("TEST",BS_DEFPUSHBUTTON | WS_VISIBLE|WS_CHILD,  
  5.         CRect(0,0,100,100),this,123);  
  6. }  
我们发现,程序运行时,当我们点击Add按钮,的确会显示一个新的按钮,但是我们再次点击,程序就会错误,这是因为,当点击时,会调用OnBtnAdd,这个函数调用Create函数创建了一个按钮;但是如果你又点击一次,那么还会调用Create函数将按钮与m_btn关联,可是m_btn已经和第一次创建的按钮相关联了,所以会出错。为此,我们可以增加一个成员变量m_bIsCreate来记录窗口是否已经创建,如果创建,那么就会销毁窗口,而在下一次点击时,又能创建了:
  1. void CTestDlg::OnBtnAdd()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.     if(m_bIsCreate == FALSE)  
  5.     {  
  6.         m_btn.Create("TEST",BS_DEFPUSHBUTTON | WS_VISIBLE|WS_CHILD,  
  7.             CRect(0,0,100,100),this,123);  
  8.         m_bIsCreate = TRUE;  
  9.     }  
  10.     else  
  11.     {  
  12.         m_btn.DestroyWindow();  
  13.         m_bIsCreate = FALSE;  
  14.     }  
  15.   
  16. }  
其实这里还有一个简单的办法,并不使用成员变量,而是用静态局部变量也能解决这个问题。
实际上,有一种更为直接的方法解决这个问题:CWnd有一个成员变量m_hWnd指向窗口句柄,我们可以利用这个句柄是否为空来判断:
  1. if(!m_btn.m_hWnd)  
  2. {  
  3.     m_btn.Create("TEST",BS_DEFPUSHBUTTON | WS_VISIBLE|WS_CHILD,  
  4.         CRect(0,0,100,100),this,123);  
  5. }  
  6. else  
  7. {  
  8.     m_btn.DestroyWindow();  
  9. }  

下面我们我们看看控件的访问:
我们给对话框上添加3个静态文本框没把他们改名为Number1,Number2,Number3;再添加3个编辑框。我们可以通过Layout菜单提供的功能来将它们对其。
首先我们实现一个简单的功能:当点击Number1以后,将它的显示变为“数字1”:
大的思路肯定是对这个控件响应BN_CLICKED消息。可是我们发现,这个控件的ID是IDC_STATIC,但是却在类向导中根本找不见这个ID,原因是因为这个所有的静态文本框都是这个ID。实际上,这个控件一般是用来当做标签用的,并不是用来响应消息的。但是如果我们非要让它响应消息,也是可以的,但是需要改变它的标签,我们改为IDC_NUMBER1,然后为其添加消息响应函数:
首先,按钮也是一个窗口,我们先要获得这个静态文本框,然后再获得它上面的文本。然后把文本重新设置一下:
  1. void CTestDlg::OnNumber1()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.     CString str;  
  5.     if(GetDlgItem(IDC_NUMBER1)->GetWindowText(str),str == "Number1")  
  6.     {  
  7.         GetDlgItem(IDC_NUMBER1)->SetWindowText("数值1");  
  8.     }  
  9.     else  
  10.     {  
  11.         GetDlgItem(IDC_NUMBER1)->SetWindowText("Number1");  
  12.     }  
  13. }  
但是程序运行以后并没有反应,原因是因为静态文本控件在默认状态下,是不接收通告消息的:我们得在这个控件的属性->样式中,把通知选上,就行了。可见,为了让静态文本框响应消息,需要两个特殊的步骤:1.改变它的ID。2.在它的样式中,选择通知消息。


下面实现一个稍微复杂一点的功能:在两个编辑框中输入数字,然后点击Add按钮,在第三个编辑框中显示结果。
实现的方法有很多种,我们一种一种介绍:
第1种:
  1. void CTestDlg::OnBtnAdd()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.     int num1,num2,num3;  
  5.     char ch1[10],ch2[10],ch3[10];  
  6.     GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10);  
  7.     GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);  
  8.     num1 = atoi(ch1);  
  9.     num2 = atoi(ch2);  
  10.     num3 = num1 + num2;  
  11.     itoa(num3,ch3,10);  
  12.     GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);  
  13. }  
这种方法思路清晰:定义的3个字符串和3个int。将编辑框中的字符串获取出来以后转化为int,然后做加法,做完以后转化回去,然后显示在编辑框上。


第二种:
  1. int num1,num2,num3;  
  2. char ch1[10],ch2[10],ch3[10];  
  3. GetDlgItemText(IDC_EDIT1,ch1,10);  
  4. GetDlgItemText(IDC_EDIT2,ch2,10);  
  5. num1 = atoi(ch1);  
  6. num2 = atoi(ch2);  
  7. num3 = num1 + num2;  
  8. itoa(num3,ch3,10);  
  9. SetDlgItemText(IDC_EDIT3,ch3);  

其实跟第一种差不多,但是通过GetDlgItemText和SetDlgItemText完成了第一种中需要两步才能完成工作。


第三种:
使用GetDlgItemInt 函数,这个函数直接通过控件ID获取空间的文本,并把它转化为int类型,然后使用SetDlgItemInt函数:
  1. int num1,num2,num3;  
  2. num1 = GetDlgItemInt(IDC_EDIT1);  
  3. num2 = GetDlgItemInt(IDC_EDIT2);  
  4. num3 = num1 + num2;  
  5. SetDlgItemInt(IDC_EDIT3,num3);  
第四种:
将编辑框分对话框类的三个成员变量相关,然后通过成员变量来检索和设置编辑框的文本。
首先,先要为这三个编辑框关联3个成员变量:在类视图下的建立向导类中,选择CTestDlg类,依次选择
IDC_EDIT1、IDC_EDIT2、IDC_EDIT3,为它们添加int型的变量m_num1、m_num2、m_num3。我们发现classWizzard为我们添加3处代码:
在头文件中,注释宏之间:
  1. // Dialog Data  
  2.     //{{AFX_DATA(CTestDlg)  
  3.     enum { IDD = IDD_DIALOG1 };  
  4.     int     m_num1;  
  5.     int     m_num2;  
  6.     int     m_num3;  
  7.     //}}AFX_DATA  
源文件的构造函数中:
  1. CTestDlg::CTestDlg(CWnd* pParent /*=NULL*/)  
  2.     : CDialog(CTestDlg::IDD, pParent)  
  3. {  
  4.     //{{AFX_DATA_INIT(CTestDlg)  
  5.     m_num1 = 0;  
  6.     m_num2 = 0;  
  7.     m_num3 = 0;  
  8.     //}}AFX_DATA_INIT  
  9. }  
在源文件的DoDataExchange中:
  1. void CTestDlg::DoDataExchange(CDataExchange* pDX)  
  2. {  
  3.     CDialog::DoDataExchange(pDX);  
  4.     //{{AFX_DATA_MAP(CTestDlg)  
  5.     DDX_Text(pDX, IDC_EDIT1, m_num1);  
  6.     DDX_Text(pDX, IDC_EDIT2, m_num2);  
  7.     DDX_Text(pDX, IDC_EDIT3, m_num3);  
  8.     //}}AFX_DATA_MAP  
  9. }  

其中DDX_Text函数完成数据转化的功能。
我们发现,程序运行时,编辑框里的值被自动的设为0了。这个映射关系时通过DoDataExchange函数来实现的。如果这么方便,我们在OnBtnAdd函数中添加一句代码就够了:
  1. void CTestDlg::OnBtnAdd()   
  2. {  
  3.     m_num3 = m_num1 + m_num2;  
  4. }  

可是编译后发现运行时不能显示正确的结果,这是为什么呢?通过单步调试,我们可以发现:不论我们的输入是多少,执行到m_num3 = m_num1 + m_num2时,m_num1 和 m_num2的值总是为0!看来我们有必要仔细看看DoDataExchange函数,通过MSDN,我们可以看出,这个函数不是直接调用的,而是通过UpdateData 函数调用的。而UpdateData 函数有一个参数:当他为true时(默认值),是从对话框获取数据,当他为false时,向对话框写数据。于是我们只要改为下面的代码就行了:
  1. UpdateData();  
  2. m_num3 = m_num1 + m_num2;  
  3. UpdateData(FALSE);  

假设我们向带画框输入一个字符,那么点击Add的时候,它会提示你请输入一个整数;甚至,在类向导中,你可以设置整数的范围。如果越界也会提示错误。


第五种方式:
将编辑框与控件变量相关联。我们这里选择的是CEdit类型。有了这些成员变量,我们就不需要第一种方法中的GetDlgItem(IDC_EDIT1)步骤了,直接用这些变量来调用GetWindowText或者SetWindowText就行了。
  1. int num1,num2,num3;  
  2. char ch1[10],ch2[10],ch3[10];  
  3. m_edit1.GetWindowText(ch1,10);  
  4. m_edit2.GetWindowText(ch2,10);  
  5.   
  6. num1 = atoi(ch1);  
  7. num2 = atoi(ch2);  
  8. num3 = num1 + num2;  
  9. itoa(num3,ch3,10);  
  10.   
  11. m_edit3.SetWindowText(ch3);  
第六种方式:
通过消息。通过向编辑框发送指定的消息(获取文本WM_GETTEXT、设置文本WM_SETTEXT),来实现:
  1. int num1,num2,num3;  
  2. char ch1[10],ch2[10],ch3[10];  
  3. ::SendMessage(m_edit1.m_hWnd,WM_GETTEXT,10,  
  4.     (LPARAM)ch1);  
  5. ::SendMessage(m_edit2.m_hWnd,WM_GETTEXT,10,  
  6.     (LPARAM)ch2);  
  7. num1 = atoi(ch1);  
  8. num2 = atoi(ch2);  
  9. num3 = num1 + num2;  
  10. itoa(num3,ch3,10);  
  11. m_edit3.SendMessage(WM_SETTEXT,0,(LPARAM)ch3);  

注意:
1.SendMessage是SDK下的函数,所以调用前需要加上::.
2.发送WM_GETTEXT消息时,wParam指明最大字节数,lParam填字符串的地址,但是需要强制类型转化。
3.发送WM_SETTEXT消息时,wParam不使用,填为0,lParam填字符串的地址,但是需要强制类型转化。
第七种方式:
直接给对画框的子控件发送消息:
  1. int num1,num2,num3;  
  2. char ch1[10],ch2[10],ch3[10];  
  3.   
  4. SendDlgItemMessage(IDC_EDIT1,WM_GETTEXT,10, (LPARAM)ch1);  
  5. SendDlgItemMessage(IDC_EDIT2,WM_GETTEXT,10, (LPARAM)ch2);  
  6. num1 = atoi(ch1);  
  7. num2 = atoi(ch2);  
  8. num3 = num1 + num2;  
  9. itoa(num3,ch3,10);  
  10. SendDlgItemMessage(IDC_EDIT3,WM_SETTEXT,0,  (LPARAM)ch3);  
使用成员函数,就省去了获取编辑框的工作。


如果想获取编辑框中的一部分文本,可以通过发送EM_SETSEL消息来获得,其中wParam表示起始位置,lParam表示结束位置。但是如果你想获取一部分文本,那么你的焦点必须设在这个文本框上:
  1. SendDlgItemMessage(IDC_EDIT3,EM_SETSEL,1,2);  
  2. m_edit3.SetFocus();  

总结起来,7中方法中:1、4、5比较常用。


下面我们看另外一个例子:对话框伸缩功能的实现。
这个功能其实也很常见,比如windowsXP下的画图中的颜色菜单中的编辑颜色,点击规定自定义颜色时,这个对话框就被扩充起来了。通常对于一些用户不太需要的功能,可以把它们放在隐藏的区域内,这样能让用户考虑主要的问题。
我们考虑如何设计这个问题:首先肯定是有一个按钮(名字为“收缩”),点击一下以后之后,对话框收缩一部分,按钮的名字改为“扩张”。我们先搞定点击之后名字转化的问题:
  1. void CTestDlg::OnButton1()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.     CString str;  
  5.     if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩")  
  6.     {  
  7.         SetDlgItemText(IDC_BUTTON1,"扩张");  
  8.     }  
  9.     else  
  10.     {  
  11.         SetDlgItemText(IDC_BUTTON1,"收缩");  
  12.     }  
  13. }  
接下来的才是重头戏,这种对话框分割的秘密在于:在原来的对话框上添加了一个看不见的,比较细的图像控件(一般是矩形),通过这个矩形确定点击收缩、扩张之后,对话框的位置。
我们先将这个图像控件改名为IDC_SEPERATOR,然后将它的样式改为下陷。
  1. static CRect rectLarge;  
  2. static CRect rectSmall;  
  3. if(rectLarge.IsRectNull())  
  4. {  
  5.   
  6.     CRect rectSeparator;  
  7.     GetWindowRect(&rectLarge);  
  8.     GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSeparator);  
  9.   
  10.     rectSmall.left = rectLarge.left;  
  11.     rectSmall.top = rectLarge.top;  
  12.     rectSmall.right = rectLarge.right;  
  13.     rectSmall.bottom = rectSeparator.bottom;  
  14. }  
  15.   
  16. if(str == "收缩")  
  17. {  
  18.     SetWindowPos(NULL,0,0,rectSmall.Width(),rectSmall.Height(),  
  19.         SWP_NOMOVE | SWP_NOZORDER );  
  20. }  
  21. else  
  22. {  
  23.     SetWindowPos(NULL,0,0,rectLarge.Width(),rectLarge.Height(),  
  24.         SWP_NOMOVE | SWP_NOZORDER );          
  25. }  

在处理函数中,我们定义了3个矩形,rectLarge是对话框的原始尺寸,rectSmall是点击收缩以后对话框的尺寸,而rectSeparator则是我们添加的一条细线的尺寸。rectSmall的left、top、right都等于rectLarge,只是把它的button改为rectSeparator的button即可。
获取了窗口的大小,我们只需要使用SetWindowPos重新设置窗口的位置,大小就行了。

我们的程序有一点小瑕疵,当输入一个数字以后,按一下回车结果程序就窗口就关闭了,我们希望按下回车后能转到第二编辑框继续输入。这该怎么办呢?
首先解决第一个问题,为什么按一下回车就关闭了。我们看一下确定按钮的属性,在style里面看到,它是默认选中的。所以如果我们按了回车,就会由确定键的消息响应函数来处理。虽然我们没有写,但是基类中OnOK函数却写了,作用就是关闭对话框。
所以,如果我们希望实现自己的功能,那么得在派生类中重写OK的消息响应函数。我们为其添加响应函数时,类向导会默认的帮我们添加函数,调用基类的成分:
  1. void CTestDlg::OnOK()   
  2. {  
  3.     // TODO: Add extra validation here  
  4.       
  5.     CDialog::OnOK();  
  6. }  

如果我们把它注释起来,点击ok就不会关闭窗口了。
那么如何实现回车以后把输入焦点转移到第二个编辑框中呢?
有两种大的思路:1.让编辑框控件生成一个相关联的类,然后利用这个类来出来键盘消息;2.修改编辑框控件的窗口过程函数,在这个函数中判断,如果输入回车,则移动到下一个编辑框。
我们采用第二种思路。首先,改变窗口的消息处理函数的代码应该放在哪里?肯定不能放在create下面,因为此时对话框还没有创建完成呢。实际上,当对话框及其子控件创建完成,显示之前会发送消息:WM_INITDIALOG,我们为CTestDlg添加这个消息的响应函数。
  1. WNDPROC prevProc;//声明先前的消息响应函数  
  2.   
  3. //新的消息处理函数  
  4. LRESULT CALLBACK NewEditProc(  
  5.   HWND hwnd,      // handle to window  
  6.   UINT uMsg,      // message identifier  
  7.   WPARAM wParam,  // first message parameter  
  8.   LPARAM lParam   // second message parameter  
  9. )  
  10. {  
  11.     if(uMsg == WM_CHAR && wParam == 0x0d)  
  12.     {  
  13.         ::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));  
  14.         return 1;  
  15.     }  
  16.     else  
  17.     {  
  18.         return prevProc(hwnd,uMsg,wParam,lParam);  
  19.     }  
  20. }  

其实这个函数并不复杂,如果消息是回车,那么把焦点设置为下一个窗口。如果不是,则调用原来的消息处理函数来处理。原来的处理函数是哪里来的呢?
  1. BOOL CTestDlg::OnInitDialog()   
  2. {  
  3.     CDialog::OnInitDialog();  
  4.       
  5.     // TODO: Add extra initialization here  
  6.     prevProc = (WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd,  
  7.         GWL_WNDPROC,(LONG)NewEditProc);  
  8.     return TRUE;  // return TRUE unless you set the focus to a control  
  9.                   // EXCEPTION: OCX Property Pages should return FALSE  
  10. }  
SetWindowLong函数的返回值就是了!
唯一需要注意的是得给编辑框选上多行选项。因为没有这个选项,编辑框就只能接受一行消息,所以就不能接受回车键了。
我们也注意到,我们只是为第一个编辑框写了代码,所以第二个编辑框下按回车是不起作用的。下面介绍另一种获得窗口句柄的方法:GetWindow它返回与指定窗口有关系的窗口,所以也可以这样写:
  1. ::SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));  
最后介绍一种获得获得窗口句柄的方法:GetNextDlgTabItem。这个函数返回一个指定控件前面后者后面的一个具有WS_TABSTOP风格的控件。那么什么是WS_TABSTOP风格呢?对于按钮的属性,都有制表站这一项,如果选上了,就有这个风格,如果没选,就没有。
  1. ::SetFocus(::GetNextDlgTabItem(::GetParent(hwnd),hwnd,FALSE));  
说了这么多,其实这些方法都很麻烦:你得为每个控件写一推代码,有没有简单的方法呢?
很简单,我们把响应回车键的默认函数改了就行了:
  1. void CTestDlg::OnOK()   
  2. {  
  3.     // TODO: Add extra validation here  
  4.     GetFocus()->GetNextWindow()->SetFocus();  
  5. //  CDialog::OnOK();  
  6. }  

获取当前焦点,获取这个焦点的下个窗口,将焦点设置在这个窗口,一目了然。
但这个程序是有问题的:当你多输入几次回车以后,就崩溃了,因为最后一个窗口调用GetNextWindow()时,会得到一个空指针,对它进行SetFocus();就会出错。为了解决这个问题,可以使用GetNextDlgTabItem来代替。这个函数的作用是按照顺序查找具有tabstop属性的控件。在layout菜单的taborder下,可以看到他们的顺序。
  1. void CTestDlg::OnOK()   
  2. {  
  3.     // TODO: Add extra validation here  
  4.     GetNextDlgTabItem(GetFocus())->GetNextWindow()->SetFocus();  
  5. //  CDialog::OnOK();  
  6. }  

最后再看看默认按钮:对于这个对话框,默认按钮时确定,但是可以通过修改属性来让别的按钮响应。
  • 0
    点赞
  • 0
    收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值