[C++]Windows API 打开/另存为对话框文件路径以及扩展名的获取

最近在做Windows 开发过程中遇到一个问题,需要打开文件对话框和另存为对话框,获取所选文件的路径信息,这部分很容易就实现了,可当另存为文件时如何获得格式不同文件的扩展名却遇到了一点问题,经过一番资料搜索和官方文档查阅,遂解决。先说心得,windows API 相关的问题还是要直接查询微软官方文档来的快,上面解释的很清楚,不要被英文和那些奇奇怪怪的定义吓到,自己动手实现几次就明白是怎么回事了。东拉西扯,进入正题,
Windows 打开和另存为对话框如下所示,
(1) 打开对话框
打开文件对话框
(2)另存为对话框
在这里插入图片描述
这里用一个例子说明几个概念,文件目录,文件路径,文件名,如下图所示,在F盘Demo文件夹下有一个名为"Test"的txt文件
文件Test.txt的文件名是“Test”
文件目录是 “F:\Demo";
文件路径是:“F:\Demo\Test.txt";
文件扩展名是:“.txt”
在这里插入图片描述
这打开“打开”对话框以及打开“另存为对话框”这两个两个窗口涉及到的API分别为
GetOpenFileName()
GetSaveFileName()
微软官方的文档的建议是这两个API已经过时了,建议使用 IFileOpenDialog 或者IFileSaveDialog,这个不是本文的主题,有兴趣的朋友可以去官网查询新方法的用法,此处暂且不表。
以GetOpenFileName() 为例,做以解释。官方文档给出的函数定义是

BOOL GetOpenFileNameA(
  LPOPENFILENAMEA Arg1
);

参数 Arg1 类型 LPOPENFILENAME
(小提示,如果觉得官方文档大段的英文看起来很费劲,可以用chrome浏览器自带的翻译功能,鼠标右键有翻译选项,翻译的结果也八九不离十)关于类型LPOPENFILENAME 文档给出的解释是:
A pointer to an OPENFILENAME structure that contains information used to initialize the dialog box. When GetSaveFileName returns, this structure contains information about the user’s file selection.(一个指向OPENFILENAME结构的指针,该结构包含用于初始化对话框的信息。当GetSaveFileName返回时,此结构包含有关用户文件选择的信息。)
因此我们知道GetOpenFileNameA的参数我们需要传入的是一个指向 OPENFILENAME 结构的指针,马不停蹄的我们要立即看看这个OPENFILENAME 结构的内容是什么,因为只有知道他的结构我们才能知道如何初始化我们的结构体指针。如果经常使用Windows API 你就会发现这是很常见的模式。 不同的开发人员有不同的需要,Windows 把框架丢给你,让你自己定义,比如我们打开的窗口的标题,窗口的模态,保存文件类型的数目,是否允许多选,等等。这也是我们初始化这个结构体的意义所在。

点进OPENFILENAME 的链接,我们看到如下结构体定义

typedef struct tagOFNA {
  DWORD         lStructSize;
  HWND          hwndOwner;
  HINSTANCE     hInstance;
  LPCSTR        lpstrFilter;
  LPSTR         lpstrCustomFilter;
  DWORD         nMaxCustFilter;
  DWORD         nFilterIndex;
  LPSTR         lpstrFile;
  DWORD         nMaxFile;
  LPSTR         lpstrFileTitle;
  DWORD         nMaxFileTitle;
  LPCSTR        lpstrInitialDir;
  LPCSTR        lpstrTitle;
  DWORD         Flags;
  WORD          nFileOffset;
  WORD          nFileExtension;
  LPCSTR        lpstrDefExt;
  LPARAM        lCustData;
  LPOFNHOOKPROC lpfnHook;
  LPCSTR        lpTemplateName;
  LPEDITMENU    lpEditInfo;
  LPCSTR        lpstrPrompt;
  void          *pvReserved;
  DWORD         dwReserved;
  DWORD         FlagsEx;
} OPENFILENAMEA, *LPOPENFILENAMEA;

文档里面每一项都解释的很清楚,这里就不做一一解读了。挑其中几个用的比较多的解释一下。
lStructSize:结构的长度,以字节为单位。此参数使用 sizeof (OPENFILENAME)。
hwndOwner:拥有该对话框的窗口的句柄。这个成员可以是任何有效的窗口句柄。如果对话框里有没有所有者,它可以为 NULL。
lpstrFilter:筛选器,这个关键字很常用,他决定着对话框的文件类型。这个关键字通常由两对字符串构成,每一对中的第一个字符串是一个显示字符串,描述了该筛选器 (例如,“文本文件”),和第二个字符串指定筛选器模式 (例如,"*.TXT")。例如

ofn.lpstrFiler = TEXT("所有文件\0*.*\0\0");

我们打开的对话框保存类型如下所示,
在这里插入图片描述
如果我们想要在一个条目内有多个格式选择,以分号分割,例如我们只想打开txt或者doc文件,以此类推;

ofn.lpstrFiler = TEXT("所有文件\0*.txt;*.doc*\0\0");

如果我们想显示多个条目,实现如下图效果,
在这里插入图片描述
则继续添加筛选器即可,例如

ofn.lpstrFilter = TEXT("所有文件\0*.*\0文本 文件(*.txt)\0*.txt*\0图片(*.jpg)\0*.jpg*\0\0");

nFilterIndex:在文件类型控件中当前选定的筛选器的索引。 如果你有3条筛选器,这个值指定为2, 则打开对话框时默认显示的文件类型是第二条筛选器。 这个值还有一个重要的应用就是如果当我们设置了对话框回调函数时,这个值保存着用户的筛选器选择,有了他我们便可以知道用户选择文件的对应的扩展名。(在另存为对话框)
nMaxFile:以字符数计算,lpstrFile 所指向的缓冲区的大小。缓冲区必须足够大以存储的路径和文件名称字符串或字符串,包括终止 NULL 字符。缓冲区应至少 256 个字符长。
lpstrInitialDir:初始目录。留空由系统选择,但选择的初始目录的算法在不同系统上各不相同。
lpstrTitle:要放在对话框的标题栏中的字符串。如果此成员为 NULL,则系统使用默认标题 。
lpstrFile:用来初始化文件名称的文件名称的编辑控件。
当使用 GetOpenFileName时, 如过只允许选择单个文件,该缓冲区包含驱动器符、 路径、 文件名称和所选文件的扩展名, 如果设置可以多选文件,则lpstrFile 包含当前选择文件的目录,后面跟着对应所选的多个文件的文件名已经扩展名。
而当使用GetSaveFileName时,lpstrFile 只包含当前文件目录以及文件名,并没有包含文件扩展名,这是和打开文件对话框不同的地方,如果我们希望获取用户选择的对应文件的文件扩展名,需要通过设置lpfnHook 回调函数来获取这一参数,后面会说到。
Flags: 设置对话框风格标志位。如果希望实现多选文件,则可以设置标志位为

ofn.Flags = OFN_EXPLORER |OFN_ALLOWMULTISELECT;

一定要有OFN_EXPLOER, 否则窗口风格会变。下面上代码

int main()
{
	OPENFILENAME ofn = { 0 };
	TCHAR strFilename[MAX_PATH] = { 0 }; //用于接收文件名
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.lpstrFile = strFilename;
	ofn.lpstrFilter =TEXT("Cpp(*.cpp)\0*.cpp*\0文本(*.txt)\0*.txt*\0图片(*.jpg)\0*.jpg*\0\0");
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_EXPLORER|OFN_HIDEREADONLY |OFN_NOCHANGEDIR |OFN_PATHMUSTEXIST| OFN_ALLOWMULTISELECT;
	//ofn.lpfnHook = (LPOFNHOOKPROC)SaveAsHookPrc;
	TCHAR * pszFileName;
	CString szDirectory;
	CString szFileName;
	CStringArray szMultiFliePath; //如果允许多选,该array中记录着所有被选中的文件路径。
	if (GetOpenFileName(&ofn))
	{
		pszFileName = ofn.lpstrFile;
		szDirectory = pszFileName; 
		//如果只允许单选, 缓冲区ofn.lpstrFile包含完整的文件路径(包括文件名以及扩展名)
        //如果允许多选,缓冲区ofn.lpstrFile指针包含内容分为两个部分,第一部分是选择文件所在的目录,第二部分是选择的所有文件名,每个文件名以“\0”作为分隔;
        //缓冲区中的最后一个字符串以两个空字符终止,因此可以通过移动指针来判断是否包含多个文件
		pszFileName = pszFileName + szDirectory.GetLength() + 1;
		while (*pszFileName)
		{
			szFileName = pszFileName;
			szMultiFliePath.Add(szDirectory + pszFileName);
			pszFileName = pszFileName + szFileName.GetLength() + 1;
		}
	}
	return true;
}

“另存为”对话框的使用方法与“打开”对话框的使用方法类似,这里要重点说明的是如何获取另存为对话框的文件扩展名。因为使用GetSaveFileName() 如果用户没有键入文件的扩展名,ofn对象的文件缓冲区并不包含文件的扩展名,
如下图所示
在这里插入图片描述
如果用户不手动添加文件扩展名“.txt”,ofn.lpstrFile 文件缓冲区只能得到不包含文件扩展名的文件路径。
如果我们想实现自动根据用户选择的文件扩展来添加扩展名,可以通过添加回调函数到ofn.lpfnHook的方法来获取用户的选择的nFilterIndex. 其中nFilterIndex的值与 ofn.lpstrFilter 设定的顺序一样。例如,

ofn.lpstrFilter =TEXT("Cpp(*.cpp)\0*.cpp*\0文本(*.txt)\0*.txt*\0图片(*.jpg)\0*.jpg*\0\0")

当用户选择第一项(Cpp)时,nFilterIndex为1,选择第二项(文本)时,nFilterIndex =2, 以此类推。
当用户选择了不同的文件名扩展,windows会讲用户选择响应一个CDN_TYPECHANGE消息,而该消息包含在WM_NOTIFY中,同时CDN_TYPECHANGE消息包含一个指向OFNOTIFY的结构指针,该结构指针里包含了我们所需要的对象LPOPENFILENAMEA 指针。代码如下
设置nFlag 为

ofn.Flags = OFN_EXPLORER| OFN_ALLOWMULTISELECT|OFN_ENABLEHOOK;

同时添加回调函数:CbGetFileExtension();

#include <afx.h>
#include <windows.h>
#include <commdlg.h>
#include <iostream>
#include <string>
#include <cstring>
using namespace std;



static int g_nFilterIndex(0); // 记录用户选择的index.
void GetFilterInde(OFNOTIFY* pFile)
{
	switch (pFile->lpOFN->nFilterIndex)
	{
	case 1:
		g_nFilterIndex = 1;
		break;
	case 2:
		g_nFilterIndex = 2;
		break;
	case 3:
		g_nFilterIndex = 3;
		break;
	default:
		break;
	}

}
UINT_PTR CALLBACK CbGetFileExtension(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_NOTIFY:
	{   NMHDR *pHdr = (NMHDR*)lParam;
		switch (pHdr->code)
		{
		case CDN_TYPECHANGE:
			GetFilterInde((OFNOTIFY*)pHdr);
			break;
		default:
			break;
		}
		break;
	}
	default:
		break;
	}
	return 0;
}
int main()
{
	OPENFILENAME ofn = { 0 };
	TCHAR strFilename[MAX_PATH] = { 0 };
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.lpstrFile = strFilename;
	ofn.lpstrFilter = TEXT("Cpp(*.cpp)\0*.cpp*\0文本(*.txt)\0*.txt*\0图片(*.jpg)\0*.jpg*\0\0");
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST |OFN_ENABLEHOOK;
	ofn.lpfnHook = &CbGetFileExtension; //添加回调函数
	CString szFilePath;
	CStringArray szMultiFliePath;
	if (GetSaveFileName(&ofn))
	{
		szFilePath = ofn.lpstrFile;
		switch (g_nFilterIndex)
		{
		case 1:
			szFilePath = szFilePath + ".cpp";
		case 2:
			szFilePath = szFilePath + ".txt";
			break;
		case 3:
			szFilePath = szFilePath + ".jpg";
			break;
		default:
			break;
		}
	}
	return true;
}

猜你喜欢

转载自blog.csdn.net/Murphy_CoolCoder/article/details/88983684