3.8 通用对话框

通用对话框是操作系统提供给所有应用程序使用的对话框,其功能已经实现好了,可以直接拿来使用了。比如,打开/保存文件对话框、字体选择对话框、颜色选择对话框、打印设置和打印对话框、查找和替换对话框等。在Visual C++ 2013中,针对不同的通用对话框提供了不同的类。我们只需定义这些类的对象,然后调用其中的方法即可。

Visual C++ 2013中,所有的通用对话框都是从CCommonDialog继承而来。

3.8.1 文件对话框的使用

文件对话框就是打开文件或保存文件的对话框,在文件对话框上用户可以设置路径名和文件名等,比如在记事本程序里,选择菜单“打开”或“保存”出现的对话框就是文件对话框。文件对话框是实际软件开发中经常会碰到的。

MFC提供了类CFileDialog来实现文件对话框的各种功能。当CFileDialog的构造函数的第一个参数是TRUE时,为打开对话框;第一个参数是FALSE时,为保存对话框。CFileDialog的构造函数原型:

        CFileDialog(
          BOOL bOpenFileDialog,
          LPCTSTR lpszDefExt = NULL,
          LPCTSTR lpszFileName = NULL,
          DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
          LPCTSTR lpszFilter = NULL,
          CWnd* pParentWnd = NULL,
          DWORD dwSize = 0
        );

其中,bOpenFileDialog如果是TRUE,则为打开对话框,否则是保存对话框;lpszDefExt指向默认的文件扩展名的字符串;dwFlags用来定制文件对话框的标记;lpszFilter用于过滤显示的文件类型,比如一个合法的过滤字符串为:

        static  char  BASED_CODE  szFilter[]  =  "Chart  Files  (*.xlc)|*.xlc|Worksheet
    Files     (*.xls)|*.xls|Data     Files     (*.xlc; *.xls)|*.xlc;     *.xls|All     Files
    (*.*)|*.*||";

注意:除了结尾用||外,其他都是用|来分割。

pParentWnd为父窗口指针;dwSize为结构体OPENFILENAME的大小,一般保持默认值0即可。

文件对话框的功能都是通过CFileDialog的成员函数来实现的。常用的函数有:

下面列举文件对话框的常见用法。

【例3.10】 文件对话框的常见用法

(1)打开Visual C++ 2013,新建一个对话框工程,工程名是Test。

(2)切换到资源视图并打开对话框编辑器,去掉上面所有控件,并添加几个按钮后的设计界面如图3-47所示。

图3-47

其中,横线上方的按钮都是演示打开对话框的,横线下面演示保存对话框。图中的横线是一个图片控件,可以从工具箱里拖放一个Picture Control到对话框上,然后把它上下两条线纵向拉在一起,再把它横向拉直成一条长线,再设置它的属性Color为Etched(蚀刻)。

(3)为“最简单的文件打开对话框”的按钮添加代码如下:

        void CTestDlg::OnBnClickedButton1() //最简单文件打开对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(TRUE, NULL, _T("MyDat"), NULL, NULL, this);
         dlg.DoModal();
        }

为“设置初始目录的文件打开对话框”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton2()  //设置初始目录的文件打开对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(TRUE, NULL, NULL, NULL, NULL, this);
         dlg.m_ofn.lpstrInitialDir = _T("c:\\windows\\system32");
         dlg.DoModal();
         AfxMessageBox(_T("你设置的初始目录路径是:") + dlg.GetFolderPath());
        }

为“获取文件打开对话框所选的路径名”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton3() //获取文件打开对话框所选的路径名
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(TRUE, NULL, NULL, NULL, NULL, this);
         dlg.m_ofn.lpstrInitialDir = _T("c:");
         if (IDOK == dlg.DoModal())
            AfxMessageBox(_T("你所选文件的路径是:") + dlg.GetPathName());
        }

为“获取文件打开对话框所选的文件名”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton4() //获取文件打开对话框所选的文件名
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(TRUE, NULL, NULL, NULL, NULL, this);
         dlg.m_ofn.lpstrInitialDir = _T("c:");
         if (IDOK == dlg.DoModal())
            AfxMessageBox(_T("你所选文件的文件名是:") +dlg.GetFileName());
        }

为“获取文件打开对话框所选的文件标题”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton5() //获取文件打开对话框所选的文件标题
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(TRUE, NULL, NULL, NULL, NULL, this);
         dlg.m_ofn.lpstrInitialDir = _T("c:");
         if (IDOK == dlg.DoModal())
            AfxMessageBox(_T("你所选文件的文件名是:") + dlg.GetFileTitle());
        }

为“获取打开对话框所选的文件扩展名”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton6() //获取打开对话框所选的文件扩展名
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(TRUE, NULL, NULL, NULL, NULL, this);
         dlg.m_ofn.lpstrInitialDir = _T("c:");
         if (IDOK == dlg.DoModal())
            AfxMessageBox(_T("你所选文件的扩展名是:") + dlg.GetFileExt());
        }

为“通过打开文件对话框来选择多个文件”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton7() //通过打开文件对话框来选择多个文件
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  mFileDlg(TRUE,  NULL,  NULL,  OFN_ALLOWMULTISELECT,  _T("文本文件(*.txt)|*.txt|All Files (*.*)|*.*||"), this);
         CString strRes = _T("你选择了这些文件:\r");
         #define  NAMEBUF  1024
         mFileDlg.m_ofn.lpstrFile = new TCHAR[NAMEBUF];    // 重新定义lpstrFile缓冲大小
         memset(mFileDlg.m_ofn.lpstrFile, 0, NAMEBUF);  // 初始化定义的缓冲
         mFileDlg.m_ofn.nMaxFile = NAMEBUF;            // 重定义nMaxFile
         CString pathName;
         if (mFileDlg.DoModal() == IDOK)
         {
            POSITION mPos = mFileDlg.GetStartPosition();
            while (mPos ! = NULL)
            {
                pathName = mFileDlg.GetNextPathName(mPos);
                strRes += pathName;
                strRes += _T("\n");
            }
            AfxMessageBox(strRes);
         }
         delete[] mFileDlg.m_ofn.lpstrFile; // 切记使用完后释放资源
        }

为“设置文件打开对话框的过滤功能”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton8() //设置文件打开对话框的过滤功能
        {
         // TODO:  在此添加控件通知处理程序代码
         CString   strFilter = _T("文本文件(*.txt)|*.txt|所有文件(*.*)|*.*||");
         CFileDialog  dlg(TRUE, NULL, NULL, NULL, strFilter, this);
         dlg.DoModal();
        }

为“带有标题的文件打开对话框”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton9() //带有标题的文件打开对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  Dlg(TRUE, NULL, NULL, NULL, NULL, this);
         Dlg.m_pOFN->lpstrTitle = _T("请麻烦您请选择要打开的文件:-)");
         Dlg.DoModal();
        }

为“最简单的文件保存对话框”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton10() //最简单的文件保存对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(FALSE, NULL, NULL, NULL, NULL, this);
        dlg.DoModal();
       }

为“带自定义保存文件名的保存文件对话框”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton11() //带自定义保存文件名的保存文件对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         CFileDialog  dlg(FALSE, NULL, _T("MyData"), NULL, NULL, this);
         dlg.DoModal();
        }

为“带自定义文件名和扩展名的保存文件对话框”按钮添加代码如下:

        void CTestDlg::OnBnClickedButton12() //带自定义文件名和扩展名的保存文件对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         LPCTSTR lpszFilters;
         lpszFilters  =  _T("BMP  文件 |*.bmp|DIB  文件 |*.dib|JPG  文件 |*.jpg|TGA  文件  |*.tga|PCX文件|*.pcx|TIF文件|*.tif||");
         CFileDialog   dlgFile(FALSE,   _T(""),   _T("  图  片  "),   OFN_HIDEREADONLY   |   OFN_OVERWRITEPROMPT, lpszFilters, NULL);
         if (dlgFile.DoModal() == IDOK)
         {
            CString sPathName = dlgFile.GetPathName();
            AfxMessageBox(sPathName);
         }
        }

(4)保存工程并运行,运行结果如图3-48所示。

图3-48

3.8.2 字体对话框的使用

字体对话框可以让用户选择字体的字符集、字体大小和是否斜体粗体等属性。MFC库提供了一个类CFontDialog来实现字体对话框。CFontDialog的构造函数原型如下:

        CFontDialog(
          LPLOGFONT lplfInitial = NULL,
          DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS,
          CDC* pdcPrinter = NULL,
          CWnd* pParentWnd = NULL
        );

其中,lplfInitial是一个LOGFONT类型指针,用于设置默认的字体;dwFlags设置字体对话框的行为,用分隔符|来合并多个标记;pdcPrinter为指向打印机设备上下文的指针,默认为NULL,如果提供,此参数指向字体要选择的打印机中的打印机设备上下文;pParentWnd指向字体对话框的父窗口的指针。

CFontDialog类中常用的成员函数如下表所示。

GetCurrentFont是最重要的函数,它返回一个指向字体结构的指针,该结构记录了用户在对话框中所选择的字体的名称、大小、颜色等信息。字体的结构包含字体的属性,比如字体高度、宽度,磅值、是否粗体等,字体结构体定义如下:

        typedef struct tagLOGFONT
        {
        LONG lfHeight;
        LONG lfWidth;
        LONG lfEscapement;
        LONG lfOrientation;
        LONG lfWeight;
        BYTE lfItalic;
        BYTE lfUnderline;
        BYTE lfStrikeOut;
        BYTE lfCharSet;
        BYTE lfOutPrecision;
        BYTE lfClipPrecision;
        BYTE lfQuality;
        BYTE lfPitchAndFamily;
        TCHAR lfFaceName[LF_FACESIZE];
        } LOGFONT;

其中,

● lfHeight:以逻辑单位指定字体字符元(character cell)或字符的高度。字符高度值为字符元高度值减去内部行距(internal-leading)值。当lfHeight大于0时,字体映射程序将该值转换为设备单位,并将它与可用字体的字符元高度进行匹配;当该参数为0时,字体映射程度将使用一个匹配的默认高度值;如果参数的值小于0,则将其转换为设备单位,并将其绝对值与可用字体的字符高度进行匹配。对于任何一种情况,字体映射程度最终得到的字体高度值不会超过所指定的值。以MM_TEXT映射模式下,字体高度值和磅值有如下的换算公式:lfHeight=-MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72)。

● lfWidth:以逻辑单位指定字体字符的平均宽度。如果lfWidth的值为0,则根据设备的纵横比从可用字体的数字转换纵横中选取最接近的匹配值,该值通过比较两者之间的差异的绝对值得出。

● lfEscapement:以十分之一度为单位指定每一行文本输出时相对于页面底端的角度。

● lfOrientation:以十分之一度为单位指定字符基线相对于页面底端的角度。

● lfWeight:指定字体重量。在Windows中,字体重量这个术语用来指代字体的粗细程度。lfWeight的范围为0到1000,正常情况下的字体重量为400,粗体为700。如果lfWeight为0,则使用默认的字体重量。

● lfItalic:当lfItalic为TRUE时使用斜体。

● lfUnderline:当lfUnderline为TRUE时给字体添加下划线。

● lfStrikeOut:当lfStrikeOut为TRUE时给字体添加删除线。

● lfCharSet:指定字符集。可以使用以下预定义的值:ANSI_CHARSET、BALTIC_CHARSET、CHINESEBIG5_CHARSET、DEFAULT_CHARSET、EASTEUROPE_CHARSET、GB2312_CHARSET、GREEK_CHARSET、HANGUL_CHARSET、MAC_CHARSET、OEM_CHARSET、RUSSIAN_CHARSET、SHIFTJIS_CHARSET、SYMBOL_CHARSET、TURKISH_CHARSET。在这些字符集中,OEM_CHARSET表示字符集依赖本地操作系统;DEFAULT_CHARSET表示字符集基于本地操作系统。例如,系统位置是English (United States),字符集将设置为ANSI_CHARSET。

● lfOutPrecision:指定输出精度。输出精度定义了输出与所要求的字体高度、宽度、字符方向等的接近程度。它可以为下面的值之一:OUT_CHARACTER_PRECIS、OUT_DEFAULT_PRECIS、OUT_STRING_PRECIS、OUT_STROKE_PRECIS。

● lfClipPrecision:指定剪辑精度。剪辑精度定义了当字符的一部分超过剪辑区域时对字符的剪辑方式,它可以为下列值之一:CLIP_CHARACTER_PRECIS、CLIP_DEFAULT_PRECIS、CLIP_STROKE_PRECIS。

● lfQuality:定义输出质量。输出质量定义了图形设备接口在匹配逻辑字体属性到实际的物理字体所使用的方式,它可以为下列值之一:DEFAULT_QUALITY (默认质量)、DRAFT_QUALITY (草稿质量)、PROOF_QUALITY (正稿质量)。

● lfPitchAndFamily:指定字体的字符间距和族。最低两位指定字体的字符间距为以下值之一:DEFAULT_PITCH、FIXED_PITCH、VARIABLE_PITCH;第4到7位指定字体族为以下值之一:FF_DECORATIVE、FF_DONTCARE、FF_MODERN、FF_ROMAN、FF_SCRIPT、FF_SWISS。字符间距和字体族可以使用逻辑或(OR)运算符来进行组合。

● lfFaceName:一个指定以NULL结尾的字符串,它指定所用的字体名。该字符串的长度不得超过32个字符,如果lfFaceName为NULL,图形设备接口将使用默认的字体名。

下面以一个实例来说明字体对话框的使用,要注意的是,本例在字体对话框上选择了字体后,将用所选的字体对对话框进行写一行字,在对话框上写字将涉及到不少Visual C++图形方面的知识,这里不理解也没关系,照着做即可,重点放在字体对话框的使用上。

【例3.11】 字体对话框的使用

(1)打开Visual C++ 2013,新建一个对话框工程,工程名是test。

(2)切换到资源视图,打开对话框编辑器,去掉上面所有控件,并添加一个按钮,名称是“设置字体”。

(3)切换到类视图,双击CtestDlg,为类CtestDlg添加两个成员变量:

        LOGFONT m_font;
        COLORREF m_clr;

其中,m_font用来保存选择的字体,m_clr用来保存所选的字体颜色。

(4)打开函数CtestDlg::OnInitDialog(),在末尾return TRUE前添加代码如下:

        m_font.lfHeight=25;
        m_font.lfWidth=0;
        m_font.lfEscapement=0;
        m_font.lfOrientation=0;
        m_font.lfWeight=FW_NORMAL;
        m_font.lfItalic=FALSE;
        m_font.lfUnderline=FALSE;
        m_font.lfStrikeOut=FALSE;
        m_font.lfCharSet=GB2312_CHARSET;
        m_font.lfOutPrecision=OUT_STROKE_PRECIS;
        m_font.lfClipPrecision=CLIP_STROKE_PRECIS;
        m_font.lfQuality=DRAFT_QUALITY;
        m_font.lfPitchAndFamily=VARIABLE_PITCH|FF_MODERN;
       _tcscpy(m_font.lfFaceName, _T("黑体"));
        m_clr = RGB(0,0,255);
        Invalidate();

这段代码主要初始化字体和颜色变量。其中,_tcscpy是strcpy的通用版本(在unicode字符集和多字节字符集环境都可以使用的版本); Invalidate函数的作用是使整个窗口客户区无效,窗口的客户区无效意味着需要重绘,这将引起OnPaint的执行。真正显示字体的函数在OnPaint中。见下一步。

(5)打开类CtestDlg的成员函数OnPaint,在else后面添加代码如下:

        CPaintDC dc(this); // 用于绘制的设备上下文
         CFont NewFont;
         CFont *pOldFont;
         NewFont.CreateFontIndirect(&m_font);
         pOldFont = dc.SelectObject(&NewFont);
         dc.SetTextColor(m_clr);
         dc.TextOut(20,20, _T("你好,本例演示字体对话框。"));
         dc.SelectObject(pOldFont);

其中,CPaintDC是用于绘制的设备上下文,参数用了this, this表示当前对话框的窗口指针,所以当前的设备上下文就是当前对话框的窗口;CFont是MFC库中关于字体的类;CreateFontIndirect是CFont的成员函数,该函数创建一种在指定结构定义其特性的逻辑字体,这种字体可在后面的应用中被任何设备环境选作字体;SelectObject是CPaintDC的成员函数,用来把创建的字体选择进当前的设备上下文中,最后输出完毕的时候,还要把原来的字体重新选择回设备上下文中;SetTextColor用于设置当前设备环境的字体颜色;TextOut是在当前窗口上输出一个字符串,前两个参数是要输出文字的坐标。

(6)切换到资源视图,打开对话框编辑器,双击按钮,添加事件代码如下:

        void CtestDlg::OnBnClickedButton1()
        {
         // TODO: 在此添加控件通知处理程序代码
         CFontDialog dlg;
        dlg.m_cf.lpLogFont = &m_font;
        dlg.m_cf.rgbColors = m_clr;
        dlg.m_cf.Flags |= CF_INITTOLOGFONTSTRUCT;
        if(dlg.DoModal()==IDOK)
        {
            dlg.GetCurrentFont(&m_font); //得到所选的字体
            m_clr = dlg.GetColor();  //得到所选的颜色
            Invalidate();
        }
       }

其中,标记CF_INITTOLOGFONTSTRUCT的意思是当字体对话框显示的时候,用dlg.m_cf.lpLogFont来选中初始字体。

(7)保存工程并运行,结果如图3-49所示。

图3-49

3.8.3 颜色对话框的使用

颜色对话框可以让用户选择颜色。在颜色对话框上,用户既可以直接选择颜色,也可以自己定义一个颜色(比如输入颜色的RGB三个值)。关于颜色,Visual C++中有一个COLORREF类型,COLORREF类型用来描绘一个RGB颜色。其定义如下:

        typedef DWORD COLORREF;
        typedef DWORD *LPCOLORREF;

其实就是一个DWORD类型,32位的双字类型,可以存放4个字节的数据,对于COLORREF,我们通常使用宏RGB对其进行赋值,宏RGB的定义如下:

        #define RGB(r, g, b)
        ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))

其中,r表示颜色中红色分量的强度,g表示颜色中绿色分量的强度,b表示颜色中蓝色分量的强度,r、g、b的取值范围是0到255。RGB对COLORREF变量赋值:

        COLORREF color=RGB(0,255,0);

其中,红色和蓝色值都为0,所以在该颜色中没有红色和蓝色,绿色为最大值,所以color表示绿色。

了解了颜色的知识外,我们来看下颜色对话框的实现。

Visual C++ 2013提供类CColorDialog来实现颜色对话框,在类CColorDialog的构造函数中就可以用颜色值对其进行初始化,构造函数原型如下:

        CColorDialog(
          COLORREF clrInit = 0,
          DWORD dwFlags = 0,
          CWnd* pParentWnd = NULL
        );

其中,clrInit为颜色对话框显示时,默认的颜色选择。如果未指定任何值,则默认值为RGB (0,0,0) (黑色); dwFlags为定义颜色对话框的功能和外观的标志;pParentWnd为颜色对话框的父窗口指针。

颜色对话框类CColorDialog中常用成员函数有:

下面以一个实例来说明CColorDialog的使用。

【例3.12】 颜色对话框的使用

(1)新建一个对话框工程,工程名是Test。

(2)为类CTestDlg添加成员变量:

        COLORREF  m_clr;

m_clr用来保存在颜色对话框上所选的颜色。

(3)切换到资源视图,在对话框上添加一个按钮“显示颜色对话框”,然后添加按钮事件处理代码如下:

        void CTestDlg::OnBnClickedButton1()
        {
         // TODO:  在此添加控件通知处理程序代码
         CColorDialog dlg(0, CC_FULLOPEN, this);
         if (IDOK == dlg.DoModal())
         {
            m_clr = dlg.GetColor();
            Invalidate();
         }
        }

(4)在对话框的初始化函数CTestDlg::OnInitDialog()的末尾添加m_clr的初始化代码:

        m_clr = RGB(240,240,240);

再定位到类CTestDlg的成员函数OnPaint处,在else代码段内的CDialogEx::OnPaint();前添加设置背景色的代码:

        else
         {
            //注意:要添加在CDialogEx::OnPaint();前
            CPaintDC dc(this); // 用于绘制的设备上下文
            CBrush  brush(m_clr);
            CRect Rect;
            GetClientRect(&Rect);
            dc.FillRect(Rect, &brush);
            CDialogEx::OnPaint();
         }

其中,CBrush是MFC关于画刷的类;CRect是关于矩形的类;GetClientRect是获取当前对话框客户区的大小;FillRect是用画刷进行填充矩形。

(5)保存工程并运行,结果如图3-50所示。

图3-50

3.8.4 浏览文件夹对话框的使用

浏览文件夹对话框可以让用户来选择文件夹。显示文件夹对话框可用系统函数SHBrowseForFolder,该函数由操作系统的shell32.lib提供,可以拿来直接使用。

涉及的重要函数或结构体如下:

结构体BROWSEINFO

        typedef struct _browseinfo {
        HWND hwndOwner; // 父窗口句柄
        LPCITEMIDLIST pidlRoot; // 要显示的文件夹的根(Root)
        LPTSTR pszDisplayName; // 保存被选取的文件夹路径的缓冲区
        LPCTSTR lpszTitle; // 显示位于对话框左上部的标题
        UINT ulFlags; // 指定对话框的外观和功能的标志
        BFFCALLBACK lpfn; // 处理事件的回调函数
        LPARAM lParam; // 应用程序传给回调函数的参数
        int iImage; // 保存被选取的文件夹的图片索引
        } BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO

其中,

● hwndOwner:浏览文件夹对话框的父窗体句柄。

● pidlRoot:ITEMIDLIST结构的地址,包含浏览时的初始根目录,而且只有被指定的目录和其子目录才显示在浏览文件夹对话框中。该成员变量可以是NULL,在此时桌面目录将被使用。

● pszDisplayName:用来保存用户选中的目录字符串的内存地址。该缓冲区的大小默认是定义的MAX_PATH常量宏。

● lpszTitle:该浏览文件夹对话框的显示文本,用来提示该浏览文件夹对话框的功能、作用和目的。

● ulFlags:该标志位描述了对话框的选项。它可以为0,也可以是以下常量的任意组合:

➢ BIF_BROWSEFORCOMPUTER:返回计算机名。除非用户选中浏览器中的一个计算机名,否则该对话框中的“OK”按钮为灰色。

➢ BIF_BROWSEFORPRINTER:返回打印机名。除非选中一个打印机名,否则“OK”按钮为灰色。

➢ BIF_BROWSEINCLUDEFILES:浏览器将显示目录,同时也显示文件。

➢ BIF_DONTGOBELOWDOMAIN:在树形视窗中,不包含域名底下的网络目录结构。

➢ BIF_EDITBOX:浏览对话框中包含一个编辑框,在该编辑框中用户可以输入选中项的名字。

➢ BIF_RETURNFSANCESTORS:返回文件系统的一个节点。仅仅当选中的是有意义的节点时,“OK”按钮才可以使用。

➢ BIF_RETURNONLYFSDIRS:仅仅返回文件系统的目录。例如:在浏览文件夹对话框中,当选中任意一个目录时,该“OK”按钮可用,而当选中“我的电脑”或“网上邻居”等非有意义的节点时,“OK”按钮为灰色。

➢ BIF_STATUSTEXT:在对话框中包含一个状态区域。通过给对话框发送消息使回调函数设置状态文本。

➢ BIF_VALIDATE:当没有BIF_EDITBOX标志位时,该标志位被忽略。如果用户在编辑框中输入的名字非法,浏览对话框将发送BFFM_VALIDATEFAILED消息给回调函数。

● lpfn:应用程序定义的浏览对话框回调函数的地址。当对话框中的事件发生时,该对话框将调用回调函数。该参数可用为NULL。

● lParam:对话框传递给回调函数的一个参数指针。

● iImage:与选中目录相关的图像。该图像将被指定为系统图像列表中的索引值。

【例3.13】 浏览文件夹对话框的使用

(1)新建一个对话框工程,工程名是Test。

(2)切换到资源视图,打开对话框编辑器,去掉上面所有控件,并添加一个按钮,用来显示文件夹选择对话框。添加按钮的单击事件函数代码如下:

        void CTestDlg::OnBnClickedButton1()
        {
         // TODO: 在此添加控件通知处理程序代码
         BROWSEINFO BrowInfo;
         TCHAR csFolder[MAX_PATH] = {0};
         memset(&BrowInfo,0, sizeof(BROWSEINFO));
         BrowInfo.hwndOwner = m_hWnd;
         BrowInfo.pszDisplayName = csFolder;
         BrowInfo.lpszTitle = _T("请选择路径");
         BrowInfo.ulFlags = BIF_EDITBOX;
         ITEMIDLIST *pitem = SHBrowseForFolder(&BrowInfo);
         if (pitem)
         {
            SHGetPathFromIDList(pitem, csFolder); //把所选的内容转换成路径字符串
            CString str;
            str.Format(_T("你选择的路径是:%s"), csFolder);
            AfxMessageBox(str);
         }
        }

这是传统的方法,现在有更简单的方法只需一行代码:

        theApp.GetShellManager()->BrowseForFolder(strSelectedFolder,               this,
    strInitFolder);

其中,strSelectedFolder是用户在文件夹对话框上选择的文件夹的路径,strInitFolder是文件夹对话框刚刚打开的时候预设的路径。

(3)保存并运行工程,得到运行结果如图3-51所示。

图3-51

3.8.5 查找/替换对话框的使用

在Windows通用对话框中,查找/替换对话框是比较特殊的一个,因为它是一个非模态对话框,所以它的使用与其他通用对话框有所不同。值得注意的是,查找/替换对话框本身没有查找/替换功能,它只是为我们提供了一个接收用户要求的接口,使我们知道用户提出了何种查找/替换要求,真正的查找/替换工作需另行编程实现。这一点与文件对话框相似,用打开文件对话框不能真的打开文件,它只是让我们知道用户想要打开哪个文件而已。

在Visual C++ 2013中,类CFindReplaceDialog对查找/替换对话框进行了封装。既然是非模态对话框,作为非模态对话框,必须用new操作符分配存储空间,再用Create函数进行初始化,最后用ShowWindow函数显示对话框。而类CFindReplaceDialog里面就有成员函数Create,函数原型如下:

        BOOL Create(
        BOOL bFindDialogOnly,
        LPCTSTR lpszFindWhat,
        LPCTSTR lpszReplaceWhat=NULL,
        DWORD dwFlag=FR_DOWN,
        CWnd* pParentWnd=NULL);

其中,bFindDialogOnly为对话框类型,为TRUE时,显示查找对话框,为FALSE时,显示的是查找/替换对话框;lpszFindWhat为在查找框中显示的字符串;lpszReplaceWhat为在替换框中显示的字符串;dwFlag为标志位,用来定制对话框,它可以是一个或多个标志的组合,主要取值如下:

● FR_DOWN:如果设置,对话框中的“向下查找”单选按钮被选中;如果没有设置,“向上查找”单选按钮被选中。

● FR_HIDEUPDOWN:不显示查找方向单选按钮。

● FR_HIDEMATCHCASE:不显示区分大小写复选按钮。

● FR_HIDEWHOLEWORD:不显示全字匹配复选按钮。

● FR_MATCHCASE:使区分大小写复选按钮处于选中状态。

● FR_WHOLEWORD:使全字匹配复选按钮处于选中状态。

● FR_NOMATCHCASE:使区分大小写复选按钮处于禁止(变灰)状态。

● FR_NOUPDOWN:使查找方向单选按钮处于禁止(变灰)状态。

● FR_NOWHOLEWORD:使全字匹配复选按钮处于禁止(变灰)状态。

● FR_SHOWHELP:在对话框中显示一个帮助按钮。

pParentWnd指向对话框的父窗口,如果为NULL,则为主框架窗口,使用时需让它指向接收查找/替换消息的窗口。

值得注意的是,在Create()创建对话框前,也可以用成员变量m_fr对对话框进行更详细的定制。

查找/替换对话框类CFindReplaceDialog中常用成员函数有:

下面以一个实例来说明查找/替换对话框的使用。

【例3.14】 查找/替换对话框的使用

(1)新建一个对话框工程,工程名是Test。

(2)切换到解决方案视图,打开TestDlg.cpp,在开头定义几个全局变量:

        CFindReplaceDialog* pFindReplaceDlg = NULL; //指向查找对话框或替换对话框
        BOOL bLastCase = FALSE; //记录上一次的大小写情况
        int pos = 0, curpos; //pos是查找索引;curpos是当前索引
        //gstrEdit存放上一次编辑框中的正文内容;gstrLast存放上一次查找框中的内容
        CString gstrEdit, gstrLast;

(3)切换到资源视图,打开对话框,把上面的控件都删掉,添加一个编辑框,这个编辑框中显示正文内容,设置编辑框的Want Return属性和Multiline属性为True,再为编辑框添加控件变量m_edt和值变量m_strEdit。然后添加一个按钮“显示查找对话框”,并添加按钮单击事件处理代码:

        void CTestDlg::OnBnClickedButton2()//显示查找对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         if (! gpFindReplaceDlg)
         {
            gpFindReplaceDlg = new CFindReplaceDialog(); //开辟空间
            gpFindReplaceDlg->Create(true, NULL, NULL, FR_DOWN, this);  //创建查找对话框
        }
        gpos = 0; //初始化搜索索引值
        gpFindReplaceDlg->ShowWindow(SW_SHOW);    //显示对话框
        }

再添加一个按钮“显示替换对话框”,然后添加按钮单击事件处理代码:

        void CTestDlg::OnBnClickedButton1()//显示替换对话框
        {
         // TODO:  在此添加控件通知处理程序代码
         if (! gpFindReplaceDlg)
         {
            gpFindReplaceDlg = new CFindReplaceDialog(); //开辟空间
            gpFindReplaceDlg->Create(false,  m_FindString,  m_ReplaceString,  FR_DOWN,this); // 创建替换对话框
         }
         gpos = 0; //初始化搜索索引值
         gpFindReplaceDlg->ShowWindow(SW_SHOW);    //显示对话框
        }

查找/替换对话框显示后,在上面单击其上任何一个按钮都会产生消息,但我们不需要为每个按钮添加消息函数,可以向父窗口注册一个消息,让父窗口能响应查找/替换对话框上的按钮消息,并让父窗口提供响应消息的处理函数。

注册消息的位置应该在查找/替换对话框的父窗口,这里是类CTestDlg中注册,打开TestDlg.h,然后定义一个全局变量:

          static UINT WM_FINDREPLACE = ::RegisterWindowMessage
    (FINDMSGSTRING); //注册消息

RegisterWindowMessage是系统API函数,前面“::”表示它是个Win32 API函数,在MFC中使用Win32 API函数必须前面有“::”。

消息注册后,要添加消息处理函数,首先在头文件TestDlg.h中声明函数,定位到DECLARE_MESSAGE_MAP()前面添加:

        afx_msg LONG OnFindReplace(WPARAM wParam, LPARAM lParam);

再打开TestDlg.cpp,添加函数:

        LONG CTestDlg::OnFindReplace(WPARAM wParam, LPARAM lParam)
        {
         BOOL bCase = gpFindReplaceDlg->MatchCase();
         BOOL bDown = gpFindReplaceDlg->SearchDown();
         CString strRawFind;
         //判断是否关闭查找替换对话框
         if (gpFindReplaceDlg->IsTerminating())
         {
            gpFindReplaceDlg = NULL;
            return 0;
         }
         //获取需要查找的文本
         CString strFind = gpFindReplaceDlg->GetFindString();
         int lenStrFind = strFind.GetLength();
        //获取需要替换所查找的文本
        CString strReplace = gpFindReplaceDlg->GetReplaceString();
        int lenStrReplace = strReplace.GetLength();
        CString strEdit;
        m_edt.GetWindowText(strEdit); //获取查找/替换对话框上的查找框中的字符串
        if(gbLastCase ! = bCase)//大小写状态发生了变化,准备重新开始搜索
        {
            gbLastCase = bCase; //保持本次大小写状态
            if (bDown)
                gpos = 0;
            else
                gpos = strEdit.GetLength() -1;
        }
        if (gstrEdit.Compare(strEdit)! =0)//如果用户修改了正文内容,则索引要初始化
        {
            gstrEdit = strEdit; //保存本次正文内容
            if (bDown)
                gpos = 0;
            else
                gpos = strEdit.GetLength() -1;
        }
        if (gstrLast.Compare(strFind) ! = 0)//如果用户修改了查找框中的内容,则索引要初始化
        {
            gstrLast = strFind; //保存本次查找框中的内容
            if (bDown)
                gpos = 0;
            else
                gpos = strEdit.GetLength() -1;
        }
        strRawFind = strFind;
        if (! bCase) //如果不区分大小写,则都转成大写
        {
            strEdit.MakeUpper(); //转换成大写
            strFind.MakeUpper(); //转换成大写
        }
        if (gpFindReplaceDlg->FindNext()) //查找下一个
        {
            if (bDown) //如果是向下搜索
            {
                if (gpos == strEdit.GetLength() -1)
                {
                    MessageBox(_T("已经向下查找到文件结尾,但没找到"), _T("查找替换"),MB_OK | MB_ICONINFORMATION);
                    goto end;
                }
                gpos = strEdit.Find(strFind, gpos); //在正文字符串中查找
                if (gpos == -1)
                {
                    gpos = strEdit.GetLength() -1;
                    MessageBox(_T("无法找到: ") + strRawFind);
                }
                else
                {
                    m_edt.SetFocus();
                    m_edt.SetSel(gpos, gpos + lenStrFind);
                    gcurpos = gpos;
                    gpos = gpos + lenStrFind; //更新索引,准备下一次查找
                }
            }
            else //如果是向上搜索
            {
                if (gpos == 0)
                {
                    MessageBox(_T("已经向上查找到文件开头,但没找到"), _T("查找替换"),MB_OK | MB_ICONINFORMATION);
                    goto end;
                }
                wstring str = strEdit.GetBuffer(0);
                gpos = str.find_last_of(strFind, gpos); //find_last_of是反向找
                strEdit.ReleaseBuffer();
                if (gpos == -1)
                {
                    gpos = 0;
                    MessageBox(_T("无法找到: ") + strRawFind);
                }
                else
                {
                    m_edt.SetFocus();
                    m_edt.SetSel(gpos, gpos + lenStrFind);
                    gcurpos = gpos;
                    gpos = gpos - lenStrFind;  //向后更新索引
                }
            }
        }
        //处理替代
        if (gpFindReplaceDlg->ReplaceCurrent()) //是否按下“替代”按钮
        {
            if (gcurpos >= 0)
            {
                m_edt.SetFocus();
                m_edt.SetSel(gcurpos, gcurpos + lenStrFind);
                m_edt.ReplaceSel(strReplace);
                m_edt.SetSel(gcurpos, gcurpos + lenStrReplace);
                gpos = gcurpos + lenStrReplace;
            }
        }
        //处理替代全部
        if (gpFindReplaceDlg->ReplaceAll()) //是否按下“替代全部”按钮
        {
            UpdateData(TRUE);
            m_strEdit.Replace(strFind, strReplace);
            UpdateData(FALSE);
        }
        end:
        return 0;
        }

这个函数代码较长,难点是字符串的一些查找操作、查找/替换对话框的消息状态的获取。从这个函数可以知道,查找/替换对话框只是提供一个人机界面,主要功能是获取用户的一些操作事件,而真正实现查找替换功能必须由自己实现,这需要对字符串操作熟悉才行。这里的字符串涉及两个,一个是MFC中的字符串CString,另外一个是C++标准库中的字符串string,这两个内容前面章节都已经介绍过。这里不再赘述。关于编辑框的一些操作,下一章会详细讲述。

(4)保存工程并运行,运行结果如图3-52所示。

图3-52

3.8.6 打印对话框的使用

顾名思义,打印对话框提供了让用户进行打印的人机接口,大大简化了以前实现打印功能的烦琐操作。在Visual C++ 2013中,类CPrintDialog实现了打印对话框,打印对话框的所有功能都可以通过该类的成员函数来实现。类CPrintDialog的构造函数定义如下:

        CPrintDialog(    BOOL    bPrintSetupOnly,    DWORD    dwFlags    =    PD_ALLPAGES    |
    PD_USEDEVMODECOPIES   |   PD_NOPAGENUMS   |   PD_HIDEPRINTTOFILE   |   PD_NOSELECTION,
    CWnd* pParentWnd = NULL );

其中,bPrintSetupOnly如果为TRUE,则创建“打印设置”对话框,如果为FALSE,则创建“打印”对话框;dwFlags用来定义打印对话框属性的一组标记;pParentWnd为指向打印对话框父窗口的指针。

CPrintDialog常用的成员函数如下:

下面通过一个实例来说明打印对话框的使用。

【例3.15】 打印对话框的使用

(1)新建一个对话框工程,工程名是Test。

(2)为类CTestDlg添加一个成员函数PrintText,函数定义如下:

        /*
        函数功能:打印字符串
        参数说明:
        str:为要打印的内容
        bShowPrintDlg:表示是否显示打印对话框
        返回值:无
        */
        void CTestDlg::PrintText(CString str, BOOL bShowPrintDlg)
        {
         HDC PrintDC;
         DOCINFO docin;
         docin.cbSize = sizeof(DOCINFO);
         docin.lpszDocName = _T("打印测试文件");
         docin.lpszOutput = NULL;
         CPrintDialog PrintDialog(TRUE, PD_ALLPAGES | PD_NOPAGENUMS, NULL);
         if (bShowPrintDlg)
         {
            if (PrintDialog.DoModal() ! = IDOK) //显示打印对话框
                return;
         }
         else
         {
            if (! PrintDialog.GetDefaults()) //如果不显示打印对话框,则使用默认值
                return;
         }
         PrintDC = PrintDialog.CreatePrinterDC(); // 返回一个打印DC句柄
         //重新定义纸张大小
         DEVMODE* lpDevMode = (DEVMODE*)PrintDialog.GetDevMode();
         lpDevMode->dmPaperSize = DMPAPER_USER; //设定为自定义纸张尺寸
         lpDevMode->dmFields |= DM_PAPERSIZE; //允许重新设置纸张大小
         lpDevMode->dmPaperLength = 300; //设定纸长为3厘米
         ResetDC(PrintDC, lpDevMode); //使设置的参数发挥作用
         StartDoc(PrintDC, &docin); // 启动打印工作
         StartPage(PrintDC); // 一页开始
         TextOut(PrintDC, 0, 0, str, str.GetLength()); //打印内容
         EndPage(PrintDC); // 一页结束
         EndDoc(PrintDC); // 终止打印工作
         if (DeleteDC(PrintDC))// 删除打印机DC
            return;
         else
         {
            AfxMessageBox(_T("打印出错"), MB_OK);
            return;
         }
        }

(3)切换到资源视图,在对话框上添加1个按钮,标题是“显示打印对话框后打印”,然后添加按钮单击事件,代码如下:

        void CTestDlg::OnBnClickedButton1()
        {
         // TODO:  在此添加控件通知处理程序代码
         PrintText(L"一二三四", TRUE);
        }

再在对话框上添加一个按钮,标题是“不显示打印对话框直接打印”,然后添加按钮单击事件,代码如下:

        void CTestDlg::OnBnClickedButton2()
        {
         // TODO:  在此添加控件通知处理程序代码
         PrintText(L"一二三四", FALSE);
        }

(4)保存工程并运行,运行结果如图3-53所示。

如果电脑上安装有Adobe Reader软件,则可以选择打印到pdf文件,如图3-54所示为打印到pdf文件中的结果。

图3-53

图3-54