Customizing MFC Document Recovery

Introduction

The promise of Microsoft Foundation Class (MFC) is delivering the boiler-plate functionality required to build a professional Windows C++ application with minimal effort while allowing .NET developers to customize aspects of MFC behavior where it makes sense. This article demonstrates how MFC 10 delivers on this promise, showing how the new document auto-recovery behavior of MFC can be simply customized.
Windows Application Restart and Recovery (ARR) provides a consistent and robust experience for dealing with application crashes and the auto-saving and recovery of a users documents. It also provides a complete implementation of ARR functionality, my previous article covered the basics of this implementation, Improve Application Quality with Microsoft Foundation Class (MFC) Restart and Recovery. In this article, customizing MFC’s ARR behavior will be covered.

MFC centralizes all the code that manages the interaction with ARR in a single class called CDataRecoveryHandler. CDataRecoveryHandler, which is defined in afxdatarecovery.h, is used by CWinApp each time the application needs to complete some activity related to ARR, such as the auto-saving of temporary document that can be recovered in the event that the application crashes or is forcibly closed due to a patch-related operating system re-boot.

All CDataRecoveryHandler members are designed for derivation and declared as virtual, and all MFC code that deals with CDataRecoveryHandler retrieves a pointer through the virtual CWinApp::GetDataRecoveryHandler factory method. Therefore, the two pre-requisite steps required to customize MFC ARR behavior are to derive a class from CDataRecoveryHandler and provide an instance of this derived class in an over-ridden implementation of GetDataRecoveryHandler:



//this declares the derived class
#include “afxdatarecovery.h”
class CCustomDataRecoveryHandler :
public CDataRecoveryHandler
{
public:
CCustomDataRecoveryHandler(
 _In_ DWORD dwRestartManagerSupportFlags,
 _In_ int nAutosaveInterval);
virtual ~CCustomDataRecoveryHandler(void);
};

//this returns the an instance of the derived class from the
//derived CWinApp class
CDataRecoveryHandler *CRestartDemoApp::GetDataRecoveryHandler()
{
static BOOL bTriedOnce = FALSE;

// Since the application restart and application recovery are supported only on Windows
// Vista and above, we don’t need a recovery handler on Windows versions less than Vista.
if (afxGlobalData.bIsWindowsVista &&
 (SupportsRestartManager() || SupportsApplicationRecovery()))
{
 if (!bTriedOnce && m_pDataRecoveryHandler == NULL)
 {
  m_pDataRecoveryHandler =
    new CCustomDataRecoveryHandler(
     m_dwRestartManagerSupportFlags, m_nAutosaveInterval);
  if (!m_pDataRecoveryHandler->Initialize())
  {
   delete m_pDataRecoveryHandler;
   m_pDataRecoveryHandler = NULL;
  }
 }
}

bTriedOnce = TRUE;
return m_pDataRecoveryHandler;
}


The over-ridden GetDataRecoveryHandler is relatively simple, and follows the logic checks present in CWinApp. Unfortunately, these checks, such as ensuring the operating system is Windows Vista or higher, are not declared in separate methods, and need to be copied into the over-ridden implementation. The CDataRecoveryHandler pointer is stored in CWinApp::m_pDataRecoveryHandler, which is declared as protected, allowing derived classes to write to the variable directly.

Two customizations of ARR will be covered in this article – allowing the user to control the auto-save path, which is the location where working copies of open documents are stored, and the replacement of the user-interface presented to the end-user when auto-saved documents are detected on application re-start.

The first customization is the simplest, and can be accomplished in two separate ways. MFC will call the virtual function CDataRecoveryHandler::GetAutosavePath to retrieve the location where auto-saved files should be written, and it is possible to override this function and provide a different location. The slight problem with this approach is that GetAutosavePath is declared as a const member function, so the value that the function looks up (in the sample application that accompanies this article, the registry is used to store the customized storage location) cannot be cached in a member variable, and needs to be looked up each time GetAutosavePath is called, which is a small but unnecessary performance hit, and could also introduce unexpected behavior if a new value is introduced while the application is running and temporary files have been saved.

The issues with the first approach can be avoided by overriding CDataRecoveryHandler::Initialize, looking up the appropriate auto-save location in this method, and then calling CDataRecoveryHandler::SetAutosavePath in the Initialize method. The code below illustrates this customization. There are a number of CDataRecoveryHandler that are most efficiently accomplished by this technique rather than direct method over-ridding.


BOOL CCustomDataRecoveryHandler::Initialize()
{
BOOL retVal = CDataRecoveryHandler::Initialize();
if (retVal){
 //check to see if temp file location is over-ridden in the registry
 CRegKey settings;
 
 if (settings.Open(HKEY_CURRENT_USER,
 _T(“Software\\MyCompany\\RestartDemo\\Settings”), KEY_READ)
 == ERROR_SUCCESS)
{
  ULONG length= MAX_PATH;
  TCHAR filePath[MAX_PATH];
  LONG res = settings.QueryStringValue(_T(“AutosavePath”),
   filePath, &length);
  if (res == ERROR_SUCCESS){
   SetAutosavePath(filePath);
  }
 }
}

With this code in place, the AutosavePath registry value determines where temporary working copies of documents will be saved.

Figure 1 shows the default document recovery dialog provided by MFC.The dialog is rather wordy, and developers may wish to provide a simpler implementation.



Figure 1. Default Document Recovery Dialog

The CDataRecoveryHandler method that is responsible for interacting with the user to determine if auto-saved documents should be recovered is QueryRestoreAutosavedDocuments. This method is responsible for filling the member variable m_mapDocNameToAutosaveName with a list of documents names and whether they should be auto-recovered. It is up to the implementer of QueryRestoreAutosavedDocuments to decide whether they should list all the possible documents that can be auto-recovered for the user to select them, or to simply offer an all-or-nothing choice like the default implementation. The code below shows an overridden implementation of QueryRestoreAutosavedDocuments that simply displays a message box informing that user that auto-saved documents have been detected and querying whether they should be restored.


void CCustomDataRecoveryHandler::QueryRestoreAutosavedDocuments(){
if (IDYES ==
 AfxMessageBox(_T(“Would you like to recover documents that were auto-saved prior” +  
  “to the previous application exit?”), MB_YESNO | MB_ICONQUESTION))
{
 //loop through all the auto-saved documents and set them for recovery
 POSITION posAutosave = m_mapDocNameToAutosaveName.GetStartPosition();
 while (posAutosave != NULL)
 {
  CString strDocument, strAutosave;
  m_mapDocNameToAutosaveName.GetNextAssoc(posAutosave, strDocument, strAutosave);

  if (!strAutosave.IsEmpty())
  {
   m_mapDocNameToRestoreBool[strDocument] = true;
  }
 }
}
}





Figure 2. Simple Auto-Recovery Message Box

Conclusion

MFC’s automated application restart and recovery provides a wealth of well-factored functionality that delivers a very good default implementation. In circumstances where the default implementation isn’t suited to a particular application, customization can be simply accomplished by sub-classing CDataRecoveryHandler and over-ridding the method that is responsible for the functionality that needs to be customized. CDataRecoveryHandler has its methods well-factored to allow fine-gained customization that does not require a detailed understanding of how Windows and MFC implement ARR.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read