《PicSi的实现细节》 第2节 窗口类CAboutDlg的定义

来源:互联网 发布:淘宝号登录 编辑:程序博客网 时间:2024/06/05 15:29

为了把重点集中在窗口类的定义方法上,本节以较为简单的AboutDlg为例,阐述如何定义窗口类。

在第一节中,我们已经在Resource View(资源视图)中添加了所需的窗口。在这里简单总结一下:向主窗口中添加Tab控制,用作菜单选项卡;添加IDD_DLG_CREATOR子窗口,作为Main子窗口;向IDD_ABOUTBOX子窗口添加了一个Edit控制,用来显示PicSi的使用说明,作为About子窗口。

切换到Solution Explorer视图,我们可以看到Application Wizard自动生成的源文件AboutDlg.h,双击它进入编辑窗口。AboutDlg.h比较简短,全部代码如下: 

// aboutdlg.h : interface of the CAboutDlg class///////////////////////////////////////////////////////////////////////////////#pragma onceclass CAboutDlg : public CDialogImpl<CAboutDlg>{public:        enum { IDD = IDD_ABOUTBOX };private:        CEdit m_editAbout;public:        BEGIN_MSG_MAP(CAboutDlg)                MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)                COMMAND_ID_HANDLER(IDOK, OnCloseCmd)                COMMAND_ID_HANDLER(IDCANCEL, OnCloseCmd)        END_MSG_MAP()// Handler prototypes (uncomment arguments if needed)://      LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)//      LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)//      LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)        LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)        {                m_editAbout.Attach(GetDlgItem(IDC_EDIT_ABOUT));                display_information();                return TRUE;        }        LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)        {                //EndDialog(wID);                return 0;        }private:        // display information        void display_information() throw()        {                WTL::CString strReadme;                if(!PS_GetInstallationDirectory(strReadme))                        return;                if(!PS_PathAppendBackslash(strReadme))                        return;                strReadme += _T("readme.txt");                ATL::CComPtr<IStream> spStream;                HRESULT hr = ::SHCreateStreamOnFile(strReadme, STGM_READ | STGM_SHARE_DENY_WRITE, &spStream);                if(FAILED(hr))                        return;                ULONG nCount;                WTL::CString strInfo;                TCHAR szInfo[1025];                while(true) {                        ZeroMemory(szInfo, 1025 * sizeof(TCHAR));                        hr = spStream->Read(szInfo, 1024, &nCount);                        if(FAILED(hr))                                return;                        else {                                strInfo += szInfo;                                if(nCount < 1024)                                        break;                        }                } // end while                m_editAbout.SetWindowText(strInfo);        }};
pragma once表示AboutDlg.h头文件只被Project包含进一次,从而避免重复定义。

第10行指定About窗口类对应的资源ID,当调用Create方法创建About子窗口时,程序将根据ID号加载所需的资源。

第13行定义了成员变量m_editAbout,它对应于About子窗口上的文本框,用来显示PicSi的说明文档。

第17到21行是CAboutDlg的消息映射,之所以它具有处理消息的能力,是因为它继承自CDialogImpl类。CDialogImpl类是一个mix-in class,这是WTL中模板的典型用法,它提供了一种trunk机制,将窗口过程函数的HWND参数替换成this指针,使CAboutDlg也能捕获并处理消息。

从消息映射可以看出,About子窗口只需处理三条消息WM_INITDIALOG/IDOK/IDCANCEL,其中较为复杂的是WM_INITDIALOG消息。它在窗口创建的时候抛出,定义OnInitDialog对它进行响应。在Application Wizard自动生成的代码中,OnInitDialog的第一行代码是CenterWindow(),对于非模态(modaless)的窗口是没有意义的,可以将它删除。我们首先调用CEdit::Attach方法,将变量m_editAbout与控制IDC_EDIT_ABOUT关联起来,这样我们就可通过m_editAbout来控制Edit控制的行为。比如更改文本框的显示内容,就可调用m_editAbout.SetWindowText方法来实现。随后我们调用了一个私有方法display_information,它定义在第43到70行。

OnCloseCmd方法响应了IDOK和IDCANCEL消息,这里需将EndDialog(wID)注释掉。原因在于About子窗口现在是modaless的,只有在父窗口销毁时才被销毁,自身不能提前清理。如果不注释EndDialog(wID),当用户敲击Enter键时可能会触发IDOK消息,这样会使程序运行时出错。

现在重点说明私有方法display_information的实现。顾名思义,此方法用来显示相关信息(实际上就即PicSi使用说明文档)。思路分为4步:(1)获得readme.txt文件的全路径;(2)为readme.txt文件创建IStream接口指针;(3)从readme.txt文件中读取文本信息;(4)将读到的文本显示到文本框控制中。

为了获得readme.txt的全路径,我们调用了公共函数PS_GetInstallationDirectory,它定义在private/Fcommon.h头文件中,代码为:

// PS_GetInstallationDirectoryinlineBOOL PS_GetInstallationDirectory(WTL::CString& strPath) throw(){        TCHAR szPath[MAX_PATH];        if(!GetModuleFileName(NULL, szPath, MAX_PATH))                return FALSE;        strPath = szPath;        int nPos = strPath.ReverseFind(_T('\\'));        strPath = strPath.Mid(0, nPos);        return TRUE;}

这段代码很好理解,你只需查一查GetModuleFileName这个API函数的作用就明白了。

为readme.txt文件创建ISteam接口指针调用了API函数 SHCreateStreamOnFile ,传入标签参数STGM_READ  |  STGM_SHARE_DENY_WRITE表示以读方式打开readme.txt,而且不允许其它进程对它进行Write操作。

接着进入一个while循环,一次读取1024个字符,直至读到文本的末尾。值得注意的是,每次读之前都要调用ZeroMemory函数对szInfo进行清零操作,否则会出现乱码。判断是否读完readme.txt的方法很简单,只要将每次实际读取的字符数与1024进行比较即可,若实际读取的字符数比1024小,则说明已经读到了readme.txt的末尾。每次成功读取到的字符串szInfo都附加到WTL::CString变量strInfo的尾部。

最后将strInfo显示到About子窗口的文本框控制中,调用m_editAbout.SetWindowText方法即可实现。

可能你已发现上述每个函数/方法体最后都有一句throw(),它表示本函数/方法不会抛出异常。这是一种强烈的信号,警惕程序员必须在函数/方法内部将所有可能的异常处理掉。

CAboutDlg的定义就讲到这里,在下一节中,博主将详细介绍CPicsiCreatorDlg窗口类的定义。CPicsiCreatorDlg比CAboutDlg要复杂得多,它是PicSi的核心部分。