|
bool GetFolder(CString& csFolder, CString csCaption = NULL, HWND hWndOwner = NULL)
{
bool bSuccess = false;
// The BROWSEINFO struct tells the shell
// How it should display the dialog.
BROWSEINFO browseInfo;
memset(&browseInfo, 0, sizeof(browseInfo));
browseInfo.ulFlags = BIF_USENEWUI;
browseInfo.hwndOwner = hWndOwner;
browseInfo.lpszTitle = csCaption;
// Must call this if using BIF_USENEWUI
::OleInitialize(NULL);
// Show the dialog and get the itemIDList for the selected folder.
LPITEMIDLIST pIDL = ::SHBrowseForFolder(&browseInfo);
if(pIDL != NULL)
{
// Create a buffer to store the path, then get the path.
TCHAR lpzTempFolder[MAX_PATH + 1];
if(::SHGetPathFromIDList(pIDL, lpzTempFolder) != 0)
{
bSuccess = true;
}
// Free the item id list
CoTaskMemFree(pIDL);
csFolder = lpzTempFolder;
}
::OleUninitialize();
return bSuccess;
}
Hope this helps,
theme
|
|
|
|
|
The code was just what I wanted - simple to use and works great. Your time in doing the article and code helped me and I appreciate it! I programmed from Fortran through many languages - a lot of code - but always non-GUI data-in > data-out type tasks. As an engineer that took up Visual C++ and MFC and GUI tasks after retirement, I rely on the WEB for a lot of help ... and The Code Project (and others) come through again and again.
|
|
|
|
|
papa_coder wrote: Your time in doing the article and code helped me and I appreciate it!
well for that it's worth it! glad it helped!
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
what about a path longer then MAX_PATH?
like unc network pathes?
is there also a solution?
|
|
|
|
|
That should work, just make the char buffer big enough to take it. I used MAX_PATH just for convienence.
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
uhm, i thought i tried it with MAX_PATH*2 buffer size and it doesnt make any sense...
i think shbrowseforfolder works only with a MAX_PATH size...
|
|
|
|
|
As MSDN states, the path buffer must be at least MAX_PATH characters in size.
Use TCHAR instead of char in order to allocate output buffer.
You can also allocate it dynamically in order to be more polite to the memory.
Regards
|
|
|
|
|
I couldn't yet find a way to preset the box to a specific folder. If you have deep nested folders and want to go ub/down just one or two levels, it is annoying to have to walk down rather than starting at a certain folder.
Does anyone know a possibility to achieve this?
Thanks
Christof
|
|
|
|
|
Christof Schardt wrote: Does anyone know a possibility to achieve this?
sheesh! I think we have more comments than lines of code on this! Nevertheless, all the better. I do believe there are flags/params for the BROWSEINFO struct that will specify this, I'll look into it and add an arg to the fcn. Check back tomorrow.
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
Nitron wrote: I do believe there are flags/params for the BROWSEINFO struct that will specify this
I looked for it but didn't find.
pidlRoot cannot be used, because it doesn't show the tree above the specified folder, so you are not able to navigate upwards from the starting-point.
Christof
|
|
|
|
|
Christof Schardt wrote: I looked for it but didn't find.
pidlRoot cannot be used, because it doesn't show the tree above the specified folder, so you are not able to navigate upwards from the starting-point.
The discussions today with Nitron got me interested in this stuff, and I just wrote a blog entry on how you can specify a default folder using SHBrowseForFolder .
Selecting a default folder with SHBrowseForFolder[^]
Regards,
Nish
|
|
|
|
|
Nishant Sivakumar wrote: The discussions today with Nitron got me interested in this stuff, and I just wrote a blog entry on how you can specify a default folder using SHBrowseForFolder.
Great, Thanks a lot! It worked for me (after I removed the L"..." from the string).
Christof
|
|
|
|
|
Christof Schardt wrote: Great, Thanks a lot! It worked for me (after I removed the L"..." from the string).
Yeah, MSDN incorrectly says an Unicode string should be used, when they meant Unicode-compliant string. Better use _T("...") there, so your code works in the Unicode version too.
Regards,
Nish
|
|
|
|
|
Can this technique be used to select a special folder, say My Documents?
Thanks,
Steve
|
|
|
|
|
SBJ wrote: Can this technique be used to select a special folder, say My Documents?
Yes, set WPARAM to FALSE, and set LPARAM to the PIDL for My Documents.
Regards,
Nish
|
|
|
|
|
|
Here is a version that allows you to specify a starting location, but you cannot go up from it, only down:
bool GetFolder(std::string& folderpath, LPCTSTR szCaption = NULL, LPCWSTR szStartPath = NULL, HWND hOwner = NULL)
{
bool retVal = false;
LPITEMIDLIST pIDLRoot = NULL;
if (szStartPath != NULL)
{
LPSHELLFOLDER pShellFolder = NULL;
HRESULT hResult = NULL;
ULONG chUsed = 0L;
BSTR bstrPath = NULL;
bstrPath = ::SysAllocString ( szStartPath );
if ( NULL != bstrPath )
{
if (::SHGetDesktopFolder (&pShellFolder) == NOERROR)
{
hResult =
pShellFolder->ParseDisplayName
(
NULL,
NULL,
bstrPath,
&chUsed,
&pIDLRoot,
NULL
);
if (FAILED(hResult))
{
pIDLRoot = NULL;
}
pShellFolder->Release();
}
}
::SysFreeString(bstrPath);
}
BROWSEINFO bi;
memset(&bi, 0, sizeof(bi));
bi.pidlRoot = pIDLRoot;
bi.ulFlags = BIF_USENEWUI;
bi.hwndOwner = hOwner;
bi.lpszTitle = szCaption;
::OleInitialize(NULL);
LPITEMIDLIST pIDL = ::SHBrowseForFolder(&bi);
if(pIDL != NULL)
{
char buffer[_MAX_PATH] = {'\0'};
if(::SHGetPathFromIDList(pIDL, buffer) != 0)
{
folderpath = buffer;
retVal = true;
}
::CoTaskMemFree(pIDL);
}
::OleUninitialize();
return retVal;
}
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
I have something for what you are looking for. It's a wrapper class called CShellHelpers that execute all the commands needed to :
1 - Set the starting path of SHBrowseForFolder before opening of the dialog
2 - Then Retrieve the selected path when the dialog is closed
With this wrapper, you can go up from your starting location. It use the BFFM_INITIALIZED message in the BrowseCtrlCallback function to sets the startup folder location.
There are also some other functions like :
1 - Retrieve the icon belonging to the selected folder.
2 - Shell context menu for a given path.
3 - Retrieve volume serial number
4 - Shell About..
Whenever I need to browse for a folder, I use it!
I don't remember when I did this code. From what I can remember, I took some portion of code here and there and assembled it to make this wrapper. The class is still in constant devellopment and is not really ready for release to public but since I came across this discussion, I thought it would be kind from me to let you have my code. I included a small example that show how it work.
Email me if you want to have it. I can't find the damn button to "Add files" to this post!
Have fun!
Stef
Programming looks like taking drugs...
I think I did an overdose.
|
|
|
|
|
You can get the pidl using SHSimpleIDListFromPath. Then you can assign a callback function to your BROWSEINFO struct to set the initial start path upon initialization of the window.
LPITEMIDLIST getPidl(const CString& str)
{
CStringW wStr(str);
return ::SHSimpleIDListFromPath(wStr);
}
int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
switch(uMsg)
{
case BFFM_INITIALIZED:
return SendMessage(hwnd, BFFM_SETSELECTIONA, false, lpData);
}
return 0;
}
Then, just set the BROWSEINFO struct info as follows and you have the start path of your choosing. You can now navigate anywhere in the directory structure.
void ChooseDirectoryDialog(){
BROWSEINFO bi;
ZeroMemory(&bi, sizeof(bi))
bi.lpszTitle = "Choose a Directory";
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM)getPidl("C:\MyPath");
LPITEMIDLIST idList = SHBrowseForFolder(&bi);
if (idList != NULL)
{
//get info from struct...
}
return 0;
}
Note: This isn't copied straight from source code so it may not compile right away.
Cheers,
Rich
|
|
|
|
|
MSDN: "To close the COM library gracefully, each successful call to OleInitialize, including those that return S_FALSE, must be balanced by a corresponding call to OleUninitialize."
CoTaskMemFree is the counterpart to SHGetPathFromIDList?
MSDN: CoTaskMemFree. "Frees a block of task memory previously allocated through a call to the CoTaskMemAlloc or CoTaskMemRealloc function."
Anyway, your refactored code confirms that SESE (Single Entry Single Exit) is superior to multiple return statements.
|
|
|
|
|
Roland Pibinger wrote: MSDN: "To close the COM library gracefully, each successful call to OleInitialize, including those that return S_FALSE, must be balanced by a corresponding call to OleUninitialize."
Roland Pibinger wrote: CoTaskMemFree is the counterpart to SHGetPathFromIDList?
Well, here's how it works (so I think)... The ::SHBrowseForFolder will return a pointer to a ITEMIDLIST that's created using CoTaskAlloc under the hood, and the user is responsible for freeing the memory if the returned pointer is not NULL. So in essence, CoTaskMemFree is freeing allocated memory from SHBrowseForFolder, not SHGetPathFromIDList.
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
ok, fixed. Thanks for pointing that out!
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
Nitron wrote: ok, fixed. Thanks for pointing that out!
You'd have thought this morning that a simple article like that would have been pretty smooth, and here you end up with nasty people like Roland and me making you update the article every 1 hour.
Btw, just kidding - this will be useful for a lot of people!
Regards,
Nish
|
|
|
|
|
Nishant Sivakumar wrote: You'd have thought this morning that a simple article like that would have been pretty smooth, and here you end up with nasty people like Roland and me making you update the article every 1 hour.
Well, I must say, after it's all said and done, I think there's a bit of production-quality code here. Thanks for the reviews!
~Nitron.
ññòòïðïðB A start
|
|
|
|
|
Your call to OleUninitialize() without regards to the result of OleInitialize() might be an incorrect interpretation of what MSDN states on the matter. Someone correctly pointed out that S_FALSE is actually a successful result for OleInitialize() and requires a balanced call to OleUninitialize() but the current revision of your code neglects to account for the other potential unsuccessful result codes from OleInitialize() that would not require a balanced call to OleUnitialize(). The SUCCEEDED macro might be in order here.
Contemplate this...
HRESULT hr;
hr=::OleInitialize(NULL);
if (SUCCEEDED(hr)) {
TRACE("Got here 1, %d %d\n",hr,S_OK);
hr=::OleInitialize(NULL);
if (SUCCEEDED(hr)) {
// Not an error
TRACE("Got here 2, %d %d\n",hr,S_FALSE);
// These would be errors
TRACE("%d %d %d %d\n",E_INVALIDARG,E_OUTOFMEMORY,
E_UNEXPECTED,RPC_E_CHANGED_MODE);
if ((SUCCEEDED(E_INVALIDARG)) ||
(SUCCEEDED(E_OUTOFMEMORY)) ||
(SUCCEEDED(E_UNEXPECTED)) ||
(SUCCEEDED(RPC_E_CHANGED_MODE))) {
TRACE("Never get here\n");
}
/*
SUCCEEDED macro still treats S_FALSE as a successful result.
#define SUCCEEDED(Status) ((HRESULT)(Status) >= 0)
*/
::OleUninitialize();
}
::OleUninitialize();
}
|
|
|
|