Click here to Skip to main content
15,891,136 members
Articles / Desktop Programming / WTL

TDXML: XML-Based Task Dialogs with a Visual Task Dialog Editor

Rate me:
Please Sign up or sign in to vote.
4.92/5 (67 votes)
19 Mar 200713 min read 100.5K   1.4K   90  
A library and a visual editor that make it easy to build task dialogs and use them in your C++ applications
#include "stdafx.h"
#include "resource.h"
#include "aboutdlg.h"
#include "EditorFrame.h"

/////////////////////////////////////////////////////////////////////////////
// Construction

CEditorFrame::CEditorFrame() : m_bAllowTDClose(false)
{
}

CEditorFrame::~CEditorFrame()
{
}


/////////////////////////////////////////////////////////////////////////////
// Message handlers

BOOL CEditorFrame::OnInitDialog ( HWND hwndCtrl, LPARAM lParam )
{
CRect rcTabDisplayArea;
HICON hi = AtlLoadIconImage ( IDI_INFOICON, LR_DEFAULTCOLOR, 16, 16 );

    // Init the imagelist for the Rebuild button
    if ( NULL != hi && m_iml.Create ( 16, 16, ILC_COLOR8|ILC_MASK, 1, 1 ) )
        m_iml.AddIcon ( hi );

    // Set up the tabs
    m_wndTab = GetDlgItem(IDC_TOOLS_TABS);
    m_wndTab.ModifyStyleEx ( 0, WS_EX_CONTROLPARENT );

    m_wndTab.InsertItem ( 0, _S(IDS_TEXT_TAB) );
    m_wndTab.InsertItem ( 1, _S(IDS_BUTTONS_TAB) );
    m_wndTab.InsertItem ( 2, _S(IDS_RADIO_BUTTONS_TAB) );
    m_wndTab.InsertItem ( 3, _S(IDS_FLAGS_TAB) );

    m_wndTab.GetClientRect ( rcTabDisplayArea );
    m_wndTab.AdjustRect ( false, rcTabDisplayArea );

    // Create the child dialogs for the tabs.
    m_wndTextTools.Create ( m_wndTab );
    m_wndButtonsTools.Create ( m_wndTab );
    m_wndRadioButtonsTools.Create ( m_wndTab );
    m_wndFlagsTools.Create ( m_wndTab );
    m_wndTextTools.SetWindowPos ( NULL, rcTabDisplayArea, SWP_NOZORDER );
    m_wndButtonsTools.SetWindowPos ( NULL, rcTabDisplayArea, SWP_NOZORDER );
    m_wndRadioButtonsTools.SetWindowPos ( NULL, rcTabDisplayArea, SWP_NOZORDER );
    m_wndFlagsTools.SetWindowPos ( NULL, rcTabDisplayArea, SWP_NOZORDER );
    m_wndCurrPage = m_wndTextTools.m_hWnd;

    return TRUE;
}

LRESULT CEditorFrame::OnRebuildTaskDlg ( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
TASKDIALOGCONFIG tdc = { sizeof(TASKDIALOGCONFIG) };

    tdc.lpCallbackData = (LONG_PTR) this;
    tdc.pfCallback = &CEditorFrame::TDCallback;

    // Call each tools dialog so it can store its data in the TDCONFIG struct.
    m_wndTextTools.FillInTDConfig ( tdc );
    m_wndButtonsTools.FillInTDConfig ( tdc );
    m_wndRadioButtonsTools.FillInTDConfig ( tdc );
    m_wndFlagsTools.FillInTDConfig ( tdc );

    // Show the new task dialog.
    m_wndTD.SendMessage ( TDM_NAVIGATE_PAGE, 0, (LPARAM) &tdc );

    // Remove the image from the Rebuild button.
BUTTON_IMAGELIST bil = {0};

    SendDlgItemMessage ( IDC_REBUILD_DLG, BCM_SETIMAGELIST, 0, (LPARAM) &bil );

    return 0;
}

LRESULT CEditorFrame::OnShowApplyFlag ( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
BUTTON_IMAGELIST bil = {0};

    // Set an imagelist on the button so it shows the info icon.
    bil.himl = m_iml;
    bil.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT;
    SendDlgItemMessage ( IDC_REBUILD_DLG, BCM_SETIMAGELIST, 0, (LPARAM) &bil );

    return 0;
}


/////////////////////////////////////////////////////////////////////////////
// Command handlers

void CEditorFrame::OnAbout ( UINT uCode, int nID, HWND hwndCtrl )
{
CAboutDlg dlg;

    dlg.DoModal ( m_hWnd );
}

void CEditorFrame::OnSaveXML ( UINT uCode, int nID, HWND hwndCtrl )
try
{
IXMLDOMDocumentPtr pDoc ( __uuidof(MSXML::DOMDocument) );
IXMLDOMElementPtr pRoot;

    // Set up the root node and the file version number.
    pRoot = pDoc->createElement ( "task_dialog" );
    pRoot->setAttribute ( "version", 1 );
    pDoc->appendChild ( pRoot );

DWORD dwTextFlags = 0, dwButtonsFlags = 0, dwRadioButtonsFlags = 0,
      dwFlagsFlags = 0, dwAllFlags = 0;

    // Call each page to save its settings to the XML doc.
    if ( m_wndTextTools.SaveToXML ( pDoc, dwTextFlags ) &&
         m_wndButtonsTools.SaveToXML ( pDoc, dwButtonsFlags ) &&
         m_wndRadioButtonsTools.SaveToXML ( pDoc, dwRadioButtonsFlags ) &&
         m_wndFlagsTools.SaveToXML ( pDoc, dwFlagsFlags ) )
        {
        IXMLDOMElementPtr pFlags = pDoc->selectSingleNode ( "/task_dialog/flags" );

        if ( NULL == pFlags )
            {
            pFlags = pDoc->createElement ( "flags" );
            pDoc->documentElement->appendChild ( pFlags );
            }

        dwAllFlags = dwTextFlags | dwButtonsFlags | dwRadioButtonsFlags | dwFlagsFlags;

        // Convert the flag values to string tokens & write any leftover flags
        // to the "extra" attribute.
        if ( WriteFlagTokens ( pFlags, dwAllFlags ) )
            {
            CString sFilename;

            if ( dwAllFlags > 0 )
                pFlags->setAttribute ( "extra", dwAllFlags );

            if ( PromptForFilename ( sFilename ) )
                pDoc->save ( (_bstr_t)(LPCTSTR) sFilename );
            }
        }
}
catch ( _com_error& e )
{
    (void) e;
    ATLTRACE(_T("Error while saving XML: %s\n"), e.ErrorMessage());
}

void CEditorFrame::OnRebuild ( UINT uCode, int nID, HWND hwndCtrl )
{
    SendMessage ( UWM_REBUILD_DLG );
}

void CEditorFrame::OnCancel ( UINT uCode, int nID, HWND hwndCtrl )
{
    // Set this flag so the TDN_BUTTON_CLICKED notification handler will allow
    // the task dlg to close.
    m_bAllowTDClose = true;
    m_wndTD.SendMessage ( TDM_CLICK_BUTTON, IDOK );
}

LRESULT CEditorFrame::OnTabSelchange ( NMHDR* phdr )
{
int nSelTab = m_wndTab.GetCurSel();

    // Hide the currently-visible dialog.
    m_wndCurrPage.ShowWindow ( SW_HIDE );

    // Determine which dlg should be visible.
    if ( 0 == nSelTab )
        m_wndCurrPage = m_wndTextTools.m_hWnd;
    else if ( 1 == nSelTab )
        m_wndCurrPage = m_wndButtonsTools.m_hWnd;
    else if ( 2 == nSelTab )
        m_wndCurrPage = m_wndRadioButtonsTools.m_hWnd;
    else if ( 3 == nSelTab )
        m_wndCurrPage = m_wndFlagsTools.m_hWnd;
    else
        ATLASSERT(0);

    m_wndCurrPage.ShowWindow ( SW_SHOW );
    return 0;   // retval ignored
}

bool CEditorFrame::WriteFlagTokens ( IXMLDOMElementPtr& pFlagsElt, DWORD& dwFlags )
try
{
struct { DWORD dwFlag; LPCTSTR szToken; } aszTokens[] = {
    { TDF_USE_COMMAND_LINKS,           _T("use_command_links") },
    { TDF_USE_COMMAND_LINKS_NO_ICON,   _T("use_command_links_no_icon") },
    { TDF_ENABLE_HYPERLINKS,           _T("allow_hyperlinks") },
    { TDF_ALLOW_DIALOG_CANCELLATION,   _T("allow_cancellation") },
    { TDF_EXPAND_FOOTER_AREA,          _T("expand_footer") },
    { TDF_EXPANDED_BY_DEFAULT,         _T("expanded_by_default") },
    { TDF_VERIFICATION_FLAG_CHECKED,   _T("checked_by_default") },
    { TDF_SHOW_PROGRESS_BAR,           _T("show_progress_bar") },
    { TDF_SHOW_MARQUEE_PROGRESS_BAR,   _T("show_marquee") },
    { TDF_POSITION_RELATIVE_TO_WINDOW, _T("position_relative") },
    { 0 }
}, *p;
CString sTokens;

    // Convert the flags we know about into their string token equivalents.
    for ( p = aszTokens; NULL != p->dwFlag && dwFlags > 0; p++ )
        {
        if ( dwFlags & p->dwFlag )
            {
            dwFlags &= ~(p->dwFlag);

            if ( !sTokens.IsEmpty() )
                sTokens += _T(" ");

            sTokens += p->szToken;
            }
        }

    if ( !sTokens.IsEmpty() )
        pFlagsElt->text = (_bstr_t)(LPCTSTR) sTokens;

    return true;
}
catch ( _com_error& e )
{
    (void) e;
    ATLTRACE(_T("Exception while building XML: %s\n"), e.ErrorMessage());
    return false;
}

bool CEditorFrame::PromptForFilename ( CString& sFilename )
{
HRESULT hr;
CComPtr<IFileSaveDialog> pDlg;
CString sXMLFilter((LPCTSTR) IDS_XML_FILES_FILTERSPEC),
        sAllFilesFilter((LPCTSTR) IDS_ALL_FILES_FILTERSPEC),
        sDlgTitle((LPCTSTR) IDS_SAVE_FILE_DLG_TITLE);
COMDLG_FILTERSPEC aFileTypes[] = {
    { sXMLFilter, L"*.xml" },
    { sAllFilesFilter, L"*.*" }
};

    // Use the new Vista file-save dialog!
    hr = pDlg.CoCreateInstance ( __uuidof(FileSaveDialog) );

    if ( FAILED(hr) )
        return false;

    pDlg->SetFileTypes ( _countof(aFileTypes), aFileTypes );
    pDlg->SetDefaultExtension ( L"xml" );
    pDlg->SetTitle ( sDlgTitle );

    hr = pDlg->Show ( m_hWnd );

    if ( FAILED(hr) )
        return false;

CComPtr<IShellItem> pSelectedItem;
LPOLESTR pwsz = NULL;

    hr = pDlg->GetResult ( &pSelectedItem );

    if ( FAILED(hr) )
        return false;

    hr = pSelectedItem->GetDisplayName ( SIGDN_FILESYSPATH, &pwsz );

    if ( FAILED(hr) )
        return false;

    sFilename = pwsz;
    CoTaskMemFree ( pwsz );
    return true;
}

HRESULT CALLBACK CEditorFrame::TDCallback (
    HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR dwRefData )
{
CEditorFrame* pThis = (CEditorFrame*) dwRefData;

    ATLASSERT(NULL != pThis);

#ifdef _DEBUG
    // In debug builds, print out a trace message showing which notification we got.
static LPCTSTR aszNotifications[] = {
    _T("TDN_CREATED"), _T("TDN_NAVIGATED"), _T("TDN_BUTTON_CLICKED"),
    _T("TDN_HYPERLINK_CLICKED"), _T("TDN_TIMER"), _T("TDN_DESTROYED"),
    _T("TDN_RADIO_BUTTON_CLICKED"), _T("TDN_DIALOG_CONSTRUCTED"),
    _T("TDN_VERIFICATION_CLICKED"), _T("TDN_HELP"),
    _T("TDN_EXPANDO_BUTTON_CLICKED")
    };
    if ( uNotification < _countof(aszNotifications) )
        ATLTRACE(_T(">>> TD notification: %s\n"), aszNotifications[uNotification]);
    else
        ATLTRACE(_T(">>> TD notification: %u\n"), uNotification);
#endif

    return pThis->TDCallbackInternal ( hwnd, uNotification, wParam, lParam );
}

HRESULT CEditorFrame::TDCallbackInternal (
    HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam )
{
HRESULT hr = 0;

    if ( TDN_DIALOG_CONSTRUCTED == uNotification )
        {
        // Store the task dialog's HWND.
        m_wndTD = hwnd;
        m_wndTextTools.m_wndTD = hwnd;
        m_wndButtonsTools.m_wndTD = hwnd;
        m_wndRadioButtonsTools.m_wndTD = hwnd;
        m_wndFlagsTools.m_wndTD = hwnd;
        }
    else if ( TDN_CREATED == uNotification )
        {
        // Get the window text of the task dialog, which will be the default
        // for the Caption option.
        m_wndTD.GetWindowText ( m_wndTextTools.m_sDefaultCaption );

        if ( NULL != Create(NULL) )
            {
            RECT rc = {0};

            if ( SystemParametersInfo ( SPI_GETWORKAREA, 0, &rc, FALSE ) )
                SetWindowPos ( NULL, rc.left+16, rc.top+16, 0, 0, SWP_NOZORDER|SWP_NOSIZE );

            ShowWindow(SW_SHOW);
            }
        }
    else if ( TDN_BUTTON_CLICKED == uNotification )
        hr = !m_bAllowTDClose;  // Return value for this message is a BOOL, true=>don't close
    else if ( TDN_NAVIGATED == uNotification )
        {
        // Notify each page that the task dlg's UI was updated, so they can make
        // any necessary changes to UI elements.
        m_wndTextTools.OnTDNavigated();
        m_wndButtonsTools.OnTDNavigated();
        m_wndRadioButtonsTools.OnTDNavigated();
        m_wndFlagsTools.OnTDNavigated();
        }
    else if ( TDN_HYPERLINK_CLICKED == uNotification )
        {
        // Show a message box to indicate that the link was clicked
        TaskDialog ( hwnd, _Module.GetResourceInstance(), MAKEINTRESOURCEW(IDR_MAINFRAME),
                     MAKEINTRESOURCEW(IDS_LINK_CLICKED_MSG), (LPCWSTR) lParam,
                     TDCBF_CLOSE_BUTTON, TD_INFORMATION_ICON, NULL );
        }
    else if ( TDN_DESTROYED == uNotification )
        DestroyWindow();        // Destroy the editor window

    return hr;
}

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) VMware
United States United States
Michael lives in sunny Mountain View, California. He started programming with an Apple //e in 4th grade, graduated from UCLA with a math degree in 1994, and immediately landed a job as a QA engineer at Symantec, working on the Norton AntiVirus team. He pretty much taught himself Windows and MFC programming, and in 1999 he designed and coded a new interface for Norton AntiVirus 2000.
Mike has been a a developer at Napster and at his own lil' startup, Zabersoft, a development company he co-founded with offices in Los Angeles and Odense, Denmark. Mike is now a senior engineer at VMware.

He also enjoys his hobbies of playing pinball, bike riding, photography, and Domion on Friday nights (current favorite combo: Village + double Pirate Ship). He would get his own snooker table too if they weren't so darn big! He is also sad that he's forgotten the languages he's studied: French, Mandarin Chinese, and Japanese.

Mike was a VC MVP from 2005 to 2009.

Comments and Discussions