Глава 17. Процедурные приложения для Windows

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

Структура приложения

В данном параграфе мы рассмотрим компоненты, из которых состоит программа SWP.C (SimpleWindowsProgram). Будет показано, что необходимо включить в программу для создания и отображения на экране окна приложения (с рамкой, строкой заголовка, системным меню и кнопками свертывания/развертывания), а также для корректного завершения приложения.

Вы также узнаете, как программу SWP.Cи связанные с ней файлы можно использовать в качестве шаблона при создании других приложений Windows. Четкое понимание структуры нашего примера позволит вам сэкономить много времени в будущем, так как рассматриваемые компоненты составляют основу практически любого приложения.

Но для начала в качестве справки перечислим наиболее часто используемые типы данных Windows, которые могут встретиться нам в процессе работы (табл. 17.1).

Таблица 17.1. Часто используемые типы данных
Тип Описание
callback Замещает спецификацию farpascal в функциях обратного вызова
handle 32-разрядное беззнаковое целое число, используемое в качестве дескриптора
HDC

Дескриптор контекста устройства

hwnd Дескриптор окна
LONG 32-разрядное целое число со знаком
lparam Используется для описания младшего аргумента сообщения
LPCSTR То же, что и lpstr, но используется для указания константных строк
lpstr

32-разрядный указатель на строку

lpvoid Обобщенный тип указателя, эквивалентен void*
lresult Используется для возвращения значений из оконных процедур
uint

32-разрядное беззнаковое целое число

wchar 16-разрядный символ UNICODE; используется для представления печатных символов всех языков мира
winapi Замещает спецификацию farpascal в описаниях API-функций
wparam Используется для описания старшего параметра сообщения

В табл. 17.2 приведено несколько стандартных структур Windows.

Таблица 17.2. Часто используемые стандартные структуры
Структура Что определяет
msg

Параметры сообщения

PAINTSTRUCT

Область окна, требующую перерисовки

rect Прямоугольную область
WNDCLASS Класс окна

Основные компоненты приложения

Приложения Windowsсодержат два общих элемента: функцию winMain() и оконную процедуру. Функция winMain() составляет основу любого приложения. Она служит как бы точкой входа в приложение и выполняет ту же роль, что и функция main() в обычных программах на С и C++.

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

Функция WinMain()

Функция winMain(), как уже было сказано, является обязательным компонентом любого приложения Windows. С нее начинается выполнение программы и ею же обычно заканчивается. Функция WinMain() отвечает за следующее:

 

В функцию WinMain() передаются четыре параметра:

int WINAPI WinMain(HINSTANCE
hlnst, HINSTANCE
hPrelnst, LPSTR
IpszCmdLine, int nCmdShow)

Первый параметр, hlnst, представляет собой дескриптор экземпляра приложения. Второй, hPrelnst, всегда содержит значение null, что указывает на отсутствие других, запущенных ранее, экземпляров приложения.

Примечание

В ранних версиях Windows(вплоть до 3.x) параметр hPrelnst позволял определить, были ли запущены другие копии приложения. В Windows 95/98/NTдля каждого запу­щенного модуля выделяется отдельное адресное пространство, в связи с чем параметр hPrelnstвсегда устанавливается равным NULL.

Третий параметр, IpszCmdLine, служит указателем на строку, завершающуюся нулевым символом и содержащую командную строку программы.

Последний параметр, nCmdShow, задает одну из многочисленных констант, определяющих возможные варианты отображения окна, например SW_SHOWNORMAL, SWSHOWMAXIMIZEDИЛИ SWSHOWMINIMIZED.

Структура WNDCLASS

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

Примечание

В ранних версиях Windows зарегистрированные классы окон становились доступными для всех программ, выполняющихся в системе. В связи с этим программисты должны были при регистрации классов следить за тем, чтобы используемые ими имена не конфликтовали с другими зарегистрированными классами окон. В Windows 95/98/NTтребуется, чтобы не только приложения, но и все экземпляры одного приложения регистрировали собственные классы окон.

Для описания всех классов окон используется единая структура. Следующий пример взят непосредственно из файла WINUSER.H, включаемого в файл WINDOWS.H с помощью директивы #include.

typedef  struct  tagWNDCLASSW ( 

UINT style

WNDPROC IpfnWndProc

int cbClsExtra;

int cbWndExtra;

HANDLE hlnstance;

HICON hlcon;

HCURSOR hCursor

HBRUSH hbrBackground;

LPCWSTR IpszMenuName;

LPCWSTR IpszClass'Name;

} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;

Структура wndclassw предназначена для работы с UNICODE-строками. Имеется также аналогичная ей структура wndclassa, ориентированная на работу с ASCII-строками. С помощью typedef-определений от этих структур порождается универсальная Структура wndclass, позволяющая программисту не заботиться о формате используемых строк.

Чтобы определить класс окна, приложение должно объявить переменную следующего типа:

WNDCLASS   wcApp; 

Впоследствии переменная wcApp заполняется информацией о классе окна. Ниже описываются различные поля структуры wndclass.

style

Определяет свойства окна. С помощью операции побитового ИЛИ можно задать комбинацию из нескольких свойств. Возможные свойства поля style перечислены в табл. 17.3.

 
Свойство Описание
CS_BYTEALIGNCLIENT Задает выравнивание по границе байта размера рабочей области по горизонтали
CS_BYTEALIGNWINDOW Задает выравнивание по границе байта размера окна по горизонтали
CS_CLASSDC Указывает, что все окна данного класса должны использовать один и тот же контекст устройства
CS_DBLCLKS При установке этого флага окну будут посылаться сообщения о двойном щелчке
CS_GLOBALCLASS Указывает, что класс окна является глобальным и должен регистрироваться независимо от значения параметра hlnstance
CS_HREDRAW Задает операцию перерисовки окна при изменении горизонтального размера
CS_NOCLOSE Делает неактивной команду close в системном меню
CS_ОWNDC Задает вьщеление каждому окну собственного контекста устройства
CS_PARENTDC Указывает, что область отсечения дочернего окна будет равна области отсечения родительского окна

CS_SAVEBITS

Служит указанием системе сохранять часть окна, перекрываемую другим окном, чтобы впоследствии не посылать перекрытому окну сообщения WM_PAINT, а восстанавливать его содержимое самостоятельно
CS_VREDRAW Задает операцию перерисовки окна при изменении вертикального размера

IpfnWndProc

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

cbClsExtra

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

cbWndExtra

Содержит информацию о количестве байтов, выделяемых системой после создания экземпляра окна. Этот параметр может быть равным NULL.

hlnstance

Содержит дескриптор экземпляра приложения, регистрирующего класс окна.

hlcon

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

NULL.

hCursor

Содержит дескриптор указателя мыши, используемого в окне приложения. Этот параметр может быть равным null.

hbrBackground

Определяет кисть, используемую для заливки фона окна. Может содержать дескриптор реальной кисти или одну из следующих констант (к задаваемой константе следует прибавить 1):

COLOR_ACTIVEBORDER

COLOR_ACTIVECAPTION

COLOR_APPWORKSPACE

COLOR_BACKGROUND

COLOR_BTNSHADOW

COLORJBTNTEXT  

COLOR_CAPTIONTEXT

CQLORJ3RAYTEXT   

COLOR_HIGHLIGHT  

COLOR_HI GHLIGHTTEXT  

CQLOR_INACTIVEBORDER 

COLOR_INACTIVECAPTION

COLOR_MENU     

COLOR_MENUTEXT     

COLOR_SCROLLBAR

COLOR_WINDOW

COLOR_WINDOWFRAME

COLOR_WINDOWTEXT

Если значение параметра hbrBackground равно NULL, то приложение должно самостоятельно управлять заливкой фона окна   при   получении сообщения

WM_ERASEBKGND.

lpszMemiName

Содержит указатель на строку, завершающуюся нулевым символом и представ­ляющую собой имя ресурса меню. Этот параметр может быть равным null.

lpszClassName

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

Структура WNDCIASSEX

Существует расширенный вариант структуры wndclass, получивший название wndclassex. От wndclassон отличается тем, что дополнительно позволяет исполь­зовать в приложении значок малого размера.

typedef struct     _WNDCLASSEX {

UINT   cbSize

UINT   style; 

WNDPROC    IpfnWndProc;      

int    cbClsExtra;

int    cbWndExtra;  

HANDLEhlnstance;

HICON  hlcon;

HCURSOR    hCursor

HBRUSHhbrBackground;

LPCTSTR    IpszMenuName;

LPCTSTR    IpszClassName;

HICON  hlconSm
) WNDCLASSEX;

В поле cbSize задается размер всей структуры. Это обычно делается с помощью выражения sizeof(WNDCLASSEX). В поле hlconSm содержится дескриптор малого значка.

Определение класса окна

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

Следующий фрагмент взят из файла SWP.C и демонстрирует, как можно создать и проинициализировать структуру wndclass:

char szProgName[]="ProgName";

WNDCLASS wcApp;

wcApp.lpszClassName = szProgName; wcApp.hlnstance    = hlnst;
wcApp.lpfnWndProc  = WndProc;

wcApp.hCursor      = LoadCursor(NULL, IDC_ARROW);
wcApp.hlcon        = NULL; wcApp.lpszMenuName  = 0;

wcApp.hbrBackground = GetStockObject(WHITE_BRUSH);
wcApp. style         = CS_HREDRAW | CS_VREDRAW;
wcApp.cbClsExtra   = 0;
wcApp.cbWndExtra   = 0; if (IRegisterClass(SwcApp))
return 0;

Имя программы хранится в переменной szProgNameи записывается в класс окна в поле wcApp.IpszClassName.   

Второму полю структуры wndclass, называемому wcApp. hinstance, передается аргумент hlnstфункции winMain(), хранящий дескриптор текущего экземпляра приложения. Полю ipfnWndProc присваивается указатель на оконную процедуру, которая будет обрабатывать все сообщения, посылаемые окну. В файле SWP.C эта функция называется WndProc().

Имя WndProc() задано программистом, т.е. не является стандартным. Прототип функции должен быть описан до выполнения операции присваивания.

В поле wcApp.hCursor хранится дескриптор указателя мыши текущего экземпляра приложения. В нашем примере создается указатель idc_arrow(соответствует обычному указателю мыши в виде наклоненной стрелки), дескриптор которого возвращается функцией LoadCursor(). Поскольку в приложении нет устанавливаемого по умолчанию значка, полю wcApp.hlcon присваивается значение null.

Если поле wcApp.lpszMenuName содержит NULL, то Windows считает, что у приложения нет собственного меню. В противном случае должно быть указано название ресурса меню, взятое в кавычки. Функция GetStockObject() возвращает дескриптор кисти, которая используется для заливки фона окна, созданного на базе данного класса. В нашем случае применяется стандартная белая кисть — WHITEBRUSH.

В поле wcApp. style свойства окна задаются как cs_HREDRAW в сочетании с cs_vredraw. Константы с префиксом cs_ определены в файле WINUSER.H и могут объединяться с помощью операции побитового ИЛИ (|). Константы cs_hredraw и cs_vredraw указывают Windows, что рабочую область окна нужно перерисовывать всякий раз, когда размер окна изменяется по вертикали или горизонтали.

Последние два поля, wcApp.cbClsExtra и wcApp.cbWndExtra, обычно устанавливают равными 0. Их предназначение — указание дополнительного количества байтов памяти, которые должны быть зарезервированы после создания класса окна или экземпляра самого окна.

Ранее мы уже говорили о том, что в 16-разрядных версиях Windows приложение регистрировало класс окна только в том случае, если не существовало ранее загруженных копий этой же программы. Зарегистрированный класс окна становился доступным для всех остальных программ, поэтому необходимость в повторной его регистрации не возникала. Вот как это делалось:

if (IhPrelnst)

{

if (IRegisterClass(SwcApp))

return FALSE; }

В 32-разрядных версиях Windows параметр hPrelnst всегда равен NULL, поэтому подобная проверка избыточна.

В функцию Register-Class() передается указатель на только что созданную структуру класса окна. Если Windows не может зарегистрировать новый класс (скажем, в случае нехватки памяти), функция возвращает 0, вследствие чего выполнение программы завершается.

Создание окна

Регистрация класса окна еще не свидетельствует о создании самого окна. Окно создается в результате вызова функции CreateWindow(). Этот процесс одинаков во всех версиях Windows. В то время как класс окна определяет общие свойства всех окон данного семейства, параметры функции CreateWindowOзадают описание конкретного экземпляра окна. Если функция CreateWindow() выполняется успеш­но, она возвращает дескриптор созданного окна, в противном случае — null.

В файле SWP.Cфункция CreateWindow() вызывается следующим образом:

hWnd = CreateWindow(szProgNarae, "SimpleWindowsProgram",
WSJDVERLAPPEDWINDOW, CW_USEDEFAOLT,
CW__USEDEFAULT, CW_DSEDEFAULT,
CWJJSEDEFAULT, (HWND)NULL, (HMENU)NULL,
(HANDLE) hlnst, (LPSTR)NDLL;  

Первый параметр, szProgName, содержит название класса окна. За ним в кавычках указывается содержимое строки заголовка окна — "SimpleWindowProgram". Третий параметр задает стиль окна — ws_overlappedwindow. Это стандартный стиль Windows, определяющий обычное масштабируемое окно со строкой заголовка, системным меню, кнопками свертывания, развертывания и закрытия, а также с рамкой.

Следующие шесть параметров (cw_usedefault или null) определяют координаты начальной точки окна, его размеры, а также дескрипторы родительского окна и меню. Всем этим параметрам в нашем примере присвоены значения по умолчанию. Десятый параметр, hinst, содержит дескриптор экземпляра приложения. Окну не передаются никакие дополнительные значения, поэтому последний параметр равен NULL.

Отображение и обновление окна

Для отображения окна на экране предназначена функция ShowWindow(}:

ShowWindow(hWnd, nCmdShow);

Дескриптор окна hwnd былсоздан функцией CreateWindow (). Второй параметр, nCmdShow, устанавливает режим начального отображения окна. Так, задание константы sw_showminnoactive, определенной в файле WINUSER.H, приведет к отображению окна в виде значка на панели задач:

ShowWindow(hWnd,   SW_SHOWMINNOACTIVE);

Константа sw_showmaximized отвечает за развертывание окна на весь экран. Режим sw_showminimized аналогичен режиму sw_showminnoactive за исключением того, что окно остается активным, т.е. имеет фокус.

Последний этап выведения окна на экран реализует функция UpdateWindow():

UpdateWindow(hWnd);

Вызов функции ShowWindow() с установленным параметром sw_shownormal приводит к заливке фона окна выбранной кистью. А функция UpdateWindow (генерирует сообщение wm_paint, указывающее на формирование содержимого рабочей области окна.

Цикл обработки сообщений

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

Как правило, цикл организуется с помощью такой конструкции:

while (GetMessage(&lpMsg, NULL, 0, 0) ) {

TranslateMessage(SlpMsg) ;

DispatchMessage(slpMsg);}

Функция GetMessage( )

Функция GetMessage() читает очередное сообщение из очереди и записывает его в структуру msg, адресуемую указателем IpMsg.

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

После запуска цикла только одно сообщение может остановить его выполнение. Как только в цикл передается сообщение wm_quit, функция GetMessage() возвращает значение false, в результате чего цикл завершается, выполняются заключительные операции функции winMain() и работа приложения прекращается.

Функция TranslateMessage( )

Сообщения о нажатии виртуальных клавиш могут быть преобразованы в сим­вольные сообщения с помощью функции TranslateMessage(), которая формирует сообщение wm_charиз пары wm_keydown/wm_keyup. Эта функция необходима только тем приложениям, которые обрабатывают ввод данных с клавиатуры. Важность функции заключается в том, что она позволяет пользователям выбирать команды меню путем нажатия клавиш, а не только щелчками тныши.

Функция DispatchMessage( )

Функция DispatchMessage() направляет полученное сообщение соответствующей оконной процедуре, автоматически определяя окно, которому адресовано сообщение.

Оконная процедура

Оконная процедура регистрируется в системе и вызывается всякий раз, когда Windowsвыполняет какую-либо операцию над окном приложения. В следующем фрагменте показан текст этой процедуры из файла SWP.C(за исключением нескольких специфичных для приложения строк):

LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,

WPARAM wParam, LPARAM IParam) {

HOC hdc;

PAINTSRUCT ps;

switch (messg) {

case WM_PAINT:

hdc = BeginPaint (hWnd, Sps); 

ValidateRect(hWnd, NULL);

EndPaint(hWnd, &ps); break;

case WM_DESTROY:

PostQuitMessage (0);  

break;      

default:        
return(DefWindowProc(hWnd, messg, wParam, IParam));

}   
return(0);
}   
        

Windowsпредполагает, что имя этой процедуры указанно в поле ipfnWndProcструктуры wcApp, описывающей класс окна. Все окна, созданные на базе данного класса, будут управляться процедурой WndProc().

В Windowsсуществует более двухсот различных сообщений, которые могут посылаться окнам. Все они имеют префикс wm_ (windowmessage— оконное сообщение), например: wm_create, wm_size, wm_paint.

Первым параметром процедуры WndProc() является дескриптор окна, которому посылается сообщение. Поскольку одна процедура может обрабатывать сообщения сразу нескольких окон, этот дескриптор позволяет определить, какому именно окну адресовано сообщение. .Второй параметр содержит идентификатор сообщения. Два оставшихся параметра, wParamи IParam, несут дополнительную информацию, зависящую от конкретного сообщения.

В нашей процедуре WndProc() определяются две локальные переменные: hdc, содержащая дескриптор контекста устройства (экрана), и ps, задающая структуру paintstruct, которая необходима для хранения информации, отображаемой в рабочей области окна.

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

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

Первое сообщение, которое в нашем примере обрабатывает процедура HndProc(), — это WM_PAINT. Поскольку Windows является многозадачной системой, то одно приложение может открыть свое окно поверх окна другого приложения. Это создает определенную проблему, так как при закрытии или перемещении верхнего окна нижнее не знает о происходящем, в результате чего Область, которую занимало верхнее окно, становится некорректной. Windows решает данную проблему путем направления сообщения wm_paintнижнему окну, информируя о том, что его содержимое требует обновления.

Помимо самого первого сообщения wm_paint, которое генерируется функцией UpdateWindow() внутри функции WinMain(), сообщение WM_PAINT также посылается в следующих ситуациях:

Любая часть окна, которая была закрыта другим объектом, например диалоговым окном, помечается как недействительная. Наличие недействительной области заставляет Windows послать приложению сообщение wm_paint, причем система отслеживает координаты такой области. Если окно оказалась закрыто несколькими объектами, Windowsвычислит координаты суммарной области. Другими словами, Windows не станет посылать несколько сообщений wm_paint для каждого отдельно взятого прямоугольного участка, а сгенерирует одно сообщение сразу для всей недействительной области сложной формы.

При обработке сообщения wm_paint вначале выполняется функция BeginPaint (), которая подготавливает указанное окно к операции рисования, заполняет структуру paintstruct данными об обновляемой области и возвращает дескриптор контекста устройства, связанного с этим окном.

Вызов функции invalidateRect() заставляет Windowsпометить указанную область окна как недействительную и сгенерировать для нее сообщение wm_paint. Приложение может получить координаты этой области, вызвав функцию GetUpdateRect (). Функция ValidateRect () удаляет указанный фрагмент из определения области, требующей перерисовки.

Функция EndPaint() вызывается всякий раз по завершении операций вывода данных в рабочую область окна. Функция ставит Windowsв известность, что приложение закончило обработку сообщений о перерисовке и имеющийся контекст устройства можно удалить.

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

Когда пользователь выбирает в системном меню команду Close или нажимает кнодку закрытия окна, Windows посылает приложению сообщение WM_DESTROY. В ответ на это приложение завершает свою работу, вызывая функцию PostQuitMessage (), которая направляет в очередь сообщений приложения сообщение WM_QUIT, вследствие чего цикл обработки завершается.

Функция DefWindowProc( )

Функция DefWindowProcOобычно вызывается в блоке defaultоператора switch. Она возвращает все нераспознанные или необработанные сообщения обратно системе, гарантируя таким образом, что все посылаемые приложению сообщения будут корректно обработаны.

Создание проекта

Чтобы начать новый проект, выберите в окне компилятора в меню File команду New, а затем в открывшемся диалоговом окне New на вкладке Projects активизируйте элемент Win32 Application(рис. 17.1).


Рис. 17.1. Выбор типа проекта

Задайте для проекта имя SWP, щелкните на кнопке ОК, в появившемся окне мастера выберите опцию An empty project и вновь нажмите ОК, после чего будет создан пустой проект.

Чтобы добавить в проект программный файл, выберите в меню Project подменю Add To Project, а в нем — команду New(рис. 17.2).


Рис. 17.2. Для добавления в проект нового файла используется команда New из подменю Add To Project

Воспользовавшись командой New, откройте диалоговое окно New(рис. 17.3), выделите в нем элемент C++ SourceFile и введите имя файла (в нашем примере это SWP.C).


Рис.17.3. Выбор типа добавляемого файла

Далее в окне редактора наберите текст программы (рис. 17.4).


Рис. 17.4. Исходный текст программы в окне редактора

Завершив ввод текста, сохраните файл, выбрав в меню File команду Save.

Следующий этап создания нового приложения состоит в построении исполняемого файла. Но для начала важно правильно установить опции проекта. Выберите в меню Project команду Settings.... Открывающееся при этом диалоговое окно ProjectSettings содержит множество вкладок, на которых представлены всевозможные опции сборки приложения. Убедитесь в том, что на вкладке General в списке MicrosoftFoundationClasses выделена опция NotUsingMFC(рис. 17.5).


Рис. 17.5. Установки вкладки General

По умолчанию для проекта создаются две конфигурации — Debug(отладонная) и Release(финальная), из которых активной является первая, поэтому все файлы создаются и сохраняются в папке DEBUG. Когда вы будете уверены в корректности работы приложения и решите создать окончательную версию, выберите в меню Build команду Set Active Configuration иукажитеконфигурациюRelease.

На вкладке Link в поле Output file name указано, в какую папку будет помещен исполняемый файл и как он будет называться (рис. 17.6). Обратите внимание, что по умолчанию выходной папкой является DEBUG.


Рис. 17.6. Установки вкладки Link

Текст программы

Мы уже рассмотрели большую часть файла SWP.C. Ниже показан полный его текст.

/*
*       swp.c
*  Простейшее приложение Windows.
*/

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, DINT, WPARAM, LPARAM); char szProgName[] = "ProgName";

int WINAPI WinMain(HINSTANCE hlnst, HINSTANCE hPrelnst,

LPSTR IpszCmdLine, int nCmdShow) {

HWND hWnd;

MSG IpMsg;

WNDCLASS wcApp;

wcApp.lpszClassName = szProgName;  
wcApp.hlnstance    = hlnst;     
wcApp.lpfnWndProc  = WndProc;
wcApp.hCursor      = LoadCursor(NULL, IDC_ARROW);

wcApp.hlcon        = 0; wcApp.lpszMenuName = 0;

wcApp.hbrBackground = Getstockobject (WHITE_BRUSH) ;
wcApp. style         = CS_HREDRAW | CS_VREDRAW; 
wcApp.cbClsExtra   = 0;
wcApp . obWndExtra   = 0;
if ( IRegisterClass (swcApp) )
return 0;

{

hWnd = CreateWindow(szProgName, "Simple Windows Program", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAOLT,, CWJJSEDEFAULT, CW_USEDEFAULT, (HWND) NULL, (HMENU)NULL, (HANDLE) hlnst, (LPSTR) NULL) ; ShowWindow(hWnd, nCmdShow) ; UpdateWindow(hWnd) ;

while (GetMessage (SlpMsg, 0, 0, 0)){ TranslateMessage(SlpMsg) ;
DispatchMessage (SlpMsg) ; }
return (IpMsg.wParam) ;

}

LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,

WPARAM wParam, LPARAM IParam) {

HOC hdc;

PAINTSTRUCT ps;

switch (messg)

{case WM_PAINT:

hdc = BeginPaint(hWnd, Sps) ;

MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, 639,429); MoveToEx(hdc, 300, 0, NULL); LineTo(hdc, 50, 300);

TextOut(hdc, 120,-30,"<-a few lines ->",17);.

ValidateRect(hWnd, NULL);

EndPaint(hWnd, sps);

break; case WM_DESTROY:

PostQuitMessage(0);

break; default:

return(DefWindowProc(hWnd, messg, wParam, IParam));

break; }

return(0); }

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

MoveToEx(hdc. О, О, NULL);

LineTo{hdc, 639,429);

MoveToEx(hdc, 300, О, NULL);

LineTofhdc, 50, 300);

TextOut(hdc, 120, 30, "<- a few lines ->",17);

В нескольких следующих параграфах мы будем модифицировать текст этого блока, чтобы поэкспериментировать с различными встроенными функциями Windows, предназначенными для рисования графических объектов GDI, которые называются графическими примитивами.

А пока что скомпилируйте и скомпонуйте приложение с помощью команды Build или RebuildAll меню Build, после чего посредством команды Execute запустите программу на выполнение. Окно приложения показано на рис. 17.7. Программа осуществляет вывод в окно двух линий и небольшого сообщения.


Рис. 17.7. Окно программы

Рисование эллипса

Функция Ellipse() используется для построения эллипса или окружности. Центр фигуры будет одновременно и центром воображаемого прямоугольника, в который вписывается эллипс. Координаты этого прямоугольника: xl,ylи х2,у2 .

Эллипс представляет собой замкнутую фигуру. Внутренняя область эллипса закрашивается цветом текущей кисти. Дескриптор контекста устройства задается параметром hdc. Все остальные параметры имеют тип int. Функция в случае успешного завершения возвращает значение true.

Синтаксис функции Ellipse() таков:

Ellipse (hdc,   xl,   yl,   x2,   y2}

В приведенном ниже фрагменте программы создаются небольшой эллипс и сопровождающая его надпись (рис. 17.9):

Ellipse(hdc, 200, 200, 275, 250);
TextOut(hdc, 210, 215, "<-an ellipse", 13);



Рис. 17.9. Эллипс, нарисованный в окне приложения

Рисование сегмента

Функция chord() рисует замкнутую дугу, конечные точки которой соединены линией с координатами хЗ,уЗ и х4,у4. Построение фигуры осуществляется против часовой стрелки (от точки хЗ,уЗ до точки х4,у4). Внутренняя область фигуры закрашивается цветом активной кисти. Дескриптор контекста устройства задается параметром hdc. Все остальные пара­метры имеют тип int. В случае успешного завершения функция возвращает значение

TRUE

Синтаксис функции Chord():

Chord (hdc,   xl,   yl,   x2,   y2,   хЗ,   уЗ,   х4,   y4)

В приведенном ниже фрагменте программы создается сегмент и сопровождающая его надпись (рис. 17.11):

Chordfhdc, 550, 20, 630,80, 555, 25,625,70);

TextOutfhdc, 470, 30, " A Chord ->",11); 


Рис. 17.11. Сегмент, нарисованный в окне приложения

Рисование сектора

Для рисования сектора предназначена функция Pie(). Центр эллипса, из которого вырезается сектор, будет одновременно и центром воображаемого прямоугольника, в который вписывается эллипс. Координаты этого прямоугольника — x1,y1 и х2,у2. Концы сектора обозначаются точками с координатами хЗ,уЗ и х4,у4. Построение фигуры осуществляется против часовой стрелки (от точки хЗ,уЗ до точки х4,у4). Внутренняя область сектора закрашивается цветом активной кисти. Дескриптор контекста устройства задается параметром hdc. Все остальные параметры имеют тип int. В случае успешного завершения функция возвращает значение true.

Синтаксис функции Pie():

Pie (hdc,   xl,  yl,   x2,   y2,   хЗ,   уЗ,   х4,   y4)

В приведенном ниже фрагменте программы создается сектор и сопровождающая его надпись (рис. 17.13):

Pie(hdc, 300, 50,-400, 150, 300, 50,300, 100);
TextOut(hdc, 350, 80, "<-A Pie Wedge", 14);


Рис. 17.13. Сектор, нарисованный в окне приложения

Рисование прямоугольника

Функция Rectangle() предназначена для построения прямоугольника или квадрата с использованием двух точек с. координатами xl,ylи х2,у2. Получаемая замкнутая фигура закрашивается цветом текущей кисти. Дескриптор контекста устройства задается параметром hdc. Все остальные параметры имеют тип int. В случае успешного завершения функция возвращает значение true.

Синтаксис функции Rectangle ():

Rectangle (hdc,   xl,   yl,   x2,   y2)

В приведенном ниже фрагменте программы создается прямоугольник и сопровождающая его надпись (рис. 17.14):

Rectangle(hdc, 50, 300, 150, 400);
TextOut(hdc, 160,350, "<- A Rectangle", 14);


Рис. 17.14. Прямоугольник, нарисованный в окне приложения

Использование файла SWP.C в качестве шаблона

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

/*
* Sine.с
* Программа, которая рисует синусоиду.
* Построена на базе файла swp.c.
*/

#include <windows.h>

#include <math.h>

#define pi 3.14159265359

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);   : char szProgName[]="ProgName";

int WINAPI WinMain (HINSTANCE hlnst, HINSTANCE hPrelnst, ,  ,., LPSTR IpszCmdLine, int nCmdShow)

{  

HWND hWnd; 

MSG  IpMsg;  

WNDCLASS wcApp;

wcApp.lpszClassName = szProgName; wcApp.hlnstance    = hlnst; wcApp.lpfnWndProc   = WndProc;

wcApp.hCursor      = LoadCursor(NULL, IDC_ARROW); wcApp.hlcon        = NULL; wcApp.lpszMenuName  = NULL;

wcApp.hbrBackground = GetStockObject(WHITE_BRUSH); wcApp. style         = CS_HREDRAW | CS_VREDRAW; wcApp.cbClsExtra   = 0; wcApp.cbWndExtra   = 0; if (IRegisterClass(SwcApp)) return 0;

hWnd = CreateWindow(szProgName, "A Sine Wave",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, (HWND)NULL,.(HMENU)NULL, (HANDLE)hlnst, (LPSTR)NULL); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);

while (GetMessage(&lpMsg, 0, 0 , 0) ) { TranslateMessage(SlpMsg); DispatchMessage(SlpMsg);

}     

return (IpMsg.wParam) ;  

LRESULT CALLBACK WndProc(HWND hWnd, DINT messg,

WPARAM wParam, LPARAM IParam) {

HOC hdc;

PAINTSTRUCT ps;

double y; int i;

{

switch (messg) {

case WM_PAINT:

hdc=BeginPaint(hWnd, Sps) ;


/* построение осей координат */
MoveToEx(hdc, 100, 50, NULL);
LineTo(hdc, 100, 350);
MoveToEx(hdc, 100, 200, NULL);
LineTo(hdc, 500, 200);
MoveToEx(hdc, 100, 200, NULL);

/* построение синусоиды */

for (i=0; i < 400; i++) {

у = 120.0*sin(pi*i*(360.0/400.0)/180.0) LineTo(hdc, i+100, (int)(200.0-y));

}

ValidateRect (hWnd, NULL);

EndPaint (hWnd, Sps);  

break; case WM_DESTROY:

PostQuitMessage (0);

break;  
default:  

return (DefWindowProc (hWnd, messg, wParam, IParam) ) ;

break; } return (0);

}

Если проанализировать текст файла SINE. С и сравнить его с текстом рассмот­ренного ранее файла SWP.C, то можно увидеть, что внесенные изменения действительно незначительны.

Обратите внимание, что в процедуре WndProc( ) определяются две новые переменные:

double у; int i;

Оси координат строятся с помощью функций MoveToEx( ) и LineTo( ) :

/* построение осей координат */

MoveToEx(hdc, 100, 50, NULL);    

LineTo (hdc, 100, 350);

MoveToEx (hdc, 100, 200, NULL) ;

LineTo (hdc, 500, 200);

MoveToEx (hdc, 100, 200, NULL) ;

Синусоида рисуется в цикле for. Ее амплитуда составляет 120 пикселей относительно горизонтальной оси. Для построения синусоиды используется функция sin( ) , объявленная в файле МАТН.Н. Константа piнеобходима для преобразования значений углов из градусов в радианы.

/* построение синусоиды */
for(i=0;i < 400; i++)
{

у = 120. 0*sin(pi*i* (360.0/400.0)/180.0);

LineTo(hdc, i+100, (int)(200.0-y));

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


Рис. 17.15. Синусоида, выведенная на экран программой SINE.C

Создание диаграмм

Круговые диаграммы широко применяются в коммерческих приложениях при создании различного рода презентаций. В представленной ниже программе будут объединены многие приемы, рассмотренные в этой и предыдущей главах. В частности, в программе задействованы меню и разработанные нами диалоговые окна Aboutи PieChartData. В окне ввода данных пользователю будет предложено задать до десяти значений, определяющих размеры секторов. На основании введенных целочисленных значений будут вычислены угловые размеры секторов в пропорции к общему размеру круга — 360 градусов. Минимальный размер сектора — 1 градус. Для закрашивания секторов определен массив IColor[] с набором цветов. Пользователь может также ввести подпись к диаграмме.

Для работы над данным приложением необходимо иметь четыре файла: PIE.H, PIE.RC, PIE.Cи PIE.CUR. Последний, содержащий изображение указателя мыши, вы можете разработать самостоятельно с помощью редактора ресурсов.

Ниже показан текст файла заголовков PIE.H, в котором содержатся идентификаторы команд меню и элементов управления диалоговых окон:

#define IDM_ABOUT  10

#define IDM_INPUT  20

#define IDM EXIT  30

#define DM_TITLE   160

#define DM_P1161

#define DM_P2162

#define DM_P3163

#define DM_P4164

#define DM_P5165

#define DM_P6166

#define DM_P7167  

#define DM_P8168

#define DM_P9169

#define DM_P10170

В файле ресурсов PIE.RC описываются меню и два диалоговых окна (более подробно об этих ресурсах говорилось в предыдущей главе), а также ряд вспомогательных ресурсов.

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////

#include "pie.h"

#define APST0DIO_HIDDEN_SYMBOLS

#include "windows. h"

tundef APSTUDIO_HIDDEN_SYMBOLS

//////////////////////////////////////

tundef APSTUDIO_READONLY_SYMBOLS

//////////////////////////////////////
// Ресурсы для английского (США) языка

#if !defined(AFX_RESOURCE_DLL) || defined (AFX_TARG_END)     -

tifdef _WIN32

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

tpragma code_page (1252)

#endif //_WIN32

////////////////////////////////////////

//

// Указатель мыши       

//

PIECURSOR CURSOR DISCARDABLE "pie.cur"

////////////////////////////////////////

//

// Меню

//

PIEMENU MENU DISCARDABLE BEGIN

POPUP "Pie_Chart_Data"

BEGIN

MENUITEM     "About...",     IDM_ABOUT

MENUITEM       "Input...",  IDM_INPUT

MENUITEM       "Exit",     IDM EXIT

END

END

/////////////////////////////////////////

// Диалоговые окна
//

ABOUTDLGBOX DIALOG DISCARDABLE 50,300, 180, 84

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE I WS_CAPTION I WS_SYSMENU

CAPTION "About"

FONT 8, "MS Sans Serif"

BEGIN

CTEXT "Microsoft СPie Chart Program", -1,3, 29,176,10

CTEXT "by William H. Murray and Chris H. Pappas", -1, 3, 16,176,10

PUSHBUTTON "ОК", IDOK, 74,51,32,14

END

PIEDLGBOX DIALOG DISCARDABLE 93,37,195,159

STYLE DS_MODALFRAME | WS_POPUP I WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Pie Chart Data"

FONT 8, "MS Sans Serif"

BEGIN

GROUPBOX "Chart Title:",100, 5, 3, 182, 30, WSJTABSTOP
GROUPBOX "Pie Wedge Sizes:",101, 3, 34,187, 95,WS_TABSTOP
LTEXT "Title: ", -1,10,21,30,8
EDITTEXT DMJTITLE, 40,18,140, 12

LTEXT "Wedge #1:", -1,  10,  50,  40,8, NOT WS_GROUP
LTEXT "Wedge #2:", -1,  10,  65,  40,8. NOT WS_GROUP
LTEXT "Wedge #3:", -1,  10,  80,  40, 8, NOT WS_GROUP
LTEXT "Wedge t4: ", -1,  10,  95,  40, 8, NOT WS_GROUP
LTEXT "Wedge #5:", -1,  10,  110, 40,8, NOT WS_GROUP
LTEXT "Wedge #6:", -1,  106,50,  40,8, NOT WS_GROUP
LTEXT "Wedge #7:", -1,  106,65,  40,8. NOT WS_GROUP
LTEXT "Wedge #8:", -1,  106,80,  40,8, NOT WS_GROUP
LTEXT "Wedge #9:", -1,  106,95,  40, 8, NOT WS_GROUP
LTEXT "Wedge #10:", -1,102, 110, 45,8, NOT WS_GROUP
EDITTEXT DM_P1,  55,  45,  30,12
EDITTEXT DM_P2,  55,  60,  30, 12
EDITTEXT DM_P3,  55,  75,  30, 12
EDITTEXT DM_P4,  55,  90,  30, 12
EDITTEXT DM_P5,  55,  105, 30, 12
EDITTEXT DM_P6,  150, 44,  30,12
EDITTEXT DM_P7,  150, 61,  30,12
EDITTEXT DM_P8,  150, 76,  30,12
EDITTEXT DM_P9,  149,91,  30,12
EDITTEXT DM_P10, 149,106,30, 12

PUSHBUTTON "ОК", IDOK, 39,135, 24,14

PUSHBUTTON "Cancel", IDCANCEL, 122, 136,34,14
END

tifdef APSTUDIO_INVOKED 

///////////////////////////////////////////

1 TEXTINCLUDE DISCARDABLE   
BEGIN

"resource.h\0" END

2 TEXTINCLUDE DISCARDABLE BEGIN

"#include ""pie.h""\r\n"

"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"

"#include ""windows.h""\r\n".

"#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"

"\0"
END  

3 TEXTINCLUDE DISCARDABLE

BEGIN 

"\r\n"

"\0" END

#endif    // APSTUDIO_INVOKED

#endif   // Ресурсы для английского (США) языка

Следует помнить, что файл PIE.RC— это просто текстовый эквивалент описаний меню и диалоговых окон, созданных с помощью редактора ресурсов (см. предыдущую главу).

Файл PIE.С содержит основной текст программы. Несмотря на достаточно большой его размер, вы легко можете найти здесь знакомые блоки из файла SWP.C.

/*
* PIE.С
* Создание круговых диаграмм.
*/

#include <windows.h>

#include <string.h>
#include <math.h>
#include "pie.h"

#define radius     180

#define maxnurawedge 10

#define pi  3.14159265359

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK PieDlgProc(HWND, UINT, WPARAM, LPARAM);

char szProgName[] = "ProgName"; char szApplName[] = "PieMenu"; char szCursorName[] = "PieCursor";

char szTString[80] = "(piechart title area)";

unsigned int iwedgesize[maxnumwedge] = {5,20,10,15};

long IColor[maxnumwedge] = {OxOL,OxFFL, 'OxFFOOL, OxFFFFL,

OxFFOOOOL, OxFFOOFFL, OxFFFFOOL, OxFFFFFFL, OxSOSOL, Ox808080L);

int WINAPI WinMain(HINSTANCE hlnst, HINSTANCE hPrelnst,

LPSTR IpszCmdLine, int nCmdShow) {

HWND hWnd;

MSG IpMsg;

WNDCLASS wcApp;

wcApp.lpszClassName = szProgName; wcApp.hlnstance    = hlnst; wcApp.lpfnWndProc  = WndProc;

wcApp.hCursor      = LoadCursor (hlnst, szCursorName); wcApp.hlcon        = Ldadlcon(hlnst,szProgName); wcApp.lpszMenuName = szApplName;

wcApp.hbrBackground = GetStockObject(WHITE_BRUSH); wcApp.style        = CS_HREDRAW | CS_VREDRAW; wcApp.cbClsExtra  = 0; wcApp.cbWndExtra   = 0; if (IRegisterClass(SwcApp)) return 0;

hWnd = CreateWindow(szProgName, "PieChart Application", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, (HWND)NULL, (HMENU)NULL, (HANDLE)hlnst, (LPSTR)NULL); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(SlpMsg, 0, 0, 0)) {

TranslateMessage(SlpMsg); DispatchMessage(SlpMsg); )

return(IpMsg.wParam); }

BOOL CALLBACK AboutDlgProc(HWND hdlg, UINT messg,

WPARAM wParam, LPARAM IParam) I

switch (messg) {

case WM_INITDIALOG:

break;

case WM_COMMAND: switch (wParam)

{

case IDOK:

EndDialog(hdlg, TRUE),

break; default:

return FALSE; }

break; default:

return FALSE; } return TRUE;

}

BOOL CALLBACK PieDlgProc(HWND hdlg, UINT messg,

WPARAM wParam, LPARAM IParam) {

switch (messg) {

case WM_INITDIALOG:

return FALSE; case WM_COMMAND: switch (wParam) {

case IDOK:

GetDlgltemText(hdlg, DMJTITLE, szTString, 80); iWedgesize[0] = GetDlgltemlnt(hdlg, DM_P1, NULL, 0) iWedgesize[l] = GetDlgltemlnt(hdlg, DM_P2, NULL, 0) iWedgesize[2] = GetDlgltemlnt(hdlg, DM_P3, NULL, 0) iWedgesize[3] = GetDlgltemlnt(hdlg, DM_P4, NULL, 0) iWedgesize[4] = GetDlgltemlnt(hdlg, DM_P5, NULL, 0) iWedgesize[5] = GetDlgltemlnt(hdlg, DM_P6, NULL, 0) iWedgesize[6] = GetDlgltemlnt(hdlg, DM_P7, NULL, 0) iWedgesize[7] = GetDlgltemlnt(hdlg, DM_P8, NULL, 0) iWedgesize[8] - GetDlgltemlnt(hdlg, DM_P9, NULL, 0) iWedgesize[9] = GetDlgltemlnt(hdlg, DM_P10, NULL, 0); EndDialog(hdlg, TRUE); break;

case IDCANCEL:

EndDialog(hdlg, FALSE); break;

default:

return FALSE; }

break; default:

return FALSE; } return TRUE;

}

LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,

WPARAM wParam, LPARAM IParam) {

HDC hdc;

PAINTSTRUCT ps;

HBRUSH hBrush;

static HINSTANCE hlnstl, hlnst2;

static int xClientView, yClientView;

unsigned int iTotalwedge[maxnumwedge+1]; int i, iNWedges; 

iNWedges = 0;

ford = 0; i. < maxnurawedge; i++) {

if<iWedgesize[i] != 0) iNWedges++; } iTotalwedge[0] = 0;

for(i = 0; i < iNWedges; i++)

iTotalwedge[i+1] = iTotalwedge[i] + iWedgesize[i];

switch (messg) (

case WM_SIZE:

xClientView - LOWORD(IParam); yClientView = HIWORD(IParam); break; case WM_CREATE:

hlnstl = ((LPCREATESTRUCT) IParam)->hlnstance; hlnst2 = ((LPCREATESTRUCT) IParam)->hlnstance; break; case WM_COMMAND:

switch (wParam)  {

case IDM_ABOUT:

DialogBox(hlnstl, "AboutDlgBox", hWnd, (DLGPROC) AboutDlgProc)i break/­case IDM_INPUT:

DialogBox(hlnst2, "PieDlgBox", hWnd, (DLGPROC) PieDlgProc); InvalidateRect(hWnd, NULL, TRUE); UpdateWindow(hWnd); break; case IDM_EXIT:

SendMessage(hWnd, WM_CLOSE, 0, OL); 
break; default:

break; }

break; case WM_PAINT:

hdc = BeginPaint(hWnd, Sps);

SetMapMode(hdc, MM_ISOTROPIC);

SetWindowExtExfhdc, 500, 500, NULL);

SetViewportExtEx(hdc, xClientView, -yClientView, NULL);

SetViewportOrgEx(hdc, xClientView/2, yClientView/2, NULL);

if(xClientView > 200)

TextOut(hdc, strlen (szTString) * (-8/2),

240, szTString, strlen (szTString));

for(i= 0; i < iNWedges; i++){ ' hBrush = CreateSolidBrush.(lCo;l.or [i]) ; SelectObject(hdc, hBrush); Pie(hdc, -200, 200, 200, -20:0,,..    :

(int)(radius*cos(2*pi*iTotalWedge[i]/

iTotalWedgeJiNWedges])),

(int)(radius*sin(2*pi*iToialWedge[iJ/

ITotalWedge[iNWedges])),

(int)(radius*cos(2*pi*iTotalWedge[i+1]/

iTotalWedge tiNWedges])),

(int)(radius*sin(2*pi*iTotalWedge[i+l]/

iTotalWedge[iNWedges]))); }

ValidateRect (hWnd, NULL);

EndPaint (hWnd, Sps); 1  break; case WM_DESTROY:

PostQuitMessage (0);

break; default:

return (DefWindowProc (hWnd, messg, wParam, IParam) ) ;

)

return(0);

)   

Ниже мы несколько подробнее проанализируем содержимое приведенных файлов.

Файл PIЕ.Н

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

Файл РIЕ.RС

Файл ресурсов приложения содержит команду подключения указателя мыши (piecursor), а также описания меню (piemenu) и двух диалоговых окон (aboutdlgbox и piedlgbox). На рис. 17.16 показано диалоговое окно About.


Рис. 17.16. Окно About

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

Файл РIЕ.С

Программа, записанная в файле PIE.C, создает круговую диаграмму, содержащую до десяти секторов. Если в меню выбрать команду Input, откроется диалоговое окно PieChartData, в котором пользователь может установить число секторов и задать их относительную ширину, а также ввести заголовок диаграммы. Эти данные передаются в программу после щелчка на кнопке ОК. Информация, поступающая от диалоговых окон, обрабатывается в ветви caseIDOKпосредством процедуры PieDlgProcO. Текст заголовка возвращается функцией GetDlgltemText(), а числовые значения — функцией GetDlgltemlnt(). Последняя принимает четыре аргумента. Первые два — это дескриптор окна и идентификатор элемента управления. Третий аргумент явля­ется указателем на булеву переменную, устанавливаемую в TRUEпри успешном завершении функции. В нашем случае для простоты его значение задано равным null. Четвертый аргумент определяет, следует ли вводимое значение интерпретиро­вать как знаковое (аргумент не равен нулю) или беззнаковое (аргумент равен нулю). Введенные значения сохраняются в глобальном массиве iWedgesize[ ].

Основная работа выполняется в процедуре WndProc(), где обрабатываются пять сообщений: wm_size, wm_create, wm_command, wm_paintи wmjdestroy. Сообще­ние wm_sizeпосылается всякий раз, когда изменяются размеры окна приложения. Информация о размерах сохраняется в переменных xciientviewи yClientViewи впоследствии, при обработке сообщения wm_paint, используется для настройки масштаба диаграммы.

В процессе обработки сообщения wm_createсоздаются два дескриптора прило­жения, hlnstlи hlnst2, которые затем передаются функции DialogBox(), гене­рирующей экземпляры двух диалоговых окон.

При выборе какой-либо команды меню генерируется сообщение wm_command. Если это сообщение имеет подтип IDM_ABOUT, открывается окно About, а если подтип idm_input— окно ввода данных. При получении сообщения idm_exitприложение закрывает свое главное окно и завершает работу.

Код рисования непосредственно диаграммы содержится в блоке обработки сообщения wm_paint. По умолчанию в системе установлен режим отображения мм_техт. В этом режиме координаты объектов отсчитываются (в пикселях) начиная с верхнего левою угла окна, имеющего координаты 0,0. Вот почему в программе SINE.Cвид изображения будет меняться всякий раз при изменении разрешения экрана. В программе PINE.Cустанавливается режим отображения mm_isotropic.

SetMapMode(hdc, MM_ISOTROPIC) ; SetWindowExtEx(hdc, 500, 500, NULL);

SetViewportExtEx(hdc, xClientView, -yClientView, NULL); SetViewportOrgEx(hdc, xClientView/2, yClientView/2, NULL);

Режимы отображения, существующие в Windows, перечислены в табл. 17.4.

Таблица 17.4. Режимы отображения
Режим Описание
mm_anisotropic Одной логической единице соответствует произвольная физическая единица; масштабирование по осям х и у независимое
mm_hienglish Одной логической единице соответствует 0,001 дюйма; ось у направлена снизу вверх
mm_himetric Одной логической единице соответствует 0,01 мм; ось у направлена снизу вверх
mm_isotropic Одной логической единице соответствует произвольная физическая единица; масштаб по осям х и у одинаков
mm_loenglish Одной логической единице соответствует 0,01 дюйма; ось у направлена снизу вверх
mm_lometric Одной логической единице соответствует 0,1 мм; ось у направлена снизу вверх
MM_TEXT Одной логической единице соответствует один пиксель; ось у направ­лена сверху вниз (это режим по умолчанию)
MM_TWIPS

Одной логической единице соответствует 1/20 точки принтера (1/1440 дюйма); ось у направлена снизу вверх

Режим mm_isotropic позволяет программисту самостоятельно выбирать масштаб изображения по осям х и у. Функция SetWindowExtEx( ) задает размеры окна по горизонтали и вертикали (в нашем примере они равны 500). Эти логические размеры автоматически преобразуются системой в соответствии с физическими размерами экрана. В функции SetviewportExtEx( ) устанавливается размер области просмотра (в пикселях). Отрицательное значение координаты у означает, что ось у направлена снизу вверх. Система сравнивает размеры окна и области просмотра и вычисляет соотношение между логическими единицами и пикселями. Функция SetViewportOrgEx( ) устанавливает координаты точки начала области просмотра (в пикселях). В нашем примере это будет центр окна. После вызова этих функций все последующие координаты можно указывать в логических единицах.

Заголовок диаграммы выводится уже с учетом текущего режима отображения. Если размеры окна слишком малы, заголовок не показывается вообще.

if (xClientView > 200) 

TextOutfhdc, strlen(szTString)*(-8/2) , 240, szTString,. ,,1" strlen (szTString) );

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

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

iNWedges=0;

for(i=0;i < maxnumwedge; i++)
{ if (iWedgesizeli] != 0) iNWedges++;}

Приращение переменой iNWedgesвыполняется каждый раз, когда в массиве iWedgesize [i] обнаруживается ненулевое значение. Таким образом, по окончании цикла будет известно число секторов диаграммы.

Накопительные размеры секторов хранятся в массиве iTotalWedge[] . Эти значения необходимы для вычисления границ между секторами. Например, если пользователь ввел в качестве размеров секторов значения 5, 10, 7 и 20, то в массиве iTotalWedge[] .. будут представлены значения 0, 5, 15, 22 и 42. Вот как они получаются:

iTotalWedge[0] = 0;

for(i= 0; i < iNWedges; i++)      
iTotalWedge[i+l] = iTotalWedge [i]+ iWedgesize [i];

Элементы массива iTotalWedge[ ] используются для вычисления начального и конечного углов каждого сектора. Вспомните, что функция Pie( ) принимает девять параметров. Первый является дескриптором контекста устройства. Следующие четыре определяют координаты прямоугольника, в который вписывается эллипс. В нашем примере выбраны координаты -200,200 и 200,-200. Оставшиеся четыре параметра определяют координаты начальной и конечной точек сектора. Координата х вычисляется с помощью функции cos( ) , а координата у — с помощью функции sin( ) . Так, начальную координату х первого сектора можно определить как произведение радиуса окружности на косинус значения 2*pi*iTotalWedge[0] . Коэффициент 2*piздесь необходим для преобразования градусов в радианы. Координаты конечной точки сектора определяются по тому же алгоритму, но для их вычисления использу­ется следующий элемент массива iTotalWedge[] — iTotalWedge[1 ]. Чтобы все секторы вписались в круг, значение координаты делится на общий накопительный размер всех секторов — iTotalWedge [iNWedges].

for(i = 0; i < iNWedges; i++) {
hBrush = CreateSolidBrush(lColor [i]) ;
SelectObject (hdc, hBrush);
Pie(hdc, -200, 200, 200, -200,

(int)(radius*cos (2*pi*iTotalWedge [i]/

iTotalWedge [iNWedges] )), (int)(radius*sin (2*pi*iTotalWedge [i]/

iTotalWedge [ iNWedges] )), (int)(radius*cos(2*pi*iTotalWedge[i+l] / iTotalWedge [iNWedges] ),

(int)(radius*sin(2*pi*iTotalWedge[i+l] / ,
       iTotalWedge [ iNWedges ]);            

}

Весь цикл повторяется столько раз, сколько указано в переменной iNWedges. Диаграмма, представленная на рис. 17.18, строится по умолчанию, а показанная на рис. 17.19 — на основании значений, введенных пользователем.


Рис. 17.18. Диаграмма, построенная по умолчанию



Рис. 17.19. Диаграмма, построенная с учетом введеных пользователем данных