在 Windows 上用 C++ 搞一个打开/保存文件对话框

做 Windows 桌面开发,基本绕不开让用户选文件。不管是打开已有文件还是另存为新文件,总得弹个系统对话框。
网上搜一圈,方法不少,但很多要么是 MFC 的,要么是纯 Win32 的,混在一起讲容易乱。我这篇就只聊文件对话框(文件夹的下一期再说),把两种常用路子都过一遍,顺带记几个我踩过的坑。


先上 MFC 的 CFileDialog(如果你用 MFC 的话)

如果你的项目本身是 MFC 框架,那直接用 CFileDialog 最省事,不用自己折腾结构体。

打开文件

#include <afxdlgs.h>

void DoOpenFile()
{
    CFileDialog dlg(TRUE, NULL, NULL,
        OFN_HIDEREADONLY | OFN_FILEMUSTEXIST,
        _T("文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*||"),
        this);

    if (dlg.DoModal() == IDOK)
    {
        CString path = dlg.GetPathName();
        // 拿到路径,该读就读
        AfxMessageBox(_T("选了: ") + path);
    }
}

那个过滤器字符串的格式是固定的:"描述|模式|描述|模式||",最后一定要俩竖线,少一个就弹不出来,别问我怎么知道的。
OFN_FILEMUSTEXIST 保证用户不能瞎填一个不存在的文件,省得你后面再校验。

保存文件

保存模式就是把第一个参数改成 FALSE,顺手加上覆盖提示。

void DoSaveFile()
{
    CFileDialog dlg(FALSE,
        _T("txt"),
        _T("默认名字"),
        OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST,
        _T("文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*||"),
        this);

    if (dlg.DoModal() == IDOK)
    {
        CString path = dlg.GetPathName();
        // 创建文件写入
    }
}

OFN_OVERWRITEPROMPT 必须加,不然用户选了个已存在的文件,你的程序直接覆盖掉,连个招呼都不打,等着被骂吧。


不用 MFC 的话,Win32 老路子 GetOpenFileName / GetSaveFileName

很多朋友的项目是纯 Win32 或者 ATL 之类的,没 MFC 那一套。那就用 commdlg.h 里的这两个 API,它们从 Win95 就存在了,兼容性无敌。

打开文件

#include <commdlg.h>

CString OpenFileDialog(HWND hWnd)
{
    OPENFILENAME ofn = { 0 };
    TCHAR szFile[MAX_PATH] = { 0 };

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    // 注意过滤器的写法:用 \0 分隔,最后两个 \0 结尾
    ofn.lpstrFilter = _T("文本文件\0*.txt\0所有文件\0*.*\0");
    ofn.nFilterIndex = 1;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

    if (GetOpenFileName(&ofn))
        return CString(szFile);
    return _T("");   // 取消或失败
}

这里最容易写错的就是 lpstrFilter,它用的是双空字符分隔,不是 |。如果你复制了 MFC 那种 | 分隔的字符串过来,对话框直接罢工。
另外 szFile 缓冲区要足够大,MAX_PATH 通常是 260 个字符,一般够用,但长路径的话可以考虑 _MAX_PATH 或动态分配,不过一般不用太纠结。

保存文件

CString SaveFileDialog(HWND hWnd)
{
    OPENFILENAME ofn = { 0 };
    TCHAR szFile[MAX_PATH] = _T("未命名.txt");   // 预填默认名

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFilter = _T("文本文件\0*.txt\0所有文件\0*.*\0");
    ofn.nFilterIndex = 1;
    ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;

    if (GetSaveFileName(&ofn))
        return CString(szFile);
    return _T("");
}

跟打开的区别不大,就是 Flags 换了 OFN_OVERWRITEPROMPTszFile 里给了个初始字符串,用户可以在对话框里改。



项目类型 推荐接口 说明
基于 MFC 框架 CFileDialog 无需额外依赖,接口与 MFC 风格一致
纯 Win32 / ATL / 无 MFC GetOpenFileName / GetSaveFileName 仅依赖 commdlg.dll,无框架开销
需适配 Windows 10/11 界面 两者均可 底层均调用系统通用对话框,外观一致
需兼容 Windows XP 两者均可 GetOpenFileName 原生支持 XP;CFileDialog 需使用 v141_xp 工具集编译 MFC

几个容易忽略的细节

  1. 用户点了取消:两种方式都会返回 FALSEIDCANCEL,一定要判断,别拿着空路径去读写。
  2. 默认扩展名CFileDialog 的第二个参数可以指定默认后缀,如果用户没输后缀,系统会自动补上。Win32 版的话,OPENFILENAME 里也有 lpstrDefExt 可以设。
  3. 多选文件:这里没讲,因为篇幅长,而且多数场景只要单选。如果需要多选,CFileDialog 可以加 OFN_ALLOWMULTISELECT,但处理起来会复杂一些。
  4. Unicode 和 ANSI:现在基本都用 Unicode 了,代码里的 _T 宏和 TCHAR 能自动适应,如果是 VS 的新项目,默认就是 Unicode。

版权声明:本文为博主原创,转载注明出处即可。


更多推荐