Browse for File Dialog (in DLL Custom Action) |
This uses the "guts" of a DLL custom action by "Kallely L Sajan" (from InstallSite, see "File Browse Dialog" by Jeff Briggs) and the "TryMeDllCustomAction.MM" sample to create a "browse for file" dialog in an msi.
The following code was extracted from Kallely's "BrowseForFile.cpp":
//*********************************************************** //** Call back function for EnumChildWindows() //**--------------------------------------------------------- //** Author: Kallely L Sajan //*********************************************************** BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam) { TCHAR buf[100]; GetClassName( hwnd, (LPTSTR)&buf, 100 ); if ( _tcscmp ( buf, (_T("RichEdit20W")) ) == 0 ) { *(HWND*)lParam = hwnd; return FALSE; } } UINT __stdcall BrowseForFile(MSIHANDLE hInstall) { //MessageBox(NULL, "BrowseForFile", "BrowseForFile", MB_OK); //Set up storage for PATHTOFILE TCHAR szOriginalPath[MAX_PATH]; DWORD cchValue = sizeof(szOriginalPath)/sizeof(TCHAR); ZeroMemory(szOriginalPath, sizeof(TCHAR)*MAX_PATH); // Get PATHTOFILE MsiGetProperty(hInstall, TEXT("PATHTOFILE"), szOriginalPath, &cchValue); long lErrMsg = 0; OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(ofn)); TCHAR szFilters[] = _T("All Files (*.*)\0*.*\0") _T("Text File (*.txt)\0*.txt\0"); // Initialize OPENFILENAME structure. ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = GetForegroundWindow(); ofn.lpstrFile = szOriginalPath; ofn.nMaxFile = sizeof(szOriginalPath); ofn.lpstrFilter = szFilters; ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = NULL; //ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; //ofn.Flags = OFN_HIDEREADONLY; if (GetOpenFileName(&ofn)) { MsiSetProperty(hInstall, TEXT("PATHTOFILE"), szOriginalPath); //This next bit of code fixes a problem that occurs if the author is //using an Edit box for the path. For whatever reason, if a user //types into the edit box before browsing for a file or folder //the Edit box is not updated once the custom action completes. //Much of the following code was modified from //Kallely L Sajan's IsLicensedViewed article from InstallSite.org HWND hWnd; HWND hWndChild=NULL; hWnd = FindWindow(NULL, "Default - InstallShield Wizard"); if (hWnd) { EnumChildWindows( hWnd, EnumChildProc, (LPARAM)&hWndChild ); if ( hWndChild ) { TCHAR buf[100]; _tcscpy(buf, szOriginalPath); //the following call will not work if _UNICODE is defined in Prepocessor definitions SendMessage(hWndChild, WM_SETTEXT, 0, (LPARAM)szOriginalPath); } } } return ERROR_SUCCESS; }
Insert the above code and the stub below just before the "<$/DllCa-C>" command in "TryMeDllCustomAction.MM":
//============================================================================ <$DllCaEntry "Browse4File"> //============================================================================ { //--- Call Kallely's code --- CaDebug(PROGRESS_LOG, "Invoking Kallely's BrowseForFile()"); UINT BrowseRc; BrowseRc = BrowseForFile(hInstall); CaDebug(PROGRESS_LOG, "Completed Kallely's BrowseForFile()"); return( BrowseRc ); } <$/DllCaEntry>
Then we need to schedule the invokation etc by inserting the following immediately after the "<$/DllCa-C>" command in "TryMeDllCustomAction.MM":
;--- Set starting directory and file mask --- <$PropertyCa "PATHTOFILE" Value="[TempFolder]*.*" Seq="CostFinalize-" SeqTable=^InstallUISequence^> ;--- Invoke the DLL --------------------------------------------------------- <$DllCa Binary="MyTestDll.dll" Entry=^<$DllCaEntry? "Browse4File">^ Seq="CostFinalize-" SeqTable=^InstallUISequence^ Type="Immediate" Condition="<$DLLCA_CONDITION_INSTALL_ONLY>"> ;--- Make a copy (you may wish to use more than once) --- <$PropertyCa "RETURNED_FILE" Value="[PATHTOFILE]" Seq="CostFinalize-" SeqTable=^InstallUISequence^> ;--- Display the returned value (also abort - don't really want to install...) --- #( <$AbortIf Condition="<$DLLCA_CONDITION_INSTALL_ONLY>" Message=^The DLL returned "[RETURNED_FILE]". Aborting the install now...^ SeqTable="InstallUISequence" Seq="CostFinalize-" > #)
You can then build and test the msi, a verbose log will give you more details.
Improvements |
I left Kallely's code exactly as was purely to demonstrate how you can easily grab code and insert it into a dll based custom action.
Some recommended improvements: