Материал рассматривается на примере пакета Borland C++ 5.5 command line tools
Программа на Си для Windows, как и для любой другой платформы, должна обязательно содержать некоторую "стартовую" функцию, которой передается управление при запуске программы. Вообще говоря, имя такой "стартовой" функции может различаться в различных компиляторах, но исторически сложилось так (а, кроме того, имеются еще и стандарты ANSI и ISO, к которым, правда, производители коммерческих компиляторов типа Microsoft и Borland/Inprise относятся без особого трепета), что такой функцией является:
int main()У этой функции может быть до трех параметров:
int main(int argc, char *argv[], char *env[])
Многие компиляторы для Windows "понимают" такую стартовую функцию. Однако при этом они создают хотя и 32-битное, но консольное приложение. Пример 1 (example1.cpp):
#include <stdio.h> int main() { printf("Hello, world!"); getc(stdin); return 0; }Компилируем:
bcc32 example1.cppЗапускаем:
При использовании стандартных библиотек (stdio, stdlib и т. п.) вам не потребуется никаких лишних телодвижений по сравнению с обычными методами написания программ на Си. Если же ваша цель - 32-битное приложение с графическим интерфейсом, то черное консольное окошко будет вас раздражать. Пример (example2.cpp):
#include <windows.h> int main() { MessageBox(NULL,"Hello, World!","Test",MB_OK); return 0; }
В общем, чтобы получить нормальное приложение без каких-либо "довесков" типа консольного окошка, используется другая стартовая функция:
int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hpi, LPSTR cmdline, int ss)
#include <windows.h> int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { MessageBox(NULL,"Hello, World!","Test",MB_OK); return 0; }
Кроме того, компилятору и компоновщику нужно сообщить о том, что вы делаете графическое приложение. Для bcc32 это опция -tW:
bcc32 -tW example3.cpp
Если же вы соскучитесь по черному консольному окошку, его можно в любой момент создать при помощи вызова Win32 API
BOOL AllocConsole(void)
Как известно, в Си есть лишь три базовых типа (char, int, float/double) и еще несколько их вариаций с модификаторами signed/unsigned, short/long. Однако фирме Microsoft зачем-то понадобилось описывать функции Win32 API с помощью переопределенных типов:
typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned int UINT; typedef int INT; typedef long BOOL; #define FALSE 0 #define TRUE 1 typedef long LONG; typedef unsigned long DWORD; typedef void *LPVOID; typedef char CHAR; typedef CHAR *LPSTR; typedef const CHAR *LPCSTR;
Кроме перечисленных простых типов, практически ни один вызов Win32 API не обходится без "штучек" с "ручками" - переменных типа handle ("ручка"), которые идентифицируют некоторый объект ("штучку"). Такие "ручки" принято называть дескрипторами. Реально такая переменная представляет собой всего лишь указатель на некоторую системную структуру или индекс в некоторой системной таблице.
typedef void *HANDLE; /* абстрактный дескриптор (например, файла) */ typedef void *HMODULE; /* дескриптор модуля */ typedef void *HINSTANCE; /* дескриптор экземпляра программы */ typedef void *HKEY; /* дескриптор ключа в реестре */ typedef void *HGDIOBJ; /* дескриптор графического примитива (перо, шрифт, кисть, палитра,...) */ typedef void *HWND; /* дескриптор окна */ typedef void *HMENU; /* дескриптор меню */ typedef void *HICON; /* дескриптор иконки */ typedef void *HBITMAP; /* дескриптор картинки */ typedef void *HFONT; /* дескриптор шрифта */
При заполнении различных структур часто требуется указать такую "ручку" от какой-нибудь "штучки". Очень часто вместо конкретного дескриптора допустимо передавать значение NULL, означающее, что вы еще не обзавелись такой "штучкой" или собираетесь использовать "штучку" по умолчанию.
В стандартных версиях Си для функций используются два варианта соглашения о передаче параметров: соглашение языка Си (параметры функции помещаются в стек в порядке обратном их описанию, очистку стека производит вызывающая процедура) и соглашение языка Паскаль (параметры функции помещаются в стек в (прямом) порядке их описания, очистку стека производит вызываемая процедура). Для этих соглашений использовались, соответственно, модификаторы cdecl и pascal. При описании функций Win32 API используется модификатор WINAPI, а для описания пользовательских функций обратного вызова - модификатор CALLBACK. Оба этих модификатора являются переопределением специального модификатора _stdcall, соответствующего соглашению о передаче параметров, использующегося исключительно в Win32 API, - Standard Calling Convention (параметры функции помещаются в стек в порядке обратном их описанию, очистку стека производит вызываемая процедура).
Окно - это прямоугольная область экрана, в котором приложение отображает информацию и получает реакцию от пользователя. Одновременно на экране может отображаться несколько окон, в том числе, окон других приложений, однако лишь одно из них может получать реакцию от пользователя - активное окно. Пользователь использует клавиатуру, мышь и прочие устройства ввода для взаимодействия с приложением, которому принадлежит активное окно. Каждое 32-битное приложение создает, по крайней мере, одно окно, называемое главным окном, которое обеспечивает пользователя основным интерфейсом взаимодействия с приложением.
Окно приложения может содержать строку заголовка title bar (1),
строку меню menu bar (2), системное меню system menu (3),
кнопку сворачивания окна minimize box (4), кнопку разворачивания окна maximize box (5),
рамку изменения размеров sizing border (6), клиентскую область client area (7),
горизонтальную и вертикальную полосы прокрутки scroll bars (8):
Меню, строка заголовка с системными кнопками, системное меню, рамка изменения размеров и полосы прокрутки относятся к области окна, называемой неклиентской областью (non-client area). С неклиентской областью Windows справляется сама, а вот за содержимое и обслуживание клиентской области отвечает приложение.
Кроме главного окна, приложение может использовать еще и другие типы окон: управляющие элементы (controls), диалоговые окна (dialog boxes), окна-сообщения (message boxes). Управляющий элемент - окно, непосредственно обеспечивающее тот или иной способ ввода информации пользователем. К управляющим элементам относятся: кнопки, поля ввода, списки, полосы прокрутки и т.п. Управляющие элементы обычно не болтаются сами по себе, а проживают в каком-либо диалоговом окне. Диалоговое окно - это временное окно, напичканное управляющими элементами, обычно использующееся для получения дополнительной информации от пользователя. Диалоговые окна бывают модальные (modal) и немодальные (modeless). Модальное диалоговое окно требует, чтобы пользователь обязательно ввел обозначенную в окне информацию и закрыл окно прежде, чем приложение продолжит работу. Немодальное диалоговое окно позволяет пользователю, не закрывая диалогового окна, переключаться на другие окна этого приложения. Окно-сообщение - это диалоговое окно предопределенного системой формата, предназначенное для вывода небольшого текстового сообщения с одной или несколькими кнопками. Пример такого окна показан в Примере 3.
В отличие от традиционного программирования на основе линейных алгоритмов, программы для Windows строятся по принципам событийно-управляемого программирования (event-driven programming) - стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна. С каждым окном в Windows связана определенная функция обработки событий. События для окон называются сообщениями. Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров (WPARAM и LPARAM), интерпретация которых зависит от типа сообщения. В заголовочном файле windows.h для кодов сообщений определены константы с интуитивно понятными именами:
#define WM_CREATE 0x0001 /* сообщение о создании окна */ #define WM_DESTROY 0x0002 /* сообщение об уничтожении окна */ #define WM_SIZE 0x0005 /* сообщение об изменении размеров окна */ #define WM_COMMAND 0x0111 /* сообщение от команды меню или управляющего элемента */
Таким образом, задача любого приложения - создать главное окно и сообщить Windows функцию обработки событий для этого окна. Все самое интересное для приложения будет происходить именно в функции обработки событий главного окна.
Для стандартных управляющих элементов (библиотека Common Controls Library - COMCTL32.DLL) в Windows имеются предопределенные обработчики событий, которые при наступлении интересных событий сообщают всяческую полезную информацию окну, содержащему этот управляющий элемент. Стандартная библиотека Common Dialog Box Library (COMDLG32.DLL) содержит несколько готовых весьма полезных диалоговых окон с обработчиками: диалоги выбора файла, настроек печати, выбора шрифта, выбора цвета и др. Кроме того, любая среда разработки (VisualBasic, Delphi, VisualC++ и т.п.) навязывает разработчику дополнительный набор готовых управляющих элементов и диалогов - иногда достаточно удобных, иногда не очень.
Программа для Win32 обычно состоит из следующих блоков:
#include <windows.h> int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR cmdline,int ss) { |
Каждое окно принадлежит определенному классу окон. Окна одного класса имеют схожий вид, обслуживаются общей процедурой обработки событий, имеют одинаковые иконки и меню. Обычно каждое приложение создает для главного окна программы свой класс. Если приложению требуются дополнительные нестандартные окна, оно регистрирует другие классы. Стандартные диалоги и управляющие элементы принадлежат к предопределенным классам окон, для них не надо регистрировать новые классы. Чтобы определить новый класс окон, надо заполнить структуру WNDCLASS, содержащую следующие поля:
Класс регистрируется при помощи функции:
WORD WINAPI RegisterClass(const WNDCLASS *lpwc)При успешном завершении функция возвращает целочисленный код, соответствующий строке-имени класса в общесистемной таблице строк (такой код называется атомом). При ошибке возвращается 0.
Для создания окна вызывается функция:
HWND WINAPI CreateWindow( LPCSTR lpClassName, /* имя класса */ LPCSTR lpWindowName, /* имя окна (заголовок) */ DWORD dwStyle, /* стиль (поведение) окна */ int x, /* горизонтальная позиция окна на экране */ int y, /* вертикальная позиция окна на экране */ int nWidth, /* ширина окна */ int nHeight, /* высота окна */ HWND hWndParent, /* дескриптор родительского окна */ HMENU hMenu, /* дескриптор меню */ HANDLE hInstance, /* дескриптор экземпляра программы */ LPVOID lpParam /* указатель на какую-нибудь ерунду */ )
Вместо параметров x, y, nWindth, nHeight допустимо передавать константу CW_USEDEFAULT, позволяющую операционной системе задать эти числа по ее усмотрению.
Интерпретация кода стиля определяется классом окна. Стиль определяет не только оформление окна, но и его поведение. Общие для всех классов константы стилей (при необходимости объединяются операцией побитовое ИЛИ):
Во время выполнения функции CreateWindow процедуре обработки событий окна посылается сообщение WM_CREATE. При успешном выполнении функции возвращается дескриптор созданного окна, при неудаче - NULL.
После создания окна неплохо бы сделать его видимым (отобразить), если только оно не создано со стилем WS_VISIBLE:
BOOL WINAPI ShowWindow(HWND hw, int ss)Второй параметр этой функции - код состояния отображения окна. В качестве этого кода можно взять значение четвертого параметра, с которым была запущена функция WinMain. Другие возможные значения этого параметра:
Если перед вызовом этой функции окно было видимым, функция возвращает TRUE, если же окно было скрыто - FALSE.
Если клиентская область главного окна приложения содержит объекты, прорисовываемые по сообщению WM_PAINT, имеет смысл прорисовать эти объекты сразу после отображения главного окна на экране. Функция UpdateWindow непосредственно вызывает процедуру обработки событий указанного окна с сообщением WM_PAINT (минуя очередь сообщений приложения):
BOOL WINAPI UpdateWindow(HWND hw)
Windows использует два способа доставки сообщений процедуре обработки событий окна:
К внеочередным сообщениям относятся те сообщения, которые непосредственно влияют на окно, например, сообщение активации окна WM_ACTIVATE и т.п. Кроме того, вне очереди сообщений обрабатываются сообщения, сгенерированные различными вызовами Win32 API, такими как SetWindowPos, UpdateWindow, SendMessage, SendDlgItemMessage...
К откладываемым сообщениям относятся сообщения, связанные с реакцией пользователя: нажатие клавиш на клавиатуре, движение мышки и клики. Чтобы извлечь сообщение из очереди, программа вызывает функцию
BOOL WINAPI GetMessage( MSG *lpmsg, /* сюда попадает сообщение со всякими параметрами */ HWND hw, /* извлекать только сообщения для указанного окна (NULL - все) */ UINT wMsgFilterMin, /* фильтр сообщений (нам не надо - ставим 0) */ UINT wMsgFilterMax /* фильтр сообщений (нам не надо - ставим 0) */ )Эта функция возвращает FALSE, если получено сообщение WM_QUIT, и TRUE в противном случае. Очевидно, что условием продолжения цикла обработки событий является результат этой функции. Если приложение хочет завершить свою работу, оно посылает само себе сообщение WM_QUIT при помощи функции
void WINAPI PostQuitMessage(int nExitCode)Ее параметр - статус выхода приложения. Обычно эта функция вызывается в ответ на сообщение об уничтожении окна WM_DESTROY.
После извлечения сообщения из очереди следует вызвать функцию TranslateMessage, переводящую сообщения от нажатых клавиш в удобоваримый вид, а затем DispatchMessage, которая определяет предназначенное этому сообщению окно и вызывает соответствующую процедуру обработки событий.
BOOL WINAPI TranslateMessage(const MSG *lpmsg) LONG WINAPI DispatchMessage(const MSG *lpmsg)Результат возврата соответствует значению, которое вернула процедура обработки событий (обычно никому не нужен).
Процедура обработки сообщений окна должна быть объявлена по следующему прототипу:
LRESULT CALLBACK WindowProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp)Значения параметров: hw - дескриптор окна, которому предназначено сообщение, msg - код сообщения, wp и lp - 32-битные параметры сообщения, интерпретация которых зависит от кода сообщения. Зачастую старший/младший байт или старшее/младшее слово параметров сообщения несут независимый смысл, тогда удобно использовать определенные в windows.h макросы:
#define LOBYTE(w) ((BYTE) (w)) #define HIBYTE(w) ((BYTE) (((WORD) (w) >> 8) & 0xFF)) #define LOWORD(l) ((WORD) (l)) #define HIWORD(l) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))
Например, сообщение WM_COMMAND посылается окну в трех случаях:
При этом параметры сообщения интерпретируются следующим образом. Старшее слово параметра WPARAM содержит: 0 в первом случае, 1 во втором случае и код события в третьем случае. Младшее слово WPARAM содержит целочисленный идентификатор пункта меню, "горячей" клавиши или дочернего управляющего элемента. Параметр LPARAM в первых двух случаях содержит NULL, а в третьем случае - дескриптор окна управляющего элемента.
Процедура обработки событий должна вернуть определенное 32-битное значение, интерпретация которого также зависит от типа сообщения. В большинстве случаев, если сообщение успешно обработано, процедура возвращает значение 0.
Процедура обработки событий не должна игнорировать сообщения. Если процедура не обрабатывает какое-то сообщение, она должна вернуть его системе для обработки по умолчанию. Для этого вызывается функция:
LRESULT WINAPI DefWindowProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp)
Все описанное в данном параграфе суммируется в примере 4 (example4.cpp):
#include <windows.h> LRESULT CALLBACK MainWinProc(HWND,UINT,WPARAM,LPARAM); #define ID_MYBUTTON 1 /* идентификатор для кнопочки внутри главного окна */ int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int ss) { /* создаем и регистрируем класс главного окна */ WNDCLASS wc; wc.style=0; wc.lpfnWndProc=MainWinProc; wc.cbClsExtra=wc.cbWndExtra=0; wc.hInstance=hInst; wc.hIcon=NULL; wc.hCursor=NULL; wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName=NULL; wc.lpszClassName="Example 4 MainWnd Class"; if (!RegisterClass(&wc)) return FALSE; /* создаем главное окно и отображаем его */ HWND hMainWnd=CreateWindow("Example 4 MainWnd Class","EXAMPLE 4",WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL); if (!hMainWnd) return FALSE; ShowWindow(hMainWnd,ss); UpdateWindow(hMainWnd); MSG msg; /* цикл обработки событий */ while (GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } /* процедура обработки сообщений для главного окна */ LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) { switch (msg) { case WM_CREATE: /* при создании окна внедряем в него кнопочку */ CreateWindow("button","My button",WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE, 5,5,100,20,hw,(HMENU)ID_MYBUTTON,NULL,NULL); /* стиль WS_CHILD означает, что это дочернее окно и для него вместо дескриптора меню будет передан целочисленный идентификатор, который будет использоваться дочерним окном для оповещения родительского окна через WM_COMMAND */ return 0; case WM_COMMAND: /* нажата наша кнопочка? */ if ((HIWORD(wp)==0) && (LOWORD(wp)==ID_MYBUTTON)) MessageBox(hw,"You pressed my button","MessageBox",MB_OK|MB_ICONWARNING); return 0; case WM_DESTROY: /* пользователь закрыл окно, программа может завершаться */ PostQuitMessage(0); return 0; } return DefWindowProc(hw,msg,wp,lp); }
Приведенный пример создает окно с кнопкой "My button", при нажатии
на которую вылезает окно-сообщение:
Ресурсы - это бинарные данные, добавляемые в исполняемый файл при компоновке программы. К стандартным ресурсам относятся: иконки, курсоры, меню, диалоги, растровые изображения (BMP), векторные изображения (EMF), шрифты, таблицы горячих клавиш, таблицы строк, информация о версии программы или модуля. В процессе разработки программы ресурсы описывают в отдельном текстовом файле - файле описания ресурсов (расширение .rc), - а затем при помощи компилятора ресурсов переводят в бинарный вид и добавляют в исполняемый файл на этапе компоновки исполняемого файла. Использование ресурсов значительно облегчает работу программиста по визуализации графических примитивов интерфейса программы.
Файл описания ресурсов состоит из операторов, объединяемых в блоки. Один оператор занимает одну строку файла. Допускается использовать комментарии, определяемые так же, как в программе на языке Си. Файл описания ресурсов перед компиляцией так же обрабатывается препроцессором, поэтому в нем можно использовать директивы препроцессора (#include, #define, ...) и макроопределения. В сложных "блочных" описаниях ресурсов вместо ключевых слов BEGIN и END можно использовать { и }, соответственно.
Иконки, картинки и курсоры мыши можно описать двумя способами (квадратные скобки не являются частью синтаксиса оператора и означают необязательный элемент):
nameID RESOURCETYPE [load-option] [mem-option] filenameЗдесь nameID - численный или строковой идентификатор; RESOURCETYPE - ключевое слово, обозначающее тип ресурса: ICON, BITMAP или CURSOR; load-option и mem-option - всякие неинтересные в данный момент опции, которые можно спокойно пропустить; filename - имя файла, содержащее соответствующий ресурс.
Примеры:
disk1 BITMAP "disk.bmp" 12 ICON "myicon.ico"
Эти ресурсы можно внедрить в виде шестнадцатеричных кодов непосредственно в файл ресурсов:
nameID RESOURCETYPE BEGIN hex data END
Пример:
FltBmp BITMAP { '42 4D A2 00 00 00 00 00 00 00 3E 00 00 00 28 00' '00 00 19 00 00 00 19 00 00 00 01 00 01 00 00 00' '00 00 64 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 FF FF FF 00 FF FF' 'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF' 'FF 80 FF FF FF 80 FF FF FF 80 C0 FF 81 80 FE FF' 'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF' 'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF' 'BF 80 FE FF BF 80 FE 00 3F 80 FF FF FF 80 FF FF' 'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF' 'FF 80' }
Следует отметить, что первая иконка в ресурсах будет использоваться "Проводником" как иконка исполняемого файла.
Меню описывается следующим образом:
nameID MENU [load-option] [mem-option] BEGIN item-definitions ... ENDЗдесь item-definitions - один из трех операторов:
MENUITEM text, result [, optionlist] /* обычный пункт меню */ MENUITEM SEPARATOR /* строка-сепаратор */ POPUP text [, optionlist] BEGIN /* подменю */ item-definitions ... ENDПараметры операторов имеют следующий смысл: text - текст пункта меню или подменю (может содержать комбинации \t - табуляция, \a - выравнивание по правому краю, & - следующий символ подчеркивается, обозначает "горячую" клавишу для указанного пункта меню); result - целочисленный идентификатор пункта меню, посылаемый окну-владельцу через сообщение WM_COMMAND при выборе этого пункта меню; optionlist - необязательный список опций, разделенных запятой или пробелом:
Доступ к ресурсам, скомпонованным с исполняемым файлом, можно получить при помощи следующих функций:
HICON WINAPI LoadIcon(HINSTANCE hInst, LPCSTR lpIconName) HBITMAP WINAPI LoadBitmap(HINSTANCE hInst, LPCSTR lpBitmapName) HCURSOR WINAPI LoadCursor(HINSTANCE hInst, LPCSTR lpCursorName) HMENU WINAPI LoadMenu(HINSTANCE hInst, LPCSTR lpMenuName)Первый параметр этих функций - дескриптор экземпляра программы, второй - идентификатор соответствующего ресурса. Если ресурс идентифицируется не именем, а числом, то следует использовать макрос, объявленный в windows.h:
#define MAKEINTRESOURCE(i) (LPSTR) ((DWORD) ((WORD) (i)))Например:
HMENU hMainMenu=LoadMenu(hInst,MAKEINTRESOURCE(10));
Для закрепления полученных сведений, давайте добавим к примеру 4
какую-нибудь иконку и такое меню:
Для этого создаем файл ресурсов (example4a.rc):
Ex4_Icon ICON "myicon.ico" Ex4_Menu MENU { POPUP "&File" { MENUITEM "&Open...\tCtrl-O", 2 MENUITEM "&Save", 3 MENUITEM "Save &As...", 4 MENUITEM SEPARATOR MENUITEM "&Hex view", 5, CHECKED GRAYED MENUITEM "&Exit\tAlt-F4", 6 } POPUP "&Edit" { MENUITEM "&Copy", 7 MENUITEM "&Paste", 8 POPUP "Popup" { MENUITEM "1", 9 MENUITEM "2", 10 MENUITEM "3", 11 } MENUITEM SEPARATOR MENUITEM "Search", 12 } POPUP "&Help" { MENUITEM "&About...\tF1", 13 } }
Для перевода файла описания ресурсов в бинарный вид используется компилятор ресурсов Borland Resource Compiler:
brcc32 example4a.rcВ результате получается файл example4a.res, который потребуется в процессе компоновки.
В примере 4 надо изменить строки
wc.hIcon=NULL; wc.lpszMenuName=NULL;на
wc.hIcon=LoadIcon(hInst,"Ex4_Icon"); wc.lpszMenuName="Ex4_Menu";
Чтобы программа не была такой скучной, изменим обработчик сообщения WM_COMMAND:
case WM_COMMAND: if (HIWORD(wp)==0) { char buf[256]; switch (LOWORD(wp)) { case 6: /* команда меню Exit */ PostQuitMessage(0); default: /* все остальные команды */ wsprintf(buf,"Command code: %d",LOWORD(wp)); MessageBox(hw,buf,"MessageBox",MB_OK|MB_ICONINFORMATION); } } return 0;В результате при выборе того или иного пункта меню выводится окно-сообщение с кодом команды.
Обратите внимание: среди команд меню не используется код 1, который отведен кнопке "My button". Это типичная практика назначать всем дочерним элементам окна и командам меню разные численные идентификаторы, что облегчает обработку сообщения WM_COMMAND.
Теперь компоновка программы будет более сложной, поэтому bcc32 с этой задачей не справится. В этом примере компилятор будет использоваться только для компилирования (запускаем с ключом -с):
bcc32 -c -tW example4a.cppВ результате получаем объектный файл example4a.obj.
Чтобы собрать все части программы вместе, придется запускать компоновщик вручную (ilink32 или tlink32). В командной строке компоновщика указываются следующие параметры:
ilink32 [options] objfiles,exefile,mapfile,libfiles,deffile,resfiles
Компоновщик ilink32 умеет работать в пошаговом (инкрементирующем) режиме incremental linking, при этом он создает несколько файлов состояний (*.IL?). При последующих попытках компиляции он использует их, так что процесс компиляции занимает меньше времени. Чтобы отключить пошаговую компиляцию и не замусоривать рабочий каталог этими файлами, следует указать опцию -Gn. Например, если при отключенной пошаговой компиляции программа компилируется 8 секунд, то первая компиляция в пошаговом режиме займет 25 секунд, а все последующие - не более 2 секунд.
Итак, компонуем модифицированный пример 4:
ilink32 -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res
Если все сделано правильно, первое, что становится сразу заметным - у исполняемого файла появилась иконка:
Эта же иконка отображается в строке заголовка главного окна программы. Под
строкой заголовка отображается созданное нами меню. При выборе любой команды
меню появляется окно-сообщение с кодом команды. При выборе команды "Exit"
программа завершается.
Если ваш проект состоит из множества файлов, то компилировать и компоновать их вручную становится затруднительно. В таких случаях используются сценарии компиляции, которые обрабатываются программой make. Сценарий компиляции - текстовый файл с именем Makefile, описывающий зависимости между различными файлами проекта, следующего формата:
правило: зависимости команды для выполнения
Сценарий компиляции должен содержать как минимум одно правило. Строка с командами обязательно должна начинаться с отступа табуляцией. В качестве имени правила обычно выступает имя файла, который получится в результате выполнения команд в теле правила. Зависимости - необязательный список имен файлов, разделенных пробелами, от которых зависит данное правило. Если при вызове make окажется, что хотя бы один файл из этого списка новее, чем файл-результат правила, то выполняются все команды из этого правила. В качестве зависимостей могут указываться имена файлов-названия других правил. Тогда make будет выполнять рекурсивную проверку зависимостей. Make не выполняет команды из правила, если все файлы-зависимости старее файла-результата.
Пример:
example4a.exe: example4a.rc example4a.cpp myicon.ico brcc32 example4a.rc bcc32 -c -tW example4a.cpp ilink32 -Gn -x -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res
Если не указывать в качестве файлов-зависимостей example4a.rc и example4a.cpp, то make не станет ничего делать, когда файл example4a.exe уже существует. Тем не менее, приведенный пример - не совсем удачный сценарий компиляции. Если мы изменим только файл ресурсов, make все равно будет перекомпилировать исходный текст. Если мы изменим только исходный текст, make будет перекомпилировать еще и ресурсы. С учетом этого замечания, более удачным будет следующий сценарий:
example4a.exe: example4a.obj example4a.res ilink32 -Gn -x -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res example4a.obj: example4a.cpp bcc32 -c -tW example4a.cpp example4a.res: example4a.rc myicon.ico brcc32 example4a.rc
Если в командной строке make не указано иное, то make пытается выполнить первое правило из сценария. Именно поэтому первым правилом стоит example4a.exe - результат, который мы хотим получить после компиляции всего проекта.
Если написать подходящий сценарий компиляции, то для компиляции вашего проекта придется набирать лишь команду:
make
Большинство приложений использует диалоговые окна для запроса у пользователя дополнительной информации для выполнения каких-либо команд. Например, команда открытия файла требует указания имени файла, так что приложение создает диалоговое окно, чтобы запросить у пользователя имя файла. Пока пользователь не укажет имя файла, команда не будет выполнена. После этого программа уничтожает это диалоговое окно. В этом случае используется модальное диалоговое окно. Другой пример: текстовый редактор может использовать немодальное диалоговое окно, для команды поиска. Пока редактор ищет введенную фразу, диалоговое окно остается на экране. Более того, пользователь может вернуться к редактированию текста, не закрывая диалог поиска. Либо пользователь может ввести другую фразу для поиска. Такое диалоговое окно остается открытым, пока приложение не завершится или пользователь непосредственно не выберет команду закрытия этого диалога.
Чтобы создать диалоговое окно, приложение должно предоставить системе шаблон диалога, описывающий содержание и стиль диалога, и диалоговую процедуру. Диалоговая процедура выполняет примерно такие же задачи, что и процедура обработки событий окна. Диалоговые окна принадлежат к предопределенному классу окон. Windows использует этот класс и соответствующую процедуру обработки событий для модальных и немодальных диалогов. Эта процедура обрабатывает одни сообщения самостоятельно, а другие передает на обработку диалоговой процедуре приложения. У приложения нет непосредственного доступа к этому предопределенному классу и соответствующей ему процедуре обработки событий. Для изменения стиля и поведения диалога программа должна использовать шаблон диалогового окна и диалоговую процедуру.
Для создания модального диалога используется функция DialogBox, а для создания немодального диалога - CreateDialog:
int WINAPI DialogBox(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc) HWND WINAPI CreateDialog(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc)
Параметры: hInst - дескриптор экземпляра программы (модуля, в котором находится шаблон); template - имя ресурса, описывающего диалог; parent - дескриптор родительского окна; DlgFunc - диалоговая функция следующего формата:
BOOL CALLBACK DlgFunc(HWND hw, UINT msg, WPARAM wp, LPARAM lp)Параметры диалоговой функции такие же, как у обычной функции обработки событий. Отличие этой функции - она вызывается из предопределенной функции обработки событий для диалоговых окон. Она должна вернуть значение TRUE, если обработала переданное ей сообщение, или FALSE в противном случае. Она ни в коем случае не должна сама вызывать DefWindowProc.
При создании диалогового окна диалоговая процедура получает сообщение WM_INITDIALOG. Если в ответ на это сообщение процедура возвращает FALSE, диалог не будет создан: функция DialogBox вернет значение -1, а CreateDialog - NULL.
Модальное диалоговое окно блокирует указанное в качестве родительского окно и появляется поверх него (вне зависимости от стиля WS_VISIBLE). Приложение закрывает модальное диалоговое окно при помощи функции
BOOL WINAPI EndDialog(HWND hw, int result)Приложение должно вызвать эту функцию из диалоговой процедуры в ответ на сообщение от кнопок "OK", "Cancel" или команды "Close" из системного меню диалога. Параметр result передается программе как результат возврата из функции DialogBox.
Немодальное диалоговое окно появляется поверх указанного в качестве родительского окна, но не блокирует его. Диалоговое окно остается поверх родительского окна, даже если оно неактивно. Программа сама отвечает за отображение/сокрытие окна (с помощью стиля WS_VISIBLE и функции ShowWindow). Сообщения для немодального диалогового окна оказываются в основной очереди сообщений программы. Чтобы эти сообщения были корректно обработаны, следует включить в цикл обработки сообщений вызов функции:
BOOL WINAPI IsDialogMessage(HWND hwDlg, MSG *lpMsg)
Если эта функция вернула TRUE, то сообщение обработано и его не следует передавать функциям TranslateMessage и DispatchMessage.
Немодальное диалоговое окно уничтожается, если уничтожается его родительское окно. Во всех остальных случаях программа должна сама заботиться об уничтожении немодального диалогового окна, используя вызов:
BOOL WINAPI DestroyWindow(HWND hw)
Шаблон диалогового окна в ресурсах задается следующим образом:
nameID DIALOG [load-option] [mem-option] x, y, width, height [property-statements] BEGIN control-statements ... END
В начале блока описания диалога задается: nameID - целочисленный или строковой идентификатор ресурса, x, y - координаты диалога на экране (или относительно родительского окна), width, height - размер диалога. Координаты и размеры диалога и всех элементов внутри него задаются в диалоговых единицах (dialog units). Одна горизонтальная диалоговая единица соответствует 1/4 средней ширины символа в системном шрифте. Одна вертикальная диалоговая единица соответствует 1/8 средней высоты символа в системном шрифте.
После заголовка блока идет ряд необязательных операторов-параметров диалога (property-statements) в любом порядке:
STYLE style /* стиль диалога */ CAPTION captiontext /* заголовок диалога */ FONT pointsize, typeface /* шрифт диалога (размер, название) */ MENU menuname /* меню диалога */
В качестве стиля диалога можно применять все перечисленные для обычных окон стили. Обычно выбирают: WS_POPUP, WS_SYSMENU, WS_CAPTION, а также WS_BORDER для немодального диалога и DS_MODALFRAME - для модального. Кроме того, можно использовать DS_SETFOREGROUND, чтобы при отображении диалога перевести его на передний план, даже если его родительское окно неактивно.
В теле шаблона (control-statements) перечисляются составляющие его управляющие элементы. Один из возможных вариантов оператора:
CONTROL text, id, class, style, x, y, width, heightЗдесь text - текст управляющего элемента (заголовок), id - целочисленный идентификатор элемента 0...65535 (внутри одного диалога идентификаторы всех элементов должны различаться), class - имя класса, к которому принадлежит управляющий элемент, style - стиль управляющего элемента, x, y, width, height - положение и размер диалогового элемента относительно клиентской области диалога в диалоговых единицах.
Вот пример диалога, содержащего простое статическое текстовое поле и кнопку "OK":
Ex4_Dlg DIALOG 50,50,90,40 STYLE WS_POPUP|WS_CAPTION|DS_MODALFRAME CAPTION "MyDlg" FONT 10, "Arial" { CONTROL "", 1, "STATIC", SS_LEFT, 5, 5, 80, 10 CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 80, 12 }
Добавим этот диалог к ресурсам примера 4. В текст программы добавим две глобальных переменных:
char buf[256]=""; /* строка для текстового поля в диалоге */ HINSTANCE h; /* дескриптор экземпляра программы */
Присвоим переменной h значение дескриптора экземпляра программы в самом начале функции WinMain. Это значение нам потребуется для вызова функции DialogBox.
Изменим обработчик сообщения WM_COMMAND следующим образом:
case WM_COMMAND: switch (LOWORD(wp)) { case 6: /* команда меню Exit */ PostQuitMessage(0); default: /* все остальные команды */ wsprintf(buf,"Command code: %d",LOWORD(wp)); DialogBox(h,"Ex4_Dlg",hw,DlgProc); } return 0;
Теперь в текст программы необходимо добавить диалоговую процедуру:
BOOL CALLBACK DlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: /* сообщение о создании диалога */ SetDlgItemText(hw,1,buf); return TRUE; case WM_COMMAND: /* сообщение от управляющих элементов */ if (LOWORD(wp)==2) EndDialog(hw,0); } return FALSE; }
При создании диалога вызывается процедура SetDlgItemText, меняющая содержание текстового поля в диалоге (элемент с id=1). Для уничтожения диалога используется кнопка "OK", генерирующая сообщение WM_COMMAND с id=2.
Функция DlgProc должна быть определена или описана
до ссылки на нее в вызове DialogBox.
Управляющие элементы, как и другие окна, принадлежат тому или иному классу окон. Windows предоставляет несколько предопределенных классов управляющих элементов. Программа может создавать управляющие элементы поштучно при помощи функции CreateWindow или оптом, загружая их вместе с шаблоном диалога из своих ресурсов. Управляющие элементы - это всегда дочерние окна. Управляющие элементы при возникновении некоторых событий, связанных с реакцией пользователя, посылают своему родительскому окну сообщения-оповещения (notification messages) WM_COMMAND или WM_NOTIFY.
Как и любое другое окно, управляющий элемент может быть скрыт или отображен при помощи функции ShowWindow. Аналогично, управляющий элемент может быть блокирован или разблокирован при помощи функции:
BOOL WINAPI EnableWindow(HWND hw,BOOL bEnable)В качестве второго параметра передается флаг TRUE (разблокировать) или FALSE (блокировать). Функция возвращает значение TRUE, если перед ее вызовом окно было заблокировано. Узнать текущий статус блокирования окна можно при помощи функции:
BOOL WINAPI IsWindowEnabled(HWND hw),которая возвращает значение TRUE, если окно разблокировано.
Для многих управляющих элементов определены специальные сообщения, которые управляют видом или поведением таких элементов или позволяют получить параметры их состояния. Как и для любого другого окна эти сообщения можно отправить с помощью функции:
LRESULT WINAPI SendMessage(HWND hw, UINT msg, WPARAM wp, LPARAM lp)
Все упомянутые функции работают с дескриптором окна, который для управляющих элементов в случае создания диалога по шаблону из ресурсов непосредственно неизвестен, но может быть получен по дескриптору диалога и идентификатору управляющего элемента вызовом:
HWND WINAPI GetDlgItem(HWND hDlg, int idDlgItem)
Для функции отсылки сообщений есть специальный вариант, предназначенный для более удобной работы с управляющими элементами:
LRESULT WINAPI SendDlgItemMessage( HWND hwndDlg, /* дескриптор родительского диалога */ int idControl, /* идентификатор управляющего элемента */ UINT msg, /* код сообщения */ WPARAM wp, /* параметр сообщения */ LPARAM lp /* параметр сообщения */ )
Для управляющих элементов внутри диалогов специальный смысл имеют стили WS_TABSTOP и WS_GROUP. Если в диалоге имеются управляющие элементы со стилем WS_TABSTOP, то при нажатии пользователем на клавишу [Tab] (или [Shift]+[Tab]), текущий активный элемент диалога будет терять фокус и передавать его следующему за ним (или предыдущему) ближайшему элементу со стилем WS_TABSTOP. С помощью стиля WS_GROUP элементы диалога можно объединять в группы. Группа элементов начинается с элемента со стилем WS_GROUP и заканчивается элементом, после которого идет элемент со стилем WS_GROUP, или последним элементом в диалоге. Внутри группы только первый элемент должен иметь стиль WS_GROUP. Windows допускает перемещение внутри группы при помощи клавиш-стрелок.
Классы предопределенных управляющих элементов:
CONTROL "",-1, "STATIC", SS_BLACKFRAME, 5, 40, 20, 10 CONTROL "",-1, "STATIC", SS_GRAYFRAME, 30, 40, 20, 10 CONTROL "",-1, "STATIC", SS_WHITEFRAME, 55, 40, 20, 10 CONTROL "",-1, "STATIC", SS_BLACKRECT, 80, 40, 20, 10 CONTROL "",-1, "STATIC", SS_GRAYRECT, 105, 40, 20, 10 CONTROL "",-1, "STATIC", SS_WHITERECT, 130, 40, 20, 10 CONTROL "",-1, "STATIC", SS_ETCHEDFRAME,155, 40, 20, 10 /* Для статиков-иконок или картинок текстовое поле определяет имя ресурса */ CONTROL "Ex4_Bmp",-1, "STATIC", SS_BITMAP, 5, 55, -1, -1 CONTROL "Ex4_Icon",-1, "STATIC", SS_ICON, 65, 55, -1, -1 CONTROL "text",-1, "STATIC", SS_LEFT, 105, 55, 20, 10 CONTROL "text",-1, "STATIC", SS_CENTER, 130, 55, 20, 10 CONTROL "text",-1, "STATIC", SS_RIGHT, 155, 55, 20, 10 /* По умолчанию SS_LEFT, SS_RIGHT, SS_CENTER делают перенос по словам */ CONTROL "This is long text example",-1, "STATIC", SS_SIMPLE|SS_SUNKEN, 65, 70, 55, 15 CONTROL "This is long text example",-1, "STATIC", SS_LEFT|SS_SUNKEN, 125, 70, 50, 15Для текстовых статиков со стилями SS_LEFT, SS_RIGHT или SS_CENTER существуют более простые операторы объявления ресурсов:
LTEXT "text",-1, 105, 55, 20, 10 CTEXT "text",-1, 130, 55, 20, 10 RTEXT "text",-1, 155, 55, 20, 10 LTEXT "This is long text example",-1, 65, 70, 55, 15, SS_LEFTNOWORDWRAP|SS_SUNKEN LTEXT "This is long text example",-1,125, 70, 50, 15, SS_LEFT|SS_SUNKENЧтобы поменять текст статика ему можно послать сообщение WM_SETTEXT (wp=0; lp=(LPARAM)(LPCSTR)lpsz - адрес строки) или использовать функции:
BOOL WINAPI SetWindowText(HWND hw, LPCSTR lpsz) BOOL WINAPI SetDlgItemText(HWND hDlg, int idControl, LPCTSTR lpsz)Чтобы сменить иконку или картинку нетекстового статика, надо послать ему сообщение STM_SETIMAGE (wp=(WPARAM)fImageType - тип изображения: IMAGE_BITMAP или IMAGE_ICON; lp=(LPARAM)(HANDLE)hImage - дескриптор иконки или картинки).
/* DEFPUSHBUTTON - кнопка по умолчанию (нажимается по [Enter]) */
CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 50, 12
CONTROL "text", 3, "BUTTON", BS_PUSHBUTTON, 60, 20, 50, 12
CONTROL "GroupBox1", -1, "BUTTON", BS_GROUPBOX, 5, 35, 50, 50
CONTROL "text", 4, "BUTTON", BS_CHECKBOX, 10, 45, 30, 10
CONTROL "text", 5, "BUTTON", BS_AUTOCHECKBOX, 10, 57, 30, 10
CONTROL "text", 6, "BUTTON", BS_AUTO3STATE, 10, 69, 30, 10
CONTROL "GroupBox2", -1, "BUTTON", BS_GROUPBOX, 60, 35, 50, 50
CONTROL "text", 7, "BUTTON", BS_AUTORADIOBUTTON|WS_GROUP, 65, 45, 30, 10
CONTROL "text", 8, "BUTTON", BS_AUTORADIOBUTTON, 65, 57, 30, 10
CONTROL "text", 9, "BUTTON", BS_AUTORADIOBUTTON, 65, 69, 30, 10
Для кнопок существуют более простые операторы объявления ресурсов:
DEFPUSHBUTTON text, id, x, y, width, height [, style] PUSHBUTTON text, id, x, y, width, height [, style] GROUPBOX text, id, x, y, width, height [, style] CHECKBOX text, id, x, y, width, height [, style] RADIOBUTTON text, id, x, y, width, height [, style]Разница между стилями BS_xxx и BS_AUTOxxx заключается в том, что при щелчке по AUTO-кнопкам Windows сама автоматически переключает их состояние. Для не AUTO-кнопок это надо делать вручную в диалоговой процедуре, послав сообщение BM_SETCHECK (wp=(WPARAM)fCheck - флаг: BST_UNCHECKED, BST_CHECKED или BST_INDETERMINATE (для BS_3STATE-кнопок); lp=0) или при помощи функций:
BOOL WINAPI CheckDlgButton(HWND hDlg, int idButton, UINT fCheck) BOOL WINAPI CheckRadioButton( HWND hDlg, /* дескриптор родительского диалога */ int idFirstButton, /* id первой радио-кнопки в группе */ int idLastButton, /* id последней радио-кнопки в группе */ int idCheckButton /* id отмечаемой радио-кнопки */ )Автоматические радио-кнопки должны быть объединены в группу при помощи стиля WS_GROUP, чтобы Windows корректно их обрабатывала.
UINT WINAPI IsDlgButtonChecked(HWND hDlg, int idButton)При щелчке мыши по кнопке она присылает родительскому диалогу сообщение-оповещение WM_COMMAND (HIWORD(wp)=BN_CLICKED; LOWORD(wp)=(int)idButton; lp=(HWND)hwndButton).
/* По умолчанию эти элементы создаются вообще без рамки, поэтому добавлено WS_BORDER */
CONTROL "" 4, "EDIT", ES_MULTILINE|ES_WANTRETURN|WS_BORDER 5, 45, 60, 35
CONTROL "text", 5, "EDIT", ES_LEFT|WS_BORDER, 70, 45, 30, 10
CONTROL "text", 6, "EDIT", ES_CENTER|ES_PASSWORD|WS_BORDER, 70, 57, 30, 10
CONTROL "text", 7, "EDIT", ES_RIGHT|ES_READONLY|WS_BORDER, 70, 69, 30, 10
Стиль ES_WANTRETURN означает, что кнопка [Enter] будет
обрабатываться самим элементом, а не передаваться диалогу. Благодаря
этому стилю оказался возможен переход на новую строчку для предложения
"Она съела кусок..." (на картинке).EDITTEXT id, x, y, width, height [, style]Чтобы поменять содержимое текстового поля, программа вызывает функцию SetDlgItemText. Чтобы получить текущее содержимое текстового поля, используется функция:
UINT WINAPI GetDlgItemText( HWND hDlg, /* дескриптор родительского диалога */ int idControl, /* идентификатор поля */ LPSTR lpString, /* буфер под текст */ int nMaxCount /* размер буфера */ )Чтобы узнать размер строки в текстовом поле, надо послать элементу сообщение WM_GETTEXTLENGTH (wp=0; lp=0).
CONTROL "" 3, "LISTBOX", LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL, 5, 45, 60, 35 CONTROL "" 4, "LISTBOX", LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL, 70, 45, 60, 35 CONTROL "" 5, "LISTBOX", LBS_SORT|LBS_NOSEL|WS_BORDER, 135, 45, 60, 35или в короткой форме:
LISTBOX 3, 5, 45, 60, 35, LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL LISTBOX 4, 70, 45, 60, 35, LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL LISTBOX 5, 135, 45, 60, 35, LBS_SORT|LBS_NOSEL|WS_BORDERДля добавления элемента к списку следует послать ему сообщение LB_ADDSTRING (wp=0; lp=(LPARAM)(LPCSTR)lpsz - строка для добавления). Для того, чтобы заполнить один из списков, показанных на рисунке, в обработчик сообщения WM_INITDIALOG в диалоговую процедуру был вставлен такой фрагмент:
char *a[12]={ "jan","feb","mar","apr","may","jun", "jul","aug","sep","oct","nov","dec"}; for (int i=0; i<12; i++) { SendDlgItemMessage(hw,3,LB_ADDSTRING,0,(LPARAM)a[i]); }Кроме этого, список "понимает" следующие сообщения:
CONTROL "" 3, "COMBOBOX", CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL, 5, 45, 60, 70 CONTROL "" 4, "COMBOBOX", CBS_DROPDOWNLIST|WS_VSCROLL, 70, 45, 60, 70 CONTROL "" 5, "COMBOBOX", CBS_SIMPLE|CBS_SORT, 135, 45, 60, 70Короткий вариант этого же объявления ресурсов:
COMBOBOX 3, 5, 45, 60, 70, CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL COMBOBOX 4, 70, 45, 60, 70, CBS_DROPDOWNLIST|WS_VSCROLL COMBOBOX 5, 135, 45, 60, 70, CBS_SIMPLE|CBS_SORTДля работы с комбобоксами существуют сообщения, аналогичные списковым: CB_ADDSTRING, CB_DELETESTRING, CB_INSERTSTRING, CB_FINDSTRING, CB_GETCOUNT, CB_GETCURSEL, CB_RESETCONTENT.
Кроме предопределенных управляющих элементов, Windows предоставляет еще набор стандартных управляющих элементов посредством библиотеки Common Controls Library (COMCTL32.DLL). Чтобы воспользоваться ей, в тест программы надо включить заголовочный файл commctrl.h и добавить в блок инициализации программы вызов функции:
void WINAPI InitCommonControls(void)
Управляющие элементы из этой библиотеки, как правило, посылают сообщения-оповещения родительскому диалогу через сообщение WM_NOTIFY (wp=(int)idControl; lp=(LPARAM)(NMHDR*)pmnh - указатель на структуру со специльными параметрами сообщения-оповещения).
Классы управляющих элементов из Common Controls Library:
CONTROL "",3,WC_LISTVIEW,LVS_REPORT|WS_BORDER, 5, 45, 60, 70 CONTROL "",4,WC_LISTVIEW,LVS_LIST|WS_BORDER, 70, 45, 120, 70 CONTROL "",5,WC_LISTVIEW,LVS_ICON|WS_BORDER, 200, 45, 120, 70Приведенные в примере списки заполнялись в диалоговой процедуре при инициализации диалога (WM_INITDIALOG):
/* Создание колонок для 1го списка */ LV_COLUMN lc; lc.mask=LVCF_FMT|LVCF_TEXT|LVCF_SUBITEM|LVCF_WIDTH; lc.fmt=LVCFMT_LEFT; lc.pszText="Col1"; lc.iSubItem=0; lc.cx=40; SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,0,(LPARAM)&lc); lc.pszText="Col2"; lc.iSubItem=1; lc.cx=40; SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,1,(LPARAM)&lc); /* Создание списка иконок для 2го и 3го списков */ HIMAGELIST himl1,himl2; himl1=ImageList_Create(16,16,ILC_MASK,1,0); /* список маленьких иконок */ ImageList_AddIcon(himl1,LoadIcon(h,"Ex4_Icon")); himl2=ImageList_Create(32,32,ILC_MASK,1,0); /* список больших иконок */ ImageList_AddIcon(himl2,LoadIcon(h,"Ex4_Icon")); SendDlgItemMessage(hw,4,LVM_SETIMAGELIST,LVSIL_SMALL,(LPARAM)himl1); SendDlgItemMessage(hw,5,LVM_SETIMAGELIST,LVSIL_NORMAL,(LPARAM)himl2); /* Заполнение списков */ LV_ITEM li; li.mask=LVIF_TEXT|LVIF_IMAGE; li.iImage=0; /* номер иконки в списке */ for (int i=0; i<12; i++) { li.iItem=i; li.iSubItem=0; li.pszText=a[i]; SendDlgItemMessage(hw,3,LVM_INSERTITEM,0,(LPARAM)&li); SendDlgItemMessage(hw,4,LVM_INSERTITEM,0,(LPARAM)&li); SendDlgItemMessage(hw,5,LVM_INSERTITEM,0,(LPARAM)&li); wsprintf(str,"%d",i); /* вторая колонка для 1го списка */ li.iSubItem=1; li.pszText=str; SendDlgItemMessage(hw,3,LVM_SETITEM,0,(LPARAM)&li); }
Ex4_Dlg DIALOG 50,50,70,40 STYLE WS_POPUP|WS_CAPTION|WS_BORDER CAPTION "MyDlg" FONT 10, "Arial" { CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 10, 60, 12 CONTROL "Status text",1,STATUSCLASSNAME, 0, 0, 0, 0, 0 }При создании поля статуса не требуется указывать ни размер, ни позицию окна, Windows сама выберет эти параметры подходящим образом. Для создания поля статуса можно использовать специальную функцию:
HWND WINAPI CreateStatusWindow( LONG style, /* стиль: обязательно указываем WS_CHILD|WS_VISIBLE */ LPCTSTR lpszText, /* текст поля статуса */ HWND hwndParent, /* родительское окно */ UINT wID /* числовой id поля статуса */ )С помощью сообщения SB_SETPARTS поле статуса можно разбить на части (wp=(int)nParts - количество частей; lp=(LPARAM)(int*)widths - указатель на массив размеров частей). В таком случае текст для каждой части поля статуса задается сообщением SB_SETTEXT (wp=(int)iPart - номер части; lp=(LPARAM)(LPSTR)lpszText - текстовая строка). Пример:
CreateStatusWindow(WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP,"text",hw,100); int widths[5]={100,150,200,-1}; SendDlgItemMessage(hw,100,SB_SETPARTS,4,(LPARAM)widths); SendDlgItemMessage(hw,100,SB_SETTEXT,2,(LPARAM)"part 2"); SendDlgItemMessage(hw,100,SB_SETTEXT,3,(LPARAM)"last part");
CONTROL "0", 5, "EDIT", ES_LEFT|WS_BORDER, 5, 30, 30, 10 CONTROL "", 6, UPDOWN_CLASS, UDS_AUTOBUDDY|UDS_SETBUDDYINT, 35, 30, 10, 10Если при создании элемента "up-down" указать стиль UDS_AUTOBUDDY, то компаньоном будет назначен предыдущий управляющий элемент диалога. Программа может также передать дескриптор окна-компаньона при помощи сообщения UDM_SETBUDDY (wp=(WPARAM)(HWND)hwndBuddy - дескриптор окна-компаньона; lp=0). Если элементу "up-down" назначить стиль UDS_SETBUDDYINT, то он будет автоматически менять текст окна-компаньона, представляющий числовое значение.
HWND WINAPI CreateUpDownControl( DWORD dwStyle, /* стиль элемента */ int x, int y, /* позиция */ int cx, int cy, /* размеры */ HWND hParent, /* дескриптор родительского окна */ int ID, /* id элемента */ HINSTANCE hInst, /* дескриптор экземпляра программы */ HWND hBuddy, /* дескриптор окна-компаньона */ int nUpper, /* максимальное значение */ int nLower, /* минимальное значение */ int nPos /* текущее значение */ )
CONTROL "",3,PROGRESS_CLASS,WS_BORDER, 5, 45, 100, 10Каждый раз, когда приложение посылает этому окну сообщение PBM_STEPIT (wp=0; lp=0), заполнение полосы прогресса продвигается дальше вправо на некоторое значение.
TOOLINFO ti; HWND hwTooltip=CreateWindow(TOOLTIPS_CLASS,"",TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL,h,NULL); ti.cbSize=sizeof(TOOLINFO); ti.uFlags=TTF_SUBCLASS|TTF_IDISHWND; ti.hwnd=hMainWnd; ti.uId=(UINT)GetDlgItem(hMainWnd,ID_MYBUTTON); ti.hinst=h; ti.lpszText="Tooltip for my button"; SendMessage(hwTooltip,TTM_ADDTOOL,0,(LPARAM)&ti);
/* Шаблон вкладки "Общие" */ ODTab_General DIALOG 0,0,180,150 CAPTION "Общие" STYLE WS_CHILD|WS_VISIBLE FONT 8,"Arial" { GROUPBOX "Интерфейс",-1,5,5,170,40 PUSHBUTTON "Шрифт температуры",IDC_FONT,20,15,140,12 AUTOCHECKBOX "На полный экран",IDC_FULLSCR,20,30,140,10,WS_DISABLED GROUPBOX "Алгоритм",-1,5,50,170,80 LTEXT "Приоритет:",-1,15,65,50,10 CTEXT "priority",IDC_PRIOTEXT,70,56,85,8 CONTROL "",IDC_PRIOTRACK,TRACKBAR_CLASS,TBS_TOP|TBS_AUTOTICKS,70,64,85,17 LTEXT "Период опроса датчика:",-1,15,85,110,10 EDITTEXT IDC_TRATE,125,85,25,10,ES_RIGHT LTEXT "мс",-1,152,85,10,10 LTEXT "Период опроса формирователя:",-1,15,100,110,10 EDITTEXT IDC_FRATE,125,100,25,10,ES_RIGHT LTEXT "мс",-1,152,100,10,10 LTEXT "Тайм-аут:",-1,15,115,110,10 EDITTEXT IDC_TIMEOUT,125,115,25,10,ES_RIGHT LTEXT "мс",-1,152,115,10,10 } /* Шаблон вкладки "Датчик" */ ODTab_Sensor DIALOG 0,0,180,150 CAPTION "Датчик" STYLE WS_CHILD|WS_VISIBLE FONT 8,"Arial" { /* ... */ } /* Шаблон вкладки "Порты" */ ODTab_Ports DIALOG 0,0,200,150 CAPTION "Порты" STYLE WS_CHILD|WS_VISIBLE FONT 8,"Arial" { /* ... */ }Для создания диалога с вкладками используется функция PropertySheet, перед вызовом которой надо заполнить соответствующие системные структуры:
char *TabTemplts[NumTabs]={"ODTab_General","ODTab_Sensor","ODTab_Ports"}; PROPSHEETPAGE psp[NumTabs]; /* заполняется для каждой вкладки */ for (i=0; i<NumTabs; i++) { psp[i].dwSize=sizeof(PROPSHEETPAGE); psp[i].dwFlags=PSP_DEFAULT; psp[i].hInstance=hThisInstance; psp[i].pszTemplate=TabTemplts[i]; psp[i].pfnDlgProc=(DLGPROC)GlobDlgProc; psp[i].pfnCallback=NULL; } PROPSHEETHEADER psh; /* описывает весь диалог */ psh.dwSize=sizeof(PROPSHEETHEADER); psh.dwFlags=PSH_NOAPPLYNOW|PSH_PROPSHEETPAGE; psh.hwndParent=hWnd; psh.hInstance=hThisInstance; psh.pszCaption="Настройки"; psh.nPages=NumTabs; psh.nStartPage=0; psh.ppsp=(LPCPROPSHEETPAGE)&psp; psh.pfnCallback=NULL; PropertySheet(&psh);Для каждой вкладки может быть своя диалоговая процедура, а может быть общая для всех вкладок (как в этом примере).
TBBUTTON tbb[nButtons]; /* 1я кнопка */ tbb[0].iBitmap=STD_PROPERTIES; /*id иконки*/ tbb[0].idCommand=ID_OPTION; /*id команды*/ tbb[0].fsState=TBSTATE_ENABLED; /*состояние*/ tbb[0].fsStyle=TBSTYLE_BUTTON; /*стиль*/ tbb[0].iString=0; /*подпись под кнопкой*/ /* 2я кнопка */ tbb[1].iBitmap=STD_REDOW; tbb[1].idCommand=ID_CALCOUT; tbb[1].fsState=TBSTATE_ENABLED; tbb[1].fsStyle=TBSTYLE_BUTTON; tbb[1].iString=0; /* 3я кнопка */ tbb[2].iBitmap=STD_UNDO; tbb[2].idCommand=ID_CALCIN; tbb[2].fsState=TBSTATE_ENABLED; tbb[2].fsStyle=TBSTYLE_BUTTON; tbb[2].iString=0; /* разделитель */ tbb[3].fsStyle=TBSTYLE_SEP; /* и т.д. */ HWND htb=CreateToolbarEx( (HWND) hWndMain, /*дескриптор родительского окна*/ (DWORD) WS_CHILD|WS_VISIBLE|TBSTYLE_TOOLTIPS, /*стиль*/ (UINT) ID_TOOLBAR, /*id окна*/ (int) 15, /*количество иконок в указанном ресурсе*/ (HINSTANCE) HINST_COMMCTRL, /*дескриптор модуля, из которого берется ресурс с иконками*/ (UINT) IDB_STD_SMALL_COLOR, /*id ресурса с иконками*/ (TBBUTTON*) tbb, /*указатель на массив с информацией о кнопках*/ (int) nButtons, /*количество кнопок на панели*/ 16,16, /*размеры кнопок*/ 15,15, /*размеры иконок для кнопок*/ sizeof(TBBUTTON) /*размер структуры TBBUTTON*/ );
Windows предоставляет набор готовых стандартных диалогов посредством библиотеки Common Dialog Boxes Library (COMDLG32.DLL): диалог открытия и сохранения файла, диалог печати документа, диалог выбора цвета, шрифта и т.п. Чтобы создать один из перечисленных диалогов, надо заполнить определенную структуру и вызвать соответствующую функцию из этой библиотеки:
char filename[MAX_PATH]=""; /*буфер под имя файла*/ OPENFILENAME of; of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; /*размер структуры OPENFILENAME*/ of.hwndOwner=hw; /*дескриптор родительского окна*/ of.hInstance=h; /*дескриптор экземпляра программы*/ of.lpstrFilter="All files (*.*)\0*.*\0";/*фильтр файлов (тип)*/ of.lpstrCustomFilter=NULL; /*еще один фильтр: нам не надо*/ of.nMaxCustFilter=0; /*нам не надо*/ of.nFilterIndex=1; /*количество заданных нами фильтров*/ of.lpstrFile=filename; /*адрес буфера под имя файла*/ of.nMaxFile=MAX_PATH; /*размер буфера под имя файла*/ of.lpstrFileTitle=NULL; /*буфер под рекомендуемый заголовок: нам не надо*/ of.nMaxFileTitle=0; /*нам не надо*/ of.lpstrInitialDir=NULL; /*стартовый каталог: текущий*/ of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; /*разные флаги*/ if (GetOpenFileName(&of)) { /* действия в случае успешного выбора файла */ }
Ресурсы значительно облегчают работу программиста по созданию интерфейса программы, поэтому весьма привлекательно использовать ресурсы для описания главного окна. Это возможно в случае, если главное окно реализовано в виде немодального диалога.
Пример 5 демонстрирует использование немодального диалога в приложении типа "блокнот".
Файл example5.h содержит константы-идентификаторы команд меню и элементов диалога.
#define IDC_OPEN 10 #define IDC_SAVE 11 #define IDC_SAVEAS 12 #define IDC_EXIT 13 #define IDC_ABOUT 14 #define ID_EDIT 20 #define ID_STATUS 21
Файл example5.rc описывает ресурсы программы: иконку, меню и шаблон диалога.
#include "example5.h" Ex5_Icon ICON "myicon.ico" Ex5_Menu MENU { POPUP "&File" { MENUITEM "&Open...", IDC_OPEN MENUITEM "&Save", IDC_SAVE MENUITEM "Save &As...", IDC_SAVEAS MENUITEM SEPARATOR MENUITEM "&Exit\tAlt-F4", IDC_EXIT } POPUP "&Help" { MENUITEM "&About...", IDC_ABOUT } } Ex5_Dlg DIALOG 50,50,300,200 STYLE WS_OVERLAPPED|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_VISIBLE MENU "Ex5_Menu" CAPTION "Example 5" FONT 10, "Arial" { EDITTEXT ID_EDIT, 5, 5, 290, 180, ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL CONTROL "", ID_STATUS, STATUSCLASSNAME, 0, 0, 0, 0, 0 }
Файл example5.cpp - текст программы.
#include <windows.h> #include <commctrl.h> #include <stdlib.h> #include "example5.h" BOOL CALLBACK MainProc(HWND,UINT,WPARAM,LPARAM); HINSTANCE hThisInstance; char filename[MAX_PATH]=""; /*буфер имени файла*/ int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int) { hThisInstance=hInst; InitCommonControls(); HWND hMainWnd=CreateDialog(hInst,"Ex5_Dlg",NULL,(DLGPROC)MainProc); if (!hMainWnd) return FALSE; MSG msg; /*цикл обработки событий*/ while (GetMessage(&msg,NULL,0,0)) { if (!IsDialogMessage(hMainWnd,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } /* диалоговая процедура */ BOOL CALLBACK MainProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) { static DWORD OldIcon=0; /* id старой иконки диалога */ static OPENFILENAME of; char* buf; HANDLE hf; DWORD len,len1; switch (msg) { case WM_INITDIALOG: /* меняем иконку диалога */ OldIcon=SetClassLong(hw,GCL_HICON,(long)LoadIcon(hThisInstance,"Ex5_Icon")); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDCANCEL: /* посылается при закрытии диалога по [Esc]*/ case IDC_EXIT: /* команда меню "Exit" */ DestroyWindow(hw); break; case IDC_OPEN: /* команда меню "Open" */ of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; of.hwndOwner=hw; of.lpstrFilter="All files (*.*)\0*.*\0"; of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0; of.nFilterIndex=1; of.lpstrFile=filename; of.nMaxFile=MAX_PATH; of.lpstrFileTitle=NULL; of.nMaxFileTitle=0; of.lpstrInitialDir=NULL; of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; if (!GetOpenFileName(&of)) break; SetDlgItemText(hw,ID_STATUS,filename); /* открываем файл */ hf=CreateFile(filename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL); if (hf==INVALID_HANDLE_VALUE) { MessageBox(hw,"Open failed","Error",MB_ICONHAND|MB_OK); break; } len=GetFileSize(hf,NULL); buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */ if (!buf) { MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK); break; } ReadFile(hf,buf,len,&len1,NULL); buf[len1]=0; CloseHandle(hf); SetDlgItemText(hw,ID_EDIT,buf); free(buf); break; case IDC_SAVEAS: /* команда меню "Save As" */ of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; of.hwndOwner=hw; of.lpstrFilter="All files (*.*)\0*.*\0"; of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0; of.nFilterIndex=1; of.lpstrFile=filename; of.nMaxFile=MAX_PATH; of.lpstrFileTitle=NULL; of.nMaxFileTitle=0; of.lpstrInitialDir=NULL; of.Flags=OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY; if (!GetSaveFileName(&of)) break; case IDC_SAVE: /* команда меню "Save" */ if (lstrlen(filename)==0) { /* для нового файла - вызываем диалог "Save As" */ PostMessage(hw,WM_COMMAND,IDC_SAVEAS,lp); break; } SetDlgItemText(hw,ID_STATUS,filename); /* сохраняем файл */ hf=CreateFile(filename,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); if (hf==INVALID_HANDLE_VALUE) { MessageBox(hw,"Save failed","Error",MB_ICONHAND|MB_OK); break; } len=SendDlgItemMessage(hw,ID_EDIT,WM_GETTEXTLENGTH,0,0); buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */ GetDlgItemText(hw,ID_EDIT,buf,len+1); if (!buf) { MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK); break; } WriteFile(hf,buf,len,&len1,NULL); CloseHandle(hf); free(buf); break; case IDC_ABOUT: /* команда меню "About" */ MessageBox(hw,"Example N5 from http://dims.karelia.ru/win32","About",MB_OK|MB_ICONINFORMATION); break; } return TRUE; case WM_DESTROY: /* при закрытии окна восстанавливаем старую иконку */ SetClassLong(hw,GCL_HICON,(long)OldIcon); PostQuitMessage(0); return TRUE; } return FALSE; }