Поделиться через


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

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

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

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

Простейшая форма модального диалогового окна — это окно сообщения. Большинство приложений используют поля сообщений, чтобы предупреждать пользователя об ошибках и запрашивать указания о том, как продолжить работу после возникновения ошибки. Вы создаете поле сообщения с помощью функции 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 загружает шаблон, отображает диалоговое окно и обрабатывает все входные данные пользователя, пока пользователь не закрывает диалоговое окно.

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

Первая часть примера состоит из инструкций, создающих модальное диалоговое окно. Эти инструкции в процедуре окна главного окна приложения создают диалоговое окно, когда система получает сообщение 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; 

В этом примере приложение указывает его главное окно в качестве окна владельца диалогового окна. Когда система в первый раз отображает диалоговое окно, его положение определяется относительно левого верхнего угла клиентской области окна владельца. Приложение использует возвращаемое значение из 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 соответствуют кнопкам OK и "Отмена". После вызова процедуры EndDialogсистема отправляет дополнительные сообщения в процедуру для уничтожения диалогового окна и возвращает возвращаемое значение диалогового окна обратно в функцию, которая создала диалоговое окно.

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

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

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

Инструкции в первой части примера создают диалоговое окно без режима. Эти инструкции в обработчике окна главного окна приложения создают диалоговое окно, когда обработчик окна получает сообщение WM_COMMAND с идентификатором меню IDM_GOTO, но только если эта глобальная переменная еще не содержит действительный дескриптор. Второй частью примера является основной цикл сообщений приложения. Цикл включает функцию 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 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; 
}