多文档,新建文件类型选择对话框的设置与设计

在文档窗口创建的时候 ,它缺省总是会新建一个新文档 ,如果是单文档,则直接新建一个文档,如果是多文档,则会弹出文件类型选择对话框。 那么怎么让它不新建文档呢?就这个问题 , 我对文档视图窗口应用程序启动时的文档创建机制 , 稍稍的浅浅挖了一下 , 做了一个详细的分析 , 希望能够对初学者有所帮助 。
在App文件的InitInstance()函数中,有如下几行代码:
CCommandLineInfo  cmdInfo;
ParseCommandLine(cmdInfo);
          if (!ProcessShellCommand(cmdInfo)) return FALSE;
这几行代码是程序启动时创建新文档的关键代码 。
 
1: 我们首先来看看让CCommandLineInfo类是个什么东西:( 部分源代码 )
//in afxwin.h
 class CCommandLineInfo : public CObject
{
    public:
    // Sets default values
   CCommandLineInfo();
   BOOL m_bShowSplash;
   BOOL m_bRunEmbedded;
   BOOL m_bRunAutomated;
 
   enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister,
   AppUnregister, FileNothing = -1 } m_nShellCommand;
 
 // not valid for FileNew
 CString m_strFileName;
   . . .
   ~CCommandLineInfo();
   . . .
 };
  这里要重点注意enum {FileNew, . . . , FileNothing = -1 }m_nShellCommand;
这里联合类型定义的m_nShellCommand 就是外壳程序执行的命令类型 , 如果m_nShellCommand设置为FileNew ,那么程序就会创建新文档。如果想在文档开始时不创建新文档 , 就必须将m_nShellCommand设置为FilleNothing 。下面我们再看看CCommandLineInfo的构造函数 。
//in appcore.cpp
 CCommandLineInfo::CCommandLineInfo()
 {
        m_bShowSplash   = TRUE;
        m_bRunEmbedded  = FALSE;
        m_bRunAutomated = FALSE;
        m_nShellCommand = FileNew;
 }
这里很明白的看出 , 构造函数中 , 缺省将 m_nShellCommand设置为 FileNew 。
 
2:再来看看ParseCommandLine(cmdInfo); 函数 。
 
void CWinApp::ParseCommandLine(CCommandLineInfo& rCmdInfo)
{
    for (int i = 1; i < __argc; i++)
    {
        LPCTSTR pszParam = __targv[i];
        BOOL bFlag = FALSE;
        BOOL bLast = ((i + 1) == __argc);
        if (pszParam[0] == '-' || pszParam[0] == '/')
        {
            // remove flag specifier
            bFlag = TRUE;
            ++pszParam;
        }
        rCmdInfo.ParseParam(pszParam, bFlag, bLast);
    }
}
可以看出ParseCommandLine主要是对输入的命令行参数做一些分析 , 并调用ParseParam来进行处理 .继续分析 ParseParam函数 , 查看如下源代码:
void CCommandLineInfo::ParseParam(const TCHAR* pszParam,BOOL bFlag,BOOL bLast)
{
    if (bFlag)
    {
        USES_CONVERSION;
        ParseParamFlag(T2CA(pszParam));
    }
    else
        ParseParamNotFlag(pszParam);
 
    ParseLast(bLast);
}
其它的函数撇开不看 , 我们重点来分析一下ParseParamFlag()和ParseLast()函数 。
void CCommandLineInfo::ParseParamFlag(const char* pszParam)
{
    // OLE command switches are case insensitive, while
    // shell command switches are case sensitive
 
    if (lstrcmpA(pszParam, "pt") == 0)
        m_nShellCommand = FilePrintTo;
    else if (lstrcmpA(pszParam, "p") == 0)
        m_nShellCommand = FilePrint;
    else if (lstrcmpiA(pszParam, "Unregister") == 0 ||
             lstrcmpiA(pszParam, "Unregserver") == 0)
        m_nShellCommand = AppUnregister;
    else if (lstrcmpA(pszParam, "dde") == 0)
    {
        AfxOleSetUserCtrl(FALSE);
        m_nShellCommand = FileDDE;
    }
    else if (lstrcmpiA(pszParam, "Embedding") == 0)
    {
        AfxOleSetUserCtrl(FALSE);
        m_bRunEmbedded = TRUE;
        m_bShowSplash = FALSE;
    }
    else if (lstrcmpiA(pszParam, "Automation") == 0)
    {
        AfxOleSetUserCtrl(FALSE);
        m_bRunAutomated = TRUE;
        m_bShowSplash = FALSE;
    }
}
ParseParamFlag判断传过来的字符串 ,判断它的参数类型 , 并根据参数类型做不同的处理 。
void CCommandLineInfo::ParseLast(BOOL bLast)
{
    if (bLast)
    {
        if (m_nShellCommand == FileNew && !m_strFileName.IsEmpty())
            m_nShellCommand = FileOpen;
        m_bShowSplash = !m_bRunEmbedded && !m_bRunAutomated;
    }
}
ParseLast会判断是否是是FileNew打开新文档 , 如果是打开新文档 , 并且打开的文档名不为空的话, 就假定用户想打开这个文档 , 把命令设置为FileOpen 。

最后 , 我们可以总结一下ParseCommandLine的作用 . ParseCommandLine的作用主要是分析命令行参数,如果没有命令行参数 ,ParseCommandLine()就假定用户想新建一个文档,于是设置一个FileNew命令,如果命令行参数中有一个文件名,ParseCommandLine()就假定用户想打开该文件,于是设置一个FileOpen命令。
 
3: 最后 , 我们来重点看看外壳命令解析的主角 : ProcessShellCommand ();(部分源代码)
BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)
 {
      BOOL bResult = TRUE;
      switch (rCmdInfo.m_nShellCommand)
     {
          case CCommandLineInfo::FileNew:
                  if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
                         OnFileNew();
                  if (m_pMainWnd == NULL)
                         bResult = FALSE;
                  break;
        case CCommandLineInfo::FileOpen:      . . .
        case CCommandLineInfo::FilePrintTo:    . . .
        case CCommandLineInfo::FilePrint:      . . .
        case CCommandLineInfo::FileDDE:       . . .
        case CCommandLineInfo::AppRegister:   . . .
        case CCommandLineInfo::AppUnregister: . . .
        . . .
      }
}
代码看到这里 , 一切都很明白了 . ProcessShellCommand分析m_nShellCommand ,并根据m_nShellCommand不同的类型值进行不同的处理 。
再来分析下面两行代码:

         CCommandLineInfo cmdInfo;
         ParseCommandLine(cmdInfo);
       if (!ProcessShellCommand(cmdInfo)) return FALSE;

  1: 当CCommandLineInfo cmdInfo进行定义时 , 首先调用构造函数 , 构造函数中m_nShellCommand被设置为FileNew 
 2: 然后执行ParseCommandLine(cmdInfo);对命令进行分析 。
  3: 最后执行ProcessShellCommand (cmdInfo) , ProcessShellCommand ()判断m_nShellCommand为FileNew , 于是调用OnFileNew()创建了一个新的文档 。
   这也就是创建新文档的来龙去脉 。
 
最后, 我们看怎么样解决不想在应用程序启动时的创建新文档的问题:
直接在InitInstance()函数中用如下代码代替原来的几行即可:
CCommandLineInfo cmdInfo;
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
ParseCommandLine(cmdInfo);
       if (!ProcessShellCommand(cmdInfo)) return FALSE;
在多文档情况下,此时再单击新建,则会弹出文件类型选择对话框。我们经常需要变更的就是这个默认的"新建文件"操作, 毕竟那个mfc默认的"文档类型选择"对话框无论是从外观上还是功能上都很难满足我们的需要。

默认"文档类型选择"对话框的定义为:
class CNewTypeDlg : public CDialog
可以在VC6.0安装目录的vc98/mfc/src/docmgr.cpp文件中找到. 下面将会仿照这个对话框来设计自定义的"文档类型选择"对话框, 并可以添加自己需要的各种功能。
首先观察mfc"新建文件"操作的过程源码. 对于ID_FILE_NEW消息, 利用VC6.0 IDE的搜索功能,找到: /VC98/MFC/SRC/APPDLG.CPP(25):void CWinApp::OnFileNew()如下:
void CWinApp::OnFileNew()
{
if (m_pDocManager != NULL)
       m_pDocManager->OnFileNew();
}
m_pDocManager是CWinApp的成员. 类型为CDocManager*
然后继续找到: /VC98/MFC/SRC/DOCMGR.CPP(802):void CDocManager::OnFileNew()如下:
void CDocManager::OnFileNew()
{
if (m_templateList.IsEmpty())
{
       TRACE0("Error: no document templates registered with CWinApp./n");
       AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
       return;
}

 

 

CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
if (m_templateList.GetCount() > 1)
{
       // more than one document template to choose from
       // bring up dialog prompting user
       CNewTypeDlg dlg(&m_templateList);
       int nID = dlg.DoModal();
       if (nID == IDOK)
       pTemplate = dlg.m_pSelectedTemplate;
       else
       return;     // none - cancel operation
}

ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);

pTemplate->OpenDocumentFile(NULL);
       // if returns NULL, the user has already been alerted
}
在这个函数中, 就可以发现CNewTypeDlg的踪迹。

工程的名字假定为My, 则我们有CMyApp派生类。
我们所需要做的工作是:
1, 在菜单中添加一个新的菜单项例如ID_FILE_NEW_1, 并添加消息响应函数例如OnFileNew1(); 或者直接为原来的ID_FILE_NEW添加消息响应函数OnFileNew. 这里采用后一种。
2, 添加CMyApp::OnFileNew()后, 不要忘记删除My.cpp中MESSAGE_MAP里的这一项:
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
3, 在CMyApp::OnFileNew()中, 把上面的CWinApp::OnFileNew()和CDocManager::OnFileNew()的代码拷贝过去, 并加以改造如下:
void CMyApp::OnFileNew()
{
// from CWinApp::OnFileNew():
if (m_pDocManager == NULL)
{
   return;
}

// from CDocManager::OnFileNew():
// 由于无法访问CDocManager的保护数据成员m_templateList
// 故要使用CDocManager::GetFirstDocTemplatePosition()
// 和CDocManager::GetNextDocTemplate()接口来获得所有文档模板类指针
// 并将其添加到自定义的列表m_DocTplList中
POSITION pos = m_pDocManager->GetFirstDocTemplatePosition();
if (pos == NULL)
{
   TRACE0("Error: no document templates registered with CWinApp./n");
   AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
   return;
}

// 遍历获得各个文档模板类指针
// 注意, 在CMyApp的定义中添加成员CPtrList m_DocTplList用以放置这些指针
CDocTemplate* pTemplate = NULL;

while (pos != NULL)
{
   pTemplate = m_pDocManager->GetNextDocTemplate(pos);
   ASSERT(pTemplate != NULL);
   this->m_DocTplList.AddTail(pTemplate);
}

// 如果文档类型超过1个则使用自定义的"文档类型选择"对话框CDlgChooseClass
// 在资源编辑器中添加一个对话框, 并创建对话框类CDlgChooseClass
// 在对话框中放置一个ListBox控件, 用来容纳多个文档类型
// 稍后有具体说明
if (m_DocTplList.GetCount() > 1)
{
   CDlgChooseClass dlg(&m_DocTplList);   // 输入文档模板类指针的列表
   int nID = dlg.DoModal(); // 对话框CDlgChooseClass完成选择文档类型
          // 并且可以有其他自定义的操作
   if (nID == IDOK)
   {
    pTemplate = dlg.m_pSelectedTemplate;
   }

else
   {
    m_DocTplList.RemoveAll(); // 注意: 清空自定义的列表
    return;     // none - cancel operation
   }
}

m_DocTplList.RemoveAll(); // 注意: 清空自定义的列表

// 检查返回的值
ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);

// 选定的文档模板类: 创建新文档
pTemplate->OpenDocumentFile(NULL);
   // if returns NULL, the user has already been alerted
}

接下来是设计CDlgChooseClass, 仿照CNewTypeDlg的定义进行(vc98/mfc/src/docmgr.cpp).
class CDlgChooseClass : public CDialog
{
// Construction
public:
       // 修改默认构造函数, 增加一个参数CPtrList* pList
       CDlgChooseClass(CPtrList* pList, CWnd* pParent = NULL);

       // Dialog Data

public:
       CDocTemplate* m_pSelectedTemplate; // 选定的文档模板类
       //{{AFX_DATA(CDlgChooseClass)
       enum { IDD = IDD_DLG_CHOOSE_CLASS };
       // NOTE: the ClassWizard will add data members here
       //}}AFX_DATA

       // Overrides
       // ClassWizard generated virtual function overrides
       //{{AFX_VIRTUAL(CDlgChooseClass)
protected:
       virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
       //}}AFX_VIRTUAL

       // Implementation
protected:
       CPtrList* m_pList;        // 文档模板类指针列表

       // Generated message map functions
       //{{AFX_MSG(CDlgChooseClass)
       virtual BOOL OnInitDialog();
       virtual void OnOK();
       //}}AFX_MSG
       DECLARE_MESSAGE_MAP()
};

实现OnInitDialog()和OnOk()如下:

基本上是拷贝CNewTypeDlg::OnInitDialog()和CNewTypeDlg::OnOk()的代码, 并略做修改
BOOL CDlgChooseClass::OnInitDialog() 
{
       // IDC_DOC_TPL_LIST是ListBox的ID
       CListBox* pListBox = (CListBox*)GetDlgItem(IDC_DOC_TPL_LIST);
       ASSERT(pListBox != NULL);

       // fill with document templates in list
       pListBox->ResetContent();

       POSITION pos = m_pList->GetHeadPosition();
       // add all the CDocTemplates in the list by name
       while (pos != NULL)
       {
              CDocTemplate* pTemplate = (CDocTemplate*)m_pList->GetNext(pos);
              ASSERT_KINDOF(CDocTemplate, pTemplate);

              // 这里是添加fileNewName来代表各个文档模板。也可以设计其他的显示方式
              CString strTypeName;
              if (pTemplate->GetDocString(strTypeName, CDocTemplate::fileNewName) &&
              !strTypeName.IsEmpty())
              {
               // add it to the listbox
               int nIndex = pListBox->AddString(strTypeName);
                      if (nIndex == -1)
                      {
                            EndDialog(-1);
                            return FALSE;
                      }
                      pListBox->SetItemDataPtr(nIndex, pTemplate);
              }

       }

       int nTemplates = pListBox->GetCount();
       if (nTemplates == 0)
       {
              TRACE0("Error: no document templates to select from!/n");
              EndDialog(-1); // abort
       }
       else if (nTemplates == 1)
       {
              // get the first/only item
              m_pSelectedTemplate = (CDocTemplate*)pListBox->GetItemDataPtr(0);
              ASSERT_VALID(m_pSelectedTemplate);
              ASSERT_KINDOF(CDocTemplate, m_pSelectedTemplate);
              EndDialog(IDOK);    // done
       }
else
{
       // set selection to the first one (NOT SORTED)
       pListBox->SetCurSel(0);
}

       return CDialog::OnInitDialog();
}

void CDlgChooseClass::OnOK() 

{
       // TODO: Add extra validation here
       CListBox* pListBox = (CListBox*)GetDlgItem(IDC_DOC_TPL_LIST);
       ASSERT(pListBox != NULL);
       // if listbox has selection, set the selected template
       int nIndex;
if ((nIndex = pListBox->GetCurSel()) == -1)
{
       // no selection
       m_pSelectedTemplate = NULL;
}
else
{
       m_pSelectedTemplate = (CDocTemplate*)pListBox->GetItemDataPtr(nIndex);
       ASSERT_VALID(m_pSelectedTemplate);
       ASSERT_KINDOF(CDocTemplate, m_pSelectedTemplate);
}

CDialog::OnOK();
}

当然, 构造函数也要注意修改:
CDlgChooseClass::CDlgChooseClass(CPtrList* pList, CWnd* pParent /* = NULL */)
: CDialog(CDlgChooseClass::IDD, pParent)
{
//{{AFX_DATA_INIT(CDlgChooseClass)
   // NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT

       m_pList = pList; // 文档模板类指针列表
       m_pSelectedTemplate = NULL;   // 最后选择的文档模板类

}

到这里就完成了. 进入程序, 选择"文件"-"新建", 或者直接Ctrl+N, 出来的已经是我们自定义的CDlgChooseClass对话框了, 且基本功能和原来默认的完全一样(选择文档类型). 要增加新的功能, 或者改善外观? 这就是对话框设计的问题了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值