// 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;
}