The CTreeCtrl supports several ways to sort its content:

  • InsertItem allows to insert the child item alphabetically, when specifying TVI_SORT for hInsertAfter
  • SortChildren performs an alphabetical sorting of the child items of the given parent item in a tree
  • SortChildrenCB performs a sort with a user-defined callback (hence the CB suffix) of the children of the specified item

Let’s consider this tree and investigate these two sorting methods.

Alphabetical sorting
SortChildren() does an alphabetical sorting. It is important to note that SortChildren() does not work recursively. It only sorts the children of the specified item. Therefore the following call would only sort the immediate children of the root item.

  m_tree.SortChildren(TVI_ROOT);

To sort the entire tree one needs to traverse the tree and call SortChildren for every item that has children (actually only items with more than one child need sorting). The following method performs a depth-first traversal and sorts all nodes that have children.

void CTreeSortDemoDlg::SortItem(HTREEITEM item)
{
   if(item != NULL)
   {
      if(item == TVI_ROOT || m_tree.ItemHasChildren(item))
      {
         HTREEITEM child = m_tree.GetChildItem(item);

         while(child != NULL)
         {
            SortItem(child);
            child = m_tree.GetNextItem(child, TVGN_NEXT);
         }

         m_tree.SortChildren(item);
      }
   }
}

// ...
SortItem(TVI_ROOT);

The result for the given tree is

User defined sorting
SortChildrenCB() allows the user to set a callback functions that the framework calls each time it needs to compare two items to perform the sorting. This allows to customize the sorting. Just like SortChildren() this method only sorts the children of the specified item, and does not perform a recursive sorting of the entire sub-tree.

This method has a single argument, a pointer to a TVSORTCB structure, that is defined like this:

typedef struct tagTVSORTCB {
  HTREEITEM    hParent;
  PFNTVCOMPARE lpfnCompare;
  LPARAM       lParam;
} TVSORTCB, *LPTVSORTCB;

The fields have the following meaning:

  • hParent: is the item whose children are to be sorted
  • lpfnCompare: a pointer to the user-defined callback function that does the sorting
  • lParam: is the value that is passed as to the 3rd parameter of the callback function, which looks like this:
    int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
    

    Usually, this parameter is a pointer to the tree itself, so that you can retrieve information about the items in the callback if necessary. However, it can be anything, including NULL.

    The lParam1 and lParam2 parameters correspond to the lParam member of the TVITEM structure for the two items being compared.

    The callback function must return a negative value if the first item should precede the second, a positive value if the first item should follow the second, or zero if the two items are equivalent.

Suppose we set for each item a pointer to a structure that looks like this:

struct ItemData
{
   CString  Name;
   int      Value;

   CString ToString() const
   {
      CString str;
      str.Format(_T("%s = %d"), Name, Value);
      return str;
   }
};

ItemData* data = MakeData(base, prefix);
HTREEITEM item = m_tree.InsertItem(data->ToString(), parent);
m_tree.SetItemData(item, (DWORD_PTR)data);

A user defined callback (a class method declared static) defines the precedence between two items based on the Value field.

int CALLBACK CTreeSortDemoDlg::CustomCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
   ItemData* data1 = reinterpret_cast<ItemData*>(lParam1);
   ItemData* data2 = reinterpret_cast<ItemData*>(lParam2);

   return (data1 != NULL && data2 != NULL) ? (data1->Value > data2->Value) : 0;
}

Sorting the entire tree with this callback is similar with the previous recursive method. The call to SortChildren() is replaced with a call to SortChildrenCB().

void CTreeSortDemoDlg::CustomSortItem(HTREEITEM item)
{
   if(item != NULL)
   {
      if(item == TVI_ROOT || m_tree.ItemHasChildren(item))
      {
         HTREEITEM child = m_tree.GetChildItem(item);

         while(child != NULL)
         {
            CustomSortItem(child);
            child = m_tree.GetNextItem(child, TVGN_NEXT);
         }

         TVSORTCB tvs;
         tvs.hParent = item;
         tvs.lpfnCompare = &CTreeSortDemoDlg::CustomCompare;
         tvs.lParam = reinterpret_cast<LPARAM>(&m_tree);

         m_tree.SortChildrenCB(&tvs);
      }
   }
}

// ...
CustomSortItem(TVI_ROOT);

The result on the given example is:

For a full sample see TreeSortDemo (55).

, , , , Hits for this post: 2426 .

A window’s system menu (now called simply window menu) features by default commands like Move, Size or Close. (When the user selects one of these commands a WM_SYSCOMMAND message is sent to the window.) What if you want to remove and add these commands on the fly? Here is how you can do it.

This is how a default Window menu looks for a dialog window.

To remove the Move command:

   CMenu* pSysMenu = GetSystemMenu(FALSE);
   if(pSysMenu != NULL)
      pSysMenu->RemoveMenu(SC_MOVE, MF_BYCOMMAND);

To add the Move command, you can do something like this (you can also insert it at a specific position, other than 0):

   CMenu* pSysMenu = GetSystemMenu(FALSE);
   if(pSysMenu != NULL)
      pSysMenu->InsertMenu(0, MF_BYCOMMAND, SC_MOVE, _T("Move"));

, Hits for this post: 5521 .

In the past months I have written about what’s new in Visual Studio 2010 with regard to Visual C++. In this post I will summarize these articles.

In addition, here are some more articles about Visual C++ 2010 that I published on codeguru.com.

Hits for this post: 11199 .

VC++ Feature Pack that came with Visual Studio 2008 SP1 introduced support for the Office Fluent Ribbon. However, developers had to create ribbons entirely from code, because there was no support in the resource editor for that. Visual Studio 2010 comes with a visual designer for the ribbon.

You can choose whether to use a ribbon or a classical menu and toolbar when you create an application.

By default, the created ribbon has one category (Home) and two panels with several commands.

The ribbon can be opened from the resource editor. There is a new category called Ribbon. By default the ribbon resource is called IDR_RIBBON. The description of the ribbon is kept in an XML file called ribbon.mfcribbon-ms, located in the res folder.

When the ribbon is opened, the toolbar displays controls that can be dragged and drop into the ribbon, including categories, context categories, panels, and a series of controls such as buttons, checkbox, edits, progress bar, slider, etc.

There is support for several styles, Office like and Windows 7. These different styles can be seen in the following image.

The designer provides support for quick testing of the ribbon. On the Ribbon Editor toolbar there is a button called Test Ribbon that opens window with the ribbon. You can quickly see how it will look in the application, however, the commands are not available; clicking on the ribbon commands does not have any effect.

You can add handlers for the ribbon commands just like you do for a menu or a toolbar. In Visual Studio 2010 this can be done with the class wizard.

You can read more about the ribbon designed in MSDN or the VC++ Team’s Blog.

, , Hits for this post: 19875 .

With VC++ Feature Pack Microsoft has added new classes to MFC to provide support for new controls. However, these controls were not available from the designer. One had to manually wrote all the code for enabling an application to use these controls. Visual Studio 2010 Beta 2, released a couple of weeks ago, provides support in the designer for these controls.

MFC controls in the Toolbar

MFC controls in the Toolbar

Here is a screen shot of a dialog application with these controls:

New MFC Controls

New MFC Controls

The controls are:

  • Color button (CMFCColorButton): represent a color picker control allowing users to select a color
  • Font combo box (CMFCFontComboBox) : represent a combo control that displays a list of fonts available in the system
  • Edit browse (CMFCEditBrowseCtrl): an editable control with a button that displays a dialog for selecting a file or a folder
  • Visual Studio list box (CVSListBox): an editable list control with buttons for adding, removing or rearranging items in the list
  • Masked edit (CMFCMaskedEdit): a masked edit control that has a string template representing the structure of the allowed input, which is validated against the value provided by the user
  • Menu button (CMFCMenuButton): displays a pop-up menu (from a menu resource) and reports the command selected by the user
  • Property grid (CMFCPropertyGridCtrl): an editable property grid control
  • Shell list (CMFCShellListCtrl): a list control that displays the files and folders from you system just list Windows Explorer list view does
  • Shell tree (CMFCShellTreeCtrl): a tree control that displays the folder from your system just like the Windows Explorer folder view does
  • Link control (CMFCLinkCtrl): is a special button that has the appearance of a hyperlink and invokes the target link when pressed

Not all the properties for these controls are available from the designer. For instance the properties list still needs hand coding, it is not possible to select a menu resource for the menu button nor the starting point for the shell tree and list. However, having them available in the toolbar is a good step forward.

, , , , Hits for this post: 25110 .

Sometimes you want to customize a file dialog, maybe to provide a preview for images or files in general. Fortunately, the common file dialog can be easily extended to achieve this. I will explain in this post how to do that.

There are several things one needs to do to extend the file dialog. First step is to create a dialog template. There are several properties (styles that have to be set on this template).

  • WS_CHILD, necessary because this dialog is a child of the original file dialog
  • WS_CLIPSIBLINGS, required so that the child dialog box does not paint over the original file dialog
  • DS_3DLOOK, so that consistency of the look of the controls in the child dialog and the original dialog is preserved
  • DS_CONTROL, allows the user to navigate through the controls of the customized dialog with TAB or navigation keys

When using the template, the following should be done for the OPENFILENAME structure:

  • if the template is a resource in an application or DLL library, then:
    • Flags should contain OFN_ENABLETEMPLATE
    • hInstance must point to the module containing the resource
    • lpTemplateName should contain the template name
  • if the template is already in memory then
    • Flags should contain OFN_ENABLETEMPLATEHANDLE
    • hInstance member must identify the memory object that contains the template

The following code shows how to display a customized file dialog with a template with the ID set to “DIALOG_PREVIEW”:

	CFileDialog fileDialog(TRUE, NULL, NULL, OFN_HIDEREADONLY, _T("All files (*.*)|*.*||"));

	fileDialog.m_ofn.Flags |= OFN_ENABLETEMPLATE;
	fileDialog.m_ofn.hInstance = AfxGetInstanceHandle();
	fileDialog.m_ofn.lpTemplateName = _T("DIALOG_PREVIEW");

	fileDialog.DoModal();

The common file dialog is expanded on the sides so that the new controls have enough space. There are several rules that apply to this repositioning. I will explain them and exemplify with some images.

  • By default all the controls from the custom dialog are placed below the controls from the original file dialog. The following images show a simple dialog template with a check box and a static control (for a preview). By default, these controls are placed at the bottom of the dialog.
    Simple dialog template

    Simple dialog template


    Custom dialog with preview controls placed at the bottom

    Custom dialog with preview controls placed at the bottom

  • If the dialog template contains a static control with the id stc32 (defined in DLG.h), the controls will be positioned relative to this control (with the original dialog being displayed in its placed, in the original size).
    • all controls above and to the left of stc32 are positioned above and to the left of the original controls, with the same amount.
      Template with stc32 control position on the right and middle

      Template with stc32 control position on the right and middle


      Custom File Fialog with Preview controls on the top left

      Custom File Fialog with Preview controls on the top left

    • all controls below and to the right of stc32 are positioned below and to the right of the original controls.
      Template with stc32 control position on the left and middle

      Template with stc32 control position on the left and middle


      Custom File Fialog with Preview controls on the top and on the right

      Custom File Fialog with Preview controls on the top and on the right

Base on that, if you want to place the preview controls on the right (as I would do), you’d have to place the stc32 control on the left of all the controls from your template. In other words, the template needs to look like this:

Template with stc32 on the left of all controls

Template with stc32 on the left of all controls

The resulting file dialog would look like this:

Custom File Dialog with Preview Controls on the right side

Custom File Dialog with Preview Controls on the right side

Note: In the above images the stc32 control had the border style set one one hand to make the control visible on the dialog template and on the other hand to have the the original file dialog controls more visible within the resulting file dialog. You wouldn’t do that with an actual file dialog.

As you could see from the sample code above, it’s very simple to extend the common file dialog. Of course, the part I haven’t shown so far is how to make use of these additional controls. But that is very simple. You just derive CFileDialog, add handlers for the new controls, implement all the logic you want, and instead of instantiating a CFileDialog object you instantiate an object of your derived class. In a following post I will explain how you can add preview functionality to such a custom file dialog.

You can read more about this topic in the following articles:

, , , , Hits for this post: 19043 .

.NET allows you to expose components as COM and consume them from unmanaged code. There are many references on how to this (and you can only start with MSDN), and I will not talk about that part. What I want to explain here is something different. Suppose you have this interface:

[Guid("2F8433FE-4771-4037-B6B2-ED5F6585ED04")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IAccounts
{
      [DispId(1)]
      string[] GetUsers();
}

Method GetUsers() returns an array on string representing the user names. But what if you also wanted the user passwords or addresses? Since this is exposed as COM, you cannot return an array of User. But you can return multiple arrays of string. So, how would you deal with out string[]? This is what I want to show you in this tutorial.

This is a .NET interface exposed to COM. It has two methods, GetUsers() that returns an array of string representing user names, and GetUsers2() that returns an array of strings as an output parameters and a bool as return type, indicating whether any user was found.

namespace SampleLibrary
{
   [Guid("2F8433FE-4771-4037-B6B2-ED5F6585ED04")]
   [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
   public interface IAccounts
   {
      [DispId(1)]
      string[] GetUsers();

      [DispId(2)]
      bool GetUsers2(out string [] users);
   }
}

And this is the implementation:

namespace SampleLibrary
{
   [Guid("C4713144-5D29-4c65-BF9C-188B1B7CD2B6")]
   [ClassInterface(ClassInterfaceType.None)]
   [ProgId("SampleLibrary.DataQuery")]
   public class Accounts : IAccounts
   {
      List< string > m_users;

      public Accounts()
      {
         m_users = new List< string > {
            "marius.bancila",
            "john.doe",
            "anna.kepler"
         };
      }

      #region IDataQuery Members

      public string[] GetUsers()
      {
         return m_users.ToArray();
      }

      public bool GetUsers2(out string[] users)
      {
         users = m_users.ToArray();

         return users.Length > 0;
      }

      #endregion
   }
}

Note: If you are trying this example make sure you set the ComVisible attribute to true, either for each type or per assembly (in AssemblyInfo.cs)

[assembly: ComVisible(true)]

Second, you have to check the “Register for COM interop” setting in the Build page of the project properties.

The first thing to do in C++ is importing the .TLB file that was generated by regasm.exe.

#import "SampleLibrary.tlb"
using namespace SampleLibrary;

If we look in the .TLB file, we can see how the IAccounts interface looks like:

struct __declspec(uuid("2f8433fe-4771-4037-b6b2-ed5f6585ed04"))
IAccounts : IDispatch
{
    //
    // Wrapper methods for error-handling
    //

    // Methods:
    SAFEARRAY * GetUsers ( );
    VARIANT_BOOL GetUsers2 (
        SAFEARRAY * * users );
};

The following C++ functions, GetUsers1() retrieves the users users list using method GetUsers() from IAccounts. It puts the users in a CStringArray (notice that this container does not have an assignment operator, so the only way to return such an array is with a reference in the parameters list).

void GetUsers1(CStringArray& arrUsers)
{
   IAccountsPtr pAccounts(__uuidof(Accounts));

   SAFEARRAY* sarrUsers = pAccounts->GetUsers();

   _variant_t varUsers;
   varUsers.parray = sarrUsers;
   varUsers.vt = VT_ARRAY | VT_BSTR;

   UnpackBstrArray(varUsers, arrUsers);
   SafeArrayDestroy(sarrUsers);

   pAccounts->Release();
}

UnpackBstrArray() is a function (see below) that extracts the elements of a SAFEARRAY and adds them to a CStringArray.

Function GetUsers2() uses the second method, GetUsers2() from IAccounts. This needs the address of a pointer to a SAFEARRAY (i.e. SAFEARRAY**) that will hold the values returned by the COM method. This time we have to create an empty SAFEARRAY and then pass its address to the COM method. The rest is similar to the previous case.

void GetUsers2(CStringArray& arrUsers)
{
   IAccountsPtr pAccounts(__uuidof(Accounts));

   SAFEARRAYBOUND aDim[1];
   aDim[0].lLbound = 0;
   aDim[0].cElements = 0;

   SAFEARRAY* sarrUsers = SafeArrayCreate(VT_BSTR, 1, aDim);

   VARIANT_BOOL ret = pAccounts->GetUsers2(&sarrUsers);
   if(ret != VARIANT_FALSE)
   {
      _variant_t varUsers;
      varUsers.parray = sarrUsers;
      varUsers.vt = VT_ARRAY | VT_BSTR;
      UnpackBstrArray(varUsers, arrUsers);
   }

   SafeArrayDestroy(sarrUsers);

   pAccounts->Release();
}

The helper method UnpackBstrArray() used previous looks like this:

void UnpackBstrArrayHelper(VARIANT* pvarArrayIn, CStringArray* pstrarrValues)
{
   if (!pstrarrValues || !pvarArrayIn || pvarArrayIn->vt == VT_EMPTY)
      return;

   pstrarrValues->RemoveAll();

   VARIANT* pvarArray = pvarArrayIn;
   SAFEARRAY* parrValues = NULL;

   SAFEARRAYBOUND arrayBounds[1];
   arrayBounds[0].lLbound = 0;
   arrayBounds[0].cElements = 0;

   if((pvarArray->vt & (VT_VARIANT|VT_BYREF|VT_ARRAY)) == (VT_VARIANT|VT_BYREF) &&
      NULL != pvarArray->pvarVal &&
      (pvarArray->pvarVal->vt & VT_ARRAY))
   {
      pvarArray = pvarArray->pvarVal;
   }

   if (pvarArray->vt & VT_ARRAY)
   {
      if (VT_BYREF & pvarArray->vt)
         parrValues = *pvarArray->pparray;
      else
         parrValues = pvarArray->parray;
   }
   else
      return;

   if (parrValues != NULL)
   {
      HRESULT hr = SafeArrayGetLBound(parrValues, 1, &arrayBounds[0].lLbound);
      hr = SafeArrayGetUBound(parrValues, 1, (long*)&arrayBounds[0].cElements);
      arrayBounds[0].cElements -= arrayBounds[0].lLbound;
      arrayBounds[0].cElements += 1;
   }

   if (arrayBounds[0].cElements > 0)
   {
      for (ULONG i = 0; i < arrayBounds[0].cElements; i++)
      {
         LONG lIndex = (LONG)i;
         CString strValue = _T("");

         VARTYPE vType;
         BSTR bstrItem;

         ::SafeArrayGetVartype(parrValues, &vType);
         HRESULT hr = ::SafeArrayGetElement(parrValues, &lIndex, &bstrItem);

         if(SUCCEEDED(hr))
         {
            switch(vType)
            {
            case VT_BSTR:
               strValue = (LPCTSTR)bstrItem;
               break;
            }

            ::SysFreeString(bstrItem);
         }

         pstrarrValues->Add(strValue);
      }
   }
}

void UnpackBstrArray( const _variant_t &var, CStringArray &strarrValues  )
{
   UnpackBstrArrayHelper( &(VARIANT)const_cast< _variant_t & >(var), &strarrValues );
}

Attached you can find a demo project (C# and C++) with the complete example show in this tutorial.

output SAFEARRAY** example (365)

, , , , , Hits for this post: 22914 .

The new MFC library from Visual Studo 2010 supports another Vista specific feature, the task dialog. This is meant as a replacement for the classic dialog box, and can display command links, customized buttons, customized icons, and a footer.

Here is an example of such a dialog (from Windows Server 2008).

MFC contains a new class called CTaskDialog that implements the task dialog. You must include afxtaskdialog.h in your files to be able to use it. Since the task dialog is available only on Vista or newer versions (Server 2003, Server 2008, Windows 7) you must check whether you can use it or not. For that the class CTaskDialog provides a static method called IsSupported() that returns true if the task dialog is available on the running operating system. In addition, the task dialog is only available when you build for UNICODE.

The code below shows how to create and display the task dialog from the previous image.

void CTasksDemoDlg::OnBnClickedButtonTasks1()
{
   CString strMessage("Do you want to save your changes to the document?");
   CString strDialogTitle("Save document");
   CString strMainInstruction("Save document options");

   CString expandedLabel("Hide extra information");
   CString collapsedLabel("Show extra information");
   CString expansionInfo("You can select to save your document either as XML or binary. You should prefer to save as XML as this is the new standard format.");

   if (CTaskDialog::IsSupported())
   {
      CTaskDialog taskDialog(strMessage, strMainInstruction, strDialogTitle, TDCBF_OK_BUTTON);
      taskDialog.SetMainIcon(TD_INFORMATION_ICON);

      taskDialog.SetCommonButtons(TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);
      taskDialog.LoadCommandControls(IDS_SAVE_OPTION1, IDS_SAVE_OPTION2);
      taskDialog.SetExpansionArea(expansionInfo, collapsedLabel, expandedLabel);
      taskDialog.SetFooterText(L"Note: If you don't chose to save your changes will be lost.");
      taskDialog.SetVerificationCheckboxText(L"Remember your selection");

      INT_PTR result = taskDialog.DoModal();

      if (taskDialog.GetVerificationCheckboxState() )
      {
         // PROCESS IF the user selects the verification checkbox
      }

      switch (result)
      {
         case IDS_SAVE_OPTION1:
            AfxMessageBox(L"You chose to save as XML");
            break;
         case IDS_SAVE_OPTION2:
            AfxMessageBox(L"You chose to save as binary");
            break;
         case IDNO:
            AfxMessageBox(L"You chose not to save");
            break;
         case IDCANCEL:
            AfxMessageBox(L"You chose to cancel");
            break;
         default:
            // this case should not be hit
            ASSERT(FALSE);
            break;
      }

   }
   else
   {
      AfxMessageBox(strMessage);
   }
}

In this sample IDS_SAVE_OPTION1 (“Save in XML based format”) and IDS_SAVE_OPTION2 (“Save in binary format (old version)”) are two strings defined in the string table from the Resource editor.

There are several predefined icons in commctrl.h that can be used as the main icon.

#define TD_WARNING_ICON         MAKEINTRESOURCEW(-1)
#define TD_ERROR_ICON           MAKEINTRESOURCEW(-2)
#define TD_INFORMATION_ICON     MAKEINTRESOURCEW(-3)
#define TD_SHIELD_ICON          MAKEINTRESOURCEW(-4)

The following flags for default buttons are defined in the same header:

enum _TASKDIALOG_COMMON_BUTTON_FLAGS
{
    TDCBF_OK_BUTTON            = 0x0001, // selected control return value IDOK
    TDCBF_YES_BUTTON           = 0x0002, // selected control return value IDYES
    TDCBF_NO_BUTTON            = 0x0004, // selected control return value IDNO
    TDCBF_CANCEL_BUTTON        = 0x0008, // selected control return value IDCANCEL
    TDCBF_RETRY_BUTTON         = 0x0010, // selected control return value IDRETRY
    TDCBF_CLOSE_BUTTON         = 0x0020  // selected control return value IDCLOSE
};
typedef int TASKDIALOG_COMMON_BUTTON_FLAGS;

An easier way to create a task dialog, but with fewer customization options is using the static method ShowDialog() from CTaskDialog. The following example displays a dialog similar to the first one.

void CTasksDemoDlg::OnBnClickedButtonTasks2()
{
   HRESULT result2 = CTaskDialog::ShowDialog(
      L"Do you want to save your changes to the document?",
      L"Save document options",
      L"Save document",
      IDS_SAVE_OPTION1,
      IDS_SAVE_OPTION2,
      TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON,
      TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS,
      L"Note: If you don't chose to save your changes will be lost.");
}

, , Hits for this post: 25694 .

One of the new features available in MFC in the Visual Studio 2010 CTP is the Restart Manager. This was introduced with Windows Vista to offer support for restarting application when a crash occurs or when an automatic update needs to close and then restart an application.

When you create a new MFC project in Visual Studio 2010, in the Advanced Features property page you can specify the level of support you want for the restart manager.

You can select one of the following:

  • Support Restart Manager: restarts after crash or upgrade
  • Reopen previously open documents: reopens previously open documents
  • Support application recovery: recovers auto saved documents

There are three flags defined for these three options:

  • AFX_RESTART_MANAGER_SUPPORT_RESTART for Support Restart Manager
  • AFX_RESTART_MANAGER_SUPPORT_RESTART_ASPECTS for Reopen previously open documents
  • AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS for Support application recovery

These are defined in the afxwin.h header.

// Restart Manager support flags
#define AFX_RESTART_MANAGER_SUPPORT_RESTART           0x01  // restart support, means application is registered via RegisterApplicationRestart
#define AFX_RESTART_MANAGER_SUPPORT_RECOVERY          0x02  // recovery support, means application is registered via RegisterApplicationRecoveryCallback
#define AFX_RESTART_MANAGER_AUTOSAVE_AT_RESTART       0x04  // auto-save support is enabled, documents will be autosaved at restart by restart manager
#define AFX_RESTART_MANAGER_AUTOSAVE_AT_INTERVAL      0x08  // auto-save support is enabled, documents will be autosaved periodically for crash recovery
#define AFX_RESTART_MANAGER_REOPEN_PREVIOUS_FILES     0x10  // reopen of previously opened documents is enabled, on restart all previous documents will be opened
#define AFX_RESTART_MANAGER_RESTORE_AUTOSAVED_FILES   0x20  // restoration of auto-saved documents is enabled, on restart user will be prompted to open auto-saved documents intead of last saved
#define AFX_RESTART_MANAGER_SUPPORT_NO_AUTOSAVE       AFX_RESTART_MANAGER_SUPPORT_RESTART |
                                                      AFX_RESTART_MANAGER_SUPPORT_RECOVERY |
                                                      AFX_RESTART_MANAGER_REOPEN_PREVIOUS_FILES
#define AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS       AFX_RESTART_MANAGER_SUPPORT_NO_AUTOSAVE |
                                                      AFX_RESTART_MANAGER_AUTOSAVE_AT_RESTART |
                                                      AFX_RESTART_MANAGER_AUTOSAVE_AT_INTERVAL |
                                                      AFX_RESTART_MANAGER_RESTORE_AUTOSAVED_FILES
#define AFX_RESTART_MANAGER_SUPPORT_RESTART_ASPECTS   AFX_RESTART_MANAGER_SUPPORT_RESTART |
                                                      AFX_RESTART_MANAGER_AUTOSAVE_AT_RESTART |
                                                      AFX_RESTART_MANAGER_REOPEN_PREVIOUS_FILES |
                                                      AFX_RESTART_MANAGER_RESTORE_AUTOSAVED_FILES
#define AFX_RESTART_MANAGER_SUPPORT_RECOVERY_ASPECTS  AFX_RESTART_MANAGER_SUPPORT_RECOVERY |
                                                      AFX_RESTART_MANAGER_AUTOSAVE_AT_INTERVAL |
                                                      AFX_RESTART_MANAGER_REOPEN_PREVIOUS_FILES |
                                                      AFX_RESTART_MANAGER_RESTORE_AUTOSAVED_FILES

Enabling this support is done with a single line in the constructor of the CWinAppEx derived class.

CRecoveryDemoApp::CRecoveryDemoApp()
{
	m_bHiColorIcons = TRUE;

	// support Restart Manager
	m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;

	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}

Here is an application with the full support for restart enabled. There are two opened documents, one that is saved (on the left) and one that is not saved (on the right).

When I hit the crash button, the application uses a null pointer and crashes.

Now, when I press the Ignore button of the crash report window, I get the restart manager window tha allows me to Restart the program.

The restart manager will attempt to restart the program and reopen my documents, which it successfully does.

However, you can see that though the support for recovering auto saved documents was enabled, the second, unsaved document was not recovered. The reason was that this document was not auto saved, because the default interval for the auto save is 5 minutes and I crashed the program immediatelly after writing into the document. This default interval can be changed by calling the SetAutosaveInterval() method of the data recovery handler. In the following example I change this interval to one minute.

BOOL CRecoveryDemoApp::InitInstance()
{
   // ...

   CDataRecoveryHandler* autohandler = AfxGetApp()->GetDataRecoveryHandler();
   autohandler->SetAutosaveInterval(60000);

   return TRUE;
}

Here is another instance of the application with the first document saved, and the second not directly saved, but auto saved after one minute of inactivity.

When the application restarts, because there was an auto saved document that can be recovered, a new window is displayed.

If I choose Recover the auto-saved documents my unsaved (but auto saved) document is recovered.

These options for restarting the application and saving and loading the application data (documents) are exposed through virtual methods in the CWinAppEx class.

virtual void PreLoadState() {}    // called before anything is loaded
virtual void LoadCustomState() {} // called after everything is loaded
virtual void PreSaveState() {}    // called before anything is saved
virtual void SaveCustomState() {} // called after everything is saved

You can override these methods in your application for custom handling of the save and load operations.

To learn more about this new feature I suggest you read the Visual C++ team’s blog.

, , Hits for this post: 21498 .

Yesterday Microsoft released the Visual C++ 2008 Feature Pack, formally known as MFC Feature Pack beta. The pack can be downloaded from Microsoft’s Download Center, is available only in English and requires Visual Studio 2008 Standard Edition or above. Installation on systems with Visual Studio 2008 Service Pack 1 BETA is not supported.

Hits for this post: 13271 .