2

I have written this function so far:

int CMFCApplication3App::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
{
    CString strContent = CString(lpszPrompt);
    CString strTitle; strTitle.LoadString(AFX_IDS_APP_TITLE);
    CTaskDialog dlgTaskMessageBox(strContent, _T(""), strTitle);
    int iPixelWidth = (::GetSystemMetrics(SM_CXSCREEN) / 100) * 30;
    int iDialogUnitsWidth = MulDiv(iPixelWidth, 4, LOWORD(GetDialogBaseUnits()));
    dlgTaskMessageBox.SetDialogWidth(iDialogUnitsWidth);

    /*
    if (nType & MB_ICONINFORMATION)
        dlgTaskMessageBox.SetMainIcon(TD_INFORMATION_ICON);
    if (nType & MB_ICONERROR)
        dlgTaskMessageBox.SetMainIcon(TD_ERROR_ICON);
    if (nType & MB_ICONWARNING)
        dlgTaskMessageBox.SetMainIcon(TD_WARNING_ICON);
    if (nType & MB_ICONQUESTION)
    {
        HICON hIcon = LoadIcon(IDI_QUESTION);
        dlgTaskMessageBox.SetMainIcon(hIcon);
    }

    int iButtons = 0;
    if (nType & IDYES)
        iButtons |= TDCBF_YES_BUTTON;
    if (nType & IDNO)
        iButtons |= TDCBF_NO_BUTTON;
    if (nType & IDCANCEL)
        iButtons |= TDCBF_CANCEL_BUTTON;
    if (nType & IDOK)
        iButtons |= TDCBF_OK_BUTTON;
    if (nType & IDRETRY)
        iButtons |= TDCBF_RETRY_BUTTON;
    dlgTaskMessageBox.SetCommonButtons(iButtons);
    */

    if (nType == (MB_YESNOCANCEL | MB_ICONERROR))
    {
        dlgTaskMessageBox.SetCommonButtons(TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);
        dlgTaskMessageBox.SetMainIcon(TD_ERROR_ICON);
    }
    if (nType == (MB_YESNOCANCEL | MB_ICONWARNING))
    {
        dlgTaskMessageBox.SetCommonButtons(TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);
        dlgTaskMessageBox.SetMainIcon(TD_WARNING_ICON);
    }
    if (nType == (MB_YESNOCANCEL | MB_ICONINFORMATION))
    {
        dlgTaskMessageBox.SetCommonButtons(TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);
        dlgTaskMessageBox.SetMainIcon(TD_INFORMATION_ICON);
    }
    /*
    if (nType == (MB_YESNOCANCEL | MB_ICONQUESTION))
    {
        dlgTaskMessageBox.SetCommonButtons(TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);
        HICON hIcon = LoadIcon(IDI_QUESTION);
        dlgTaskMessageBox.SetMainIcon(hIcon);
    }
    */
    return dlgTaskMessageBox.DoModal();
}

I have two issues with this and am happy to split as two questions:

  1. Using IDI_QUESTION is causing the application to crash.
  2. Isn't there an easier way to decode nType into the various buttons and icon required?
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164

2 Answers2

3

Using IDI_QUESTION is causing the application to crash

That's because IDI_QUESTION is a standard icon and must be loaded by passing a NULL instance handle to ::LoadIcon, but MFC's CWinApp::LoadIcon passes AfxGetResourceHandle() instead. The following bypasses MFC and calls the Win32 API directly.

HICON hIcon = ::LoadIcon(NULL, IDI_QUESTION);

Isn't there an easier way to decode nType into the various buttons and icon required?

Not a lot easier, but they could be grouped to lessen the duplication.

int nCommonButtons = 0;

switch(nType)
{
case MB_YESNOCANCEL:
    nCommonButtons |= TDCBF_CANCEL_BUTTON;
case MB_YESNO:
    nCommonButtons |= TDCBF_YES_BUTTON | TDCBF_NO_BUTTON; 
    break;

case MB_OKCANCELRETRY:
    nCommonButtons |= TDCBF_RETRY_BUTTON;
case MB_OKCANCEL:
    nCommonButtons |= TDCBF_CANCEL_BUTTON;
case MB_OK:
    nCommonButtons |= TDCBF_OK_BUTTON; 
    break;

//... etc
}
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
dxiv
  • 16,984
  • 2
  • 27
  • 49
  • Thanks. I have it working now. I added my own answer too. – Andrew Truckle Feb 02 '21 at 18:23
  • Glad it helped. Yours will work, too. Thing is that I am not aware of a builtin way to map between the two sets of flags, so it has to be done by hand one way or another. – dxiv Feb 02 '21 at 18:27
  • I see that the TaskDialog itself has some members `CTaskDialog::GetCommonButtonFlag` and `CTaskDialog::GetCommonButtonId` but they are not accessible. – Andrew Truckle Feb 02 '21 at 18:30
  • One issue, my real application also uses `MB_ICONSTOP` and I am not sure of equivalent. – Andrew Truckle Feb 02 '21 at 18:33
  • 1
    @AndrewTruckle That should be `IDI_HAND` (same as `IDI_ERROR`). And those two functions wouldn't help much here, because they map between CTaskDialog styles and the standard button IDs, which don't correspond directly to the MessageBox styles. – dxiv Feb 02 '21 at 18:39
  • So I should double up the `MB_ICONSTOP` tests with the `MB_ICONERROR` test really. – Andrew Truckle Feb 02 '21 at 18:44
  • 2
    @AndrewTruckle Yes, the two are [the same](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadicona#parameters). – dxiv Feb 02 '21 at 18:48
  • 1
    Doh - I did not need to update my code then - save values. Already caught. – Andrew Truckle Feb 02 '21 at 18:59
3

I thought I would add this as addition answer.

Taking onboard the accepted answer (@dxiv) and the comments that were made below (@sergiol):

int CMeetingScheduleAssistantApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
{
    CString strContent = CString(lpszPrompt);
    CString strTitle = CString();

    if (!CTaskDialog::IsSupported())
        return CWinAppEx::DoMessageBox(lpszPrompt, nType, nIDPrompt);

    ENSURE(strTitle.LoadString(AFX_IDS_APP_TITLE));
    CTaskDialog dlgTaskMessageBox(strContent, _T(""), strTitle);

    int iPixelWidth = (::GetSystemMetrics(SM_CXSCREEN) / 100) * 30;
    int iDialogUnitsWidth = MulDiv(iPixelWidth, 4, LOWORD(GetDialogBaseUnits()));
    dlgTaskMessageBox.SetDialogWidth(iDialogUnitsWidth);
    HICON hQuestionIcon = ::LoadIcon(nullptr, IDI_QUESTION);

    // Icon
    switch (nType & MB_ICONMASK)
    {
    case MB_ICONERROR:
        dlgTaskMessageBox.SetMainIcon(TD_ERROR_ICON);
        break;
    case MB_ICONWARNING:
        dlgTaskMessageBox.SetMainIcon(TD_WARNING_ICON);
        break;
    case MB_ICONINFORMATION:
        dlgTaskMessageBox.SetMainIcon(TD_INFORMATION_ICON);
        break;
    case MB_ICONQUESTION:
        dlgTaskMessageBox.SetMainIcon(hQuestionIcon);
        break;
    }

    // Buttons
    int nCommonButtons = 0;
    switch (nType & MB_TYPEMASK)
    {
    case MB_YESNOCANCEL:
        nCommonButtons |= TDCBF_CANCEL_BUTTON;
        [[fallthrough]];
    case MB_YESNO:
        nCommonButtons |= TDCBF_YES_BUTTON | TDCBF_NO_BUTTON;
        break;

    case MB_RETRYCANCEL:
        nCommonButtons |= TDCBF_RETRY_BUTTON | TDCBF_NO_BUTTON;
        break;

    case MB_OKCANCEL:
        nCommonButtons |= TDCBF_CANCEL_BUTTON;
        [[fallthrough]];
    case MB_OK:
    default:
        nCommonButtons |= TDCBF_OK_BUTTON;
    }
    dlgTaskMessageBox.SetCommonButtons(nCommonButtons);

    return static_cast<int>(dlgTaskMessageBox.DoModal());
}

I was not aware of these:

  • MB_ICONMASK
  • MB_TYPEMASK

The only scenario I have not catered for is MB_ABORTRETRYIGNORE because I do not see common buttons equivalents for Abort and Ignore.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    Why are you mixing logic of buttons visibility with logic of icon? For icons you can do something like `switch (nType & MB_ICONMASK)` and for buttons `switch (nType & MB_TYPEMASK)`. – sergiol Feb 03 '21 at 02:08
  • 1
    @sergiol What exactly are MB_ICONMASK etc? Are these special for retrieving the button and icon values? Where is it documented for MFC? – Andrew Truckle Feb 03 '21 at 05:37
  • 1
    The documentation on those is really poor or inexistant. I remember the flags for something we have done here before, but they are essentially a mask to filter against some `MB_*` values, depending on the purpose. Sometimes the better way to know about MFC is to look to the base source code which is supplied, or to put some breakpoints in there. – sergiol Feb 03 '21 at 09:32
  • @IInspectable is there any documentation you know about the `MB_*MASK` entities? – sergiol Feb 04 '21 at 12:30