Setting View and Sort in a File Open Dialog

DanRollins
CERTIFIED EXPERT
Published:
Updated:
How can I pre-select a view type of Thumbnails (or Details, etc.) when I use the Common Controls GetOpenFileName or GetSaveFileName API calls?

This question comes up occasionally here at Experts-Exchange and even a Google search will turn up lots of "answers" that say "It can't be done!"   True, there is no API to control these things, but with a bit of spelunking with Spy++ and some trial-and-error hacking around, I found that it is possible.  I created a C++ object, CFileDlgEx -- derived from the MFC CFileDialog class -- that does exactly that.  

Note: The program code here does use MFC, but it should be useful even if you are
          using Win32 API only, or Visual Basic, or any programming language that can access
          Windows API calls.

With CFileDlgEx, you can pre-select the desired view:
VwIcons           Icon View
VwList              List View
Details             Detail View ("Report view" format)
VwTiles            Tile View (info about each file)
VwThumbnails  A small Image for graphics files
...and you can even select a desired sort order:
ByName
BySize
ByType
ByModified  (Newest first, etc.)
Variations on FileDlgEx settingsHow is it done?
Microsoft must have had to do some complicated behind-the-scenes work to make the Common File Open/Save Dialogs work transparently.  The HWND of the dialog box is actually a child of the real dialog.  And once you climb up to its parent (the real dialog), you'll find that messages don't work quite as expected.  There are WM_COMMAND messages that control View Type and Arrange By, but they don't go to the dialog, they go to one of its embedded child controls -- A SHELLDLL_DefView that wraps a standard list control (a SysList32 class window).
Spy++ tells (well, whispers) the storyA sequence like:
CWnd* pParent= GetParent();
                      CWnd* pw= pParent->GetDlgItem( 0x0461 ); // SHELLDLL _DefView

Open in new window

...should get you the right window.  Even knowing all of that, you are likely to run into strange problems -- the embedded target window doesn't exist when you expect it to.  For instance, if you try to obtain the desired HWND in an OnDialogInit handler or an override of CFileDialog::OnInitDone, it will basically say "Huh?" -- the window has not been created yet.  

However, an override of OnFileNameChange does the trick.  Apparently the relevant notification gets made as a final step, just before the window is displayed --  when all the pieces are in place.  At that point, it is valid to send the WM_COMMAND messages that will control the all-important listview.

Setting the Sort Order
I discovered the WM_COMMAND parameter values by monitoring the messagges with Spy++.  The same WPARAM is used whether you click a column header (in Details view) or right-click and select an Arrange Icons By option.   But there was a little catch...

Setting the sort order turns out to be tricker than it might seem.  When you work with these settings manually (in Detail View) you only have the option of toggling the sort order, and sure enough, the available WM_COMMAND messages all mean, "Select that column and sort by it.  If already selected, then toggle the current order between ascending or descending."  That's awkward because I wanted to be able to specify a sort criteria and select the ascending or descending option.

I eventually hit upon this clever trick:
If you first sort by one column, then sort by another, the sort of the second is always Ascending (A-Z, old-to-new, etc.)  So... to pre-set Ascending, first sort by a different column, then sort by the desired column.  To pre-set Descending (which is especially useful for newest-to-oldest sorts), do the same thing but also just send the sortBy command again.

I ended up doing a "dummy sort" of ByAttributes -- a column that is not commonly displayed.  But I don't think the choice is really important... Even on extremely large directories, the Windows sorting logic appears to be nearly instantaneous.

The FileDlgEx header file:
#pragma once
                      // CFileDlgEx
                      
                      class CFileDlgEx : public CFileDialog
                      {
                          DECLARE_DYNAMIC(CFileDlgEx)
                      public:
                          CFileDlgEx(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
                              LPCTSTR lpszDefExt = NULL,
                              LPCTSTR lpszFileName = NULL,
                              DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                              LPCTSTR lpszFilter = NULL,
                              CWnd* pParentWnd = NULL);
                          virtual ~CFileDlgEx();
                      
                          typedef enum {        // enumerated values for WM_COMMAND wParam
                              VwNone=       0,
                              VwIcons=      0x7029,
                              VwList=       0x702b,
                              VwDetails=    0x702c,
                              VwTiles=      0x702e,
                              VwThumbnails= 0x7031,
                          } ViewType;
                          typedef enum {
                              ByNone=       0,
                              ByName=       0x7602,
                              BySize=       0x7603,
                              ByType=       0x7604,
                              ByModified=   0x7605,
                              ByAttributes= 0x7608,  // note, there are others...
                          } SortBy;
                      
                          virtual void OnFileNameChange( );  // the only member override
                      
                          ViewType   m_eView;              // some new class variables
                          SortBy     m_eSort;
                          bool       m_fSortDescending;
                      protected:
                          bool       m_fFirstPass;
                          DECLARE_MESSAGE_MAP()
                      };

Open in new window

The FileDlgEx CPP file:
// FileDlgEx.cpp : implementation file
                      //
                      #include "stdafx.h"
                      #include "FileDlgEx.h"
                      
                      IMPLEMENT_DYNAMIC(CFileDlgEx, CFileDialog)
                      
                      CFileDlgEx::CFileDlgEx(BOOL bOpenFileDialog, LPCTSTR lpszDefExt, LPCTSTR lpszFileName,
                              DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) :
                              CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd)
                      {
                          m_eSort=           ByNone;  // initialize our added variables
                          m_eView=           VwNone;
                          m_fSortDescending= false;
                          m_fFirstPass=      true;
                      }
                      CFileDlgEx::~CFileDlgEx(){}
                      BEGIN_MESSAGE_MAP(CFileDlgEx, CFileDialog)
                      END_MESSAGE_MAP()
                      
                      void CFileDlgEx::OnFileNameChange() 
                      {
                          CFileDialog::OnFileNameChange();
                          if ( !m_fFirstPass ) {
                              return;
                          }
                          m_fFirstPass= false;
                      
                          CWnd* pParent= GetParent();
                          CWnd* pw= pParent->GetDlgItem( 0x0461 ); // SHELLDLL _DefView
                          if ( ! IsWindow(*pw) ) {  // Failsafe (for Vista?)
                              return;
                          }
                          if ( m_eView ) {  // set the View type
                              pw->SendMessage( WM_COMMAND, m_eView, 0 );
                          }
                          if ( m_eSort ) {
                              if ( m_eView != VwDetails ) { // others are always ascending
                                  pw->SendMessage( WM_COMMAND, m_eSort, 0 );
                                  return;
                              }
                              // -- here's the trick to "toggle" to desired sort order
                              pw->SendMessage( WM_COMMAND, ByAttributes, 0 );
                              if ( m_fSortDescending ) {  // just do it twice
                                  pw->SendMessage( WM_COMMAND, m_eSort, 0 );
                              }
                              pw->SendMessage( WM_COMMAND, m_eSort, 0 );
                          }
                      }

Open in new window


To use this class, just add the two files to your project.   Use CFileDlgEx just like CFileDialog, but before calling the DoModal() member function, set values into m_eView, m_eSort, and optionally, m_fSortDescending.  The valid values for m_eView and m_eSort are in the header file.

Sample testing function:
void CFileDlgEx_testerDlg::OnBnClickedButton1()
                      {
                          CString sFilters="All Files (*.*)|*.*||";
                          CFileDlgEx cDlg(TRUE, 0,0,0,sFilters );
                          cDlg.GetOFN().lpstrInitialDir= "C:\\temp\\testFileDlgEx";
                      
                          cDlg.m_eView= CFileDlgEx::VwDetails;
                          cDlg.m_eSort= CFileDlgEx::ByType;
                          cDlg.m_fSortDescending= true;
                      
                          int n= cDlg.DoModal();
                          if ( n==IDOK ) {
                              MessageBox( cDlg.GetPathName(), "Result" );
                          }
                      }

Open in new window


Notes:
When in a mode other than Detail View, there is no sort toggling -- the sort always goes A-Z, old-to-new (try it manually by right-clicking and choosing "Arrange Icons By").  It might be possible to switch to Detail View, set the desired sort order, then switch to another view, but I had no use for that capability and I left that as an exercise to the reader.
I found no way to query the dialog to find out what type of view or sort order that the user preferred.  Thus, I have no way to remember the "current" setting and re-use it later.  In my production code, I had to provide a global preference setting in an "options" dialog, and use those settings.  If you find a way to obtain the final View Type and Sort values (i.e., after the user has made changes interactively in the dialog), please let me know.
This code has been tried and verified on WinXP and Win2000.  There's a chance that it will fail on Vista.  It probably won't crash and burn, though.
I mentioned that this technique could be used in different programming languages, but I did not specify how to do that... So, here goes:
1) Use your language/library's GetOpenFilename function.  You should find that it lets you process events that are associated with the common dialog.
2) Write a handler for the "OnFileNameChange" event.   In that handler, use Win API functions to get its parent and to get the child control of that parent that has a control ID of 0x0461 (1121, decimal).  
3) Send that control window WM_COMMAND messages using the WPARAM values I've provided.

Late note: I was given a link to VB project that sets the initial view.  If you are coding in VB, you can use the source code and techniques there and (optionally) add the additional code to pre-set the sort order based on what I've described in this article

References:

CFileDialog Class  (MFC)
http://msdn.microsoft.com/en-us/library/dk77e5e7(VS.80).aspx

GetOpenFileName Function (Win32 API)
http://msdn.microsoft.com/en-us/library/ms646927(VS.85).aspx

GetSaveFileName Function (Win32 API)
http://msdn.microsoft.com/en-us/library/ms646928(VS.85).aspx

GetParent Function (Win32 API)
http://msdn.microsoft.com/en-us/library/ms633510(VS.85).aspx

GetDlgItem Function (Win32 API)
http://msdn.microsoft.com/en-us/library/ms645481(VS.85).aspx

SendMessage Function (Win32 API)
http://msdn.microsoft.com/en-us/library/ms644950(VS.85).aspx

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
5
10,735 Views
DanRollins
CERTIFIED EXPERT

Comments (7)

The easy way to accomplish this is to:

When in the open/save dialog, right click and arrange by name/type/modified/..., then hold the Ctrl key and X out of the dialog box. Every time you use it, from closing out forward, it will present the setting you closed out with.
Paul must not have noticed the "whoosh" sound of this article going over his head.

Commented:
I want to add an edit box at the left top corner of the CFileDialog. And When user enters any string in it, I want to filter the list only with those which matches the string entered.

I do not want to use the explorer style of CFileDialog. I need to achieve the above functionality in Old Style CFileDialog.

I have tried the following way:
  When user enters any text in search field, we invoke a event handler through which search results will be displayed in the list control of CFileDialog.

Retrieve the list control and delete all elements which do not match with search input.
  CWnd *l_pWndParent = GetParent(); //From CFileDialog
  CWnd *l_pWnd = l_pWndParent->GetDlgItem(lst2);
  if(NULL == l_pWnd)
    return;
  CListCtrl* l_pListCtrl = (CListCtrl*) (l_pWnd->GetDlgItem(1))
  int l_nCount = l_pListCtrl->GetItemCount();
  for(int l_nIndex = 0; l_nIndex < l_nCount; l_nIndex++)
  {
    CString l_csItemText = l_pListCtrl->GetItemText(l_nIndex, 0);
    if (-1 == l_csItemText.Find(l_csSearchFilterString, 0))
    {
      l_pListCtrl->DeleteItem(l_nIndex);
      nCount--;
      l_nIndex--;
    }
  }

Open in new window




But somwhow it always deletes the last element irrespective of the index i pass.
Is there anyway i can get this work as i required.

Thanks
CERTIFIED EXPERT
Author of the Year 2009

Author

Commented:
My first guess is that your l_pListCtrl->GetItemText() is failing.  That is often a problem with techniques that request text across process boundaries (it's ususally not a problem when requesting a single integer).  The way to find out is to breakpoint and review the value in l_csItemText.  If, when you breakpoint on that line, you are seeing the expected text, then it might relate to the use of 8-bit vs. 16-bit text characters.

-- Dan

BTW, the best way to get an answer here at EE is to ask a question in the regular Q/A section rather than in an Article comment.

Commented:
l_pListCtrl->GetItemText() is not failing.

As you mentioned. I will put it up as a seperate question

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.