Click here to Skip to main content
15,896,359 members
Articles / Desktop Programming / MFC

XFileDialog - Customizing CFileDialog

Rate me:
Please Sign up or sign in to vote.
4.87/5 (24 votes)
23 Jun 2003CPOL5 min read 254.8K   16.9K   81  
XFileDialog demonstrates how to customize CFileDialog by adding a MRU combobox for folders.
// XFileDialog.cpp  Version 1.1
//
// Author:  Hans Dietrich
//          hdietrich2@hotmail.com
//
// Description:
//     XFileDialog implements CXFileDialog that uses the new Windows
//     2000 style open/save dialog.
//
// History
//     Version 1.1 - 2003 June 25
//     - Fixed some focus bugs;  added check for directory existence
//
//     Version 1.0 - 2003 June 24
//     - Initial public release
//
// This code is based partially on Paul DiLascia's Aug 2000 MSDN Magazine article
//     "Windows 2000 File Dialog Revisited"
//     http://msdn.microsoft.com/msdnmag/issues/0800/c/
//
// This software is released into the public domain.  You are free to use it
// in any way you like.
//
// This software is provided "as is" with no expressed or implied warranty.
// I accept no liability for any damage or loss of business that this software
// may cause.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <afxpriv.h>
#include "XFileDialog.h"
#include "XFileDialogRes.h"
#include "io.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////////
// CXFileDialog

IMPLEMENT_DYNAMIC(CXFileDialog, CFileDialog)

BEGIN_MESSAGE_MAP(CXFileDialog, CFileDialog)
	//{{AFX_MSG_MAP(CXFileDialog)
	ON_WM_SIZE()
	ON_CBN_SELENDOK(IDC_MRU_COMBO, OnSelendokMruCombo)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// ctor
CXFileDialog::CXFileDialog(BOOL bOpenFileDialog, LPCTSTR lpszDefExt,
	LPCTSTR lpszFileName, DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) :
	CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,
		dwFlags, lpszFilter, pParentWnd)
{
	m_dwFlags              = dwFlags;
	m_eOsVersion           = XFILEDIALOG_AUTO_DETECT_OS_VERSION;
	m_strTitle             = _T("");
	m_nIdFileNameStatic    = 1090;
	m_nIdFileNameCombo     = 1148;	// or 1152
	m_nIdFilesOfTypeStatic = 1089;
	m_nIdFilesOfTypeCombo  = 1136;
	m_nIdReadOnly          = 1040;

	SetTemplate(0, _T("IDD_XFILEDIALOG"));
}

///////////////////////////////////////////////////////////////////////////////
// DoDataExchange
void CXFileDialog::DoDataExchange(CDataExchange* pDX)
{
	CFileDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CXFileDialog)
	DDX_Control(pDX, IDC_MRU_COMBO, m_cmbRecentFolders);
	//}}AFX_DATA_MAP
}

///////////////////////////////////////////////////////////////////////////////
// DoModal override copied mostly from MFC, with modification to use
// m_ofnEx instead of m_ofn.
int CXFileDialog::DoModal()
{
	TRACE(_T("in CXFileDialog::DoModal\n"));

	ASSERT_VALID(this);
	ASSERT(m_ofn.Flags & OFN_ENABLEHOOK);
	ASSERT(m_ofn.lpfnHook != NULL); // can still be a user hook

	// zero out the file buffer for consistent parsing later
	ASSERT(AfxIsValidAddress(m_ofn.lpstrFile, m_ofn.nMaxFile));
	DWORD nOffset = lstrlen(m_ofn.lpstrFile)+1;
	ASSERT(nOffset <= m_ofn.nMaxFile);
	memset(m_ofn.lpstrFile+nOffset, 0, (m_ofn.nMaxFile-nOffset)*sizeof(TCHAR));

	// WINBUG: This is a special case for the file open/save dialog,
	// which sometimes pumps while it is coming up but before it has
	// disabled the main window.
	HWND hWndFocus = ::GetFocus();
	BOOL bEnableParent = FALSE;
	m_ofn.hwndOwner = PreModal();
	AfxUnhookWindowCreate();
	if (m_ofn.hwndOwner != NULL && ::IsWindowEnabled(m_ofn.hwndOwner))
	{
		bEnableParent = TRUE;
		::EnableWindow(m_ofn.hwndOwner, FALSE);
	}

	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	ASSERT(pThreadState->m_pAlternateWndInit == NULL);

	if (m_ofn.Flags & OFN_EXPLORER)
		pThreadState->m_pAlternateWndInit = this;
	else
		AfxHookWindowCreate(this);

	memset(&m_ofnEx, 0, sizeof(m_ofnEx));
	memcpy(&m_ofnEx, &m_ofn, sizeof(m_ofn));

	if (IsWin2000())
	{
		m_ofnEx.lStructSize = sizeof(m_ofnEx);
	}

	int nResult;
	if (m_bOpenFileDialog)
		nResult = ::GetOpenFileName((OPENFILENAME*)&m_ofnEx);
	else
		nResult = ::GetSaveFileName((OPENFILENAME*)&m_ofnEx);

	memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));
	m_ofn.lStructSize = sizeof(m_ofn);

	if (nResult)
		ASSERT(pThreadState->m_pAlternateWndInit == NULL);
	pThreadState->m_pAlternateWndInit = NULL;

	// WINBUG: Second part of special case for file open/save dialog.
	if (bEnableParent)
		::EnableWindow(m_ofnEx.hwndOwner, TRUE);
	if (::IsWindow(hWndFocus))
		::SetFocus(hWndFocus);

	PostModal();

	return nResult ? nResult : IDCANCEL;
}

///////////////////////////////////////////////////////////////////////////////
// OnNotify - when the open dialog sends a notification, copy m_ofnEx to m_ofn
// in case handler function is expecting updated information in the
// OPENFILENAME struct.
//
BOOL CXFileDialog::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));
	m_ofn.lStructSize = sizeof(m_ofn);

	return CFileDialog::OnNotify( wParam, lParam, pResult);
}

///////////////////////////////////////////////////////////////////////////////
// OnFileNameOK
BOOL CXFileDialog::OnFileNameOK()
{
	TRACE(_T("CXFileDialog::OnFileNameOK\n"));

	// save current folder to history list
	CString strFolder = GetPath();
	m_cmbRecentFolders.AddString(strFolder);
	m_cmbRecentFolders.SetWindowText(strFolder);
	m_cmbRecentFolders.SaveHistory(TRUE);

	return CFileDialog::OnFileNameOK();
}

///////////////////////////////////////////////////////////////////////////////
// OnInitDialog
BOOL CXFileDialog::OnInitDialog()
{
	TRACE(_T("in CXFileDialog::OnInitDialog\n"));

	CFileDialog::OnInitDialog();

	// load folder history - check if valid folder
	m_cmbRecentFolders.SetCheckAccess(TRUE);
	m_cmbRecentFolders.LoadHistory(_T("FolderHistory"), _T("Folder"));

	CString str;
	if (m_cmbRecentFolders.GetCount() > 0)
	{
		m_cmbRecentFolders.GetLBText(0, str);
		m_cmbRecentFolders.SetWindowText(str);
	}

	// save left margin for combo boxes
	CWnd *pWndFileNameCombo = GetParent()->GetDlgItem(m_nIdFileNameCombo);
	if (!pWndFileNameCombo)
	{
		// the File Name control ID is either 1148 or 1152, depending on whether
		// it is used as an edit box (1152) or a combo box (1148).  If the OS
		// version is < 5, it is 1152; if >= 5, it is 1148.  It will also be
		// 1152 if the registry key
		//    HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\comdlg32\NoFileMru
		// is set to 1.  For convenience, we will always refer to this control
		// as a combobox.
		m_nIdFileNameCombo = 1152;
		pWndFileNameCombo = GetParent()->GetDlgItem(m_nIdFileNameCombo);
	}
	if (pWndFileNameCombo)
	{
		CRect rectFileNameCombo;
		pWndFileNameCombo->GetWindowRect(&rectFileNameCombo);
		GetParent()->ScreenToClient(&rectFileNameCombo);
		if (IsWin2000())
			m_nComboLeftMargin = rectFileNameCombo.left;
		else
			m_nComboLeftMargin = rectFileNameCombo.left + 8;
	}
	else
	{
		ASSERT(FALSE);
		m_nComboLeftMargin = 90;
	}

	// set title if specified
	if (!m_strTitle.IsEmpty())
		GetParent()->SetWindowText(m_strTitle);

	return TRUE;	// return TRUE unless you set the focus to a control
					// EXCEPTION: OCX Property Pages should return FALSE
}

///////////////////////////////////////////////////////////////////////////////
// OnSize - position and size controls on default and new dialogs
void CXFileDialog::OnSize(UINT nType, int cx, int cy)
{
	CFileDialog::OnSize(nType, cx, cy);

	// original dialog box
	CWnd *pWndParent = GetParent();
	ASSERT(pWndParent);
	if (!pWndParent)
		return;

	// File name combobox
	CWnd *pWndFileNameCombo = GetParent()->GetDlgItem(m_nIdFileNameCombo);
	if (!pWndFileNameCombo)
		return;
	CRect rectFileNameCombo;
	pWndFileNameCombo->GetWindowRect(&rectFileNameCombo);
	GetParent()->ScreenToClient(&rectFileNameCombo);
	rectFileNameCombo.left = m_nComboLeftMargin;
	pWndFileNameCombo->MoveWindow(&rectFileNameCombo);

	// File name static
	CWnd *pWndFileNameStatic = GetParent()->GetDlgItem(m_nIdFileNameStatic);
	ASSERT(pWndFileNameStatic);
	CRect rectFileNameStatic;
	pWndFileNameStatic->GetWindowRect(&rectFileNameStatic);
	GetParent()->ScreenToClient(&rectFileNameStatic);
	rectFileNameStatic.right = m_nComboLeftMargin - 2;
	pWndFileNameStatic->MoveWindow(&rectFileNameStatic);

	// Files of type combobox
	CWnd *pWndFilesOfTypeCombo = GetParent()->GetDlgItem(m_nIdFilesOfTypeCombo);
	ASSERT(pWndFilesOfTypeCombo);
	CRect rectFilesOfTypeCombo;
	pWndFilesOfTypeCombo->GetWindowRect(&rectFilesOfTypeCombo);
	GetParent()->ScreenToClient(&rectFilesOfTypeCombo);
	rectFilesOfTypeCombo.left = m_nComboLeftMargin;
	pWndFilesOfTypeCombo->MoveWindow(&rectFilesOfTypeCombo);

	// Files of type static
	CWnd *pWndFilesOfTypeStatic = GetParent()->GetDlgItem(m_nIdFilesOfTypeStatic);
	ASSERT(pWndFilesOfTypeStatic);
	CRect rectFilesOfTypeStatic;
	pWndFilesOfTypeStatic->GetWindowRect(&rectFilesOfTypeStatic);
	GetParent()->ScreenToClient(&rectFilesOfTypeStatic);
	rectFilesOfTypeStatic.right = m_nComboLeftMargin - 2;
	pWndFilesOfTypeStatic->MoveWindow(&rectFilesOfTypeStatic);

	int nDeltaYCombo = rectFilesOfTypeCombo.top - rectFileNameCombo.bottom;
	int nDeltaYStatic = rectFilesOfTypeStatic.top - rectFileNameStatic.bottom;

	// Recent folders combobox
	CWnd *pWndRecentFoldersCombo = GetDlgItem(IDC_MRU_COMBO);
	ASSERT(pWndRecentFoldersCombo);
	CRect rectRecentFoldersCombo;
	pWndRecentFoldersCombo->GetWindowRect(&rectRecentFoldersCombo);
	GetParent()->ScreenToClient(&rectRecentFoldersCombo);
	rectRecentFoldersCombo.left = rectFilesOfTypeCombo.left;
	rectRecentFoldersCombo.right = rectFilesOfTypeCombo.right;
	int h = rectRecentFoldersCombo.Height();
	rectRecentFoldersCombo.top = rectFilesOfTypeCombo.bottom + nDeltaYCombo;
	rectRecentFoldersCombo.bottom = rectRecentFoldersCombo.top + h;
	pWndRecentFoldersCombo->MoveWindow(&rectRecentFoldersCombo);

	// Recent folders static
	CWnd *pWndRecentFoldersStatic = GetDlgItem(IDC_MRU_CAPTION);
	ASSERT(pWndRecentFoldersStatic);
	CRect rectRecentFoldersStatic;
	pWndRecentFoldersStatic->GetWindowRect(&rectRecentFoldersStatic);
	GetParent()->ScreenToClient(&rectRecentFoldersStatic);
	rectRecentFoldersStatic.left = rectFilesOfTypeStatic.left;
	rectRecentFoldersStatic.right = rectFilesOfTypeStatic.right;
	h = rectRecentFoldersStatic.Height();
	rectRecentFoldersStatic.top = rectFilesOfTypeStatic.bottom + nDeltaYStatic;
	rectRecentFoldersStatic.bottom = rectRecentFoldersStatic.top + h;
	pWndRecentFoldersStatic->MoveWindow(&rectRecentFoldersStatic);

	// Read only checkbox
	if ((m_dwFlags & OFN_HIDEREADONLY) == 0)
	{
		CWnd *pWndReadOnlyCheckbox = GetParent()->GetDlgItem(m_nIdReadOnly);
		ASSERT(pWndReadOnlyCheckbox);
		CRect rectReadOnlyCheckbox;
		pWndReadOnlyCheckbox->GetWindowRect(&rectReadOnlyCheckbox);
		GetParent()->ScreenToClient(&rectReadOnlyCheckbox);
		rectReadOnlyCheckbox.left = rectFilesOfTypeCombo.left;
		rectReadOnlyCheckbox.right = rectFilesOfTypeCombo.right;
		int h = rectReadOnlyCheckbox.Height();
		rectReadOnlyCheckbox.top = rectRecentFoldersCombo.bottom + nDeltaYCombo;
		rectReadOnlyCheckbox.bottom = rectReadOnlyCheckbox.top + h;
		pWndReadOnlyCheckbox->MoveWindow(&rectReadOnlyCheckbox);
	}


	///////////////////////////////////////////////////////////////////////////
	// following code is necessary to prevent having the
	// Recent folders combobox highlighted
	static BOOL bFirstTime = TRUE;
	if (bFirstTime)
	{
		bFirstTime = FALSE;
		m_cmbRecentFolders.SetFocus();
	}
	else
	{
		m_cmbRecentFolders.SetEditSel(-1, 0);
		pWndFileNameCombo->SetFocus();
	}
}

///////////////////////////////////////////////////////////////////////////////
// GetPath
CString CXFileDialog::GetPath()
{
	CString str = _T("");

	str = GetPathName();
	if (!str.IsEmpty())
	{
		int index = str.ReverseFind(_T('\\'));
		if (index != -1)
		{
			str = str.Left(index);
		}
	}

	TRACE(_T("str=%s\n"), str);
	return str;
}

///////////////////////////////////////////////////////////////////////////////
// OnSelendokMruCombo
void CXFileDialog::OnSelendokMruCombo()
{
	TRACE(_T("in CXFileDialog::OnSelendokMruCombo\n"));

	if (!::IsWindow(m_cmbRecentFolders.m_hWnd))
		return;

	CString strFolder;
	int index = m_cmbRecentFolders.GetCurSel();
	if (index >= 0)
	{
		m_cmbRecentFolders.GetLBText(index, strFolder);
		if (!strFolder.IsEmpty() && (_taccess(strFolder, 00) == 0))
		{
			// change to new folder, leave file name control unchanged
			TCHAR szText[_MAX_PATH*2];
			memset(szText, 0, sizeof(szText));
			GetParent()->GetDlgItem(m_nIdFileNameCombo)->SendMessage(WM_GETTEXT,
							sizeof(szText)/sizeof(TCHAR)-1, (LPARAM)szText);
			GetParent()->SendMessage(CDM_SETCONTROLTEXT, m_nIdFileNameCombo,
							(LPARAM)(LPCTSTR)strFolder);
			GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED),
							(LPARAM)GetDlgItem(IDOK)->GetSafeHwnd());
			GetParent()->SendMessage(CDM_SETCONTROLTEXT, m_nIdFileNameCombo,
							(LPARAM)szText);
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// IsWin2000
BOOL CXFileDialog::IsWin2000()
{
	if (GetOsVersion() == XFILEDIALOG_OS_VERSION_4)
		return FALSE;
	else if (GetOsVersion() == XFILEDIALOG_OS_VERSION_5)
		return TRUE;

	// Try calling GetVersionEx using the OSVERSIONINFOEX structure,
	// which is supported on Windows 2000.
	//
	// If that fails, try using the OSVERSIONINFO structure.

	OSVERSIONINFOEX osvi;
	ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

	BOOL bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi);
	if (!bOsVersionInfoEx)
	{
		// If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.

		osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		if (!GetVersionEx((OSVERSIONINFO *) &osvi))
			return FALSE;
	}

	switch (osvi.dwPlatformId)
	{
		case VER_PLATFORM_WIN32_NT:
			if (osvi.dwMajorVersion >= 5)
				return TRUE;
			break;
	}
	return FALSE;
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions