Click here to Skip to main content
15,886,199 members
Articles / Desktop Programming / MFC

XBrowseForFolder - Wrapper for SHBrowseForFolder

Rate me:
Please Sign up or sign in to vote.
4.89/5 (62 votes)
7 Mar 2008CPOL4 min read 245.2K   5.2K   79  
XBrowseForFolder wraps the SHBrowseForFolder API, provides a way to specify an initial directory, and cleans up the SHBrowseForFolder dialog.
// XBrowseForFolder.cpp  Version 1.2
//
// Author:  Hans Dietrich
//          hdietrich@gmail.com
//
// Description:
//     XBrowseForFolder.cpp implements XBrowseForFolder(), a function that
//     wraps SHBrowseForFolder().
//
// History
//     Version 1.2 - 2008 February 29
//     - Changed API to allow for initial CSIDL. 
//     - Added option to set dialog caption, suggested by SimpleDivX.
//     - Added option to set root, suggested by Jean-Michel Reghem.
//
//     Version 1.1 - 2003 September 29 (not released)
//     - Added support for edit box
//
//     Version 1.0 - 2003 September 25
//     - Initial public release
//
// License:
//     This software is released into the public domain.  You are free to use
//     it in any way you like, except that you may not sell this source code.
//
//     This software is provided "as is" with no expressed or implied warranty.
//     I accept no liability for any damage or loss of business that this
//     software may cause.
//
///////////////////////////////////////////////////////////////////////////////

// if you don't want to use MFC, comment out the following line:
#include "stdafx.h"

#ifndef __AFX_H__
#include "windows.h"
#include "crtdbg.h"
#include "tchar.h"
#endif

#include "Shlobj.h"
#include "io.h"
#include "XBrowseForFolder.h"

#pragma warning(disable: 4127)	// conditional expression is constant (_ASSERTE)
#pragma warning(disable : 4996)	// disable bogus deprecation warning

#ifndef __noop
#if _MSC_VER < 1300
#define __noop ((void)0)
#endif
#endif

#undef TRACE
#define TRACE __noop

//=============================================================================
// if you want to see the TRACE output, uncomment this line:
//#include "XTrace.h"
//=============================================================================

//=============================================================================
// struct to pass to callback function
//=============================================================================
struct FOLDER_PROPS
{
	LPCTSTR lpszTitle;
	LPCTSTR lpszInitialFolder;
	UINT ulFlags;
};

#ifndef __AFX_H__

///////////////////////////////////////////////////////////////////////////////
// CRect - a minimal CRect class

class CRect : public tagRECT
{
public:
	//CRect() { }
	CRect(int l = 0, int t = 0, int r = 0, int b = 0) 
	{ 
		left = l; 
		top = t; 
		right = r; 
		bottom = b; 
	}
	int Width() const { return right - left; }
	int Height() const { return bottom - top; }
	void SwapLeftRight() { SwapLeftRight(LPRECT(this)); }
	static void SwapLeftRight(LPRECT lpRect) { LONG temp = lpRect->left; 
											   lpRect->left = lpRect->right; 
											   lpRect->right = temp; }
	operator LPRECT() { return this; }
};
#endif

///////////////////////////////////////////////////////////////////////////////
// ScreenToClientX - helper function in case non-MFC
static void ScreenToClientX(HWND hWnd, LPRECT lpRect)
{
	_ASSERTE(::IsWindow(hWnd));
	::ScreenToClient(hWnd, (LPPOINT)lpRect);
	::ScreenToClient(hWnd, ((LPPOINT)lpRect)+1);
}

///////////////////////////////////////////////////////////////////////////////
// MoveWindowX - helper function in case non-MFC
static void MoveWindowX(HWND hWnd, CRect& rect, BOOL bRepaint)
{
	_ASSERTE(::IsWindow(hWnd));
	::MoveWindow(hWnd, rect.left, rect.top,
		rect.Width(), rect.Height(), bRepaint);
}

///////////////////////////////////////////////////////////////////////////////
// SizeBrowseDialog - resize dialog, move controls
static void SizeBrowseDialog(HWND hWnd, FOLDER_PROPS *fp)
{
	TRACE(_T("in void SizeBrowseDialog\n"));

	// find the folder tree and make dialog larger
	HWND hwndTree = FindWindowEx(hWnd, NULL, _T("SysTreeView32"), NULL);

	if (!hwndTree)
	{
		// ... this usually means that BIF_NEWDIALOGSTYLE is enabled.
		// Then the class name is as used in the code below.
		hwndTree = FindWindowEx(hWnd, NULL, _T("SHBrowseForFolder ShellNameSpace Control"), NULL);
		TRACE(_T("SHBrowseForFolder ShellNameSpace Control: hwndTree=%X\n"), hwndTree);
	}

	CRect rectDlg;

	_ASSERTE(IsWindow(hwndTree));

	if (hwndTree)
	{
		// check if edit box
		int nEditHeight = 0;
		HWND hwndEdit = FindWindowEx(hWnd, NULL, _T("Edit"), NULL);
		TRACE(_T("hwndEdit=%x\n"), hwndEdit);
		CRect rectEdit;
		if (hwndEdit && (fp->ulFlags & BIF_EDITBOX))
		{
			::GetWindowRect(hwndEdit, &rectEdit);
			ScreenToClientX(hWnd, &rectEdit);
			nEditHeight = rectEdit.Height();
		}
		else if (hwndEdit)
		{
			::MoveWindow(hwndEdit, 20000, 20000, 10, 10, FALSE);
			::ShowWindow(hwndEdit, SW_HIDE);
			hwndEdit = 0;
		}

		// make the dialog larger
		::GetWindowRect(hWnd, &rectDlg);
		rectDlg.right += 40;
		rectDlg.bottom += 30;
		if (hwndEdit)
			rectDlg.bottom += nEditHeight + 5;
		MoveWindowX(hWnd, rectDlg, TRUE);
		::GetClientRect(hWnd, &rectDlg);

		int hMargin = 10;
		int vMargin = 10;

		// check if new dialog style - this means that there will be a resizing
		// grabber in lower right corner
		if (fp->ulFlags & BIF_NEWDIALOGSTYLE)
			hMargin = ::GetSystemMetrics(SM_CXVSCROLL);

		// move the Cancel button
		CRect rectCancel;
		HWND hwndCancel = ::GetDlgItem(hWnd, IDCANCEL);
		if (hwndCancel)
			::GetWindowRect(hwndCancel, &rectCancel);
		ScreenToClientX(hWnd, &rectCancel);
		int h = rectCancel.Height();
		int w = rectCancel.Width();
		rectCancel.bottom = rectDlg.bottom - vMargin;//nMargin;
		rectCancel.top = rectCancel.bottom - h;
		rectCancel.right = rectDlg.right - hMargin; //(scrollWidth + 2*borderWidth);
		rectCancel.left = rectCancel.right - w;
		if (hwndCancel)
		{
			//TRACERECT(rectCancel);
			MoveWindowX(hwndCancel, rectCancel, FALSE);
		}

		// move the OK button
		CRect rectOK(0, 0, 0, 0);
		HWND hwndOK = ::GetDlgItem(hWnd, IDOK);
		if (hwndOK)
			::GetWindowRect(hwndOK, &rectOK);
		ScreenToClientX(hWnd, &rectOK);
		rectOK.bottom = rectCancel.bottom;
		rectOK.top = rectCancel.top;
		rectOK.right = rectCancel.left - 10;
		rectOK.left = rectOK.right - w;
		if (hwndOK)
		{
			MoveWindowX(hwndOK, rectOK, FALSE);
		}

		// expand the folder tree to fill the dialog
		CRect rectTree;
		::GetWindowRect(hwndTree, &rectTree);
		ScreenToClientX(hWnd, &rectTree);
		if (hwndEdit)
		{
			rectEdit.left = hMargin;
			rectEdit.right = rectDlg.right - hMargin;
			rectEdit.top = vMargin;
			rectEdit.bottom = rectEdit.top + nEditHeight;
			MoveWindowX(hwndEdit, rectEdit, FALSE);
			rectTree.top = rectEdit.bottom + 5;
		}
		else
		{
			rectTree.top = vMargin;
		}
		rectTree.left = hMargin;
		rectTree.bottom = rectOK.top - 10;//nMargin;
		rectTree.right = rectDlg.right - hMargin;
		//TRACERECT(rectTree);
		MoveWindowX(hwndTree, rectTree, FALSE);
	}
	else
	{
		TRACE(_T("ERROR - tree control not found.\n"));
		//_ASSERTE(hwndTree);
	}
}

///////////////////////////////////////////////////////////////////////////////
// BrowseCallbackProc - SHBrowseForFolder callback function
static int CALLBACK BrowseCallbackProc(HWND hWnd,		// Window handle to the browse dialog box
									   UINT uMsg,		// Value identifying the event
									   LPARAM lParam,	// Value dependent upon the message 
									   LPARAM lpData)	// Application-defined value that was 
														// specified in the lParam member of the 
														// BROWSEINFO structure
{
	switch (uMsg)
	{
		case BFFM_INITIALIZED:		// sent when the browse dialog box has finished initializing. 
		{
			TRACE(_T("hWnd=%X\n"), hWnd);

			// remove context help button from dialog caption
			LONG lStyle = ::GetWindowLong(hWnd, GWL_STYLE);
			lStyle &= ~DS_CONTEXTHELP;
			::SetWindowLong(hWnd, GWL_STYLE, lStyle);
			lStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
			lStyle &= ~WS_EX_CONTEXTHELP;
			::SetWindowLong(hWnd, GWL_EXSTYLE, lStyle);

			FOLDER_PROPS *fp = (FOLDER_PROPS *) lpData;
			if (fp)
			{
				if (fp->lpszInitialFolder && (fp->lpszInitialFolder[0] != _T('\0')))
				{
					// set initial directory
					::SendMessage(hWnd, BFFM_SETSELECTION, TRUE, (LPARAM)fp->lpszInitialFolder);
				}

				if (fp->lpszTitle && (fp->lpszTitle[0] != _T('\0')))
				{
					// set window caption
					::SetWindowText(hWnd, fp->lpszTitle);
				}
			}

			SizeBrowseDialog(hWnd, fp);
		}
		break;

		case BFFM_SELCHANGED:		// sent when the selection has changed
		{
			TCHAR szDir[MAX_PATH*2] = { 0 };

			// fail if non-filesystem
			BOOL bRet = SHGetPathFromIDList((LPITEMIDLIST) lParam, szDir);
			if (bRet)
			{
				// fail if folder not accessible
				if (_taccess(szDir, 00) != 0)
				{
					bRet = FALSE;
				}
				else
				{
					SHFILEINFO sfi;
					::SHGetFileInfo((LPCTSTR)lParam, 0, &sfi, sizeof(sfi), 
							SHGFI_PIDL | SHGFI_ATTRIBUTES);
					TRACE(_T("dwAttributes=0x%08X\n"), sfi.dwAttributes);

					// fail if pidl is a link
					if (sfi.dwAttributes & SFGAO_LINK)
					{
						TRACE(_T("SFGAO_LINK\n"));
						bRet = FALSE;
					}
				}
			}

			// if invalid selection, disable the OK button
			if (!bRet)
			{
				::EnableWindow(GetDlgItem(hWnd, IDOK), FALSE);
			}

			TRACE(_T("szDir=%s\n"), szDir);
		}
		break;
	}
         
	return 0;
}

///////////////////////////////////////////////////////////////////////////////
//
// XBrowseForFolder()
//
// Purpose:     Invoke the SHBrowseForFolder API.  If lpszInitialFolder is
//              supplied, it will be the folder initially selected in the tree 
//              folder list.  Otherwise, the initial folder will be set to the 
//              current directory.  The selected folder will be returned in 
//              lpszBuf.
//
// Parameters:  hWnd              - handle to the owner window for the dialog
//              lpszInitialFolder - initial folder in tree;  if NULL, the initial
//                                  folder will be the current directory; if
//                                  if this is a CSIDL, must be a real folder.
//              nRootFolder       - optional CSIDL of root folder for tree;
//                                  -1 = use default.
//              lpszCaption       - optional caption for folder dialog
//              lpszBuf           - buffer for the returned folder path
//              dwBufSize         - size of lpszBuf in TCHARs
//              bEditBox          - TRUE = include edit box in dialog
//
// Returns:     BOOL - TRUE = success;  FALSE = user hit Cancel
//
BOOL XBrowseForFolder(HWND hWnd,
					  LPCTSTR lpszInitialFolder,
					  int nRootFolder,
					  LPCTSTR lpszCaption,
					  LPTSTR lpszBuf,
					  DWORD dwBufSize,
					  BOOL bEditBox /*= FALSE*/)
{
	_ASSERTE(lpszBuf);
	_ASSERTE(dwBufSize >= MAX_PATH);

	if (lpszBuf == NULL || dwBufSize < MAX_PATH)
		return FALSE;

	ZeroMemory(lpszBuf, dwBufSize*sizeof(TCHAR));

	BROWSEINFO bi = { 0 };

	// check if there is a special root folder
	LPITEMIDLIST pidlRoot = NULL;
	if (nRootFolder != -1)
	{
		if (SUCCEEDED(SHGetSpecialFolderLocation(hWnd, nRootFolder, &pidlRoot)))
			bi.pidlRoot = pidlRoot;
	}

	TCHAR szInitialPath[MAX_PATH*2] = { _T('\0') };
	if (lpszInitialFolder)
	{
		// is this a folder path string or a csidl?
		if (HIWORD(lpszInitialFolder) == 0)
		{
			// csidl
			int nFolder = LOWORD((UINT)(UINT_PTR)lpszInitialFolder);
			TRACE(_T("csidl:  nFolder=0x%X\n"), nFolder);
			SHGetSpecialFolderPath(hWnd, szInitialPath, nFolder, FALSE);
		}
		else
		{
			// string
			_tcsncpy(szInitialPath, lpszInitialFolder, 
						sizeof(szInitialPath)/sizeof(TCHAR)-2);
		}
		TRACE(_T("szInitialPath=<%s>\n"), szInitialPath);
	}

	if ((szInitialPath[0] == _T('\0')) && (bi.pidlRoot == NULL))
	{
		// no initial folder and no root, set to current directory
		::GetCurrentDirectory(sizeof(szInitialPath)/sizeof(TCHAR)-2, 
				szInitialPath);
	}

	FOLDER_PROPS fp;

	bi.hwndOwner = hWnd;
	bi.ulFlags   = BIF_RETURNONLYFSDIRS;	// do NOT use BIF_NEWDIALOGSTYLE, 
											// or BIF_STATUSTEXT
	if (bEditBox)
		bi.ulFlags |= BIF_EDITBOX;
	bi.ulFlags  |= BIF_NONEWFOLDERBUTTON;
	bi.lpfn      = BrowseCallbackProc;
	bi.lParam    = (LPARAM) &fp;

	fp.lpszInitialFolder = szInitialPath;
	fp.lpszTitle = lpszCaption;
	fp.ulFlags = bi.ulFlags;

	BOOL bRet = FALSE;

	LPITEMIDLIST pidlFolder = SHBrowseForFolder(&bi);

	if (pidlFolder)
	{
		TCHAR szBuffer[MAX_PATH*2] = { _T('\0') };

		if (SHGetPathFromIDList(pidlFolder, szBuffer))
		{
			_tcsncpy(lpszBuf, szBuffer, dwBufSize-1);
			bRet = TRUE;
		}
		else
		{
			TRACE(_T("SHGetPathFromIDList failed\n"));
		}
	}

	// free up pidls
	IMalloc *pMalloc = NULL; 
	if (SUCCEEDED(SHGetMalloc(&pMalloc)) && pMalloc) 
	{
		if (pidlFolder)
			pMalloc->Free(pidlFolder);  
		if (pidlRoot)
			pMalloc->Free(pidlRoot);  
		pMalloc->Release(); 
	}

	return bRet;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions