CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 1 of 2 12 LastLast
Results 1 to 15 of 18
  1. #1
    Join Date
    May 2002
    Posts
    1,798

    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?
    mpliam

  2. #2
    Join Date
    Aug 2000
    Location
    West Virginia
    Posts
    7,721

    Re: How to set a starting root directory for this code that selects a folder name?

    Right now you have:
    Code:
    bi.lpfn = NULL;
    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;
    }

  3. #3
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    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.

  4. #4
    Join Date
    May 2002
    Posts
    1,798

    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 :
    value of type "INT" cannot be assigned to type LPCITEMIDLIST
    There must be some easier way.
    mpliam

  5. #5
    Join Date
    May 2002
    Posts
    1,798

    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). ?
    mpliam

  6. #6
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,395

    Re: How to set a starting root directory for this code that selects a folder name?

    Quote Originally Posted by Mike Pliam View Post
    ... 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 View Post
    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");
    Victor Nijegorodov

  7. #7
    Join Date
    May 2002
    Posts
    1,798

    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.
    mpliam

  8. #8
    Join Date
    Aug 2000
    Location
    West Virginia
    Posts
    7,721

    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;

  9. #9
    Join Date
    Nov 2000
    Location
    Voronezh, Russia
    Posts
    6,620

    Re: How to set a starting root directory for this code that selects a folder name?

    Quote Originally Posted by Mike Pliam View Post
    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 09:12 AM.
    Best regards,
    Igor

  10. #10
    Join Date
    May 2002
    Posts
    1,798

    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;
    
    }
    Last edited by Mike Pliam; January 24th, 2013 at 04:13 PM.
    mpliam

  11. #11
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: How to set a starting root directory for this code that selects a folder name?

    Why wouldn't you want to call ReleaseBuffer?

  12. #12
    Join Date
    May 2002
    Posts
    1,798

    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?
    mpliam

  13. #13
    Join Date
    Apr 1999
    Posts
    27,449

    Re: How to set a starting root directory for this code that selects a folder name?

    Quote Originally Posted by Mike Pliam View Post
    '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
    Last edited by Paul McKenzie; January 24th, 2013 at 06:24 PM.

  14. #14
    Join Date
    May 2002
    Posts
    1,798

    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.
    mpliam

  15. #15
    Join Date
    Apr 1999
    Posts
    27,449

    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

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured