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: 4966 .

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: 5321 .

.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.

[download id="4"]

Hits for this post: 6889 .

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: 10036 .

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: 8769 .

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: 10055 .

The beta version of the MFC Feature Pack (the extension to MFC 9 from Visual Studio 2008) contains an extended dialog class implementation, CDialogEx. What this brings new, publicly, is the ability to set the background of the dialog box to a color or an image.

A first new method allows to set a background color.

void SetBackgroundColor(COLORREF color, BOOL bRepaint = TRUE);

Dialog box with yellow background.

A second overloaded method allows to set an image on the background.

void SetBackgroundImage(
    HBITMAP hBitmap,
    BackgroundLocation location = BACKGR_TILE,
    BOOL bAutoDestroy = TRUE,
    BOOL bRepaint = TRUE);

BOOL SetBackgroundImage(
    UINT uiBmpResId,
    BackgroundLocation location = BACKGR_TILE,
    BOOL bRepaint = TRUE);

You can use it like this:

BOOL CDialogDemoDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// ...

	HBITMAP bmp = ::LoadBitmap(AfxGetResourceHandle(), MAKEINTRESOURCE(IDB_BITMAP_LOGO));
	SetBackgroundImage(bmp, BACKGR_TILE);

	return TRUE;
}

Dialog box with image on the background

What is very weird is that the only styles for the image are these:

enum BackgroundLocation
{
	BACKGR_TILE,
	BACKGR_TOPLEFT,
	BACKGR_TOPRIGHT,
	BACKGR_BOTTOMLEFT,
	BACKGR_BOTTOMRIGHT,
};

This implementation lacks two basic styles: Center and Stretch. I really don’t understand why they were not implemented. It’s much more likely to need an image centered or stretched that aligned at the bottom-left, for instance. Thus, I don’t see how good this class is if I have to override the entire painting myself if I need that functionality.

Here is an implementation of a dialog class that supports those styles.

Hits for this post: 14587 .

At the beginning of January, Microsoft released a beta version of MFC Feature Pack, an exstension to the MFC shipped with Visual Studio 2008. This feature pack allows developers to create applications with the look and feel of Office, Visual Studio and Internet Explorer. MFC application can now support:

  • Office Ribbon
  • Office 2003, XP and 2007 look and feel
  • docking toolbars and panes in the Visual Studio style
  • customizable toolbars and menus
  • advanced GUI controls
  • advaced MDI tabs and groups

I have published an article on www.codeguru.com about enabling Office 2007 style on a MDI application. The article is called MFC Feature Pack: An Introduction. I encourage you to read it. A Romanian version will be available soon at www.codexpert.ro.

, , , , , Hits for this post: 15757 .

Let’s asume you want to change the font of a window\control and for the sake of simplicity let’s consider an MFC dialog application. In that case, there are several steps you should follow:

  • declare a CFont variable in the dialog class
  • create the font in OnInitDialog (using CreateFont)
  • set the font to the control (using SetFont)

For example, say that you want to use Arial size 12 for a multiline edit control. In that case you could put the following code in OnInitDialog:

BOOL CFontSampleDlg::OnInitDialog()
{
	CDialog::OnInitDialog();   

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon   

	VERIFY(editFont.CreateFont(
		12,                        // nHeight
		0,                         // nWidth
		0,                         // nEscapement
		0,                         // nOrientation
		FW_NORMAL,                 // nWeight
		FALSE,                     // bItalic
		FALSE,                     // bUnderline
		0,                         // cStrikeOut
		ANSI_CHARSET,              // nCharSet
		OUT_DEFAULT_PRECIS,        // nOutPrecision
		CLIP_DEFAULT_PRECIS,       // nClipPrecision
		DEFAULT_QUALITY,           // nQuality
		DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
		_T("Arial")));             // lpszFacename   

	GetDlgItem(IDC_EDIT_SAMPLE)->SetFont(&editFont);   

	return TRUE;  // return TRUE  unless you set the focus to a control
}

The result should be the following:

control with Arial of wrong size

Look carefully and you’ll see that’s not the right size 12? Why? Because the height and width (which doesn’t matter in this case) parameters for CreateFont() are given in logical units and not pixels. When you passed 12 we meant pixels, so to correctly create a font of size 12 pixels, you must convert from pixels to logical units. To do that, you need to determine the number of pixels per logical inch along the screen height. For that you have to use GetDeviceCaps() with the appropriate device context. To compute the actual value, MulDiv() is used. This function multiplies two 32-bit integers and devides the 64-bit result by a third 32-bit integer.

In this case, OnInitDialog() becomes:

BOOL CFontSampleDlg::OnInitDialog()
{
	CDialog::OnInitDialog();   

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon   

	CDC* pDC = GetDC();
	int desiredHeight = 12;
	int height = -MulDiv(desiredHeight, pDC->GetDeviceCaps(LOGPIXELSY), 72);
	ReleaseDC(pDC);   

	VERIFY(editFont.CreateFont(
		height,                    // nHeight
		0,                         // nWidth
		0,                         // nEscapement
		0,                         // nOrientation
		FW_NORMAL,                 // nWeight
		FALSE,                     // bItalic
		FALSE,                     // bUnderline
		0,                         // cStrikeOut
		ANSI_CHARSET,              // nCharSet
		OUT_DEFAULT_PRECIS,        // nOutPrecision
		CLIP_DEFAULT_PRECIS,       // nClipPrecision
		DEFAULT_QUALITY,           // nQuality
		DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
		_T("Arial")));             // lpszFacename   

	GetDlgItem(IDC_EDIT_SAMPLE)->SetFont(&editFont);   

	return TRUE;  // return TRUE  unless you set the focus to a control
}

and the result is:

Arial with correct size 12 pixels

and that is what we wanted in the first place.

, , , , , Hits for this post: 14701 .

You probably noticed that IE7 or Windows Media Player 11 don’t have the menu shown by default; it only shows up when you press the ALT key. This is one particularity of the look and feel of Windows Vista Aero. You can find guidelines about designing menus here:

As revealed by the VC++ team some time ago, MFC 9.0 offers support for hiding the menus automatically and manually, in accordance with the Aero look and feel. A recent article in the MSDN Magazine by Tarek Madkour, explains how: by calling the SetMenuBarVisibility method from CFrameWnd with the AFX_MBV_DISPLAYONFOCUS. What is not told is that works only for SDI applications.

Menus in SDI applications 

In a SDI application you have a class derived from CFrameWnd, usually called CMainFrame. In the OnCreate() method, after calling the CFrameWnd’s OnCreate, you can call SetMenuBarVisibility:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
      return -1; 

   SetMenuBarVisibility(AFX_MBV_DISPLAYONFOCUS); 

   // other stuff here 

   return 0;
}



AFX_MBV_DISPLAYONFOCUS is defined in afxwin.h:

// Frame window menu bar visibility styles
#define AFX_MBV_KEEPVISIBLE    0x01L // always visible
#define AFX_MBV_DISPLAYONFOCUS 0x02L // toggle state on ALT
#define AFX_MBV_DISPLAYONF10   0x04L // display on F10



The result is that the menu is automatically hidden (as can be seen in the image)

SDI menu is hidden 

and shown only when pressing the ALT key.

SDI menu is shown

Menus in MDI applications

The same technique does not work for MDI menus. In such an application you have

  • a main frame, represented by a (usually called) CMainFrame class derived from CMDIFrameWnd, which in turn is derived from CFrameWnd, and
  • one or more child frames, derived from CMDIChildFrame, which in turn is derived from CFrameWnd

Both the main window and the child windows can have menus, but the menu of a child window is shown on the same bar with the menu of the main window. As long as a child window exists the menu of that document is shown. When no child window exists the menu of the main window is shown. Both on the same bar.

Trying to make the menu of the child window(s) hidden by default does not work. Calling SetMenuBarVisibility has no effect:

int CChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)
      return -1;

   SetMenuBarVisibility(AFX_MBV_DISPLAYONFOCUS);

   return 0;
}



Calling SetMenuBarVisibility for the main frame results in an assertion failure because the implementation in the CMDIFrameWnd expects the parameter to be always AFX_MBV_KEEPALIVE; it doesn’t do anything else, such as calling the function from the base class.

void CMDIFrameWnd::SetMenuBarVisibility(DWORD dwStyle)
{
   ENSURE_ARG(dwStyle == AFX_MBV_KEEPVISIBLE);
   ASSERT(m_dwMenuBarVisibility == AFX_MBV_KEEPVISIBLE);
}



Overriding the method in CMainFrame accept any parameter and call the method in CFrameWnd to bypass CMDIFrameWnd does not have any effect either.

void CMainFrame::SetMenuBarVisibility(DWORD dwStyle)
{
   CFrameWnd::SetMenuBarVisibility(dwStyle);
}



In conclusion MFC 9.0 supports the Aero look-and-feel for the automatically hiding of menus, but only in SDI applications.

Hits for this post: 11114 .