<

Setting View and Sort in a File Open Dialog

Published on
15,630 Points
8,630 Views
5 Endorsements
Last Modified:
Approved
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
Author:DanRollins
Enjoy this complimentary article view.

Get unlimited access to our entire library of technical procedures, guides, and tutorials written by certified industry professionals.

Get 7 days free
Click here to view the full article

Using this article for work? Experts Exchange can benefit your whole team.

Learn More
COLLABORATE WITH CERTIFIED PROFESSIONALS
Experts Exchange is a tech solutions provider where users receive personalized tech help from vetted certified professionals. These industry professionals also write and publish relevant articles on our site.
Ask questions about what you read
If you have a question about something within an article, you can receive help directly from the article author. Experts Exchange article authors are available to answer questions and further the discussion.
Learn from the best.