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
and stc2
are the two label ctrls edt1
and cmb1
are the lower edit and combo ctrls IDOK
and IDCANCEL
are the dialog buttons lst1
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();
UINT AddSize = 300;
const UINT cNum = 7;
CWnd *pW = GetParent();
RECT Rect;
pW->GetWindowRect(&Rect);
pW->SetWindowPos( NULL, 0, 0,
Rect.right - Rect.left,
Rect.bottom - Rect.top + AddSize,
SWP_NOMOVE );
int Control[cNum] = { stc3, stc2,
edt1, cmb1,
IDOK, IDCANCEL,
lst1 };
CWnd *c;
RECT cRect;
for (int i = 0 ; i < cNum ; i++)
{
c = pW->GetDlgItem(Control[i]);
c->GetWindowRect(&cRect);
pW->ScreenToClient(&cRect);
if (Control[i] != lst1)
c->SetWindowPos( NULL,
cRect.left, cRect.top + AddSize,
0, 0, SWP_NOSIZE );
else
c->SetWindowPos( NULL, 0, 0,
cRect.right - cRect.left,
cRect.bottom - cRect.top + AddSize,
SWP_NOMOVE );
}
if (this->m_ofn.Flags & OFN_ALLOWMULTISELECT)
{
pW->GetWindowRect(&Rect);
pW->ScreenToClient(&Rect);
RECT bRect;
bRect.bottom = Rect.bottom - 8;
bRect.top = Rect.bottom - 30;
bRect.left = Rect.left + 8;
bRect.right = Rect.left + 102;
SelAll = new MyCButton(pW);
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 )
{
CButton::OnLButtonDown(nFlags, point);
char* path = (char*) malloc(260);
p->SendMessage(CDM_GETFOLDERPATH, 260, (LPARAM) path);
CString fdpath = path;
LPOLESTR ofdpath = fdpath.AllocSysString();
LPMALLOC pMalloc = NULL;
::SHGetMalloc(&pMalloc);
LPITEMIDLIST pidFile = NULL;
LPITEMIDLIST pidFolder = NULL;
IShellFolder *sfRoot = NULL;
IShellFolder *sfFolder = NULL;
::SHGetDesktopFolder(&sfRoot);
sfRoot->ParseDisplayName(NULL, NULL, ofdpath, NULL, &pidFolder, NULL);
sfRoot->BindToObject(pidFolder, NULL, IID_IShellFolder, (void**) &sfFolder);
IShellBrowser* pShBrowser =
(IShellBrowser*)::SendMessage( p->m_hWnd, WM_GETISHELLBROWSER, 0, 0 );
IShellView* pShView = NULL;
pShBrowser->QueryActiveShellView( &pShView );
LPENUMIDLIST pidList;
sfFolder->EnumObjects(NULL, SHCONTF_NONFOLDERS |
SHCONTF_INCLUDEHIDDEN
, &pidList);
pidList->Reset();
while( S_FALSE != (pidList->Next(1, &pidFile, NULL)) )
pShView->SelectItem(pidFile, SVSI_SELECT);
pidList->Release();
pShView->UIActivate(SVUIA_ACTIVATE_FOCUS);
pMalloc->Free(pidFile);
pMalloc->Free(pidFolder);
pMalloc->Release();
}
I have a keen interest in IT Security, Internet applications, and systems/embedded development. My recent research interests have included secure networks, models of trust, trusted agents, information exchange, and software development methodologies.