Introduction
The UI of the file open dialog has always fascinated me. I have customized it according to my needs in one of my previous articles. Its Places Bar looks very neat. Its inclusion in the file open dialog makes it easier to use. I have tried the same thing in this article: to make SHBrowseForFolder
look better and make it more useful to the user. Here, I am adding the same Places Bar in this dialog box.
How it Works
First of all, here is the code which shows the dialog:
CoInitialize(NULL);
BROWSEINFO bi;
ZeroMemory(&bi, sizeof(bi));
TCHAR szDisplayName[MAX_PATH];
szDisplayName[0] = '\0';
bi.hwndOwner = NULL;
bi.pidlRoot = NULL;
bi.pszDisplayName = szDisplayName;
bi.lpszTitle = TEXT("Browsing");
bi.ulFlags = BIF_EDITBOX | BIF_VALIDATE ;
bi.lpfn = BrowseCallbackProc;
bi.lParam = NULL;
bi.iImage = 0;
SHBrowseForFolder(&bi);
CoUninitialize();
In the above code, everything is normal. I am just showing the dialog, but here, I also set the callback function for the dialog, so I can know what is happening at any time and do customizing work at the proper time.
Handling the Callback Function
I handled this function because it is easier to take advantage of this function using hooks. You can see my article on working with hooks here. In this callback function, first, I handle the BFFM_INITIALIZED
message which is sent when the dialog box is complete in memory but not yet shown. It is the best time to do any modification or customization according to your needs. I also do the same here, and make the SHBrowseForFolder
dialog a bit fatter so it can have the appropriate place to reside with my toolbar over it. Here is the code that creates my toolbar:
hwndTB = CreateWindowEx(WS_EX_NOPARENTNOTIFY TOOLBARCLASSNAME, (LPSTR) NULL,
WS_CHILD |WS_VISIBLE|WS_TABSTOP| CCS_TOP|CCS_NODIVIDER |
CCS_NOPARENTALIGN |CCS_NORESIZE| TBSTYLE_FLAT|TBSTYLE_TRANSPARENT|
TBSTYLE_WRAPABLE|TBSTYLE_TOOLTIPS|TBSTYLE_CUSTOMERASE,
pt.x-40,pt.y+55,84,272,hwnd, NULL, NULL, NULL);
Another message which I handle in this callback is BFFM_SELCHANGED
. This is sent whenever the user changes the selection in the dialog. Here, I update my toolbar to update itself according to the selection. E.g., whenever the user selects "My Documents" from the tree control of the SHBrowseForFolder
dialog, the Toolbar button which has "My Documents" written over it will be pressed.
char Path[MAX_PATH];
SHGetPathFromIDList((LPCITEMIDLIST)lParam, Path);
LPITEMIDLIST pidl ,pidl2;
pidl2 = (LPITEMIDLIST)lParam;
SHGetSpecialFolderLocation(NULL,CSIDL_DESKTOP, &pidl);
if(ILIsEqual(pidl,pidl2))
{::SendMessage(hwndTB,TB_CHECKBUTTON,(WPARAM) ID_FIRST_BUTTON,(LPARAM)true);
return 0;}
SHGetSpecialFolderLocation(NULL,CSIDL_PERSONAL, &pidl);
if(ILIsEqual(pidl,pidl2) || ILIsParent(pidl,pidl2,false))
{::SendMessage(hwndTB,TB_CHECKBUTTON,(WPARAM) ID_SECOND_BUTTON,(LPARAM)true);
return 0;}
SHGetSpecialFolderLocation(NULL,CSIDL_DRIVES , &pidl);
if(ILIsEqual(pidl,pidl2) || ILIsParent(pidl,pidl2,false))
{::SendMessage(hwndTB,TB_CHECKBUTTON,(WPARAM) ID_THIRD_BUTTON,(LPARAM)true);
return 0;}
SHGetSpecialFolderLocation(NULL,CSIDL_NETWORK , &pidl);
if(ILIsEqual(pidl,pidl2) || ILIsParent(pidl,pidl2,false))
{::SendMessage(hwndTB,TB_CHECKBUTTON,(WPARAM) ID_FOURTH_BUTTON,(LPARAM)true);
return 0;
Watching Messages for More Customizations
Actually, as you see, the file open dialog's Places Bar has a different background than the dialog itself. So, to do this and to handle the messages which my Toolbar will originate, I must watch the messages of the SHBrowseForFolder
dialog. One way to do this is to use hooks, but here, I am trying another method: I just grab the default dialogProc
, takes its pointer, and place my own dialogproc here. In this dialogproc, I only handle those messages which interest me, and throw every thing else to the default dialogProc
.
Changing the Background
Here, I handle the NM_CUSTOMDRAW
notification:
case NM_CUSTOMDRAW :
if( pcd->nmcd.hdr.hwndFrom == hwndTB)
{
switch(pcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT :
FillRect(pcd->nmcd.hdc, &pcd->nmcd.rc,hBkBrush);
FrameRect(pcd->nmcd.hdc, &pcd->nmcd.rc,
(HBRUSH)GetStockObject(LTGRAY_BRUSH));
SelectObject(pcd->nmcd.hdc,GetStockObject(WHITE_PEN));
MoveToEx(pcd->nmcd.hdc, pcd->nmcd.rc.right, pcd->nmcd.rc.top,NULL);
LineTo(pcd->nmcd.hdc,pcd->nmcd.rc.left,pcd->nmcd.rc.top);
LineTo(pcd->nmcd.hdc,pcd->nmcd.rc.left,pcd->nmcd.rc.bottom);
SetWindowLong(hwnd , DWL_MSGRESULT, CDRF_NOTIFYITEMDRAW );
return true;
case CDDS_ITEMPREPAINT :
SetWindowLong(hwnd , DWL_MSGRESULT,CDRF_NOTIFYPOSTPAINT );
return true;
case CDDS_ITEMPOSTPAINT :
int index = pcd->nmcd.lItemlParam;
ImageList_Draw(hIL, index, pcd->nmcd.hdc, 24,11+(index*68),
ILD_TRANSPARENT);
SetWindowLong(hwnd , DWL_MSGRESULT,CDRF_NOTIFYPOSTPAINT );
return true;
}
}
break;
ToolTips
Here is the code:
case TTN_GETDISPINFO :
LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO) lParam;
switch(lpnmtdi->hdr.idFrom)
{
case ID_FIRST_BUTTON :
lpnmtdi->lpszText = MAKEINTRESOURCE(IDS_STRING_DESKTOP_TT);
break;
case ID_SECOND_BUTTON :
lpnmtdi->lpszText = MAKEINTRESOURCE(IDS_STRING_MYDOCU_TT);
break;
case ID_THIRD_BUTTON :
lpnmtdi->lpszText = MAKEINTRESOURCE(IDS_STRING_MYCOMP_TT);
break;
case ID_FOURTH_BUTTON :
lpnmtdi->lpszText = MAKEINTRESOURCE(IDS_STRING_MYNET_TT);
break ;
}
break;
My Toolbar Changes the Selection in the SHBrowseForFolder Dialog
Yes, everything is here:
HWND htree = ::FindWindowEx(hwnd,NULL,"SysTreeView32",NULL);
HWND hedit = ::FindWindowEx(hwnd,NULL,"Edit",NULL);
switch(LOWORD(wParam))
{
case ID_FIRST_BUTTON:
{
HTREEITEM hRoot,hDesktopItem;
hRoot = TreeView_GetRoot(htree);
TreeView_SelectSetFirstVisible(htree,hRoot, TVGN_FIRSTVISIBLE);
TVITEM pitem;
pitem.hItem = hRoot;
pitem.mask = TVIF_TEXT;
char buffer[100];
pitem.pszText = buffer;
pitem.cchTextMax = 99;
TreeView_GetItem(htree,&pitem);
::SendMessage(hedit,WM_SETTEXT,(WPARAM) 0,(LPARAM)pitem.pszText);
break;
Updates
I will try to update this article more. And, my blog is an ongoing process in this direction, which I keep updating with my VC++ tips.