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?
Right now you have:
Change it to something like:
Code:
bi.lpfn = BrowseCallbackProc;
And add the non-member callback
Code:
INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg,LPARAM lp, LPARAM pData)
{
TCHAR szDir[MAX_PATH];
switch(uMsg)
{
case BFFM_INITIALIZED:
_tcscpy_s(szDir,MAX_PATH,_T("C:\\cygwin"));
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir);
break;
}
return 0;
}
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?
Not knowing exactly how to do this, I tried as I believe you suggested:
Code:
bi.pidlRoot = BrowseCallbackProc(NULL, 0, 0, 0);
which was met rather ungraciously with :
Quote:
value of type "INT" cannot be assigned to type LPCITEMIDLIST
There must be some easier way.
Re: How to set a starting root directory for this code that selects a folder name?
Exactly Arjay, but how to set the code in question to start in a more convenient (say the last saved root directory - save it where you will). ?
Re: How to set a starting root directory for this code that selects a folder name?
Quote:
Originally Posted by
Mike Pliam
... 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?
Quote:
Originally Posted by
Mike Pliam
Not knowing exactly how to do this, I tried as I believe you suggested:
Code:
bi.pidlRoot = BrowseCallbackProc(NULL, 0, 0, 0);
which was met rather ungraciously with :
There must be some easier way.
Mike, as Philip Nicoletti already mentioned, you should pass the string with this starting folder as LPARAM. So do:
Code:
bi.pidlRoot = NULL;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM)_T("d:\\My Initial Folder");
Re: How to set a starting root directory for this code that selects a folder name?
OK. What I've done is to save the last accessed m_csMyLastDir as a CString member variable. Then try:
Code:
bi.pidlRoot = NULL;
bi.lpfn = BrowseCallbackProc;
//bi.lParam = (LPARAM)_T("d:\\My Initial Folder");
bi.lParam = (LPARAM)(m_csMyLastDir.GetBuffer(0)); m_csMyLastDir.ReleaseBuffer();
And here's my BFF:
Code:
// BFFCALLBACK function pointer (Windows)
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb762598(v=vs.85).aspx
// Specifies an application-defined callback function used to send messages to, and
// process messages from, a Browse dialog box displayed in response to a call to
/// SHBrowseForFolder.
INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
{
TCHAR szDir[MAX_PATH];
switch(uMsg)
{
case BFFM_INITIALIZED:
//_tcscpy_s(szDir,MAX_PATH,_T("C:\\"));
//_tcscpy_s(szDir, MAX_PATH, pData);
_tcscpy_s(szDir, MAX_PATH, (LPARAM)pData);
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir);
break;
}
return 0;
}// BrowseCallbackProc(HWND hwnd, UINT uMsg,LPARAM lp, LPARAM pData)
But notice there are 2 LPARAMS in this CALLBACK. I have not figured out how either one can be used.
Please help. Thanks.
Re: How to set a starting root directory for this code that selects a folder name?
1) Does that compile ? The third parameter to _tcscpy is not an LPARAM. I should probably be:
Code:
_tcscpy_s(szDir,MAX_PATH,reinterpret_cast<LPCTSTR>(pData));
2) I don't think you need to use GetBuffer/ReleaseBuffer
Code:
bi.lParam = (LPARAM)(LPCTSTR)m_csMyLastDir;
Re: How to set a starting root directory for this code that selects a folder name?
Quote:
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:
Quote:
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.
Quote:
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. ;)
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
and this works!!
Code:
INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg,LPARAM lp, LPARAM pData)
{
TCHAR szDir[MAX_PATH];
switch(uMsg)
{
case BFFM_INITIALIZED:
_tcscpy_s(szDir,MAX_PATH,reinterpret_cast<LPCTSTR>(pData));
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir);
break;
}
return 0;
}
Re: How to set a starting root directory for this code that selects a folder name?
Why wouldn't you want to call ReleaseBuffer?
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?
Quote:
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?
See this MFC/ATL string adapter class:
http://msdn.microsoft.com/en-us/library/08thta63.aspx
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
Re: How to set a starting root directory for this code that selects a folder name?
Paul,
Quote:
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.
Regards,
Paul McKenzie
Re: How to set a starting root directory for this code that selects a folder name?
ReleaseBuffer resets the length of the string so that functions like GetLength() work properly. The CSimpleStringT destructor does not call ReleaseBuffer, it's just calls an internal Release which frees up the memory if the string ref count is zero. No need to call ReleaseBuffer to reset the length of the string since the string object is going away.
ReleaseBuffer should be called after GetBuffer so you can use other string functions. Presumably you use GetBuffer to put data into the string from apis that take a string buffer. After putting the data in, call ReleaseBuffer to set the length of the string. I usually match up the calls and not worry about it further. I'd rather code this way than have to remember what ReleaseBuffer does and when I really need to call it.
Here's a snippet to ponder...
Code:
CString sTest(_T("I am a test string."));
int size = sTest.GetLength(); // 19 characters
LPTSTR wz = _tcscpy(sTest.GetBuffer(35), _T("I am a larger test string: 34 chars"));
size = sTest.GetLength(); // 19 characters - oops
sTest.ReleaseBuffer();
size = sTest.GetLength(); // 35 characters
Re: How to set a starting root directory for this code that selects a folder name?
Quote:
Originally Posted by
Mike Pliam
Code:
//bi.lParam = (LPARAM)(LPCTSTR)m_csMyLastDir; // this won't compile
I wonder how it would not.
Code:
#include <windows.h>
#include <tchar.h>
#include <atlstr.h>
int _tmain()
{
CString str = TEXT("text");
LPARAM lptr1 = (LPARAM)(LPCTSTR)str;
LPARAM lptr2 = (LPARAM)str.GetString();
LPARAM lptr3 = (LPARAM)str.GetBuffer();
_tprintf(TEXT("%x\n%x\n%x\n%s\n"), lptr1, lptr2, lptr3, lptr1);
return 0;
}
Code:
D:\Temp\75>cl 75.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
75.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:75.exe
75.obj
D:\Temp\75>75.exe
31a418
31a418
31a418
text
Re: How to set a starting root directory for this code that selects a folder name?
Quote:
Originally Posted by
Mike Pliam
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.
It's always safer to use GetString() in case when your code is not going to modify the string content (which I hope you're always aware of).
Typically it's very easy to tell if the string content may be modified by Win32 API. Non-modifiable will be of type LPC(T)STR. Modifiable will be LP(T)STR.
Code:
CString str;
typedef struct _RO
{
LPCTSTR readonly;
} RO;
RO ro;
ro.readonly = str; // casting to LPCTSTR is equivalent to calling GetString
typedef struct _RW
{
LPTSTR readwrite;
} RW;
RW rw;
rw.readwrite = str.GetBuffer(200);
. . .
str.ReleaseBuffer();
Though a special class of cases exists when the same structure type is used in WinAPI for both reading and modification. Good example would be LVITEM. The same field LPTSTR pszText is used for sending text to ListView as well as retrieving it from it. So, less restrictive type LPTSTR is used here. But when you just set the text to control, the control is never going to modify your string, so you are safe to do the following:
Code:
lvi.pszText = (LPTSTR)(LPCTSTR)str;
// here with second cast you just make compiler be happy
// but internal string still remains constant
But when you retrieve the string from control you have to provide a real writable buffer:
Code:
lvi.pszText = str.GetBuffer(200);
lvi.cchTextMax = 200;
. . . // calling API to retrieve the text
str.ReleaseBuffer();
In case of passing string via LPARAM again you must be aware if this is going to modify your string content or not. If it's not, you use GetString() or operator(LPCTSTR). If it is, you use GetBuffer(ANTICIPATED_SIZE).