Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Resizing subclassed CFileDialog and adding a 'Select All' button

0.00/5 (No votes)
1 Jan 2002 1  
This article shows how to subclass the CFileDialog dialog and add a 'Select All' button

Resized subclassed CFileDialog with added '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:

  1. Accessing each of the various controls on the dialog and moving/resizing them appropriately.
  2. Obtaining the IShellBrowser interface of the dialog's folder view control
  3. 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(); //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();
}
/////////////////////////////////////////////////////////////////////////////////

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here