winapi

Keep dialog box param object passed to DialogBoxParam for the duration of the dialog and its messages


I have a parameterized generic dialog that I create with DialogBoxParam that accepts a param data object,

INPUT_PARAMS* inputParams = new INPUT_PARAMS();
inputParams->caption = ...;
inputParams->text = ...;
inputParams->okFunc = ...

DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_INPUT), InputDlgProc, (LPARAM)inputParams);

In the dialog proc, the custom data is available to me in WM_INITDIALOG as LPARAM, but not in any of the other messages.

INT_PTR CALLBACK InputDlgProc(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam) {
    switch (message)
    {
        case WM_INITDIALOG:
        {
            INPUT_PARAMS* inputParams = (INPUT_PARAMS*)lParam;
            // ... Set dialog attributes based on params
            SetWindowText(hWndDlg, inputParams->caption.c_str());
            // etc.
            return TRUE;
        }
        case WM_COMMAND:
           switch (LOWORD(wParam)) {
              case IDOK:
              {
                 // ... Still need to use data object here for OK logic, not available

                 EndDialog(hWndDlg, LOWORD(wParam));
                 break;
              }
           }
           break;
      }
      return DefWindowProc(hWndDlg, message, wParam, lParam);  
}

Throughout the dialog's duration I need to have the object available so that the IDOK WM_COMMAND can use it for its logic. I wish I could store it like a data attribute in an overarching class, but I'm not using C++ or MFC here. Any good solutions?

Also, the only place to free the object would seem to be WM_INITDIALOG but that's too early. The dialog always needs that param object to live alongside itself.


Solution

  • You can use SetWindowLongPtr() to store the object pointer inside the dialog HWND, and GetWindowLongPtr() to retrieve it, eg:

    INT_PTR CALLBACK InputDlgProc(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam) {
        switch (message)
        {
            case WM_INITDIALOG:
            {
                INPUT_PARAMS* inputParams = reinterpret_cast<INPUT_PARAMS*>(lParam);
                SetWindowLongPtr(hWndDlg, DWLP_USER, reinterpret_cast<LONG_PTR>(inputParams));
                // ...
                return TRUE;
            }
    
            case WM_COMMAND:
               switch (LOWORD(wParam)) {
                  case IDOK:
                  {
                     INPUT_PARAMS* inputParams = reinterpret_cast<INPUT_PARAMS*>(GetWindowLongPtr(hWndDlg, DWLP_USER));
                     // ...
                     EndDialog(hWndDlg, LOWORD(wParam));
                     return TRUE;
                  }
               }
               break;
        }
        return FALSE;
    }
    

    Also, DialogBoxParam() is a blocking function - it does not exit until the dialog is closed:

    The DialogBoxParam function uses the CreateWindowEx function to create the dialog box. DialogBoxParam then sends a WM_INITDIALOG message (and a WM_SETFONT message if the template specifies the DS_SETFONT or DS_SHELLFONT style) to the dialog box procedure. The function displays the dialog box (regardless of whether the template specifies the WS_VISIBLE style), disables the owner window, and starts its own message loop to retrieve and dispatch messages for the dialog box.

    When the dialog box procedure calls the EndDialog function, DialogBoxParam destroys the dialog box, ends the message loop, enables the owner window (if previously enabled), and returns the nResult parameter specified by the dialog box procedure when it called EndDialog.

    So, there is no need to allocate the object dynamically at all. Just declare it locally, pass a pointer to it, and let it go out of scope after the dialog is finished, eg:

    INPUT_PARAMS inputParams;
    inputParams.caption = ...;
    inputParams.text = ...;
    inputParams.okFunc = ...;
    
    DialogBoxParam(..., reinterpret_cast<LPARAM>(&inputParams));
    

    Also, DO NOT call DefWindowProc() inside of your InputDlgProc() function, per the documentation:

    DLGPROC callback function

    Typically, the dialog box procedure should return TRUE if it processed the message, and FALSE if it did not. If the dialog box procedure returns FALSE, the dialog manager performs the default dialog operation in response to the message.

    If the dialog box procedure processes a message that requires a specific return value, the dialog box procedure should set the desired return value by calling SetWindowLong(hwndDlg, DWL_MSGRESULT, lResult) immediately before returning TRUE. Note that you must call SetWindowLong immediately before returning TRUE; doing so earlier may result in the DWL_MSGRESULT value being overwritten by a nested dialog box message.

    ...

    You should use the dialog box procedure only if you use the dialog box class for the dialog box. This is the default class and is used when no explicit class is specified in the dialog box template. Although the dialog box procedure is similar to a window procedure, it must not call the DefWindowProc function to process unwanted messages. Unwanted messages are processed internally by the dialog box window procedure.