應用程式運用兩種類型的文件:由使用者建立的文件,及由應用程式所建立的文件。您的應用程式可以使用
SHGetFolderPath shell 函數,擷取正確的資料夾位置,以存放特定使用者與應用程式的資料。這對於支援多位使用者使用同一台電腦,並讓使用者可以迅速切換的 Windows XP 應用程式而言是很重要的。
本文將告訴您,如何以下列的步驟將使用者的資料存放於正確的位置:
- 建立 Win32 應用程式。
- 新增 [另存新檔] 選項到 [檔案] 功能表。
- 使用標準的 [檔案儲存] 對話方塊預先定義的正確位置。
- 確認正確的檔案儲存位置。
- 記憶使用者先前的選擇。
- 確認使用者先前的選擇。
在下列的步驟中,本文也會告訴您必須把應用程式資料存放在哪裡,以及如何確定它是存放在適當的位置:
- 分類應用程式資料。
- 存放應用程式資料到正確的位置。
- 謹慎地使用登錄。
需求
下面清單提列了建議使用的硬體、軟體、網路基礎架構以及所需安裝的 Service Pack:
- Windows XP Home Edition (家用版) 和 Windows XP Professional (商用版)
- Visual Studio .NET 或 Visual Studio version 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 做為功能表項目的 ID。
- 在 SavingData.cppm 內尋找應用程式的 WndProc window 程序,並在 WM_COMMAND 區段內新增新的 case 陳述式,以處理 [另存新檔] 功能表選項。呼叫 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 Pictures] 與存放音訊檔的 [My Music])。
注意:千萬不要將應用程式內的路徑寫死,因為您永遠無法保證它的實體位置。例如,系統管理員可能會將 [我的文件] 資料夾重新配置到網路上的位置。
- 在 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 鍵以建置專案。
- 執行應用程式,並按一下 [檔案] 功能表上的 [另存新檔] 。
- 確認 [檔案儲存] 對話方塊預設至 [My Pictures] 資料夾,如 CSIDL_MYPICTURES 指定的。
- 按一下 [取消] 以關閉對話方塊,並結束應用程式。
記憶使用者先前的選擇
為了之後使用
[檔案儲存] (或
[檔案開啟]) 對話方塊,建議您將該對話方塊預設到使用者先前選取的位置。
如果您沒有在
OPENFILENAME 結構內提供初始的資料夾位置,則
GetSaveFileName (及
GetOpenFileName) 會顯示標準的
[檔案儲存] 或
[檔案開啟] 對話方塊,它指向 [我的文件] 資料夾。此外,如果使用者之前已經用過這些對話方塊的其中一個,並且選擇了一個非預設的資料夾,則這些函數會自動預設為先前使用的資料夾。
為了支援所建議之最佳慣例目標資料夾位置 (例如 [My Pictures]),使用者第一次儲存或是載入檔案後,使用者先前選取的位置會隨之設定為預設值,您可以使用布林變數,追蹤這是否為使用者初次執行儲存或是開啟作業。
- 在 OnFileSaveAs 函數中建立一個命名為 bFirstSave 的 static BOOL 變數,並將它初始為 TRUE。
- 在 OnFileSaveAs 內修改程式碼,以呼叫 SHGetFolderPath 並將 lpstrInitialDir 設定為 OPENFILENAME 結構的成員 (僅限當 bFirstSave 是 TRUE 時)。
- 如果使用者按一下 [檔案儲存] 對話方塊中的 [儲存] ,則將 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 Pictures] 資料夾。
- 關閉對話方塊,並結束應用程式。
分類應用程式資料
您不應該將應用程式特定的資料 (例如暫時檔、使用者喜好設定、應用程式組態檔等等) 存放在 [我的文件] 資料夾。相反地,應該使用 Windows 登錄中的適當位置 (適用於不超過 64 KB 的資料) 或是位在正確 [Application Data] 資料夾內的應用程式特定檔。
在正確的位置存放應用程式資料是很重要的,如此才能讓多個人使用同一部電腦,而不會損毀或是覆寫彼此的資料和設定。
如果要判斷應用程式資料的最適當位置,請使用以下的類別分類您的資料:
- 每個使用者 (漫遊):這個類別描述了特定使用者的特殊應用程式資料,並且使用者可以在相同網域內的電腦中移動時使用 (例如,使用者自訂的字典)。請注意,這個設定無法套用於在網域環境中執行的應用程式。
- 每個使用者 (非漫遊):這個類別描述了特定使用者的特殊應用程式資料,但是僅能運用於單一的電腦上 (例如,使用者特定的監視器解析度)。
- 每台電腦 (非使用者特定與非漫遊):這個類別描述了套用在所有使用者及特定電腦的應用程式資料 (例如,應用程式目錄、記錄檔,或暫時檔)。
在正確的位置存放應用程式資料
您可以使用
SHGetFolderPath 函數,擷取正確的 [Application Data] 資料夾。請勿直接存放此資料夾中的應用程式資料。相反地,使用
PathAppend 函數,將子資料夾附加到
SHGetFolderPath 傳回的路徑。確認您使用了下列的慣例:
公司名稱\產品名稱\產品版本
例如,所產生的完整路徑可能類似於以下所列:
\Documents and Settings\All Users\Application Data\My Company\My Product\1.0
如果要尋找正確的 [Application Data] 資料夾,請依據應用程式的類別傳遞適當的
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 );
}
}
}
}
謹慎地使用登錄
警告:如果使用登錄編輯器的方式不正確,可能會導致必須重新安裝作業系統的嚴重問題。Microsoft 不保證您可以解決錯誤使用登錄編輯器所造成的問題。請自行承擔使用登錄編輯器的一切風險。
您也可以使用登錄,存放小量的應用程式資料。對於超出 64 KB 的資料,您必須使用 [Application Data] 資料夾。當您使用登錄,存放應用程式資料時,請遵守下列的準則:
疑難排解
- 為了協助確認應用程式可以在 Windows XP 之外較舊的 Windows 版本上執行,請永遠連結到在 Shfolder.dll 的 SHGetFolderPath 實作。雖然 Windows XP 在 Shell32.dll 中包括了 SHGetFolderPath ,較舊的 Windows 版本在此動態連結程式庫 (DLL) 中並未支援該函數。
- Shfolder.dll 是一個可轉散發元件,並且可以與您的應用程式一起散佈。
- 在應用程式特定的位置之內,請不要將完整格式的路徑存放到 [我的文件] 資料夾 (或是其他的系統資料夾),例如最近使用過的檔案清單 ,因為使用者或系統管理員可能會在隨後您所使用的應用程式之間,重新配置這些資料夾。
如需其他常被點選之 Visual C++ .NET Microsoft Knowledge Base 文件,請造訪下列 Microsoft 網站的 Visual C++ .NET 支援中心:
如需更多有關 Visual C++ .NET 的一般性資訊,請造訪下列 Microsoft Usenet 新聞群組:
如需有關
SHGetFolderPath 能夠識別之完整資料夾集合的詳細資訊,請參閱下列的 Microsoft Platform Software Development Kit (SDK) 說明文件:
如需有關 Shell 程式設計的詳細資訊,請查詢下列 Microsoft Developer Network (MSDN) 網站: