应用程序使用两种类型的文档, 一种是用户创建的,另一种是应用程序创建的。 应用程序应使用
SHGetFolderPath shell 函数来检索有效的文件夹位置,用以存储用户和应用程序特定的数据。 对于 Windows XP 应用程序支持使用同一计算机的多个用户并让用户能够快速切换,这是必不可少的。
本文将在以下步骤中介绍如何将用户数据存储在正确的位置:
- 创建 Win32 应用程序。
- 在文件菜单上添加另存为选项。
- 使用标准的保存文件对话框默认保存到正确的位置。
- 验证文件保存位置是否正确。
- 记住用户的前一个选择。
- 验证用户的前一个选择。
在以下步骤中,本文还将说明必须将应用程序数据保存在什么位置,以及如何确保存储位置正确:
- 对应用程序数据进行分类。
- 将应用程序数据存储在正确的位置。
- 审慎地使用注册表。
要求
下表概括了推荐使用的硬件、软件、网络架构、技能、知识及所需的 Service Pack:
- Windows XP Home Edition 或 Windows XP Professional
- Visual Studio .NET 或 Visual Studio 6.0 版
- 应了解 Win32 应用程序开发
创建 Win32 应用程序
启动 Visual Studio,新建一个名为 SavingData 的 Win32 应用程序。
- 在 Visual C++ 6.0 中,在可用的项目类型列表中单击 Win32 应用程序,然后在应用程序安装向导中选择典型的“Hello World”应用程序选项。
- 在 Visual Studio .NET 中,单击项目类型下的 Visual C++ 项目,然后单击模板下的 Win32 项目。 接受应用程序安装向导显示的默认应用程序设置。
在“文件”菜单上添加“另存为”选项
- 单击资源视图,然后双击 IDC_SAVINGDATA。
- 在文件菜单上添加另存为菜单选项。 将 IDM_FILE_SAVEAS 用作该菜单项的标识。
- 在 SavingData.cpp 中找到该应用程序的 WndProc 窗口过程,将新的 case 语句添加到 WM_COMMAND 节中,用以处理另存为菜单选项。 调用 OnFileSaveAs 函数(您将在下一部分中创建它)。 此函数不使用参数。
您的代码应如下所示:
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections.
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDM_FILE_SAVEAS:
OnFileSaveAs();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
使用标准的“保存文件”对话框默认保存到正确的位置
当用户第一次显示应用程序的
保存文件(或
打开文件)对话框时,该对话框必须默认定位到用户的 My Documents 文件夹(或 My Documents 的子级文件夹,例如用来保存图像数据的 My Pictures 和保存音频文件的 My Music)。
备注: 任何时候都不要对应用程序中的路径进行硬编码,因为您无法保证其物理位置不变。 例如,管理员可能会将 My Documents 文件夹重新定位到某个网络位置。
- 在 SavingData.cpp 的上方,添加下面的 include 语句:
#include <commdlg.h> // for GetSaveFileName
#include <shlobj.h> // for SHGetFolderPath
- 添加 OnFileSaveAs 函数的以下原型:
- 创建新的 OnFileSaveAs 函数。 在此函数内,将 SHGetFolderPath 函数和 CSIDL_MYPICTURESCSIDL 标识符一起使用,以检索正确的文件夹位置来存储图片数据。 将此文件夹位置传递到 GetSaveFileName 函数,以显示标准的保存文件对话框。
您的代码应如下所示:
void OnFileSaveAs()
{
OPENFILENAME openFile;
TCHAR szPath[MAX_PATH];
// Initialize OPENFILENAME structure.
ZeroMemory( &openFile, sizeof(OPENFILENAME) );
openFile.lStructSize = sizeof(OPENFILENAME);
// Default to My Pictures. First, get its path.
if ( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_MYPICTURES,
NULL, 0, szPath ) ) )
{
// Set lpstrInitialDir to the path that SHGetFolderPath obtains.
// This causes GetSaveFileName to point to the My Pictures folder.
openFile.lpstrInitialDir = szPath;
}
// Display the standard File Save dialog box, defaulting to My Pictures.
if ( GetSaveFileName( &openFile ) == TRUE )
{
// User clicks the Save button.
// Save the file
}
else
{
// User cancels the File Save dialog box.
}
}
验证文件保存位置是否正确
- 按 F5 键以生成项目。
- 运行该应用程序,然后单击文件菜单中的另存为。
- 验证标准的保存文件对话框是否如 CSIDL_MYPICTURES 指定的那样默认使用 My Pictures 文件夹。
- 单击取消关闭该对话框,然后关闭应用程序。
记住用户的前一个选择
为了便于以后使用
保存文件(或
打开文件)对话框,建议将该对话框默认定位到用户以前所选择的位置。
如果没有在
OPENFILENAME 结构中提供初始文件夹位置,则
GetSaveFileName(和
GetOpenFileName)将显示标准的
保存文件或
打开文件对话框,两者均指向 My Documents 文件夹。 此外,如果用户以前已用过其中一个对话框,并选择了非默认文件夹,则这些函数将自动默认定位到以前用过的文件夹。
若要支持所推荐的最佳方法(即用户第一次保存或加载文件时以特定的文件夹为目标位置(如 My Pictures),并在以后默认定位到该用户以前选择的位置),则应使用 Boolean 变量来进行跟踪,判断是不是该用户第一次执行保存或打开操作。
- 在 OnFileSaveAs 函数中创建一个名为 bFirstSave 的 static BOOL 变量,然后将它初始化为 TRUE。
- 修改 OnFileSaveAs 中的代码,使它只在 bFirstSave 为 TRUE 时,才调用 SHGetFolderPath 并设置 OPENFILENAME 结构的 lpstrInitialDir 成员。
- 如果用户单击保存文件对话框中的保存,则将 bFirstSave 设置为 FALSE。
您的代码应如下所示:
void OnFileSaveAs()
{
OPENFILENAME openFile;
TCHAR szPath[MAX_PATH];
static BOOL bFirstSave = TRUE;
// Initialize OPENFILENAME structure.
ZeroMemory( &openFile, sizeof(OPENFILENAME) );
openFile.lStructSize = sizeof(OPENFILENAME);
// The first time the user saves a document, default to My Pictures.
if ( TRUE == bFirstSave )
{
if ( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_MYPICTURES,
NULL, 0, szPath ) ) )
{
// Set lpstrInitialDir to the path that SHGetFolderPath obtains.
// This causes GetSaveFileName to point to the My Pictures folder.
openFile.lpstrInitialDir = szPath;
}
}
// Display standard File Save dialog box, defaulting to My Pictures
// or the user's previously selected location.
if ( GetSaveFileName( &openFile ) == TRUE )
{
// User clicks Save.
// Save the file.
bFirstSave = FALSE;
}
else
{
// User cancels the File Save dialog box.
}
}
验证用户的前一个选择
- 生成项目,然后运行应用程序。
- 在文件菜单上,单击另存为。
- 从 My Pictures 文件夹浏览到 My Documents 文件夹,选择一个文件,单击保存。
- 在文件菜单上,再次单击另存为。
- 验证该对话框是否默认定位到以前选择的文件夹(在这里是 My Documents)。
- 单击取消关闭该对话框,然后关闭应用程序。
- 运行该应用程序,在文件菜单上单击另存为。
- 验证该对话框是否默认回到 My Pictures 文件夹。
- 关闭该对话框,然后退出应用程序。
对应用程序数据进行分类
不应将应用程序特定的数据(如临时文件、用户首选项、应用程序配置文件等)存储在 My Documents 文件夹中。 而应使用 Windows 注册表(用于存储不超过 64 KB 的数据)中合适的位置或者使用有效的应用程序数据文件夹中应用程序特定的文件进行存储。
将应用程序数据存储在正确的位置很重要,因为这样才能使多个用户可以使用同一计算机而不会破坏或改写彼此的数据和设置。
若要确定存储应用程序数据的最佳位置,可按以下类别对数据进行分类:
- 适用于各个用户(漫游): 此类别描述的是特定于具体某个用户的应用程序数据;并且当该用户在某个域范围内的不同计算机之间移动时,也总可以使用该数据(如自定义词典)。 请注意,此设置不适用于不是为在域环境中运行而设计的应用程序。
- 适用于各个用户(非漫游): 此类别描述的是特定于具体某个用户的应用程序数据,但此数据只能用于一台计算机(如用户指定的监视器分辨率)。
- 适用于各台计算机(非用户特定和非漫游): 此类别描述的是适用于所有用户和特定计算机的应用程序数据(如应用程序词典、日志文件或临时文件)。
将应用程序数据存储在正确的位置
使用
SHGetFolderPath 函数检索正确的应用程序数据文件夹。 不要将应用程序数据直接保存在此文件夹中。 而应使用
PathAppend 函数将一个子文件夹添加到
SHGetFolderPath 返回的路径中。 确保使用以下约定:
Company Name\Product Name\Product Version
例如,结果全路径可显示为:
\Documents and Settings\All Users\Application Data\My Company\My Product\1.0
若要定位到正确的应用程序数据文件夹,应根据应用程序数据的类别来传递合适的
CSIDL 值。
- 对于各个用户(漫游)数据,使用 CSIDL_APPDATA 值。这样会默认使用下面的路径:
\Documents and Settings\< User Name >\Application Data
- 对于各个用户(非漫游)数据,使用 CSIDL_LOCAL_APPDATA 值。这样会默认使用下面的路径:
\Documents and Settings\< User Name >\Local Settings\Application Data
- 对于各台计算机(非用户特定和非漫游)数据,使用 CSIDL_COMMON_APPDATA 值。这样会默认使用下面的路径:
\Documents and Settings\All Users\Application Data
下面的代码片段说明如何打开临时日志文件(该文件位于
CSIDL_COMMON_APPDATA 的下面):
void CreateTemporaryFile()
{
TCHAR szPath[MAX_PATH];
// Get path for each computer, non-user specific and non-roaming data.
if ( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_COMMON_APPDATA,
NULL, 0, szPath ) ) )
{
TCHAR szTempFileName[MAX_PATH];
// Append product-specific path.
PathAppend( szPath, "\\My Company\\My Product\\1.0\\" );
// Generate a temporary file name within this folder.
if (GetTempFileName( szPath,
"PRE",
0,
szTempFileName ) != 0 )
{
HANDLE hFile = NULL;
// Open the file.
if (( hFile = CreateFile( szTempFileName,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL )) != INVALID_HANDLE_VALUE )
{
// Write temporary data (code omitted).
CloseHandle( hFile );
}
}
}
} 审慎地使用注册表
WARNING:“注册表编辑器”使用不当会导致可能需要重新安装操作系统的严重问题。Microsoft 不保证能够解决因为“注册表编辑器”使用不当而产生的问题。使用“注册表编辑器”需要您自担风险。
有关如何编辑注册表的信息,请查看注册表编辑器 (Regedit.exe) 中的“改变项和值”帮助主题,或 Regedt32.exe 中的“添加和删除注册表中的信息”和“编辑注册表数据”帮助主题。注意,编辑注册表之前,应当先备份注册表。如果您运行的是 Windows NT 或 Windows 2000,还应该更新“紧急修复磁盘”(ERD)。
还可以用注册表存储少量的应用程序数据。 如果数据大小超过 64 千字节 (KB),则必须使用应用程序数据文件夹。 用注册表存储应用程序数据时,应遵守以下原则:
疑难解答
- 为了确保应用程序除了可在 Windows XP 上运行外,还可以在早期版本的 Windows 上运行,应始终链接到 Shfolder.dll 中的 SHGetFolderPath 实现。虽然 Windows XP 在 Shell32.dll 中包括了 SHGetFolderPath,但是早期版本的 Windows 可能不支持动态链接库 (DLL) 中的函数。
- Shfolder.dll 是可重新分发的组件,可用您的应用程序进行分发。
- 不要将完全限定路径存储在 My Documents 文件夹(或其他系统文件夹)中应用程序特定的位置(如最近用过的文件的文件列表),因为用户或管理员可能会在先后使用应用程序时重新定位这些文件夹。
有关
SHGetFolderPath 能够标识的完整文件夹组的更多信息,请参阅以下 Microsoft 平台软件开发工具包 (SDK) 文档:
有关 shell 编程的更多一般性信息,请访问 MSDN Web 站点:
有关 Visual C++ .NET 的更多一般性信息,请访问以下 Usenet 新闻组:
访问 Visual C++ .NET 支持中心,网址是:
文章编号: 310294 - 最后修改: 2002年2月23日 - 修订: 1.1
这篇文章中的信息适用于:
- Microsoft Visual C++ .NET 2002 标准版?当用于
- Microsoft Windows XP Professional
| kbhowtomaster kbnewsgrouplink KB310294 |
Microsoft和/或其各供应商对于为任何目的而在本服务器上发布的文件及有关图形所含信息的适用性,不作任何声明。 所有该等文件及有关图形均"依样"提供,而不带任何性质的保证。Microsoft和/或其各供应商特此声明,对所有与该等信息有关的保证和条件不负任何责任,该等保证和条件包括关于适销性、符合特定用途、所有权和非侵权的所有默示保证和条件。在任何情况下,在由于使用或运行本服务器上的信息所引起的或与该等使用或运行有关的诉讼中,Microsoft和/或其各供应商就因丧失使用、数据或利润所导致的任何特别的、间接的、衍生性的损害或任何因使用而丧失所导致的之损害、数据或利润不负任何责任。