Resizing subclassed CFileDialog and adding a 'Select All' button






4.10/5 (8 votes)
This article shows how to subclass the CFileDialog dialog and add a 'Select All' button
Introduction
This article will demonstrate how to resize the instance of a subclassed CFileDialog
and then add a 'SelectAll' button that will select all files (but not folders)
in the currently displayed folder. The article assumes that you already know
how to subclass MFC provided classes and deal with the associated issues. We
therefore will concentrate on how to resize the dialog and re-layout the controls
on it (so that they properly cover the enlarged dialog window area). Then, we
will show how to add the button, capture the user clicks on it and select all
currently displayed files in response.
What needs to be done
What we are trying to accomplish is the following
- Resize our
CFileDialog
derived dialog instance - Rearrange the existing controls on our dialog according to the new dialog window size
- Add a 'Select All' button control at the bottom of the dialog
- Capture user clicks on that button
- Respond to such clicks by selecting all currently displayed files in the file browser control
As you will see the last task is the hardest.
In general, when implementing a custom dialog based on an existing MFC common dialog there are two basic ways to go about it:
- Import the common dialog template as a dialog resource and use the resource editor and class wizard in customizing the dialog and adding functionality.
- Directly subclass from the common dialog class and deal with various added issues.
The second approach (which is the one demonstrated in this article) has the added advantage that if the common dialog class changes in the future, your applications gets the advantages of the new class without any extra work.
The issues
In resizing a subclassed CFileDialog
instance and adding a button
to it that will select all files currently displayed there are 3 major issues
that need to be resolved:
- Accessing each of the various controls on the dialog and moving/resizing them appropriately.
- Obtaining the
IShellBrowser
interface of the dialog's folder view control - Obtaining the full path of the currently displayed folder
Issue 1
The resource names of the controls in the CFileDialog
can be found in the Dlgs.h
file that resides in the same directory as the common dialog templates (i.e.
The 'Include' directory of your VC++ CD or Platform SDK CD). You need to import
"Dlgs.h" into your project in order to be able to access the controls
in the dialog window. In the "open file" dialog variant shown in the
sample picture above there are 6 controls:
stc3
andstc2
are the two label ctrlsedt1
andcmb1
are the lower edit and combo ctrlsIDOK
andIDCANCEL
are the dialog buttonslst1
is the file browser control (that also owns the upper edit and combo style controls)
Using these names and GetDlgCtrl()
calls on the parent window you can obtain
pointers to the controls.
Issue 2
The lst1
control of the dialog cannot be treated as a CListCtrl
control because
it simply is not although it looks like one and is declared as one in the CFileDialog
template (and this is why attempts to, for instance, get the list count of the
control in this way will always result to the value of zero). This control is
really a file browser (or folder view control) just like the one our well known
file explorer is built around.
Obtaining the IShellBrowser
COM interface of the control is not
straight forward (or at least I could not find a more straight forward way than
the one I finally used). I initially tried to get the IUnknown
interface using a pointer to the control but the interface simply could not
get fetched this way.
Apparently, the only way to get the IShellBrowser
interface is
to send the undocumented WM_GETISHELLBROWSER
message to the dialog's
parent. WM_GETISHELLBROWSER
should be explicitly defined as WM_USER
+ 7
since it is not yet defined in the standard header files. Here is
a snippet of the code doing that:
#define WM_GETISHELLBROWSER (WM_USER + 7) IShellBrowser* pShBrowser = (IShellBrowser*)::SendMessage( p->m_hWnd, WM_GETISHELLBROWSER, 0, 0 );
Issue 3
In order to get the full path of the currently displayed dialog we again need
to send a message to the dialog's parent. This is the CDM_GETFOLDERPATH
message. Assuming that p
is a pointer to the dialog's parent (obtained
through a GetParent()
call), here is the code that would do just
that:
char* path = (char*) malloc(260); p->SendMessage(CDM_GETFOLDERPATH, 260, (LPARAM) path);
Other Tasks
In order to capture the extra button's clicks I have subclassed my button from
CButton
and overridden its OnLButtonDown()
method. Of course the relevant entry
should be added to the message map of the button class:
BEGIN_MESSAGE_MAP(MyCButton, CButton) ON_WM_LBUTTONDOWN( ) END_MESSAGE_MAP()
Also note that in order to be able to select multiple files in a CFileDialog
derived dialog the underlying CFileDialog should be initialized with the OFN_ALLOWMULTISELECT
flag set.
Code
The code listing only includes the basic and important portions of the code. This is not a full application.
The OnInitDialog()
override
The first part is the listing of the subclassed (from CFileDialog
)
dialog's overridden OnInitDialog()
method. We use this method to
resize the dialog's window and then rearrange the position or size of the contained
controls. Also, it is here that we initialize and add the extra button, which
is also a member of our 'file open' dialog class (but does not really need to
be).
///////////////////////////////////////////////////////////////////////////////// BOOL MyFileDialog::OnInitDialog() { CFileDialog::OnInitDialog(); //Call base class method first UINT AddSize = 300; //Indicates how much bigger //the dialog window will be const UINT cNum = 7; //Number of controls //in subclassed CFileDialog CWnd *pW = GetParent(); // Get a pointer to the parent window. RECT Rect; pW->GetWindowRect(&Rect); //Get rect of parent window // Change the size of parent window according to pW->SetWindowPos( NULL, 0, 0, Rect.right - Rect.left, Rect.bottom - Rect.top + AddSize, SWP_NOMOVE ); //Create an array that contains the control names int Control[cNum] = { stc3, stc2, //The two label ctrls edt1, cmb1, //Edit and combo ctrls IDOK, IDCANCEL, //The dialog buttons lst1 }; //The Explorer window CWnd *c; //c will hold the address of each control in the dialog RECT cRect; //The control's rect (coords) //Move all controls to a compensate for the new parent size for (int i = 0 ; i < cNum ; i++) { c = pW->GetDlgItem(Control[i]); //Fetch control c->GetWindowRect(&cRect); //Get control's rect pW->ScreenToClient(&cRect); //Convert to child window coords if (Control[i] != lst1) //For any control except the lst1 //move the control appropriately c->SetWindowPos( NULL, cRect.left, cRect.top + AddSize, 0, 0, SWP_NOSIZE ); else //size the folder view control appropriately c->SetWindowPos( NULL, 0, 0, cRect.right - cRect.left, cRect.bottom - cRect.top + AddSize, SWP_NOMOVE ); } //If this is a multi-select dialog //then create the 'Select All' button if (this->m_ofn.Flags & OFN_ALLOWMULTISELECT) { pW->GetWindowRect(&Rect); //Get latest parent rect pW->ScreenToClient(&Rect); //Make rect values relative //to parent window //Position the 'Select All' button RECT bRect; bRect.bottom = Rect.bottom - 8; bRect.top = Rect.bottom - 30; bRect.left = Rect.left + 8; bRect.right = Rect.left + 102; //Create new subclassed CButton //holding pointer to parent window SelAll = new MyCButton(pW); //Create() requires relative rect struct for child window creation SelAll->Create("Select All", BS_PUSHBUTTON, bRect, pW, 4711); SelAll->ShowWindow(SW_SHOW); } return TRUE; } /////////////////////////////////////////////////////////////////////////////////
The OnLButtonDown()
override
Here is the code listing for the CButton
derived class' overridden OnLButtonDown()
method:
///////////////////////////////////////////////////////////////////////////////// afx_msg void MyCButton::OnLButtonDown( UINT nFlags, CPoint point ) { //call base class method first CButton::OnLButtonDown(nFlags, point); //Get the full path of the currently displayed folder //in the CFileDialog subclass instance. Member var p //holds the address of the dialog instance char* path = (char*) malloc(260); p->SendMessage(CDM_GETFOLDERPATH, 260, (LPARAM) path); CString fdpath = path; //Hold the full path in a CString //Copy the CString contents in a system string LPOLESTR ofdpath = fdpath.AllocSysString(); LPMALLOC pMalloc = NULL; ::SHGetMalloc(&pMalloc); //Get pointer to shell alloc LPITEMIDLIST pidFile = NULL; //Will hold single item //PIDL to a file LPITEMIDLIST pidFolder = NULL; //Will hold full PIDL //to the current folder IShellFolder *sfRoot = NULL; //Interface to shell space root IShellFolder *sfFolder = NULL; //Interface to current folder ::SHGetDesktopFolder(&sfRoot); //Get shell namespace's root //Get PIDL of current folder by parsing its full path sfRoot->ParseDisplayName(NULL, NULL, ofdpath, NULL, &pidFolder, NULL); //Since we now have the current folder's PIDL //we can bind it's IShellFolder Interface to //our sfFolder pointer sfRoot->BindToObject(pidFolder, NULL, IID_IShellFolder, (void**) &sfFolder); //We now need to get a hold of the IShellBrowser interface as exposed //by the subclassed CFileDialog instance's folder view COM server. //The easiest way is to send th undocumended WM_GETISHELLBROWSER message //(See Q157247 in MS knowledge base) IShellBrowser* pShBrowser = (IShellBrowser*)::SendMessage( p->m_hWnd, WM_GETISHELLBROWSER, 0, 0 ); //Get the current view's IShellView interface //from the IShellBrowser interface IShellView* pShView = NULL; pShBrowser->QueryActiveShellView( &pShView ); LPENUMIDLIST pidList; //PIDL enumeration that will hold //all relative PIDLs of files in //the current folder //Enumerate file objects including hidden ones //and excluding folders sfFolder->EnumObjects(NULL, SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN , &pidList); pidList->Reset(); //Make sure we are at the //start of the enumeration //Select all items in the current view //using each file PIDL in current folder's enumeration while( S_FALSE != (pidList->Next(1, &pidFile, NULL)) ) pShView->SelectItem(pidFile, SVSI_SELECT); //Release the enumeration pidList->Release(); //Move focus to the folder view so that //the selected items show properly pShView->UIActivate(SVUIA_ACTIVATE_FOCUS); //Free shell allocated memory pMalloc->Free(pidFile); pMalloc->Free(pidFolder); pMalloc->Release(); } /////////////////////////////////////////////////////////////////////////////////