需控制他人程式 (已顯示) 的「另存新檔」對話框,強制其瀏覽至指定目錄。標準 CFileDialog::m_ofn.lpstrInitialDir 僅對尚未顯示的對話框有效。對於既有視窗,需透過跨進程視窗操作實現。
觀察發現:在檔名編輯框輸入目錄路徑並按「儲存」鈕,對話框會切換至該目錄而不關閉。利用此行為,流程如下:
FindWindow)。Edit) 與「儲存按鈕」 (Class Button, 標題「儲存(&S)」)。WM_SETTEXT 將指定路徑寫入編輯框 (不可用 SetWindowText,跨進程失效)。WM_LBUTTONDOWN + WM_LBUTTONUP 模擬點擊儲存按鈕。void ForceSaveDialogPath(const CString& sPath)
{
// 1. 尋找「另存為」對話框 (標題可能因語系不同需調整)
CWnd* pWnd = FindWindow(NULL, _T("另存為"));
if (!pWnd) return;
CString filename;
TCHAR BUF[512];
CWnd* pEdit = NULL;
// 2. 列舉子視窗
CWnd* pChild = pWnd->GetWindow(GW_CHILD);
char* buf = new char[512];
while (pChild)
{
::GetClassName(pChild->GetSafeHwnd(), buf, 512);
CString cls(buf); cls.TrimRight();
if (cls == _T("Edit")) // 檔名編輯框
{
pEdit = pChild;
pChild->SendMessage(WM_GETTEXT, sizeof(BUF)/sizeof(TCHAR), (LPARAM)BUF);
filename = BUF; // 備份原始檔名
}
else if (cls == _T("Button")) // 按鈕
{
pChild->SendMessage(WM_GETTEXT, sizeof(BUF)/sizeof(TCHAR), (LPARAM)BUF);
CString btnText(BUF); btnText.TrimRight();
if (btnText == _T("儲存(&S)")) // 找到儲存按鈕
{
// 3. 寫入目標路徑 (跨進程必須用 WM_SETTEXT)
lstrcpy(BUF, sPath);
pEdit->SendMessage(WM_SETTEXT, 0, (LPARAM)BUF);
// 4. 模擬點擊儲存按鈕
::SendMessage(pChild->GetSafeHwnd(), WM_LBUTTONDOWN, 0, 0);
::SendMessage(pChild->GetSafeHwnd(), WM_LBUTTONUP, 0, 0);
}
}
pChild = pChild->GetNextWindow();
}
// 5. 還原原始檔名
if (pEdit)
pEdit->SendMessage(WM_SETTEXT, 0, (LPARAM)(LPCTSTR)filename);
delete[] buf;
}
| 技術 | 說明 |
|---|---|
FindWindow | 依視窗標題尋找目標對話框。注意:標題隨系統語系變化 (如 "Save As"),實際部署需多語系支援。 |
GetClassName + 列舉子視窗 | 識別編輯框 (Edit) 與按鈕 (Button)。 |
WM_SETTEXT vs SetWindowText | 跨進程設定文字 必須 使用 SendMessage(WM_SETTEXT),SetWindowText 內部同樣發送此訊息但可能因安全限制失效。 |
| 模擬點擊 | 發送 WM_LBUTTONDOWN/UP 至按鈕視窗,觸發其 BN_CLICKED 通知。 |
| 還原檔名 | 避免干擾使用者原本輸入的檔名。 |
ChangeWindowMessageFilterEx。WM_INITDIALOG 時修改 OPENFILENAME 結構,根治源頭但複雜度高。IFileDialog 自訂對話框 (Vista+):若為自家程式,直接使用新版 API 設定 SetFolder。跨進程操作現有檔案對話框屬於「逆向 UI 自動化」範疇。雖透過 WM_SETTEXT + 模擬點擊可行,但脆弱性高、維護成本大。若為自家開發專案,強烈建議在對話框建立前以正規 API 設定初始目錄;若為第三方程式控制,請評估風險並做好版本相容測試。