How to set a starting root directory for this code that selects a folder name?
The following code is useful to allow a user to select a particular folder in the disk directory.
Code:
void OpenFolder()
{
LPMALLOC pMalloc; //,pMalloc2;
CString strDirectory;
BROWSEINFO bi;
// Gets the Shell's default allocator
wchar_t pszBuffer[MAX_PATH];
LPITEMIDLIST pidl;
// Get help on BROWSEINFO struct - it's got all the bit settings.
bi.hwndOwner = ::GetDesktopWindow();
bi.pidlRoot = NULL;
bi.pszDisplayName = pszBuffer;
bi.lpszTitle = _T("Select A Directory");
bi.ulFlags = BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS | BIF_NEWDIA********;
bi.lpfn = NULL;
bi.lParam = 0;
if (::SHGetMalloc(&pMalloc) == NOERROR)
{
if ((pidl = ::SHBrowseForFolder(&bi)) != NULL)
{
if (::SHGetPathFromIDList(pidl, pszBuffer))
{
strDirectory = pszBuffer;
}
pMalloc->Free(pidl);
}
pMalloc->Release();
}
TRACE0("strDirectory =: ");
OutputDebugString(strDirectory); TRACE0("\n");
}
However, if one uses this code over and over, one quickly becomes tired of threading one's way all the way from root directory C:\. There is undoubtedly some way to designate a starting directory. The most obvious candidate to set such a target is the:
Code:
bi.pidlRoot = NULL;
But attempting to set bi.pidlRoot to a CString (directory address) errors because 'no suiitable conversion from CString to LPCITEMIDLIST exists'
How can I set the code to open at a designated address in the directory tree?
Re: How to set a starting root directory for this code that selects a folder name?
When the user selects a folder, store it in the registry and then use it the next time the user opens the browse for folder dialog. That's the behavior I expect anyway.
Re: How to set a starting root directory for this code that selects a folder name?
Originally Posted by Mike Pliam
The most obvious candidate to set such a target is the:
Code:
bi.pidlRoot = NULL;
Mike, common sense misguides too often. From MSDN:
pidlRoot
Type: PCIDLIST_ABSOLUTE
A PIDL that specifies the location of the root folder from which to start browsing. Only the specified folder and its subfolders in the namespace hierarchy appear in the dialog box. This member can be NULL; in that case, the namespace root (the Desktop folder) is used.
Setting pidlRoot to some particular IDList has a very special meaning: user will be able to select a path under or equal to the root, and won't be able to go above. Say you select "C:\My data\options" as a root. Then you never be able to select "C:\My data", "C:\" or any other drive. Evidently this is not just a "starting folder" that you want.
But attempting to set bi.pidlRoot to a CString (directory address) errors because 'no suiitable conversion from CString to LPCITEMIDLIST exists'
How can I set the code to open at a designated address in the directory tree?
Seems you never heard about Shell Namespace. Firstly introduced in Windows NT3.51, AFAIR. It's about 1995.
Last edited by Igor Vartanov; January 24th, 2013 at 08:12 AM.
Re: How to set a starting root directory for this code that selects a folder name?
Thanks Phil. That works nicely - it also allows the user to navigate in both directions from the newly designated root, up and down the tree. I would never have figured that out myself.
This works (but I always wonder about using *.ReleaseBuffer() as some claim is necessary)
Code:
bi.pidlRoot = NULL;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM)(m_csLastEncryptDir.GetBuffer(0)); // this works !!
//bi.lParam = (LPARAM)(LPCTSTR)m_csMyLastDir; // this won't compile
Re: How to set a starting root directory for this code that selects a folder name?
Arjay, I know we've discussed this in the past. I have been under the impression that it was good practice to call ReleaseBuffer anytime one uses GetBuffer(0) on a given CString, in order to 'free' the buffer (whatever that implies). I almost always do that, even though it makes the code less 'pretty'. Then one of the gurus, I don't recall which one of you, said something to the effect 'No well-designed function would require such a release', implying either CString is well-designed and consequently should not need such followup, or that CString is poorly designed and does. What the consensus on this?
Re: How to set a starting root directory for this code that selects a folder name?
Originally Posted by Mike Pliam
'No well-designed function would require such a release', implying either CString is well-designed and consequently should not need such followup, or that CString is poorly designed and does. What the consensus on this?
This will call ReleaseBuffer() on destruction of whatever MFC/ATL string type that it is constructed with. So if you forget to call ReleaseBuffer(), this class will do it for you.
As a matter fof fact, it is a good idea in general to create or use classes that will "release" or "close" on destruction (RAII). This way, you're not writing code where at every single return point, catch handler, etc., you're scrambling doing this work yourself.
Regards,
Paul McKenzie
Last edited by Paul McKenzie; January 24th, 2013 at 05:24 PM.
Re: How to set a starting root directory for this code that selects a folder name?
Paul,
This is possible because the wrapper object goes out of scope naturally in the case of an exception or multiple exiting code paths; causing its destructor to free the string resource.
But what if one uses the same string source repeatedly in the same function (scope) ? I suspect it's safer to use ReleaseBuffer in each instance of GetBuffer(0) and let M$ worry about implementing the details of cleanup.
Re: How to set a starting root directory for this code that selects a folder name?
If you forget to call ReleaseBuffer when the function exits, what happens? Does the constructor for CString call ReleaseBuffer()? If it doesn't then I don't know what happens.
Look at it this way, you want a safe way to make sure you aren't leaking memory or resources. The sure-fire way to make sure of this is to use a class that guarantees that on destruction, the proper cleanup occurs.
If an exception is thrown, how is your "ReleaseBuffer" everywhere approach going to work, unless you remember to write a try/catch handler and call it there also? See how unwieldly it gets -- that's why the adapter class was created, so as to make it safer if you do plan to use GetBuffer() and ReleaseBuffer() continuously in your code.
Bookmarks