[Next] [ Previous ]     Chapter 31
   [Contents ]  [Chapter 30: Multithreading in Presentation Manager Applications   ] [ Appendix A - Window Messages]


Using Dialog Window  as Main Application's Window

Использование диалога в качестве главного окна приложения

VicTor Smirnoff

Итак, вы начали разрабатывать свою новую программу и уже придумали внешний вид главного окна, продумали элементы управления и систему меню, а также использование клавиш-акселераторов. И тут вы отмечаете, что главное окно вашей программы поразительно похоже на окно диалога. И что было бы очень даже неплохо воспользоваться средствами разработки диалогов (dlgedit или другими) вместо достаточно скучного программирования размещения элементов управления в обычном окне. Это, кроме всего прочего, позволило бы хранить шаблон диалога в ресурсах и, тем самым, сделало бы программу независимой от формы диалога. А если следовать рекомендациям IBM и собрать в ресурсы все текстовые строки, то это существенно облегчит дальнейшие работы по переводу интерфейса вашей программы на другие языки. Например, тогда для создания англоязычного варианта програмы достаточно будет перевести на английский необходимые текстовые строки в исходных файлах ресурсов и заменить ресурсы в исполняемом модуле, воспользовавшись компилятором ресурсов rc. Перекомпиляция самой программы не потребуется.

Но может ли диалог быть главным окном приложения? Сохранятся ли при этом привычные атрибуты и свойства главного окна приложения, такие как собственный значок приложения, пользовательское меню, клавиши-акселераторы и т.д.? Чтобы попытаться понять и ответить на эти вопросы, обратимся к тому, как трактуется понятие "диалог (dialog box)" в Presentation Manager:

Цитата из RedBooks "OS/2 V2.0 Volume 3: PM and Workplace Shell"
Подраздел "Dialog Box"

Диалог (dialog box) - это окно специального типа для использования в определенных обстоятельствах, когда требуется дополнительный обмен данными с пользователем. Диалоги могут быть двух отдельных типов - модальные и немодальные.

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

В модальном диалоге пользователь не может взаимодействовать с другими окнами до тех пор, пока диалог не будет закрыт. Это свойство может распространяться как на отдельное приложение (в этом случае невозможно общение с другими окнами того же самого приложения; общение же с другими приложениями в системе не запрещено), так и на всю систему (в этом случае запрещено общение с любыми другими окнами в системе до завершения диалога).

В немодальном диалоге пользователь может остановиться и продолжить работу, например, в первичном окне приложения (как это сделано для окна "Поиск" в OS/2 System Editor). Немодальный диалог предпочтительнее с позиции CUA.

Еще одно различие между диалогом и стандартным окном состоит в том, что размеры диалога не могут быть изменены (но тем не менее в определенных обстоятельствах он может быть минимизирован или максимизирован). Это свойство диалога является критичным в обеспечении целостности общения с конечным пользователем. Отметим, что хоть и нельзя менять размеры диалога, но перемещать его по рабочему столу можно.

Цитата из Redbooks "OS/2 V2.0 Volume 4: Application Development"
Подраздел "Action Windows"

По правилам CUA'91, диалог должен называться окном действия (action window). Поскольку в этой главе обсуждаются правила CUA, то термин "окно действия" будет использоваться в отношении положений, разработанных в CUA. Термин "диалог" будет использоваться в отношении применения этих концепций для Presentation Manager.

Окна действия, используемые приложениями, могут быть как модальными, так и немодальными, хотя немодальные окна предпочтительнее с позиции CUA. Окна действия обоих типов определены в CUA, как оптимизированные окна. Таким образом, они создаются предопределенного оптимального размера для своих функций и эти размеры не могут быть изменены пользователем. Конечно, использование каждого типа окон действия различно, как показано ниже.

Диалог обычно разрабатывается с помощью Dialog Editor, поставляемым в составе IBM Developer's Toolkit for OS/2, который позволяет создать шаблон диалога и сохранить его в .DLG-файле. В скрипт-файле ресурсов приложения должна существовать ссылка на этот файл (директива rcinclude).

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

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

Цитата из Redbooks "OS/2 V2.0 Volume 4: Application Development"
подраздел "Modeless Action Windows"

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

Поскольку функция WinDlgBox() автоматически создает и исполняет модальный диалог, то немодальный диалог можно создать одним из двух способов:

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

Диалог может быть размещен в заданных координатах экрана, независимо от используемого метода. В первом случае, при использовании WinCreateWindow(), диалог позиционируется во время создания. Во втором случае, диалог позиционируется во время обработки сообщения WM_INITDLG.

Результатом применения атрибута FCF_BORDER при создании диалога будет прорисовка тонкой синей линии вокруг окна (бордюр). Это соответствует правилам SAA CUA для немодальных окон действия.

Отметим, что два вышеописанных метода получают различные сообщения инициализации. Созданный функцией WinCreateWindow() диалог будет рассматриваться как "обычное" окно и ему будет передано сообщение WM_CREATE. Для диалога, созданного функцией WinDlgBox(), будет использовано сообщение WM_INITDLG. Разработчик приложения должен учитывать это при написании процедуры обработки диалога.

Из всего этого следует, что изначально не предполагалось, что диалог можно будет использовать в качестве главного окна приложения. Однако, если от использования модального диалога приходится отказаться именно в силу его модальности (кроме всего прочего, абсолютно не ясны вопросы минимизации и восстановления и то, как поведет себя, например, модальный диалог при вызове из другого модального диалога), то применение немодального диалога сдерживает только запрет на изменение размеров окна диалога пользователем, вытекающий из требований обеспечения целостности диалога ("оптимизированные окна").

Этот запрет можно просто игнорировать, если не давать пользователю возможности изменять размеры главного окна или предпринять собственные меры для обеспечения целостности диалога.

Приведем пример того, как можно построить приложение, использующее диалог в качестве главного окна.

Допустим, что в главном окне нашего приложения (назовем его "Болван диалоГовый" - коротко БГ) должны размещаться три управляющих элемента (текстовое окно, значок приложения и кнопка "Выход"). Кроме того, мы хотим использовать меню и клавиши-акселераторы (например, F3 - выход):

И, конечно же, мы хотим, чтобы пользователь мог самостоятельно изменять размеры главного окна нашего приложения (как у любого нормального окна), в том числе, и минимизировать, и максимизировать его. А сохранение позиции и размеров окна по окончании работы и восстановление их при последующем запуске вполне соответствовало бы правилам хорошего тона.
Для того, чтобы обеспечить целостность диалога, условимся о том, что размеры окна диалога не могут быть меньше некоторых минимальных значений (исключая минимизацию), и что при изменении размеров окна диалога кнопка "Выход" всегда будет располагаться в правом нижнем углу окна, а значок приложения -- в правом верхнем. Размеры текстового окна будут изменяться в соответствии с изменением размеров окна диалога. Минимальными размерами диалога условимся считать размеры окна, создаваемого системой при загрузке диалога из ресурсов.

Создадим скрипт-файл ресурсов (boldy.rc):

#include <os2.h>
#include "boldy.h"

ICON ID_MAIN boldy.ico

ACCELTABLE ID_MAIN PRELOAD
BEGIN
    VK_F3, ID_EXIT, VIRTUALKEY
END

MENU    ID_MAIN PRELOAD
BEGIN
    SUBMENU "Файл", ID_FILE
    BEGIN
      MENUITEM "Откpыть",              ID_OPEN,    MIS_TEXT
      MENUITEM "Сохpанить",            ID_SAVE,    MIS_TEXT
      MENUITEM "Сохpанить как ...",    ID_SAVEAS,  MIS_TEXT
      MENUITEM SEPARATOR
      MENUITEM "Выход\tF3",            ID_EXIT,    MIS_TEXT
    END
    SUBMENU "Справка", ID_HELP
    BEGIN
      MENUITEM "О пpогpамме...",       ID_ABOUT,  MIS_TEXT
    END
END

STRINGTABLE PRELOAD
BEGIN
     IDS_CREATEMAIN,    "Ошибка создания главного окна приложения"
     IDS_APPTITLE,      "Болван диалоГовый"
END

rcinclude boldy.dlg
 

Никаких особенностей в этом скрипт-файле нет. Отметим только, что некоторые ресурсы (иконка, таблица акселераторов и меню) имеют один и тот же идентификатор ID_MAIN. Если бы мы для создания главного окна приложения с идентификатором ID_MAIN использовали функцию WinCreateStdWindow() и при этом установили бы флаги FCF_ICON, FCF_ACCELTABLE и FCF_MENU, то для созданного окна (фрейма) автоматически были бы загружены и использованы все эти ресурсы. Кроме того, установка флага FCF_TASKLIST привела бы к тому, что наше главное окно сразу же появилось бы в списке окон.

Теперь нарисуем шаблон диалога (boldy.dlg):

  DLGINCLUDE 1 "boldy.h"

  DLGTEMPLATE ID_MAIN LOADONCALL MOVEABLE DISCARDABLE
  BEGIN
      DIALOG  "", ID_MAIN, 0, 0, 156, 43, NOT FS_DLGBORDER | FS_SIZEBORDER |
              NOT WS_SAVEBITS, FCF_SYSMENU | FCF_TITLEBAR | FCF_MINBUTTON |
              FCF_MAXBUTTON
      BEGIN
          DEFPUSHBUTTON   "Выход", ID_EXIT, 98, 0, 58, 14
          ICON            ID_MAIN, ID_ICON, 117, 20, 20, 16, WS_GROUP
          LTEXT           "Этот простой пример демонстрирует возможность созда"
                          "ния диалоговых окон с изменяемыми размерами и обычн"
                          "о отсутствующими атрибутами фрейма: (1)собственный "
                          "значок приложения, (2)пользовательское меню, (3)кла"
                          "виши-акселераторы.",
                          ID_TEXT, 1, 0, 90, 41, DT_WORDBREAK
                          PRESPARAMS PP_BACKGROUNDCOLOR, 0x00FFFFDCL
                          PRESPARAMS PP_FONTNAMESIZE, "8.Helv"
      END
  END
 

Поскольку диалог будет использоваться в качестве главного окна приложения, то ему тоже присвоен идентификатор ID_MAIN. Отметим сразу, что в описании шаблона диалога отсутствуют флаги FCF_ICON, FCF_MENU, FCF_ACCELTABLE и FCF_TASKLIST. Дело в том, что эти флаги игнорируются функциями создания окна диалога. Поэтому, независимо от того, установлены эти флаги или нет, замена вызова функции WinCreateStdWindow() на WinLoadDlg() приведет к тому, что:

  1. минизначок в левом верхнем углу окна не будет соответствовать иконке приложения, придуманой и любовно нарисованой Вами (будет использоваться стандартная иконка диалога),
  2. пользовательское меню будет отсутствовать,
  3. клавиши-акселераторы не будут работать,
  4. запущенное приложение не появится в списке окон.
Отметим также, что именно по причине, указанной в списке последней, заголовок окна в шаблоне диалога оставлен пустым. В дальнейшем, мы просто используем одну и ту же строку и для заголовка окна, и для его названия в списке окон (см. boldy.rc идентификатор IDS_APPTITLE).

Кроме того, в шаблоне диалога не используется флаг WS_VISIBLE. Это значит, что окно диалога не будет отображаться на экране до тех пор, пока мы не сделаем его видимым с помощью функций WinShowWindow() или WinSetWindowPos().

Из всего вышеизложенного следует, что нам придется самим подгружать указанные ресурсы и инициализировать их использование в программе.

Посмотрим, как это можно сделать (мы не будем рассматривать полный текст boldy.c, а только выделим и обсудим основные моменты). Как уже говорилось ранее, в качестве главного окна приложения можно использовать немодальный диалог, создав его с помощью функции WinLoadDlg(). Некоторые ресурсы (заголовок окна и иконку) можно загрузить прямо в главной процедуре приложения сразу после создания очереди сообщений:
  . . .
  // Загружаем строку с заголовком окна
     WinLoadString( hab, (HMODULE)0L, IDS_APPTITLE, MAXNAMEL,
        (PSZ)szAppTitle );

  // Загружаем икону приложения
     hptrIcon = WinLoadPointer( HWND_DESKTOP, 0L, ID_MAIN );

  // Создаем главное окно приложения
     hwndMain = WinLoadDlg( HWND_DESKTOP, // где разместить
                   HWND_DESKTOP,          // кто собственник
                   WndDlgProc,            // процедура обработки диалога
                   (HMODULE)0,            // 0 - в EXE   #0 - в DLL
                   ID_MAIN,               // идентификатор диалога в ресурсе
                   NULL);                 // данные для инициализации
  . . .

Поскольку мы использовали функцию WinLoadDlg(), то процедура обработки при инициализации окна получит сообщение WM_INITDLG, а не WM_CREATE. В принципе, можно было бы загрузить и инициализировать недостающие ресурсы при обработке этого сообщения. Но следует учитывать, что на момент его поступления окно еще фактически не существует. Поэтому, например, не удастся создать пользовательское меню или узнать размеры окна. Чтобы обойти это ограничение, обработчик сообщения WM_INITDLG может, например, поставить в очередь какое-нибудь сообщение пользователя (MY_QUERYSIZE в нашем примере), обработка которого начнется после завершения процедуры инициализации окна:

  case WM_INITDLG:
    WinPostMsg( hwnd, MY_QUERYSIZE, NULL, NULL );
    return (MRESULT)TRUE;
Обратите внимание, что обработчик сообщения WM_INITDLG возвращает значение TRUE. В этом случае, по завершении процесса инициализации диалога система не будет пытаться установить фокус на окно диалога. А нам это и не надо, поскольку мы все равно будем сами активировать свое окно.

Пользовательские сообщения MY_QUERYSIZE и MY_SETSIZE (оно нам потребуется позже) определены в файле заголовков boldy.h следующим образом:

#define MY_QUERYSIZE     WM_USER+1
#define MY_SETSIZE       WM_USER+2
Сообщение MY_QUERYSIZE поступит на обработку, когда процедура инициализации окна будет выполнена полностью. К этому времени окно уже полностью создано, но еще отсутствует на рабочем столе (при создании окна не использовался флаг WS_VISIBLE). Размеры этого окна определяются системой по неким правилам, исходя из размеров шаблона диалога и действующего разрешения экрана. Теперь мы можем загрузить и инициализировать недостающие ресурсы:
  case MY_QUERYSIZE:
    {
       SWP swp;
       SWCNTRL swctl;
       HSWITCH hswitch;
       HACCEL  hAccel;
       FRAMECDATA frame;

  // Установим собственную иконку приложения
       WinSendMsg( hwndMain,
          WM_SETICON, (MPARAM)hptrIcon, (MPARAM)0L );

  // Установим заголовок окна
       WinSetWindowText( hwndMain, szAppTitle );

  // Загрузим и инициируем таблицу акселераторов
       hAccel = WinLoadAccelTable( hab,
                   (HMODULE)NULLHANDLE, ID_MAIN );
       WinSetAccelTable( hab, hAccel, hwndMain );

  // Добавим окно в список окон
       swctl.hwnd = hwndMain;
       swctl.hwndIcon = hwndMain;
       swctl.hprog = NULLHANDLE;
       swctl.idProcess = 0;
       swctl.idSession = 0;
       swctl.uchVisibility = SWL_VISIBLE;
       swctl.fbJump = SWL_JUMPABLE;
       swctl.szSwtitle[0] = 0x00;
       swctl.szSwtitle[MAXNAMEL+1] = 0x00;
       strncpy( swctl.szSwtitle, szAppTitle, MAXNAMEL );
       hswitch = WinAddSwitchEntry( &swctl );

  // Создадим пользовательское меню
       frame.cb = sizeof( FRAMECDATA );
       frame.flCreateFlags = FCF_MENU;
       frame.hmodResources = 0L;
       frame.idResources = ID_MAIN;
       WinCreateFrameControls( hwndMain, &frame, (PSZ)0L );

Поскольку вновь созданное пользовательское меню может перекрыть некоторые управляющие окна диалога, то окно, создаваемое системой по умолчанию, надо увеличить по вертикали на высоту окна меню. После этого можно сохранить размеры окна (как мы условились раньше, это будет окно минимально возможных размеров). Флаг SWP_NOADJUST запрещает передачу сообщения WM_ADJUSTWINDOWPOS (здесь нам не требуется выравнивание элементов диалога):
 

WinQueryWindowPos( hwnd, (PSWP)&swp );
swp.cy += WinQuerySysValue( HWND_DESKTOP, SV_CYMENU );
swpDefault = swp;
swpMini = swp;
WinSetWindowPos( hwnd, HWND_TOP, 0, 0,

                 swp.cx, swp.cy, SWP_SIZE | SWP_NOADJUST );
Тут есть одна тонкость: если просто создать пользовательское меню и не изменять размеры окна (скажем, размеры меню учли при создании шаблона диалога), то пользовательское меню в окне не появится. Оно выскочит только тогда, когда пользователь начнет изменять размеры окна. :-?

Мы уже говорили о правилах хорошего тона и о том, что приложение должно уметь сохранять и восстанавливать позицию и размеры своего окна. Проще всего для этого использовать пару функций WinStoreWindowPos() / WinRestoreWindowPos(). Эти функции сохраняют / восстанавливают все параметры окна (включая параметры всех дочерних окнов, в том числе и PresParams). Таким образом, если восстановление окна было неудачным (скажем, приложение запущено впервые и еще нет данных для восстановления в файле OS2.INI), то последним действием обработчика будет постановка в очередь еще одного пользовательского сообщения MY_SETSIZE, чтобы активировать и показать на экране окно диалога:

if( !WinRestoreWindowPos( (PSZ)szAppName, (PSZ)szKeyName, hwndMain ) )
     WinPostMsg( hwnd, MY_SETSIZE,
                 MPFROMLONG(swpDefault.cx),
                 MPFROMLONG(swpDefault.cy) );
}
  break;
Сообщение MY_SETSIZE предназначено для информирования приложения об изменении размеров окна пользователем. В качестве параметров оно получает предыдущие размеры окна (т.е. те размеры, которые окно имело перед изменением размеров). Обработчик сообщения занимается тем, что определяет величину изменения размеров окна и позицинирует (и/или изменяет размеры) элементы диалога в окне нового размера, а затем показывает окно диалога на экране с помощью функции WinShowWindow().
  case MY_SETSIZE:
    {
       SWP swp;
       LONG deltaX, deltaY;

       WinSetWindowPos( hwnd, HWND_TOP, 0L, 0L, 0L, 0L, SWP_ACTIVATE );

       WinQueryWindowPos( hwnd, (PSWP)&swp );
       if ( !(swp.fl & SWP_MINIMIZE) )
       {
          deltaX = swp.cx - LONGFROMMP(mp1);
          deltaY = swp.cy - LONGFROMMP(mp2);
          if ( deltaX!=0 || deltaY!=0 )
             SetDlgItemPos( deltaX, deltaY );

          WinShowWindow( hwnd, TRUE );

       }
    }
    break;

Вызов функции WinSetWindowPos( ..., SWP_ACTIVATE ) позволяет решить одну проблему, возникающую из-за неадекватного поведения системы при максимизации окна из минимизированного состояния. Дело в том, что функция WinQueryWindowPos() будет возвращать размеры окна равные размеру миниокна (42x40 для иконки 32x32), несмотря на то, что на экране уже прорисовано окно новых размеров. Ситуация усугубляется еще тем, что простого включения флага SWP_ACTIVATE не достаточно. Функция WinSetWindowPos( ..., SWP_ACTIVATE ) не будет выполнять никаких действий, если окно уже находится в активном состоянии. Поэтому необходимо сначала сбросить флаг SWP_ACTIVATE, а потом снова включить его.

Особенность обработки диалогов состоит в том, что при изменении размеров окна для уведомления используется сообщение WM_ADJUSTWINDOWPOS, а не WM_SIZE. И сброс флага SWP_ACTIVATE лучше делать при обработке этого сообщения. Посмотрим, как устроен обработчик сообщения:
  case WM_ADJUSTWINDOWPOS:
    {
       SWP swp;
       WinQueryWindowPos( hwnd, (PSWP)&swp );

Если запрошена минимизация окна, то достаточно просто сохранить текущие размеры окна.
       if (((PSWP)mp1)->fl & SWP_MINIMIZE)
          WinQueryWindowPos( hwnd, (PSWP)&swpMini );

Если запрошено восстановление или максимизация окна, то необходимо сначала проверить не находится ли окно в минимизированном состоянии. Если - да, то обязательно требуется сбросить флаг SWP_ACTIVATE.
       else if ( (((PSWP)mp1)->fl & SWP_RESTORE) ||
                 (((PSWP)mp1)->fl & SWP_MAXIMIZE) )
       {
          if ( swp.fl & SWP_MINIMIZE )
          {
             ((PSWP)mp1)->fl = ((PSWP)mp1)->fl & ~SWP_ACTIVATE;
             WinPostMsg( hwnd, MY_SETSIZE,
                MPFROMLONG(swpMini.cx), MPFROMLONG(swpMini.cy) );
          }
          else
          {
             if ( (((PSWP)mp1)->cx<swpDefault.cx) ||
                  (((PSWP)mp1)->cy<swpDefault.cy) )
             {
                ((PSWP)mp1)->cx = max( swpDefault.cx, ((PSWP)mp1)->cx );
                ((PSWP)mp1)->cy = max( swpDefault.cy, ((PSWP)mp1)->cy );
             }
             WinPostMsg( hwnd, MY_SETSIZE,
                MPFROMLONG(swp.cx), MPFROMLONG(swp.cy) );
          }
       }

Изменение размеров окна необходимо контролировать, чтобы сохранить целостность диалога. Размеры окна не могут быть меньше некоторых минимальных значений, за которые мы приняли размеры окна диалога, создаваемого при загрузке диалога из ресурсов. Размеры окна надо контролировать как у обычного окна, так и у максимизированного.
       else if (((PSWP)mp1)->fl & SWP_SIZE)
       {
          if ( (((PSWP)mp1)->cx<swpDefault.cx) ||
               (((PSWP)mp1)->cy<swpDefault.cy) )
          {
             ((PSWP)mp1)->cx = max( swpDefault.cx, ((PSWP)mp1)->cx );
             ((PSWP)mp1)->cy = max( swpDefault.cy, ((PSWP)mp1)->cy );
          }
          WinPostMsg( hwnd, MY_SETSIZE,
             MPFROMLONG(swp.cx), MPFROMLONG(swp.cy) );
       }
    }
    return WinDefDlgProc( hwnd, msg, mp1, mp2 );

И последнее, на что стоит обратить внимание, это обработка сообщения WM_SAVEAPPLICATION. Именно здесь выполняется сохранение параметров окна:
  case WM_SAVEAPPLICATION:
    WinStoreWindowPos( (PSZ)szAppName, (PSZ)szKeyName, hwnd );
    return WinDefDlgProc( hwnd, msg, mp1, mp2 );
 
Gotcha!

В предыдущих вариантах программы БГ (Болван диалоГовый) для этих целей использовалось сообщение WM_CLOSE. Но это не совсем правильно, так как оно генерируется только в тех случаях, когда пользователь закрывает приложение двойным щелчком по минизначку, щелчком по кнопке закрытия в полосе заголовка (версия >= Warp3+fix29), из системного меню и обзора миниокон. Если же приложение закрывается из списка окон (и, естественно, при закрытии системы), то сообщение WM_CLOSE не генерируется, в отличие от сообщения WM_SAVEAPPLICATION.
 

В результате мы получаем программу, которая:

  1. использует диалог в качестве главного окна приложения (при этом шаблон диалога хранит в ресурсах и обладает всеми атрибутами главного окна приложения - собственная иконка, меню, клавиши - акселераторы и запись в списке окон),
  2. умеет правильно изменять свои размеры, выравнивая при этом элементы диалога (даже при максимизации из минимизированного состояния),
  3. сохраняет позицию и размеры окна при выходе и восстанавливает их при последующем старте.
И теперь уже можно заняться украшательством своей программы, например, организовать самораскрывающееся меню :-))

Полный текст программы БГ (Болван диалоГовый) находится в архиве boldy3.zip. Единственное, на что стоит еще обратить внимание - то, что при сборке этой программы не стоит делать размер стека меньше 16K.


[Next] [ Previous ]     Chapter 31
   [Contents ]  [Chapter 30: Multithreading in Presentation Manager Applications   ] [ Appendix A - Window Messages]