Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Vista-themed Owner-drawn and Full-custom Task Buttons & Task Dialogs

4.96/5 (62 votes)
17 Jul 2007Public Domain9 min read 3   3.1K  
Classes reproducing Vista animated Task Buttons (command links), plus Task Dialog layout

Introduction

My first article described themed owner and full-custom Vista-styled push/menu/image buttons. This article describes a set of Vista-style task buttons -- or command link buttons -- plus a task dialog base class. In Vista, the task buttons provide fading transitions and subtle glowing effects.

Task buttons have a few differences from normal image buttons. First, when unselected they appear flat with no visible border. Second, as seen below, the first line is drawn extra large to really stand out. Lastly, the hot/default/normal borders used by task buttons differ significantly.

These task button classes are compatible with Windows 98, 2000, XP and Vista. They are implemented as C++/MFC controls. The theme-aware visual effects are derived from standard push buttons and can thus leverage themed aspects on XP.

Screenshot - task.gif

Background

Vista introduced the concept of Task Dialogs, an improved version of the plain old MessageBox. There is great flexibility, ranging from the basic TaskDialog function up to advanced implementations using TaskDialogIndirect. This is described in the article by Michael Dunn.

Personally, once something starts requiring callbacks I'll shunt the code into its own class. Once that happens, it becomes a toss-up: new class for a wrapper or new class for a custom dialog. A custom dialog gives better layout control if you dislike TaskDialogIndirect results and it avoids the backwards compatibility problem. Some elements are harder with custom task dialogs, though, such as the two-tone background, large font and the task buttons themselves. But not anymore!

Other people are working on full reproductions of TaskDialogIndirect for older OSs. However, I rejected that approach as overkill. For many applications, you're adding hundreds of Kb for no reason. I wanted a small footprint approach. First, I made a task button derived from my CButtonVE_Image class. Second, I used a task dialog base class. Third, I brought in some CStatic derived classes for dressing things up, i.e. text and icons.

As in my first article, I maintain backwards compatibility with Win98 by using VC6. Gotta love virtual machines. It also compiles with VS7/2003 or VS8/2005 fine. Just open the DSW project file. VS8 users must remove entries from the RT_MANIFEST resource category beforehand. David Zhao's visual styles class is used to avoid DLL problems on Win98/2k. You'll need a platform SDK with the XP theme header files. Visual Studio VS8/2005 Express users with the Windows Server 2003 R2 Platform SDK (a common config) can also compile this article, after some tweaks. Open VS2005_Express_MFC_Stubs.zip in the demo for details.

Vista button transitions

Like my previous button classes, in Vista these custom task buttons provide smooth transitions between states. The theme-API supports five states for buttons: disabled, normal, hot, defaulted and pushed. Vista performs transitions between these states at varying speeds: some quickly and some slower. On state changes, a timer tickcount is initialized. This is used to compute an alpha blending factor for merging images of the old and new states over time. For a more complete description with example code, see my first article.

Basic task dialogs

Vista offers two primary flavors of task dialogs. Basic versions ironically don't use task buttons or command links while more complex versions can. Common aspects are the two-tone background, images and the large headline fonts. Here is a basic task dialog:

Screenshot - task2.png

CTaskDialogVE supports these natively, without any extra code. The class supports a shadowed transparent image (bitmap or icon), large headline text, content text and one or more standard buttons. Basic task dialogs are created dynamically; no dialog resource is required. They resize as necessary to accommodate varying amounts of text. The above example was created with this snippet:

C++
CTaskDialogVE dlg(
    NULL/*parent*/, AfxGetApp()->m_hInstance, 
    _T("ButtonVE CTaskDialogVE Demo"),
    _T("Your viewing the default behavior for CTaskDialogVE"),
    _T("Most features of the Vista 'TaskDialog' function are "\
    "available. You can also derive from CTaskDialogVE to "\
    "customize dialogs.\n\n"\
    "Would you like to see a customized dialog with task "\
    "buttons?"), TDCBF_YES_BUTTON|TDCBF_NO_BUTTON,
    MAKEINTRESOURCE(IDB_BITMAP1));
DWORD result = dlg.DoModal();

Upgrading from the MessageBox API to CTaskDialogVE basic dialogs really doesn't take much at all. I'll cover an even easier method later, though.

Custom task dialogs

For more elaborate CTaskDialogVE dialogs, a resource template is required. You are now in total control of the layout. The following was used to create the demo:

Screenshot - task3.png

The separator line above the Cancel button is important. You must assign it a resource ID and then configure CTaskDialogVE by calling SetHorzLine. The separator controls where the two-tone effect gets painted and provides a reference point. In this way, a WM_CTLCOLOR handler knows which controls get which background color. The two-tone is provided by a WM_PAINT handler guided by the separator position:

C++
CStatic *horz = (CStatic*)GetDlgItem(m_horz_nID);
if (horz) 
{
    CPaintDC dc(this); // device context for painting
    CRect rc, horz_rw;
    GetClientRect(&rc);
    // Convert separator to dialog client coords...
    horz->GetWindowRect(&horz_rw);
    ScreenToClient(&horz_rw);
    // Draw two-tone background...
    rc.bottom = horz_rw.bottom;
    dc.FillSolidRect(rc, m_bgcolor);
}

The background color of the controls is managed by a WM_CTLCOLOR handler, again guided by the separator:

C++
CStatic *horz = (CStatic*)GetDlgItem(m_horz_nID);
if (horz) 
{
    CRect rw,horz_rw;
    pWnd->GetWindowRect(&rw);
    horz->GetWindowRect(&horz_rw);
    if (rw.top < horz_rw.top) 
    {
        // Set text background mode so bgbrush color visible...
        pDC->SetBkMode(TRANSPARENT);
        hbr = m_bgbrush;
    }
}

The variables m_bgcolor and m_bgbrush are maintained by a WM_SYSCOLORCHANGE handler. Now two-tone dialogs are much simpler.

Indirect dialog templates

For those interested, here's a short diversion into indirect dialog templates. The basic CTaskDialogVE task dialog needs a layout template like all dialogs. The templates are made dynamically. They're composed of several chunks starting with a DLGTEMPLATE structure, followed by several variable sized fields for menu, class, title and default font. Finally, zero or more DLGITEMTEMPLATE structures are used for the dialog controls. A few wrappers simplify things:

  • AllocTemplateSpace - Add a chunk of space to contiguous dialog template.
  • AddTemplateWord - Add a 16-bit word.
  • AddTemplateString - Add a Unicode string.
  • AddTemplateItem - Add a dialog control.

To create a basic task dialog template, first we add the header:

C++
DLGTEMPLATE *pDT = (DLGTEMPLATE*)AllocTemplateSpace(sizeof(DLGTEMPLATE));
pDT->style = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME | 
    DS_SETFONT;
pDT->dwExtendedStyle = WS_EX_DLGMODALFRAME;
pDT->cdit = 0; // inc'd by AddTemplateItem
pDT->x = 0;
pDT->y = 0;
pDT->cx = TASKVE_DLGWIDTH;
pDT->cy = TASKVE_DLGHEIGHT;
AddTemplateWord(0); // menu (none)
AddTemplateWord(0); // class (default)
AddTemplateString(pszWindowTitle); // title
AddTemplateWord(8); // font point size
AddTemplateString("MS Sans Serif"); // font name

This specifies the window style, position, size, menu, registered window class, title and font. Next come the standard buttons:

C++
for (i=0; btnlist[i].mask>0; i++)
    if (dwCommonButtons & btnlist[i].mask) 
    {
        AddTemplateItem(
            TASKVE_ATOM_BUTTON, 
            BS_PUSHBUTTON|WS_TABSTOP|WS_VISIBLE, 0,
            xofs, yofs, TASKVE_BTNWIDTH, TASKVE_BTNHEIGHT, 
            btnlist[i].nID);
        xofs += (TASKVE_BTNWIDTH+TASKVE_BTNSPACEX); 
        AddTemplateString(btnlist[i].label); // item title
        AddTemplateWord(0);
    }

The btnlist array contains the config masks, button text labels and resource IDs. The xofs coordinate is updated as each button is added. The order in which controls are added corresponds to the tab order. Next, the horizontal divider line, icon, heading and content text are added. The steps are omitted here, since it gets repetitive. Finally, CDialog's InitModalIndirect is called with our custom template:

C++
InitModalIndirect((LPCDLGTEMPLATE)m_dlgtemplate, pParentWnd);

Start DoModel and you have a dialog! Almost painless, eh?

Using the code

To evaluate the Vista Effects task button, replace or subclass any push-button instance of CButton with the owner-drawn CButtonVE_Task. Just add the source files to your project, add an image and you're in business. The following classes are provided:

  • CButtonVE_Task - Owner drawn task button. Best for most cases.
  • CButtonVE2_Task - Full-custom task button. Good for injecting into other windows.
  • CTaskDialogVE - Task dialog base (basic or custom).
  • CStaticTextVE - CStatic derived class for drawing large fonts.
  • CStaticImageVE - CStatic derived class for drawing bitmaps/icons with transparency and shadows.

The task button classes derived from CButtonVE_Image provide these functions:

  • SetOwner - Specify window to receive button clicks and menu button commands. Defaults to parent.
  • SetContentHorz - Specify horizontal alignment for button image/text content. ModifyStyle can also be used.
  • SetContentVert - Specify vertical alignment for button image/text content. ModifyStyle can also be used.
  • SetContentMargin - Specify spacing between button border and content.
  • SetBackgroundColor - Specify background color.
  • SetImagePosition - Specify position of image relative to text (left, right, above or below the text).
  • SetImageSpacing - Specify spacing between image and text.
  • SetImageShadow - Control if drop shadow is shown under images.
  • SetTransparentColor - Specify bitmap background color. Default is the upper-left corner pixel.
  • SetHotImage - Specify a bitmap or icon to display when the button is hot.
  • SetDisabledImage - Specify a bitmap or icon to display when the button is disabled. By default, a shaded version of the source image is generated.
  • SetBigFontScale - Control size of large text. Default is 1.6x normal.

The CStaticTextVE class provides:

  • SetFontScale - Specify size of text. Default is 1x normal.
  • SetFontColor - Specify foreground color. Override default.
  • SetBackgroundColor - Specify background color. Override default.

The CStaticImageVE class provides:

  • SetTransparentColor - Specify transparent color. Default is upper left pixel.
  • SetBackgroundColor - Specify background color. Override default.
  • SetImageShadow - Control if Gaussian blurred drop shadow is shown under images

Adding owner-drawn buttons

Add a standard button in the dialog editor, setting the "owner draw" and "multi-line" styles. The multi-line style is needed if you want multiple lines of text, i.e. one large, one small. Add a control type member variable, sub-classing it, and replace the CButton header instance with CButtonVE_Task.

Screenshot - ownerdraw.png

Adding full custom buttons

There are two ways of adding full-custom buttons. The first method is the standard button path as above, but without the "owner draw" style.

Screenshot - custctrl1.png

The second method involves adding a custom control in the dialog editor and giving the CButtonVE2_Task class name in the properties. In your WM_INITDIALOG handler, you'll need to configure font and window text for the full-custom controls using this method.

Screenshot - custctrl2.png

Using basic task dialogs

The basic task dialog parallels Vista's TaskDialog function. Unicode parameters are not required for strings, however. All strings support MAKEINTRESOURCE as parameters, so they can be optionally resource based. The hInstance parameter is used to find said resources, i.e. if stored in a DLL. NULL means look in EXE resources only. If the window title is NULL, the program name is used instead.

C++
CTaskDialogVE(
    CWnd *pParentWnd,
    HINSTANCE hInstance, 
    LPCTSTR pszWindowTitle,
    LPCTSTR pszMainInstruction,
    LPCTSTR pszContent,
    DWORD dwCommonButtons=TDCBF_OK_BUTTON, 
    LPCTSTR pszImage=NULL)

dwCommonButtons can be any combination of:

  • TDCBF_OK_BUTTON
  • TDCBF_YES_BUTTON
  • TDCBF_NO_BUTTON
  • TDCBF_CANCEL_BUTTON
  • TDCBF_RETRY_BUTTON
  • TDCBF_CLOSE_BUTTON

The optional image parameter is MAKEINTRESOURCE to a bitmap or icon. The following standard icons are also available to use as-is:

  • TD_WARNING_ICON
  • TD_ERROR_ICON
  • TD_INFORMATION_ICON
  • TD_QUESTION_ICON

Derive custom dialog from CTaskDialogVE

Create a new CDialog class and change the base class to CTaskDialogVE. Add a WM_INITDIALOG handler to configure things. The demo uses this:

C++
// Tell CTaskDialogVE about seperator...
SetHorzLine(IDC_STATIC1);
// Setup big font for headline...
m_info.SetFontScale(1.6);
m_info2.SetFontScale(1.2);
// Setup owner-drawn task button...
HBITMAP hTask = (HBITMAP)(LoadImage(
    AfxGetApp()->m_hInstance, 
    MAKEINTRESOURCE(IDB_TASK), 
    IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR));
HBITMAP hTaskHot = (HBITMAP)(LoadImage(
    AfxGetApp()->m_hInstance, 
    MAKEINTRESOURCE(IDB_TASK_HOT), 
    IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR));
m_buttonvet.ModifyStyle(0,BS_BITMAP);
m_buttonvet.SetImage(hTask);
m_buttonvet.SetHotImage(hTaskHot);
// Setup custom-drawn task button...
m_buttonvet2.ModifyStyle(0,BS_BITMAP);
m_buttonvet2.SetImage(hTask);
m_buttonvet2.SetHotImage(hTaskHot);

First, the separator is configured with SetHorzLine, allowing the two-tone background. Next, we specify the font size for the dialog header text. The scales shown approximate Vista and also look good on Win98/2k/XP. Lastly, normal and hot images are added to both buttons. All the controls were sub-classed to make set-up easier. The rest is up to you. Add event handlers to respond to the buttons. The demo has a checkbox, so it adds a handler there also.

Other goodies

As you've seen, using CTaskDialogVE for basic dialogs is straightforward. However, if you've a suitable MessageBox instance, upgrading it to CTaskDialogVE is very easy. Just append "VE:"

C++
MessageBoxVE(
    _T("The game is over. Would you like to play again?"), 
    _T("ButtonVE_Task_demo"), 
    MB_YESNO|MB_ICONQUESTION);

The global function MessageBoxVE performs simple conversions automatically. It translates the nType field, i.e. the MB_* parameters, into icon and button requests. It parses the text field, making the first sentence into the header and balancing into task dialog content.

Screenshot - msgbox.png

Note that the phrasing of your text makes a big difference in how the task dialog comes across. The above simple example works fine. Other's prompts might need rephrasing. Enjoy!

Copyright and license

This article is Copyright © 2007 by Ian E Davis. The demo code and source code accompanying this article are hereby released to the public domain.

History

  • May 7th, 2007 - First release.
  • May 24th, 2007 - Fixed parsing problem with MessageBoxVE when given a single sentence of text. CTaskDialogVE now string queries resources for button text overrides, i.e. string resource for ID 1 replaces the "OK" label for easier localization. Fixed definition of TD_* icons to be compatible with Vista platform SDK.
  • July 17th, 2007 - Some minor changes. Fixed memory allocation problem on an error case and upgraded AlphaBlt code in ButtonVE_Helper to dynamically link the AlphaBlend API. Thanks go to bmallabon and blindd0t! Code now allowed to run on Win95 and NT4.0. Also changed arrow bitmaps to be 24-bit, since the 16-bit images didn't display properly on NT4.0.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication