5.1 使用资源编辑器编辑对话框
在Windows开发中弹出对话框是一种常用的输入/输出手段,同时编辑好的对话框可以保存在资源文件中。Visual C++提供了对话框编辑工具,利用编辑工具可以方便的添加各种控件到对话框中,而且利用ClassWizard可以方便的生成新的对话框类和映射消息。
首先资源列表中按下右键,可以在弹出菜单中选择“插入对话框”,如图1。然后再打开该对话框进行编辑,你会在屏幕上看到一个控件板,如图2。你可以将所需要添加的控件拖到对话框上,或是先选中后再在对话框上用鼠标画出所占的区域。
接下来我们在对话框上产生一个输入框,和一个用于显示图标的图片框。之后我们使用鼠标右键单击产生的控件并选择其属性,如图3。我们可以在属性对话框中编辑控件的属性同时也需要指定控件ID,如图4,如果在选择对话框本身的属性那么你可以选择对话框的一些属性,包括字体,外观,是否有系统菜单等等。最后我们编辑图片控件的属性,如图5,我们设置控件的属性为显示图标并指明一个图标ID。
接下来我们添加一些其他的控件,最后的效果如图6。按下Ctrl-T可以测试该对话框。此外在对话框中还有一个有用的特性,就是可以利用Tab键让输入焦点在各个控件间移动,要达到这一点首先需要为控件设置在Tab键按下时可以接受焦点移动的属性Tab Stop,如果某一个控件不打算利用这一特性,你需要清除这一属性。然后从菜单“Layout”选择Tab Order来确定焦点移动顺序,如图7。使用鼠标依此点击控件就可以重新规定焦点移动次序。最后按下Ctrl-T进行测试。
最后我们需要为对话框产生新的类,ClassWizard可以替我们完成大部分的工作,我们只需要填写几个参数就可以了。在编辑好的对话框上双击,然后系统回询问是否添加新的对话框,选择是并在接下来的对话框中输入类名就可以了。ClassWizard会为你产生所需要的头文件和CPP文件。然后在需要使用的地方包含相应的头文件,对于有模式对话框使用DoModal()产生,对于无模式对话框使用Create()产生。相关代码如下
void CMy51_s1View::OnCreateDlg() {//产生无模式对话框 CTestDlg *dlg=new CTestDlg; dlg->Create(IDD_TEST_DLG); dlg->ShowWindow(SW_SHOW); } void CMy51_s1View::OnDoModal() {//产生有模式对话框 CTestDlg dlg; int iRet=dlg.DoModal(); TRACE("dlg return %d\n",iRet); } 下载例子。如果你在调试这个程序时你会发现程序在退出后会有内存泄漏,这是因为我没有释放无模式对话框所使用的内存,这一问题会在以后的章节5.3 创建无模式对话框中专门讲述。
关于在使用对话框时Enter键和Escape键的处理:在使用对话框是你会发现当你按下Enter键或Escape键都会退出对话框,这是因为Enter键会引起CDialog::OnOK()的调用,而Escape键会引起CDialog::OnCancel()的调用。而这两个调用都会引起对话框的退出。在MFC中这两个成员函数都是虚拟函数,所以我们需要进行重载,如果我们不希望退出对话框那么我们可以在函数中什么都不做,如果需要进行检查则可以添加检查代码,然后调用父类的OnOK()或OnCancel()。相关代码如下;
void CTestDlg::OnOK() { AfxMessageBox("你选择确定"); CDialog::OnOK(); } void CTestDlg::OnCancel() { AfxMessageBox("你选择取消"); CDialog::OnCancel(); }
5.2 创建有模式对话框
使用有模式对话框时在对话框弹出后调用函数不会立即返回,而是等到对话框销毁后才会返回(请注意在对话框弹出后其他窗口的消息依然会被传递)。所以在使用对话框时其他窗口都不能接收用户输入。创建有模式对话框的方法是调用CDialog::DoModal()。下面的代码演示了这种用法:
CYourView::OnOpenDlg() { CYourDlg dlg; int iRet=dlg.DoModal(); } CDialog::DoModal()的返回值为IDOK,IDCANCEL。表明操作者在对话框上选择“确认”或是“取消”。由于在对话框销毁前 DoModal不会返回,所以可以使用局部变量来引用对象。在退出函数体后对象同时也会被销毁。而对于无模式对话框则不能这样使用,下节5.3 创建无模式对话框中会详细讲解。
你需要根据DoModal()的返回值来决定你下一步的动作,而得到返回值也是使用有模式对话框的一个很大原因。
使用有模式对话框需要注意一些问题,比如说不要在一些反复出现的事件处理过程中生成有模式对话框,比如说在定时器中产生有模式对话框,因为在上一个对话框还未退出时,定时器消息又会引起下一个对话框的弹出。
同样的在你的对话框类中为了向调用者返回不同的值可以调用CDialog::OnOK()或是CDialog::OnCancel()以返回IDOK或IDCANCEL,如果你希望返回其他的值,你需要调用
CDialog::EndDialog( int nResult );其中nResult会作为DoModal()调用的返回值。
下面的代码演示了如何使用自己的函数来退出对话框:
void CMy52_s1View::OnLButtonDown(UINT nFlags, CPoint point) {//创建对话框并得到返回值 CView::OnLButtonDown(nFlags, point); CTestDlg dlg; int iRet=dlg.DoModal(); CString szOut; szOut.Format("return value %d",iRet); AfxMessageBox(szOut); } //重载OnOK,OnCancel void CTestDlg::OnOK() {//什么也不做 } void CTestDlg::OnCancel() {//什么也不做 } //在对话框中对三个按钮消息进行映射 void CTestDlg::OnExit1() { CDialog::OnOK(); } void CTestDlg::OnExit2() { CDialog::OnCancel(); } void CTestDlg::OnExit3() { CDialog::EndDialog(0XFF); } 由于重载了OnOK和OnCancel所以在对话框中按下Enter键或Escape键时都不会退出,只有按下三个按钮中的其中一个才会返回。
此外在对话框被生成是会自动调用BOOL CDialog::OnInitDialog(),你如果需要在对话框显示前对其中的控件进行初始化,你需要重载这个函数,并在其中填入相关的初始化代码。利用ClassWizard可以方便的产生一些默认代码,首先打开ClassWizard,选择相应的对话框类,在右边的消息列表中选择 WM_INITDIALOG并双击,如图,ClassWizard会自动产生相关代码,代码如下:
BOOL CTestDlg::OnInitDialog() { /*先调用父类的同名函数*/ CDialog::OnInitDialog(); /*填写你的初始化代码*/ return TRUE; } 有关对对话框中控件进行初始化会在5.4 在对话框中进行消息映射中进行更详细的讲解。
5.3 创建无模式对话框
无模式对话框与有模式对话框不同的是在创建后其他窗口都可以继续接收用户输入,因此无模式对话框有些类似一个弹出窗口。创建无模式对话框需要调用
BOOL CDialog::Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );之后还需要调用
BOOL CDialog::ShowWindow( SW_SHOW);进行显示,否则无模式对话框将是不可见的。相关代码如下:
void CYourView::OnOpenDlg(void) { /*假设IDD_TEST_DLG为已经定义的对话框资源的ID号*/ CTestDlg *dlg=new CTestDlg; dlg->Create(IDD_TEST_DLG,NULL); dlg->ShowWindows(SW_SHOW); /*不要调用 delete dlg;*/ } 在上面的代码中我们新生成了一个对话框对象,而且在退出函数时并没有销毁该对象。因为如果此时销毁该对象(对象被销毁时窗口同时被销毁),而此时对话框还在显示就会出现错误。那么这就提出了一个问题:什么时候销毁该对象。我时常使用的方法有两个:
- 在对话框退出时销毁自己:在对话框中重载OnOK与OnCancel在函数中调用父类的同名函数,然后调用DestroyWindow()强制销毁窗口,在对话框中映射WM_DESTROY消息,在消息处理函数中调用delete this;强行删除自身对象。相关代码如下:
void CTestDlg1::OnOK() { CDialog::OnOK(); DestroyWindow(); } void CTestDlg1::OnCancel() { CDialog::OnCancel(); DestroyWindow(); } void CTestDlg1::OnDestroy() { CDialog::OnDestroy(); AfxMessageBox("call delete this"); delete this; } 这种方法的要点是在窗口被销毁的时候,删除自身对象。所以你可以在任何时候调用DestroyWindow()以达到彻底销毁自身对象的作用。(DestroyWindow()的调用会引起OnDestroy()的调用) - 通过向父亲窗口发送消息,要求其他窗口对其进行销毁:首先需要定义一个消息用于进行通知,然后在对话框中映射WM_DESTROY消息,在消息处理函数中调用消息发送函数通知其他窗口。在接收消息的窗口中利用ON_MESSAGE映射处理消息的函数,并在消息处理函数中删除对话框对象。相关代码如下:
/*更改对话框的有关文件*/ CTestDlg2::CTestDlg2(CWnd* pParent /*=NULL*/) : CDialog(CTestDlg2::IDD, pParent) {/*m_pParent为一成员变量,用于保存通知窗口的指针, 所以该指针不能是一个临时指针*/ ASSERT(pParent); m_pParent=pParent; //{{AFX_DATA_INIT(CTestDlg2) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CTestDlg2::OnOK() { CDialog::OnOK(); DestroyWindow(); } void CTestDlg2::OnCancel() { CDialog::OnCancel(); DestroyWindow(); } void CTestDlg2::OnDestroy() { CDialog::OnDestroy(); /*向其他窗口发送消息,将自身指针作为一个参数发送*/ m_pParent->PostMessage(WM_DELETE_DLG, (WPARAM)this); } /*在消息接收窗口中添加消息映射*/ /*在头文件中添加函数定义*/ afx_msg LONG OnDelDlgMsg(WPARAM wP, LPARAM lP); /*添加消息映射代码*/ ON_MESSAGE(WM_DELETE_DLG,OnDelDlgMsg) END_MESSAGE_MAP() /*实现消息处理函数*/ LONG CMy53_s1View::OnDelDlgMsg(WPARAM wP,LPARAM lP) { delete (CTestDlg2*)wP; return 0; } /*创建对话框*/ void CMy53_s1View::OnTest2() { CTestDlg2 *dlg=new CTestDlg2(this); dlg->Create(IDD_TEST_DLG_2); dlg->ShowWindow(SW_SHOW); } 在这种方法中我们利用消息来进行通知,在Window系统中利用消息进行通知和传递数据的用法是很多的。
同样无模式对话框的另一个作用还可以用来在用户在对话框中的输入改变时可以及时的反映到其他窗口。下面的代码演示了在对话框中输入一段文字,然后将其更新到视图的显示区域中,这同样也是利用了消息进行通知和数据传递。
/*在对话框中取出数据,并向其他窗口发送消息和数据, 将数据指针作为一个参数发送*/ void CTestDlg2::OnCommBtn() { char szOut[30]; GetDlgItemText(IDC_OUT,szOut,30); m_pParent->SendMessage(WM_DLG_NOTIFY, (WPARAM)szOut); } /*在消息接收窗口中*/ /*映射消息处理函数*/ ON_MESSAGE(WM_DLG_NOTIFY,OnDlgNotifyMsg) /*在视图中绘制出字符串 m_szOut*/ void CMy53_s1View::OnDraw(CDC* pDC) { CMy53_s1Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here pDC->TextOut(0,0,"Display String"); pDC->TextOut(0,20,m_szOut); } /*处理通知消息,保存信息并更新显示*/ LONG CMy53_s1View::OnDlgNotifyMsg(WPARAM wP,LPARAM lP) { m_szOut=(char*)wP; Invalidate(); return 0; } 此外这种用法利用消息传递数据的方法对有模式对话框和其他的窗口间通信也一样有效。
5.4 在对话框中进行消息映射
利用对话框的一个好处是可以利用ClassWizard对对话框中各个控件产生的消息进行映射,ClassWizrd可以列出各种控件可以使用的消息,并能自动产生代码。在本节中我们以一个例子来讲解如何在对话框中对子窗口消息进行映射同时还讲解如何对对话框中的子窗口进行初始化。
首先我们产生编辑好一个对话框,如图,在对话框中使用的控件和ID号如下表:
| ID | 类型 |
| IDC_RADIO_TEST_1 | 圆形按钮 |
| IDC_RADIO_TEST_2 | 圆形按钮 |
| IDC_BUTTON_TEST | 按钮 |
| IDC_CHECK_TEST | 检查按钮 |
| IDC_TREE_TEST | 树形控件 |
| IDC_LIST_CTRL | List Ctrl |
| IDC_TAB_CTRL | Tab Ctrl |
| IDC_LIST_TEST | 列表框 |
| IDC_COMBO_TEST | 组合框 |
| IDC_EDIT_TEST | 输入框 |
首先我们需要在对话框的OnInitDialog()函数中对各个控件进行初始化,这里我们使用CWnd* GetDlgItem( int nID )来通过ID号得到子窗口指针。(类似的函数还有UINT GetDlgItemInt( int nID, BOOL* lpTrans = NULL, BOOL bSigned = TRUE ) 通过ID号得到子窗口中输入的数字,int GetDlgItemText( int nID, CString& rString ) 通过ID号得到子窗口中输入的文字)。代码如下: BOOL CMy54_s1Dlg::OnInitDialog() { CDialog::OnInitDialog(); /*添加初始化代码*/ //初始化输入框 ((CEdit*)GetDlgItem(IDC_EDIT_TEST))->SetWindowText("this is a edit box"); //初始化列表框 CListBox* pListB=(CListBox*)GetDlgItem(IDC_LIST_TEST); pListB->AddString("item 1"); pListB->AddString("item 2"); pListB->AddString("item 3"); //初始化组合框 CComboBox* pCB=(CComboBox*)GetDlgItem(IDC_COMBO_TEST); pCB->AddString("item 1"); pCB->AddString("item 2"); pCB->AddString("item 3"); file://初始化Tab Ctrl CTabCtrl* pTab=(CTabCtrl*)GetDlgItem(IDC_TAB_TEST); pTab->InsertItem(0,"Tab Page1"); pTab->InsertItem(1,"Tab Page2"); pTab->InsertItem(2,"Tab Page3"); file://初始化ListCtrl CListCtrl* pList=(CListCtrl*)GetDlgItem(IDC_LIST_CTRL); pList->InsertColumn(0,"Column 1",LVCFMT_LEFT,100); pList->InsertItem(0,"Item 1"); pList->InsertItem(1,"Item 2"); pList->InsertItem(2,"Item 3"); file://初始化TreeCtrl CTreeCtrl* pTree=(CTreeCtrl*)GetDlgItem(IDC_TREE_TEST); pTree->InsertItem("Node1",0,0); HTREEITEM hNode=pTree->InsertItem("Node2",0,0); pTree->InsertItem("Node2-1",0,0,hNode); pTree->InsertItem("Node2-2",0,0,hNode); pTree->Expand(hNode,TVE_EXPAND); return TRUE; // return TRUE unless you set the focus to a control } 接下来我们需要利用ClassWizard对控件所产生的消息进行映射,打开ClassWizard对话框,选中相关控件的ID,在右边的列表中就会显示出可用的消息。如我们对按钮的消息进行映射,在选中按钮ID(IDC_BUTTON_TEST)后,会看到两个消息,如图,一个是BN_CLICKED,一个是BN_DOUBLECLICKED。双击BN_CLICKED后在弹出的对话框中输入函数名,ClassWizard会产生按钮被按的消息映射。
然后我们看看对TabCtrl的TCN_SELCHANGE消息进行映射,如图,在TabCtrl的当前页选择发生改变时这个消息会被发送,所以通过映射该消息可以在当前页改变时及时得到通知。
最后我们对输入框的EN_CHANGE消息进行映射,如图,在输入框中的文本改变后该消息会被发送。相关的代码如下:
//头文件中相关的消息处理函数定义 afx_msg void OnButtonTest(); afx_msg void OnSelchangeTabTest(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnChangeEditTest(); //}}AFX_MSG DECLARE_MESSAGE_MAP() //CPP文件中消息映射代码 ON_BN_CLICKED(IDC_BUTTON_TEST, OnButtonTest) ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_TEST, OnSelchangeTabTest) ON_EN_CHANGE(IDC_EDIT_TEST, OnChangeEditTest) //}}AFX_MSG_MAP END_MESSAGE_MAP() //消息处理函数 void CMy54_s1Dlg::OnButtonTest() { AfxMessageBox("you pressed a button"); } void CMy54_s1Dlg::OnSelchangeTabTest(NMHDR* pNMHDR, LRESULT* pResult) { TRACE("Tab Select changed\n"); *pResult = 0; } void CMy54_s1Dlg::OnChangeEditTest() { TRACE("edit_box text changed\n"); } 对于其他的控件都可以采取类似的方法进行消息映射。此外如果你对各种控件可以使用的消息不熟悉,你可以通过使用对话框,然后利用 ClassWizard产生相关代码的方法来进行学习,你也可以将ClassWizard产生的代码直接拷贝到其他需要的地方(不瞒你说,我最开始就是这样学的 :-D 这也算一个小窍门)。
5.5 在对话框中进行数据交换和数据检查
MFC提供两种方法在对话框中进行数据交换和数据检查(Dialog data exchange/Dialog data validation),数据交换和数据检查的思想是将某一变量和对话框中的一个子窗口进行关联,然后通过调用BOOL UpdateData( BOOL bSaveAndValidate = TRUE )来指示MFC将变量中数据放入子窗口还是将子窗口中数据取到变量中并进行合法性检查。
在进行数据交换时一个子窗口可以和两种类型的变量相关联,一种是控件(Control)对象,比如说按钮子窗口可以和一个CButton对象相关联,这种情况下你可以通过该对象直接控制子窗口,而不需要象上节中讲的一样使用GetDlgItem(IDC_CONTROL_ID)来得到窗口指针;一种是内容对象,比如说输入框可以和一个CString对象关联,也可以和一个UINT类型变量关联,这种情况下你可以直接设置/获取窗口中的输入内容。
而数据检查是在一个子窗口和一个内容对象相关联时在存取内容时对内容进行合法性检查,比如说当一个输入框和一个CString对象关联时,你可以设置检查CString的对象的最长/最小长度,当输入框和一个UINT变量相关联时你可以设置检查UINT变量的最大/最小值。在BOOL UpdateData( BOOL bSaveAndValidate = TRUE )被调用后,合法性检查会自动进行,如果无法通过检查MFC会弹出消息框进行提示,并返回FALSE。
设置DDX/DDV在VC中非常简单,ClassWizard可以替你完成所有的工作,你只需要打开ClassWizard并选中Member Variables页,如图,你可以看到所有可以进行关联的子窗口ID列表,双击一个ID会弹出一个添加变量的对话框,如图,填写相关的信息后按下确定按钮就可以了。然后选中你刚才添加的变量在底部的输入框中输入检查条件,如图。
下面我们看一个例子,对话框上的子窗口如图设置,各子窗口的ID和关联的变量如下表:
| ID | 关联的变量 | 作用 |
| IDC_CHECK_TEST | BOOL m_fCheck | 检查框是否被选中 |
| IDC_RADOI_TEST_1 | int m_iSel | 当前选择的圆形按钮的索引 |
| IDC_COMBO_TEST | CString m_szCombo | 组合框中选中的文本或是输入的文本 |
| IDC_EDIT_TEST | CString m_szEdit | 输入框中输入的文本,最大长度为5 |
| IDC_LIST_TEST | CListBox m_lbTest | 列表框对象 |
这时候ClassWizard会自动生成变量定义和相关代码,在对话框的构造函数中可以对变量的初始值进行设置,此外在BOOL CDialog::OnInitDialog()中会调用UpdateData(FALSE),即会将变量中的数据放入窗口中 。相关代码如下: //头文件中的变量定义,ClassWizard自动产生 // Dialog Data //{{AFX_DATA(CMy55_s1Dlg) enum { IDD = IDD_MY55_S1_DIALOG }; CListBox m_lbTest; int m_iSel; CString m_szEdit; CString m_szCombo; BOOL m_fCheck; //}}AFX_DATA //构造函数中赋初值 CMy55_s1Dlg::CMy55_s1Dlg(CWnd* pParent /*=NULL*/) : CDialog(CMy55_s1Dlg::IDD, pParent) { //{{AFX_DATA_INIT(CMy55_s1Dlg) m_iSel = -1; m_szEdit = _T(""); m_szCombo = _T(""); m_fCheck = FALSE; //}}AFX_DATA_INIT ..... } //ClassWizard产生的关联和检查代码 void CMy55_s1Dlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMy55_s1Dlg) DDX_Control(pDX, IDC_LIST_TEST, m_lbTest); DDX_Radio(pDX, IDC_RADIO_TEST_1, m_iSel); DDX_Text(pDX, IDC_EDIT_TEST, m_szEdit); DDV_MaxChars(pDX, m_szEdit, 5); DDX_CBString(pDX, IDC_COMBO_TEST, m_szCombo); DDX_Check(pDX, IDC_CHECK_TEST, m_fCheck); //}}AFX_DATA_MAP } //在OnInitDialog中利用已经关联过的变量m_lbTest BOOL CMy55_s1Dlg::OnInitDialog() { CDialog::OnInitDialog(); ... // TODO: Add extra initialization here file://设置列表框中数据 m_lbTest.AddString("String 1"); m_lbTest.AddString("String 2"); m_lbTest.AddString("String 3"); m_lbTest.AddString("String 4"); return TRUE; // return TRUE unless you set the focus to a control } //对两个按钮消息处理 //通过UpdateData(TRUE)得到窗口中数据 void CMy55_s1Dlg::OnGet() { if(UpdateData(TRUE)) { //数据合法性检查通过,可以使用变量中存放的数据 CString szOut; szOut.Format("radio =%d \ncheck is %d\nedit input is %
s\ncomboBox input is %s\n",
m_iSel,m_fCheck,m_szEdit,m_szCombo);
AfxMessageBox(szOut); } //else 未通过检查 } //通过UpdateData(FALSE)将数据放入窗口 void CMy55_s1Dlg::OnPut() { m_szEdit="onPut test"; m_szCombo="onPut test"; UpdateData(FALSE); } 在上面的例子中我们看到利用DDX/DDV和UpdateData(BOOL)调用我们可以很方便的存取数据,而且也可以同时进行合法性检查。
5.6 使用属性对话框
属性对话框不同于普通对话框的是它能同时提供多个选项页,而每页都可以由资源编辑器以编辑对话框的方式进行编辑,这样给界面开发带来了方便。同时使用上也遵守普通对话框的规则,所以学习起来很方便。属性对话框由两部分构成:多个属性页(CPropertyPage)和属性对话框(CPropertySheet)。
首先需要编辑属性页,在资源编辑器中选择插入,并且选择属性对话框后就可以插入一个属性页,如图,或者选择插入一个对话框,然后将其属性中的Style设置为Child,Border设置为Thin也可以,如图。然后根据这个对话框资源生成一个新类,在选择基类时选择CPropertyPage,ClassWizard会自动生成相关的代码。
而对于CPropertySheet也需要生成新类,并且将所有需要加入的属性页对象都作为成员变量。属性对话框也分为有模式和无模式两种,有模式属性对话框使用DoModal()创建,无模式属性对话框使用Create()创建。下面的代码演示了如何创建属性对话框并添加属性页:
//修改CPropertySheet派生类的构造函数为如下形式 CSheet::CSheet() :CPropertySheet("test sheet", NULL, 0) { m_page1.Construct(IDD_PAGE_1); m_page2.Construct(IDD_PAGE_2); AddPage(&m_page1); AddPage(&m_page2); } //创建有模式属性对话框 void CMy56_s1Dlg::OnMod() { CSheet sheet; sheet.DoModal(); } //创建无模式属性对话框 void CMy56_s1Dlg::OnUnm() { CSheet *sheet=new CSheet; sheet->Create(); } 对于属性对话框可以使用下面的一些成员函数:
- CPropertyPage* CPropertySheet::GetActivePage( )得到当前活动页的指针。
- BOOL CPropertySheet::SetActivePage( int nPage )用于设置当前活动页。
- int CPropertySheet::GetPageCount()用于得到当前页总数。
- void CPropertySheet::RemovePage( int nPage )用于删除一页。
而对于属性页来将主要通过重载一些函数来达到控制的目的:
- void CPropertyPage::OnOK() 在属性对话框上按下“确定”按钮后被调用
- void CPropertyPage::OnCancel() 在属性对话框上按下“取消”按钮后被调用
- void CPropertyPage::OnApply() 在属性对话框上按下“应用”按钮后被调用
- void CPropertyPage::SetModified( BOOL bChanged = TRUE ) 设置当前页面上的数据被修改标记,这个调用可以使“应用”按钮为允许状态。
此外利用属性对话框你可以生成向导对话框,向导对话框同样拥有多个属性页,但同时只有一页被显示,而且对话框上显示的按钮为“上一步”,“下一步” /“完成”,向导对话框会按照你添加页面的顺序依次显示所有的页。在显示属性对话框前你需要调用void CPropertySheet::SetWizardMode()。使用向导对话框时需要对属性页的BOOL CPropertyPage::OnSetActive( )进行重载,并在其中调用void CPropertySheet::SetWizardButtons( DWORD dwFlags )来设置向导对话框上显示的按钮。dwFlags的取值可为以下值的“或”操作:
- PSWIZB_BACK 显示“上一步”按钮
- PSWIZB_NEXT 显示“下一步”按钮
- PSWIZB_FINISH 显示“完成”按钮
- PSWIZB_DISABLEDFINISH 显示禁止的“完成”按钮
void CPropertySheet::SetWizardButtons( DWORD dwFlags )也可以在其他地方调用,比如说在显示最后一页时先显示禁止的“完成”按钮,在完成某些操作后再显示允许的“完成”按钮。
在使用向导对话框时可以通过重载一些函数来达到控制的目的:
- void CPropertyPage::OnWizardBack() 按下了“上一步”按钮。返回0表示有系统决定需要显示的页面,-1表示禁止页面转换,如果希望显示一个特定的页面需要返回该页面的ID号。
- void CPropertyPage::OnOnWizardNext() 按下了“下一步”按钮。返回值含义与void CPropertyPage::OnWizardBack()相同。
- void CPropertyPage::OnWizardFinish() 按下了“完成”按钮。返回FALSE表示不允许继续,否则返回TRUE向导对话框将被结束。
在向导对话框的DoModal()返回值为ID_WIZFINISH或IDCANCEL。下面的代码演示了如何创建并使用向导对话框:
//创建有模式向导对话框 void CMy56_s1Dlg::OnWiz() { CSheet sheet; sheet.SetWizardMode(); int iRet=sheet.DoModal();//返回ID_WIZFINISH或IDCANCEL } //重载BOOL CPropertyPage::OnSetActive( )来控制显示的按钮 BOOL CPage1::OnSetActive() { ((CPropertySheet*)GetParent())->SetWizardButt ons(PSWIZB_BACK|PSWIZB_NEXT); return CPropertyPage::OnSetActive(); } BOOL CPage2::OnSetActive() { ((CPropertySheet*)GetParent())->SetWizardButt ons(PSWIZB_BACK|PSWIZB_FINISH); return CPropertyPage::OnSetActive(); } 5.8 建立以对话框为基础的应用
我认为初学者使用以对话框为基础的应用是一个比较好的选择,因为这样一来可以摆脱一些开发界面的麻烦,此外也可以利用ClassWizard自动的添加消息映射。
在VC中提供了生成“以对话框为基础的应用”的功能,你所需要选择的是在使用AppWizard的第一步选择“对话框为基础的应用”,如图。VC会生成包含有应用派生类和对话框派生类的代码。在应用类的InitInstance()成员函数中可以看到如下的代码:
BOOL CMy58_s1App::InitInstance() { CMy58_s1Dlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle when the dialog is // dismissed with Cancel } return FALSE; } 这是产生一个有模式对话框并创建它,在对话框返回后通过返回FALSE来直接退出。在设计时通过编辑对话框资源你可以设计好界面,然后通过ClassWizard映射消息来处理客户的输入,由于前几节已经讲过本节也就不再重复。
同样基于对话框的应用也同样可以使用属性对话框做为界面,或者是通过使用经过派生的通用对话框作为界面。
提示:当你使用有模式对话框时最开始是无法隐藏窗口的,而只能在对话框显示后再隐藏窗口,所以这会造成屏幕的闪烁。一个解决办法就是采用无模式的对话框,无模式的对话框在创建后是隐藏的,直到你调用ShowWindow(SW_SHOW)才会显示。相关代码如下:
BOOL CMy58_s1App::InitInstance() { //必须新生成一个对象,而不能使用局部变量 CMy58_s1Dlg* pdlg=new CMy58_s1Dlg; m_pMainWnd = pdlg; pdlg->Create(); return TRUE; } 5.9 使用对话框作为子窗口使用对话框作为子窗口是一种很常用的技术,这样可以使界面设计简化而且修改起来更加容易。
简单的说这种技术的关键就在于创建一个无模式的对话框,并在编辑对话框资源时指明Child风格和无边框风格,如图。接下来利用产生一个CDialog的派生类,并进行相关的消息映射。在创建子窗口时需要利用下面的代码:
int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; //创建子窗口 m_dlgChild.Create(IDD_CHILD_DLG,this); //重新定位 m_dlgChild.MoveWindow(0,0,400,200); //显示窗口 m_dlgChild.ShowWindow(SW_SHOW); return 0; } 此外还有一中类似的技术是利用CFormView派生类作为子窗口,在编辑对话框资源时也需要指明Child风格和无边框风格。然后利用 ClassWizard产生以CFormView为基类的派生类,但是由于该类的成员函数都是受保护的,所以需要对产生的头文件进行如下修改:
class CTestForm : public CFormView { //将构造函数和构析函数改为共有函数 public: CTestForm(); virtual ~CTestForm(); DECLARE_DYNCREATE(CTestForm) ... } 有关创建子窗口的代码如下:
int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; //对于CFormView派生类必须新生成对象而不能使用成员对象 m_pformChild = new CTestForm; //由于CFormView的成员受保护,所以必须对指针进行强制转换 CWnd* pWnd=m_pformChild; pWnd->Create(NULL,NULL,WS_CHILD|WS_VISIBLE,CRect(0,210,400,400) ,this,1001,NULL); return 0; } 最后你会看到如图的窗口界面,上方的对话框子窗口和下方的FormView子窗口都可以通过资源编辑器预先编辑好。
提示:对于CFormView派生类必须新生成对象而不能使用成员对象,因为在CView的OnDestroy()中会有如下代码:delete this;所以使用成员对象的结果会造成对象的二次删除而引发异常。