SUMMARY
Applications work with two types of documents: those that
the user creates and those that the application creates. Your applications
should use the
SHGetFolderPath shell function to retrieve valid folder locations to store data
that is specific to the user and the application. This is essential for Windows
XP applications to support multiple users who are using the same computer and
to enable users to switch quickly.
This article describes how to
store user data in the correct place in the following steps:
- Create a Win32 application.
- Add a Save As option to the File menu.
- Use the standard File Save dialog box to default to the correct location.
- Verify the correct file save location.
- Remember the user's previous selection.
- Verify the user's previous selection.
In the following steps, this article also describes where you
must store application data and how to ensure that it is stored in the
appropriate locations:
- Classify application data.
- Store application data in the correct location.
- Use the registry judiciously.
Requirements
The following list outlines the recommended hardware, software,
network infrastructure, skills, knowledge, and service packs that you need:
- Windows XP Home Edition or Windows XP
Professional
- Visual Studio 2005, Visual Studio .NET, or Visual Studio version 6.0
- Prior knowledge of Win32 application development
Create a Win32 Application
Start Visual Studio, and create a new Win32 application named
SavingData.
- In Visual C++ 6.0, click Win32 Application from the list of available project types, and then select the A typical "Hello World" application option within the application setup wizard.
- In Visual Studio .NET, click Visual C++ Projects under Project Types, and then click Win32 Project under Templates. Accept the default application settings that the application
setup wizard displays.
- In Visual Studio 2005, click Visual C++ under Project Types, and then click Win32 Project under Templates. Accept the default application settings.
Add a Save As Option to the File Menu
- Click Resource View, and then double-click IDC_SAVINGDATA.
- Add a Save As menu option to the File menu. Use IDM_FILE_SAVEAS as the ID of the menu item.
- Locate the application's WndProc window procedure within SavingData.cppm and add a new case statement within the WM_COMMAND section to process the Save As menu option. Call the OnFileSaveAs function, which you will create in the next section. This
function takes no parameters.
Your code should appear as follows:
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;
Use the Standard File Save Dialog Box to Default to the Correct Location
When a user displays an application's
File Save (or
File Open) dialog box for the first time, the dialog box must default to
the user's My Documents folder (or a descendant of My Documents, such as My
Pictures for image data and My Music for audio files).
NOTE: You must never hard code a path within your application because
you can never guarantee its physical location. For example, an Administrator
may relocate the My Documents folder to a network location.
- At the top of SavingData.cpp, add the following include
statements:
#include <commdlg.h> // for GetSaveFileName
#include <shlobj.h> // for SHGetFolderPath
- Add the following prototype for the OnFileSaveAs function:
void OnFileSaveAs();
- Create the new OnFileSaveAs function. Within this function, use the SHGetFolderPath function in conjunction with the CSIDL_MYPICTURES CSIDL identifier to retrieve the correct folder location to store
picture data. Pass this folder location to the GetSaveFileName function to display the standard File Save dialog box.
Your code should appear as follows:
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.
}
}
Verify the Correct File Save Location
- Press the F5 key to build the project.
- Run the application, and click Save As from the File menu.
- Verify that the standard File Save dialog box defaults to the My Pictures folder, as CSIDL_MYPICTURES specifies.
- Click Cancel to close the dialog box, and close the application.
Remember the User's Previous Selection
For subsequent use of the
File Save (or
File Open) dialog box, it is recommended that the dialog box default to the
user's previously selected location.
If you do not supply an initial
folder location within the
OPENFILENAME structure,
GetSaveFileName (and
GetOpenFileName) display the standard
File Save or
File Open dialog box, which points to the My Documents folder. In addition,
if the user has used one of these dialog boxes previously and has chosen a
non-default folder, these functions automatically default to the previously
used folder.
To support the recommended best practice of targeting a
specific folder location (such as My Pictures) the first time a user saves or
loads a file, and to subsequently default to the user's previously selected
location, you should use a Boolean variable to track whether this is the first
time the user has performed the Save or Open operation.
- Create a static BOOL variable named bFirstSave in the OnFileSaveAs function, and initialize it to TRUE.
- Modify the code within OnFileSaveAs to call SHGetFolderPath and set the lpstrInitialDir member of the OPENFILENAME structure, only if bFirstSave is TRUE.
- If the user clicks Save in the File Save dialog box, set bFirstSave to FALSE.
Your code should appear as follows:
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.
}
}
Verify the User's Previous Selection
- Build the project, and run the application.
- From the File menu, click Save As.
- Browse from the My Pictures folder to the My Documents
folder, select a file, and click Save.
- From the File menu, click Save As again.
- Verify that the dialog box defaults to your previous
selection (in this case, My Documents).
- Click Cancel to dismiss the dialog box, and close the application.
- Run the application, and click Save As from the File menu.
- Verify that the dialog box defaults back to the My Pictures
folder.
- Close the dialog box, and quit the application.
Classify Application Data
You should not store application-specific data (such as temporary
files, user preferences, application configuration files, and so on) in the My
Documents folder. Instead, use either an appropriate location in the Windows
Registry (for data that does not exceed 64 kilobytes) or an
application-specific file that is located in a valid Application Data
folder.
It is important to store application data in the correct
location to allow several people to use the same computer without corrupting or
overwriting each other's data and settings.
To determine the most
appropriate location for your application data, use the following categories to
classify your data:
- For each user (roaming): This category describes application data that is specific to a
particular user and should be available to the user as he or she moves between
computers within a domain (for example, a custom dictionary). Note that this
setting does not apply to applications that are not designed to run in a domain
environment.
- For each user (non-roaming): This category describes application data that is specific to a
particular user but applies only to a single computer (for example, a
user-specified monitor resolution).
- For each computer (non-user specific and non-roaming): This category describes application data that applies to all
users and to a specific computer (for example, an application dictionary, a log
file, or a temporary file).
Store Application Data in the Correct Location
You use the
SHGetFolderPath function to retrieve the correct Application Data folder. Do not
store application data directly in this folder. Instead, use the
PathAppend function to append a subfolder to the path that
SHGetFolderPath returns. Make sure that you use the following convention:
Company Name\Product Name\Product Version
For example, the resultant full path may appear as follows:
\Documents and Settings\All Users\Application Data\My Company\My Product\1.0
To locate the correct Application Data folder, pass the
appropriate
CSIDL value, based on the category of your application data.
- For each user (roaming) data, use the CSIDL_APPDATA value. This defaults to the following path:
\Documents and Settings\<User Name>\Application Data
- For each user (non-roaming) data, use the CSIDL_LOCAL_APPDATA value. This defaults to the following path:
\Documents and Settings\<User Name>\Local Settings\Application Data
- For each computer (non-user specific and non-roaming) data,
use the CSIDL_COMMON_APPDATA value. This defaults to the following path:
\Documents and Settings\All Users\Application Data
The following code fragment demonstrates how to open a
temporary log file, which is located beneath
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 );
}
}
}
}
Use the Registry Judiciously
WARNING: If you use Registry Editor incorrectly, you may cause serious
problems that may require you to reinstall your operating system. Microsoft
cannot guarantee that you can solve problems that result from using Registry
Editor incorrectly. Use Registry Editor at your own
risk.
You can also use the registry to store small
amounts of application data. For data that exceeds 64 kilobytes (KB), you must
use an Application Data folder. Observe the following guidelines when you use
the registry to store application data:
- For small amounts of user data, use the HKEY_CURRENT_USER (HKCU) registry key.
- For small amounts of computer data, use the HKEY_LOCAL_MACHINE (HKLM) registry key. Your application should not write to HKLM at run time because, by default, non-administrator users only
have read-only access to the HKLM tree.
- At installation time, your application must not store more
than a total of 128 KB across HKCU and HKLM.
- Component Object Model (COM) components are registered
beneath the HKEY_CLASSES_ROOT (HKCR) registry key. The 128 KB maximum does not include HKCR.
- When you write to HKLM or HKCU, you must create keys for the company name, product name, and
product version number, as follows:
HKLM\Software\Company Name\Product Name\Product Version
HKCU\Software\Company Name\Product Name\Product Version
- Use the registry functions (such as RegCreateKeyEx and RegSetValueEx) to read and write registry entries.
Troubleshooting
- To help ensure that applications run on earlier versions of
Windows in addition to Windows XP, always link to the SHGetFolderPath implementation in Shfolder.dll. Although Windows XP includes SHGetFolderPath in Shell32.dll, earlier versions of Windows may not support the
function within this dynamic-link library (DLL).
- Shfolder.dll is a redistributable component and may be
distributed with your applications.
- Do not store fully-qualified paths to the My Documents
folder (or other system folders) within an application-specific place such as a
file list of most recently used files because a user or Administrator may
relocate these folders between successive uses of your application.