以良好的MFC风格将Shell API函数SHBrowseForFolder()
封装为一个
CWnd
子类。
使用示例
1
:
CYABFFW dlg();
if (IDOK == dlg.DoModal())


{
CString s = dlg.GetPath();
// Do something with `s'

使用示例2:
CYABFFW dlg(_T("Please select a directory"), // Hint to user

BIF_USE_NEWUI, // Flags for the dlg

this, // Parent window

CSIDL_DRIVES); // Root of search

if (IDOK == dlg.DoModal())


{
CString s = dlg.GetPath();
// Do something with `s'


YABFFW.h
class CYABFFW : public CWnd


{
// Construction
public:

/**//// Construct with a textual hint, BFF flags, parent window, a
/// root folder expressed as a CSIDL value, and a default
/// selection expressed as a textual path
CYABFFW(const CString &strHint = CString(), UINT nFlags = 0U,
CWnd *pParentWnd = NULL, int nRootFolder = CSIDL_DESKTOP,
const CString &strDefaultSel = CString());

/**//// Construct with a default selection expressed as a CSIDL
/// value, a textual hint, BFF flags, parent window, & a root
/// folder expressed as a CSIDL value
CYABFFW(int nDefaultSel, const CString &strHint = CString(),
UINT nFlags = 0U, CWnd *pParentWnd = NULL,
int nRootFolder = CSIDL_DESKTOP);

/**//// Construct with a hint from Resource, BFF flags, parent
/// window, a root folder expressed as a CSIDL value, and a
/// default selection expressed as a textual path
CYABFFW(int nHint, UINT nFlags = 0U, CWnd *pParentWnd = NULL,
int nRootFolder = CSIDL_DESKTOP,
const CString &strDefaultSel = CString());

/**//// Construct with a hint from Resource, a default selection
/// expressed as a CSIDL value, BFF flags, parent window, & a
/// root folder expressed as a CSIDL value
CYABFFW(int nDefaultSel, int nHint, UINT nFlags = 0U,
CWnd *pParentWnd = NULL, int nRootFolder = CSIDL_DESKTOP);

/**//// Construct with a textual hint, an arbitrary root folder,
/// BFF flags, a parent window, and a default selection
/// expressed as a textual path
CYABFFW(const CString &strHint, const CString &strRoot,
UINT nFlags = 0U, CWnd *pParentWnd = NULL,
const CString &strDefaultSel = CString());

/**//// Construct with a textual hint, a default selection
/// expressed as a CSIDL value, an arbitrary root folder, BFF
/// flags, & a parent window
CYABFFW(int nDefaultSel, const CString &strHint, const CString &strRoot,
UINT nFlags = 0U, CWnd *pParentWnd = NULL);

/**//// Construct with a hint from Resource, an arbitrary root
/// folder, BFF flags, a parent widnow, and a textual default
/// selection
CYABFFW(int nHint, const CString &strRoot, UINT nFlags = 0U,
CWnd *pParentWnd = NULL,
const CString &strDefaultSel = CString());

/**//// Construct with a hint from Resource, a default selection
/// expressed as a CSIDL value, an arbitrary root folder, BFF
/// flags, & a parent widnow
CYABFFW(int nDefaultSel, int nHint, const CString &strRoot,
UINT nFlags = 0U, CWnd *pParentWnd = NULL);

// Attributes
public:

/**//// Retrieve the display name of the selected item
CString GetDisplayName() const;

/**//// Retrieve the ITEMIDLIST of the selected item
LPCITEMIDLIST GetItemIdList() const;

/**//// Retrieve the path to the selected item
CString GetPath() const;

// Operations
public:

/**//// Display the Dialog - returns IDOK or IDCANCEL
int DoModal();
// Overrides
public:

/**//// Called when the BFF Dialog has initialized
virtual void OnInitBFFDialog();

/**//// Called when the selection has changed in the tree control
virtual void OnBFFSelChanged(LPITEMIDLIST pNewSel);

/**//// Called when the user types an invalid name
virtual BOOL OnBFFValidateFailed(const CString &strBadName);

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CYABFFW)
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CYABFFW();

// Generated message map functions
protected:
//{{AFX_MSG(CYABFFW) NOTE - the ClassWizard will add and remove
// member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

private:

/**//// Static method to be used for the SHBFF callback
static int CALLBACK BrowseCallbackProc(HWND hWnd, UINT nMsg,
LPARAM lParam,
LPARAM lpData);

private:

/**//// Free the memory referenced by m_pItemIdList
void FreeItemIdList(IMalloc *pMalloc = NULL);

/**//// CSIDL => ITEMIDLIST
LPITEMIDLIST ResolveCsidl(int nCsidl) const;

/**//// Textual filesystem path => ITEMIDLIST
LPITEMIDLIST ResolveFsPath(const CString &strPath) const;

private:

/**//// Display name of the selected item
CString m_strDisplayName;

/**//// Flags to be passed to the browse dialog
UINT m_nFlags;

/**//// "Hint" to be displayed above the tree control
CString m_strHint;

/**//// ITEMIDLIST identifying the selected Shell item
LPITEMIDLIST m_pItemIdList;

/**//// Parent CWnd (NULL => App main window)
CWnd *m_pParentWnd;

/**//// Selected path
CString m_strPath;

/**//// ITEMIDLIST identifying the root
LPITEMIDLIST m_pRoot;

/**//// Default selection, if given as text (empty if none)
CString m_strDefaultSel;

/**//// Default selection, if given as a CSIDL value (NULL if none)
LPITEMIDLIST m_pDefaultSel;

};


YABFFW.cpp

CYABFFW::CYABFFW( const CString &strHint /**//*= CString()*/,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/,

int nRootFolder /**//*= CSIDL_DESKTOP*/,

const CString &strDefault /**//*= CString()*/ ) :
m_nFlags(nFlags),
m_strHint(strHint),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_strDefaultSel(strDefault),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

m_pRoot = ResolveCsidl(nRootFolder);
}


CYABFFW::CYABFFW(int nDefaultSel,

const CString &strHint /**//*= CString()*/,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/,

int nRootFolder /**//*= CSIDL_DESKTOP*/):
m_nFlags(nFlags),
m_strHint(strHint),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

m_pRoot = ResolveCsidl(nRootFolder);
m_pDefaultSel = ResolveCsidl(nDefaultSel);
}


CYABFFW::CYABFFW(int nHint,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/,

int nRootFolder /**//*= CSIDL_DESKTOP*/,

const CString &strDefaultSel /**//*= CString()*/) :
m_nFlags(nFlags),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_strDefaultSel(strDefaultSel),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

if (!m_strHint.LoadString(nHint))
AfxThrowResourceException();

m_pRoot = ResolveCsidl(nRootFolder);
}

CYABFFW::CYABFFW(int nDefaultSel,
int nHint,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/,

int nRootFolder /**//*= CSIDL_DESKTOP*/) :
m_nFlags(nFlags),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

if (!m_strHint.LoadString(nHint))
AfxThrowResourceException();

m_pRoot = ResolveCsidl(nRootFolder);
m_pDefaultSel = ResolveCsidl(nDefaultSel);
}


CYABFFW::CYABFFW(const CString &strHint,
const CString &strRoot,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/,

const CString &strDefaultSel /**//*=CString()*/) :
m_nFlags(nFlags),
m_strHint(strHint),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_strDefaultSel(strDefaultSel),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

m_pRoot = ResolveFsPath(strRoot);
}


CYABFFW::CYABFFW(int nDefaultSel,
const CString &strHint,
const CString &strRoot,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/) :
m_nFlags(nFlags),
m_strHint(strHint),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

m_pRoot = ResolveFsPath(strRoot);
m_pDefaultSel = ResolveCsidl( nDefaultSel );
}


CYABFFW::CYABFFW(int nHint,
const CString &strRoot,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/,

const CString &strDefaultSel /**//*= CString()*/) :
m_nFlags(nFlags),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL),
m_strDefaultSel(strDefaultSel),
m_pDefaultSel(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

if (!m_strHint.LoadString(nHint))
AfxThrowResourceException();

m_pRoot = ResolveFsPath(strRoot);
}


CYABFFW::CYABFFW(int nDefaultSel,
int nHint,
const CString &strRoot,

UINT nFlags /**//*= 0U*/,

CWnd *pParentWnd /**//*= NULL*/) :
m_nFlags(nFlags),
m_pItemIdList(NULL),
m_pParentWnd(pParentWnd),
m_pRoot(NULL)


{
ASSERT_NULL_OR_VALID(pParentWnd); // Paranoia

if (!m_strHint.LoadString(nHint))
AfxThrowResourceException();

m_pRoot = ResolveFsPath(strRoot);
m_pDefaultSel = ResolveCsidl(nDefaultSel);
}

#ifdef _DEBUG
CString CYABFFW::GetDisplayName() const


{
return m_strDisplayName;
}


LPCITEMIDLIST CYABFFW::GetItemIdList() const


{
return m_pItemIdList;
}

CString CYABFFW::GetPath() const


{
return m_strPath;
}
#endif // _DEBUG

int CYABFFW::DoModal()


{
// We'll need this eventually 
HRESULT hr;
IMalloc *pMalloc;
if (FAILED(hr = ::SHGetMalloc(&pMalloc)))
AfxThrowOleException(hr);

// Fill out a 'BROWSEINFO' structure to hand to 'SHBrowseFor-
// Folder':
BROWSEINFO browseInfo;
::ZeroMemory(&browseInfo, sizeof(BROWSEINFO));
browseInfo.hwndOwner = (NULL == m_pParentWnd ? NULL :
m_pParentWnd->m_hWnd);

browseInfo.pidlRoot = m_pRoot;

// Use a CString for memory management
browseInfo.pszDisplayName =
m_strDisplayName.GetBufferSetLength(MAX_PATH);

browseInfo.lpszTitle = m_strHint;
browseInfo.ulFlags = m_nFlags;
browseInfo.lpfn = BrowseCallbackProc;
browseInfo.lParam = (long)this;

if (NULL != m_pItemIdList)
FreeItemIdList(); // Probably never happen, but 

if (NULL == (m_pItemIdList = ::SHBrowseForFolder(&browseInfo)))

{
// User Cancelled out - clean up & bail.
m_strDisplayName.ReleaseBuffer();
pMalloc->Release();
return IDCANCEL;
}

// Right - if we're here, the user actually selected an item.
// Try to get a full path. This will fail if the selected item
// is not part of the FileSystem.
::SHGetPathFromIDList(m_pItemIdList,
m_strPath.GetBufferSetLength(MAX_PATH));

// Cleanup time 
m_strPath.ReleaseBuffer();
m_strDisplayName.ReleaseBuffer();
pMalloc->Release();

// Note: m_pItemIdList has *not* been freed! We keep around in
// case the caller wants to retrieve it later. It will
// ultimately be free-d in the destructor.

return IDOK;
}

void CYABFFW::OnInitBFFDialog()


{
if ( ! m_strDefaultSel.IsEmpty( ) )

{
TRACE( _T( "Setting the default selection to %s.\n" ),
( LPCTSTR ) m_strDefaultSel );
SendMessage( BFFM_SETSELECTION, TRUE, ( LPARAM ) ( LPCTSTR ) m_strDefaultSel );
}
else if ( m_pDefaultSel )

{
SendMessage( BFFM_SETSELECTION, FALSE, ( LPARAM ) m_pDefaultSel );
}
}


void CYABFFW::OnBFFSelChanged(LPITEMIDLIST /**//*pNewSel*/)


{ /**//* No handling by default */ }



BOOL CYABFFW::OnBFFValidateFailed(const CString & /**//*strBadName*/)


{ return TRUE; /**//* No handling by default */ }

CYABFFW::~CYABFFW()


{
HRESULT hr;
IMalloc *pMalloc;
if (FAILED(hr = ::SHGetMalloc(&pMalloc)))
AfxThrowOleException(hr);

pMalloc->Free(m_pRoot);

FreeItemIdList(pMalloc);
}


BEGIN_MESSAGE_MAP(CYABFFW, CWnd)
//{{AFX_MSG_MAP(CYABFFW)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


int CYABFFW::BrowseCallbackProc(HWND hWnd,
UINT nMsg,
LPARAM lParam,
LPARAM lpData)


{
CYABFFW *pWnd = reinterpret_cast<CYABFFW*>(lpData);
ASSERT_VALID(pWnd);
ASSERT(NULL == pWnd->m_hWnd || hWnd == pWnd->m_hWnd);

if (NULL == pWnd->m_hWnd && !pWnd->SubclassWindow(hWnd))
AfxThrowOleException(HRESULT_FROM_WIN32(::GetLastError()));

switch (nMsg)

{
case BFFM_INITIALIZED:
// Indicates the browse dialog box has finished
// initializing. The lParam value is zero.
pWnd->OnInitBFFDialog();
return 0;
case BFFM_SELCHANGED:
// Indicates the selection has changed. The lParam parameter
// points to the item identifier list for the newly selected
// item.

{
LPITEMIDLIST p = reinterpret_cast<LPITEMIDLIST>(lParam);
ASSERT_POINTER(p, ITEMIDLIST);
pWnd->OnBFFSelChanged(p);
return 0;
}
case BFFM_VALIDATEFAILED:
// Indicates the user typed an invalid name into the edit box
// of the browse dialog box. The lParam parameter is the
// address of a character buffer that contains the invalid
// name. An application can use this message to inform the
// user that the name entered was not valid. Return zero to
// allow the dialog to be dismissed or nonzero to keep the
// dialog displayed.

{
LPTSTR p = reinterpret_cast<LPTSTR>(lParam);
ASSERT(!::IsBadStringPtr(p, UINT_MAX));
BOOL bDismissOk = pWnd->OnBFFValidateFailed(CString(p));
return bDismissOk ? 0 : 1;
}
default:
TRACE(_T("WARNING: Unknown message 0x%08x (0x%08x) ")
_T("passed to CYABFFW::BrowseCallbackProc!\n"),
nMsg, lParam);
return 0;
} // End switch on nMsg.
}


void CYABFFW::FreeItemIdList(IMalloc *pMalloc /**//*= NULL*/)


{
if (NULL == m_pItemIdList)
return;

bool bWeRelease = false;
if (NULL == pMalloc)

{
bWeRelease = true;
HRESULT hr;
IMalloc *pMalloc;
if (FAILED(hr = ::SHGetMalloc(&pMalloc)))
AfxThrowOleException(hr);
}

pMalloc->Free(m_pItemIdList);

if (bWeRelease)
pMalloc->Release();

m_pItemIdList = NULL;
}

LPITEMIDLIST CYABFFW::ResolveCsidl(int nCsidl) const


{
// Short-circuit special case 
if (CSIDL_DESKTOP == nCsidl)
return NULL;

LPITEMIDLIST pidlRoot;
HRESULT hr = ::SHGetFolderLocation(NULL, nCsidl, NULL, 0U,
&pidlRoot);
if (FAILED(hr))

{
ASSERT(NULL == pidlRoot);
AfxThrowOleException(hr);
}

return pidlRoot; // Caller assumes responsibility
}

LPITEMIDLIST CYABFFW::ResolveFsPath(const CString &strPath) const


{
USES_CONVERSION;

# ifdef _DEBUG
DWORD dwFileAttrs = ::GetFileAttributes(strPath);
ASSERT(0xffffffff != dwFileAttrs &&
FILE_ATTRIBUTE_DIRECTORY & dwFileAttrs);
# endif // _DEBUG

HRESULT hr;
IShellFolder *pDesktop;
if ( FAILED(hr = ::SHGetDesktopFolder(&pDesktop)) )
AfxThrowOleException(hr);

// Unfortunately, T2OLE expects a non-const string, so 
LPOLESTR p2 = T2OLE(const_cast<LPTSTR>(
static_cast<LPCTSTR>(strPath)));
LPITEMIDLIST pItemIdList;
if ( FAILED(hr = pDesktop->ParseDisplayName(NULL, NULL,
p2,
NULL,
&pItemIdList,
NULL)) )

{
pDesktop->Release();
AfxThrowOleException(hr);
}

pDesktop->Release();
return pItemIdList; // Caller assumes responsibility
}


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述