Использование диалоговых окон

Диалоговые окна используются для отображения сведений и запроса ввода от пользователя. Приложение загружает и инициализирует диалоговое окно, обрабатывает ввод данных пользователем и уничтожает диалоговое окно, когда пользователь завершает задачу. Процесс обработки диалоговых окон зависит от того, является ли диалоговое окно модальным или немодальным. Модальное диалоговое окно требует, чтобы пользователь закрыл диалоговое окно перед активацией другого окна в приложении. Однако пользователь может активировать окна в разных приложениях. Немодное диалоговое окно не требует немедленного ответа от пользователя. Это похоже на окно main, содержащее элементы управления.

В следующих разделах рассматривается использование диалоговых окон обоих типов.

Отображение окна сообщения

Самой простой формой модального диалогового окна является окно сообщения. Большинство приложений используют окна сообщений, чтобы предупреждать пользователя об ошибках и запрашивать инструкции о том, как продолжить работу после возникновения ошибки. Окно сообщения создается с помощью функции MessageBox или MessageBoxEx , указывая сообщение, а также количество и тип отображаемых кнопок. Система создает модальное диалоговое окно, предоставляя собственный шаблон диалогового окна и процедуру. Когда пользователь закроет окно сообщения, MessageBox или MessageBoxEx возвращает значение, определяющее кнопку, выбранную пользователем для закрытия окна сообщения.

В следующем примере приложение отображает окно сообщения, которое запрашивает у пользователя действие после возникновения ошибки. В окне сообщения отображается сообщение с описанием условия ошибки и способами его устранения. Стиль MB_YESNO предписывает MessageBox предоставить две кнопки, с помощью которых пользователь может выбрать, как продолжить:

int DisplayConfirmSaveAsMessageBox()
{
    int msgboxID = MessageBox(
        NULL,
        L"temp.txt already exists.\nDo you want to replace it?",
        L"Confirm Save As",
        MB_ICONEXCLAMATION | MB_YESNO
    );

    if (msgboxID == IDYES)
    {
        // TODO: add code
    }

    return msgboxID;    
}

На следующем рисунке показаны выходные данные из предыдущего примера кода:

окно сообщения

Создание модального диалогового окна

Модальное диалоговое окно создается с помощью функции DialogBox . Необходимо указать идентификатор или имя ресурса шаблона диалогового окна и указатель на процедуру диалогового окна. Функция DialogBox загружает шаблон, отображает диалоговое окно и обрабатывает все введенные пользователем данные, пока пользователь не закроет диалоговое окно.

В следующем примере приложение отображает модальное диалоговое окно, когда пользователь щелкает пункт Удалить элемент в меню приложения. Диалоговое окно содержит элемент управления редактированием (в котором пользователь вводит имя элемента) и кнопки ОК и Отмена . Идентификаторами этих элементов управления являются ID_ITEMNAME, IDOK и IDCANCEL соответственно.

Первая часть примера состоит из операторов, создающих модальное диалоговое окно. Эти инструкции в процедуре окна main приложения создают диалоговое окно, когда система получает WM_COMMAND сообщение с идентификатором меню IDM_DELETEITEM. Вторая часть примера — это процедура диалогового окна, которая извлекает содержимое элемента управления редактированием и закрывает диалоговое окно после получения сообщения WM_COMMAND .

Следующие инструкции создают модальное диалоговое окно. Шаблон диалогового окна является ресурсом в исполняемом файле приложения и имеет идентификатор ресурса DLG_DELETEITEM.

case WM_COMMAND: 
    switch (LOWORD(wParam)) 
    { 
        case IDM_DELETEITEM: 
            if (DialogBox(hinst, 
                          MAKEINTRESOURCE(DLG_DELETEITEM), 
                          hwnd, 
                          (DLGPROC)DeleteItemProc)==IDOK) 
            {
                // Complete the command; szItemName contains the 
                // name of the item to delete. 
            }

            else 
            {
                // Cancel the command. 
            } 
            break; 
    } 
    return 0L; 

В этом примере приложение указывает окно main в качестве окна владельца диалогового окна. Когда система изначально отображает диалоговое окно, его положение находится относительно левого верхнего угла клиентской области окна-владельца. Приложение использует возвращаемое значение из DialogBox , чтобы определить, следует ли продолжить операцию или отменить ее. Следующие инструкции определяют процедуру диалогового окна.

char szItemName[80]; // receives name of item to delete. 
 
BOOL CALLBACK DeleteItemProc(HWND hwndDlg, 
                             UINT message, 
                             WPARAM wParam, 
                             LPARAM lParam) 
{ 
    switch (message) 
    { 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDOK: 
                    if (!GetDlgItemText(hwndDlg, ID_ITEMNAME, szItemName, 80)) 
                         *szItemName=0; 
 
                    // Fall through. 
 
                case IDCANCEL: 
                    EndDialog(hwndDlg, wParam); 
                    return TRUE; 
            } 
    } 
    return FALSE; 
} 

В этом примере процедура использует GetDlgItemText для получения текущего текста из элемента управления редактированием, определяемого ID_ITEMNAME. Затем процедура вызывает функцию EndDialog , чтобы задать для возвращаемого значения диалогового окна значение IDOK или IDCANCEL в зависимости от полученного сообщения и начать процесс закрытия диалогового окна. Идентификаторы IDOK и IDCANCEL соответствуют кнопкам ОК и Отмена . После того как процедура вызывает EndDialog, система отправляет дополнительные сообщения в процедуру, чтобы уничтожить диалоговое окно и возвращает возвращаемое значение диалогового окна обратно в функцию, создающую диалоговое окно.

Создание безмодерного диалогового окна

Для создания немодируемого диалогового окна используется функция CreateDialog , указывающая идентификатор или имя ресурса шаблона диалогового окна и указатель на процедуру диалогового окна. CreateDialog загружает шаблон, создает диалоговое окно и при необходимости отображает его. Приложение отвечает за получение и отправку введенных пользователем сообщений в процедуру диалогового окна.

В следующем примере приложение отображает немодное диалоговое окно , если оно еще не отображается, когда пользователь нажимает кнопку Перейти в меню приложения. Диалоговое окно содержит элемент управления редактированием, поле проверка и кнопки ОК и Отмена. Шаблон диалогового окна является ресурсом в исполняемом файле приложения и имеет идентификатор ресурса DLG_GOTO. Пользователь вводит номер строки в элементе управления редактированием и проверяет поле проверка, чтобы указать, что номер строки относительно текущей строки. Идентификаторы элементов управления: ID_LINE, ID_ABSREL, IDOK и IDCANCEL.

Операторы в первой части примера создают диалоговое окно без режима. Эти инструкции в процедуре окна main приложения создают диалоговое окно, когда процедура окна получает WM_COMMAND сообщение с идентификатором меню IDM_GOTO, но только в том случае, если глобальная переменная еще не содержит допустимого дескриптора. Вторая часть примера — это цикл main сообщений приложения. Цикл включает функцию IsDialogMessage , чтобы гарантировать, что пользователь может использовать интерфейс клавиатуры диалогового окна в этом безмодромном диалоговом окне. Третья часть примера — это процедура диалогового окна. Процедура извлекает содержимое элемента управления редактированием и проверка поле, когда пользователь нажимает кнопку ОК. Процедура удаляет диалоговое окно, когда пользователь нажимает кнопку Отмена .

HWND hwndGoto = NULL;  // Window handle of dialog box 
                
...

case WM_COMMAND: 
    switch (LOWORD(wParam)) 
    { 
        case IDM_GOTO: 
            if (!IsWindow(hwndGoto)) 
            { 
                hwndGoto = CreateDialog(hinst, 
                                        MAKEINTRESOURCE(DLG_GOTO), 
                                        hwnd, 
                                        (DLGPROC)GoToProc); 
                ShowWindow(hwndGoto, SW_SHOW); 
            } 
            break; 
    } 
    return 0L; 

В предыдущих инструкциях CreateDialog вызывается только в том случае, если hwndGoto не содержит допустимый дескриптор окна. Это гарантирует, что приложение не отображает два диалоговых окна одновременно. Для поддержки этого метода проверки процедура диалога должна установить значение NULL при удалении диалогового окна.

Цикл сообщений для приложения состоит из следующих инструкций.

BOOL bRet;

while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) 
{ 
    if (bRet == -1)
    {
        // Handle the error and possibly exit
    }
    else if (!IsWindow(hwndGoto) || !IsDialogMessage(hwndGoto, &msg)) 
    { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    } 
} 

Цикл проверяет допустимость дескриптора окна в диалоговом окне и вызывает функцию IsDialogMessage , только если дескриптор является допустимым. IsDialogMessage обрабатывает сообщение только в том случае, если оно принадлежит диалоговому окку. В противном случае возвращается значение FALSE , а цикл отправляет сообщение в соответствующее окно.

Следующие инструкции определяют процедуру диалогового окна.

int iLine;             // Receives line number.
BOOL fRelative;        // Receives check box status. 
 
BOOL CALLBACK GoToProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
    BOOL fError; 
 
    switch (message) 
    { 
        case WM_INITDIALOG: 
            CheckDlgButton(hwndDlg, ID_ABSREL, fRelative); 
            return TRUE; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDOK: 
                    fRelative = IsDlgButtonChecked(hwndDlg, ID_ABSREL); 
                    iLine = GetDlgItemInt(hwndDlg, ID_LINE, &fError, fRelative); 
                    if (fError) 
                    { 
                        MessageBox(hwndDlg, SZINVALIDNUMBER, SZGOTOERR, MB_OK); 
                        SendDlgItemMessage(hwndDlg, ID_LINE, EM_SETSEL, 0, -1L); 
                    } 
                    else 

                    // Notify the owner window to carry out the task. 
 
                    return TRUE; 
 
                case IDCANCEL: 
                    DestroyWindow(hwndDlg); 
                    hwndGoto = NULL; 
                    return TRUE; 
            } 
    } 
    return FALSE; 
} 

В предыдущих инструкциях процедура обрабатывает WM_INITDIALOG и WM_COMMAND сообщения. Во время обработки WM_INITDIALOG процедура инициализирует поле проверка путем передачи текущего значения глобальной переменной в CheckDlgButton. Затем процедура возвращает значение TRUE , чтобы направить систему на настройку фокуса ввода по умолчанию.

Во время обработки WM_COMMAND процедура закрывает диалоговое окно, только если пользователь нажимает кнопку Отмена , то есть кнопку с идентификатором IDCANCEL. Процедура должна вызвать DestroyWindow , чтобы закрыть безрежимное диалоговое окно. Обратите внимание, что процедура также задает для переменной значение NULL , чтобы обеспечить правильную работу других операторов, зависящих от этой переменной.

Если пользователь нажимает кнопку ОК, процедура получает текущее состояние поля проверка и назначает его переменной fRelative. Затем он использует переменную для получения номера строки из элемента управления редактированием. GetDlgItemInt преобразует текст в элементе управления редактированием в целое число. Значение fRelative определяет, интерпретирует ли функция число как значение со знаком или без знака. Если текст элемента управления редактированием не является допустимым числом, GetDlgItemInt задает значение переменной fError ненулевое. Процедура проверяет это значение, чтобы определить, следует ли отображать сообщение об ошибке или выполнять задачу. В случае ошибки процедура диалогового окна отправляет сообщение в элемент управления редактирования, направляя его на выделение текста в элементе управления, чтобы пользователь смог легко заменить его. Если GetDlgItemInt не возвращает ошибку, процедура может либо выполнить запрошенную задачу самостоятельно, либо отправить сообщение окну владельца, направив его на выполнение операции.

Инициализация диалогового окна

Диалоговое окно и его содержимое инициализируется при обработке сообщения WM_INITDIALOG . Наиболее распространенной задачей является инициализация элементов управления с учетом текущих параметров диалогового окна. Другой распространенной задачей является центр диалогового окна на экране или в окне владельца. Полезной задачей для некоторых диалоговых окон является установка фокуса ввода на указанный элемент управления, а не прием фокуса ввода по умолчанию.

В следующем примере процедура диалогового окна центрируется и задает фокус ввода при обработке WM_INITDIALOG сообщения. Чтобы вывести диалоговое окно по центру, процедура извлекает прямоугольники окна для диалогового окна и окна-владельца и вычисляет новую позицию для диалогового окна. Чтобы задать фокус ввода, процедура проверяет параметр wParam , чтобы определить идентификатор фокуса ввода по умолчанию.

HWND hwndOwner; 
RECT rc, rcDlg, rcOwner; 

....
 
case WM_INITDIALOG: 

    // Get the owner window and dialog box rectangles. 

    if ((hwndOwner = GetParent(hwndDlg)) == NULL) 
    {
        hwndOwner = GetDesktopWindow(); 
    }

    GetWindowRect(hwndOwner, &rcOwner); 
    GetWindowRect(hwndDlg, &rcDlg); 
    CopyRect(&rc, &rcOwner); 

    // Offset the owner and dialog box rectangles so that right and bottom 
    // values represent the width and height, and then offset the owner again 
    // to discard space taken up by the dialog box. 

    OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); 
    OffsetRect(&rc, -rc.left, -rc.top); 
    OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); 

    // The new position is the sum of half the remaining space and the owner's 
    // original position. 

    SetWindowPos(hwndDlg, 
                 HWND_TOP, 
                 rcOwner.left + (rc.right / 2), 
                 rcOwner.top + (rc.bottom / 2), 
                 0, 0,          // Ignores size arguments. 
                 SWP_NOSIZE); 

    if (GetDlgCtrlID((HWND) wParam) != ID_ITEMNAME) 
    { 
        SetFocus(GetDlgItem(hwndDlg, ID_ITEMNAME)); 
        return FALSE; 
    } 
    return TRUE; 

В предыдущих инструкциях процедура использует функцию GetParent для получения дескриптора окна-владельца в диалоговом окне. Функция возвращает дескриптор окна-владельца в диалоговые окна, а дескриптор родительского окна — дочерним окнам. Так как приложение может создать диалоговое окно без владельца, процедура проверяет возвращенный дескриптор и при необходимости использует функцию GetDesktopWindow для получения дескриптора окна рабочего стола. После вычисления новой позиции процедура перемещает диалоговое окно с помощью функции SetWindowPos , указывая значение HWND_TOP, чтобы диалоговое окно оставалось поверх окна владельца.

Перед настройкой фокуса ввода процедура проверяет идентификатор элемента управления для фокуса ввода по умолчанию. Система передает дескриптор окна входного фокуса по умолчанию в параметре wParam . Функция GetDlgCtrlID возвращает идентификатор элемента управления, определяемого дескриптором окна. Если идентификатор не соответствует правильному идентификатору, процедура использует функцию SetFocus для установки фокуса ввода. Функция GetDlgItem необходима для получения дескриптора окна нужного элемента управления.

Создание шаблона в памяти

Иногда приложения адаптируют или изменяют содержимое диалоговых окон в зависимости от текущего состояния обрабатываемых данных. В таких случаях нецелесообразно предоставлять все возможные шаблоны диалоговых окон в качестве ресурсов в исполняемом файле приложения. Но создание шаблонов в памяти дает приложению большую гибкость для адаптации к любым обстоятельствам.

В следующем примере приложение создает шаблон в памяти для модального диалогового окна, содержащего сообщение и кнопки ОК и Справка .

В шаблоне диалогового окна все символьные строки, такие как диалоговое окно и заголовки кнопок, должны быть строками Юникода. В этом примере для создания этих строк Юникода используется функция MultiByteToWideChar .

Структуры DLGITEMTEMPLATE в шаблоне диалогового окна должны быть выровнены по границам DWORD . Для выравнивания этих структур в этом примере используется вспомогающая подпрограмма, которая принимает входной указатель и возвращает ближайший указатель, выровненный по границе DWORD .

#define ID_HELP   150
#define ID_TEXT   200

LPWORD lpwAlign(LPWORD lpIn)
{
    ULONG ul;

    ul = (ULONG)lpIn;
    ul ++;
    ul >>=1;
    ul <<=1;
    return (LPWORD)ul;
}

LRESULT DisplayMyMessage(HINSTANCE hinst, HWND hwndOwner, LPSTR lpszMessage)
{
    HGLOBAL hgbl;
    LPDLGTEMPLATE lpdt;
    LPDLGITEMTEMPLATE lpdit;
    LPWORD lpw;
    LPWSTR lpwsz;
    LRESULT ret;
    int nchar;

    hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024);
    if (!hgbl)
        return -1;
 
    lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
    // Define a dialog box.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
    lpdt->cdit = 3;         // Number of controls
    lpdt->x  = 10;  lpdt->y  = 10;
    lpdt->cx = 100; lpdt->cy = 100;

    lpw = (LPWORD)(lpdt + 1);
    *lpw++ = 0;             // No menu
    *lpw++ = 0;             // Predefined dialog box class (by default)

    lpwsz = (LPWSTR)lpw;
    nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "My Dialog", -1, lpwsz, 50);
    lpw += nchar;

    //-----------------------
    // Define an OK button.
    //-----------------------
    lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE)lpw;
    lpdit->x  = 10; lpdit->y  = 70;
    lpdit->cx = 80; lpdit->cy = 20;
    lpdit->id = IDOK;       // OK button identifier
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON;

    lpw = (LPWORD)(lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;        // Button class

    lpwsz = (LPWSTR)lpw;
    nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw += nchar;
    *lpw++ = 0;             // No creation data

    //-----------------------
    // Define a Help button.
    //-----------------------
    lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE)lpw;
    lpdit->x  = 55; lpdit->y  = 10;
    lpdit->cx = 40; lpdit->cy = 20;
    lpdit->id = ID_HELP;    // Help button identifier
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;

    lpw = (LPWORD)(lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;        // Button class atom

    lpwsz = (LPWSTR)lpw;
    nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "Help", -1, lpwsz, 50);
    lpw += nchar;
    *lpw++ = 0;             // No creation data

    //-----------------------
    // Define a static text control.
    //-----------------------
    lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE)lpw;
    lpdit->x  = 10; lpdit->y  = 10;
    lpdit->cx = 40; lpdit->cy = 20;
    lpdit->id = ID_TEXT;    // Text identifier
    lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;

    lpw = (LPWORD)(lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0082;        // Static class

    for (lpwsz = (LPWSTR)lpw; *lpwsz++ = (WCHAR)*lpszMessage++;);
    lpw = (LPWORD)lpwsz;
    *lpw++ = 0;             // No creation data

    GlobalUnlock(hgbl); 
    ret = DialogBoxIndirect(hinst, 
                           (LPDLGTEMPLATE)hgbl, 
                           hwndOwner, 
                           (DLGPROC)DialogProc); 
    GlobalFree(hgbl); 
    return ret; 
}