Microsoft KB Archive/310294

= How to write a Windows XP Application that stores user and application data in the correct location by using Visual C++ 2005 or Visual C++ .NET =

Article ID: 310294

Article Last Modified on 11/26/2007

-

APPLIES TO


 * Microsoft Visual C++ 2005 Express Edition
 * Microsoft Visual C++ .NET 2003 Standard Edition, when used with:
 * Microsoft Windows XP Professional
 * Microsoft Visual C++ .NET 2002 Standard Edition, when used with:
 * Microsoft Windows XP Professional

-



This article was previously published under Q310294



IMPORTANT: This article contains information about modifying the registry. Before you modify the registry, make sure to back it up and make sure that you understand how to restore the registry if a problem occurs. For information about how to back up, restore, and edit the registry, click the following article number to view the article in the Microsoft Knowledge Base:

256986 Description of the Microsoft Windows Registry



Note Microsoft Visual C++ 2005, Microsoft Visual C++ .NET 2002, and Microsoft Visual C++ .NET 2002 support both the managed code model that is provided by the Microsoft .NET Framework and the unmanaged native Microsoft Windows code model. The information in this article applies only to unmanaged Visual C++ code.



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 &quot;Hello World&quot; 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:   Add the following prototype for the OnFileSaveAs function: void OnFileSaveAs; </li>  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.
 * 1) include <commdlg.h>  // for GetSaveFileName
 * 2) include <shlobj.h>   // for SHGetFolderPath

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. } }                   </li></ol>

Verify the Correct File Save Location

 * 1) Press the F5 key to build the project.
 * 2) Run the application, and click Save As from the File menu.
 * 3) Verify that the standard File Save dialog box defaults to the My Pictures folder, as CSIDL_MYPICTURES specifies.
 * 4) 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.</li> Modify the code within OnFileSaveAs to call SHGetFolderPath and set the lpstrInitialDir member of the OPENFILENAME structure, only if bFirstSave is TRUE.</li>  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. } }                   </li></ol>

Verify the User's Previous Selection

 * 1) Build the project, and run the application.
 * 2) From the File menu, click Save As.
 * 3) Browse from the My Pictures folder to the My Documents folder, select a file, and click Save.
 * 4) From the File menu, click Save As again.
 * 5) Verify that the dialog box defaults to your previous selection (in this case, My Documents).
 * 6) Click Cancel to dismiss the dialog box, and close the application.
 * 7) Run the application, and click Save As from the File menu.
 * 8) Verify that the dialog box defaults back to the My Pictures folder.
 * 9) 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. <ul> For each user (roaming) data, use the CSIDL_APPDATA value. This defaults to the following path:

\Documents and Settings\<User Name>\Application Data

</li> 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

</li> 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

</li></ul>

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, &quot;\\My Company\\My Product\\1.0\\&quot; ); // Generate a temporary file name within this folder. if (GetTempFileName( szPath, &quot;PRE&quot;, 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: <ul> For small amounts of user data, use the HKEY_CURRENT_USER (HKCU) registry key.</li> 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.</li> At installation time, your application must not store more than a total of 128 KB across HKCU and HKLM.</li> Component Object Model (COM) components are registered beneath the HKEY_CLASSES_ROOT (HKCR) registry key. The 128 KB maximum does not include HKCR.</li> 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

</li> Use the registry functions (such as RegCreateKeyEx and RegSetValueEx) to read and write registry entries.</li></ul>

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.

<div class="references_section">