1 /*&YtttnjMSt Onpalnt обработки сообщения wm_paint*/
void OnPaint(HWND hwnd){
RECT г; //Структура^ овпсывахщая координаты прямоугольники PAINTSTRUCT ps;11 Структура с характеристиками рабочей области HDC hdc=BeginPaint(hwnd,&ps);//Получение контекста устройства GetClientRecCthwnd,&r];//Получение координат рабочей области int i=0;//Номер читаемого слова в буфере и х-координата на экране wbilelti<500)bbDataOK) //Берем из буфера по одному слову до конца SetPixel(hdc,i,r.bottom-5-nBuf[i + + ],RGB(0, 0, 255)) ; //Вывод точки EndPaint (hwnd. Sips) ; }
/*#уяши» обработхн сообявняя W4_DE£TROY*/
void OnDestroy(HWNDI( Pos tQuitMessage(0); )
Исходные тексты приложения включают стандартный набор: заголовочный файл 6-6.Н с объявлениями символических констант и прототипов функций, файл ресурсов 6-6.RC с описанием простой линейки меню (единственное меню "Файл" с командами "Открыть" и "Выход") и, наконец, файл 6-6.СРР с исходным текстом программы.
Программа 6-4 выводит на экран содержимое указанного ей файла данных в виде точечного графика, в котором каждое данное отображается синей точкой, расстояние которой от низа окна равно значению конкретного данного (рис. 6.31). Файл данных должен содержать набор из 500 целых коротких (двухбайтовых) чисел, не превышающих высоты главного окна в пикселах, чтобы график не вышел за пределы экрана. Для упрощения программы в ней не предусмотрено никаких средств анализа размера файла данных или
210 Win32. Основы программирования
масштабирования данных. Возможный вариант программы для полготовки файлов данных для этой программы будет описан в конце раздела.
ж 11 l i i_i i рлммя В ^
Файл
Рис. 6.31. Вывод точечного графика
При выборе пользователем пункта "Открыть" вызывается стандартный диалог Windows "Открытие файла" (рис. 6.32), обеспечивающий поиск требуемых файлов по всем дискам и каталогам. Предполагается, что пользователь заранее подготовил файлы с данными, подлежащими выводу на экран. После выбора файла он открывается, его содержимое (500 коротких целых чисел) считывается в буфер программы и вызовом функции InvalidateRectQ инициируется посылка в главное окно приложения сообщения Windows WM PAINT.
Открытие файла
Папка
мя
Файла:
|Sp5.dat
Тип
Файлов:
|фай™
данных!"
DAT]
Г"
Только
чтение
Р
ис.
6.32. Стандартный диалог Windows
"Открытие файла"
В функции обработки сообщения WMPAINT в цикле из 500 шагов просматривается содержимое буфера с данными и каждое данное выводится на экран в виде точки, образуя графическое представление массива данных.
Г
лава
6 Ресурсы:
меню
и
диалоги
211
При повторном вызове стандартного диалога "Открытие файла" на экран выводится содержимое вновь выбранного файла; старый график при этом стирается.
Общая структура программы вполне очевидна. В оконной функции обрабатываются 3 сообщения Windows: WMCOMMAND, WMPAINT и WM DESTROY. Сообщение WMCOMMAND поступает в приложение при выборе пользователем одной из двух имеющихся команд меню. При выборе команды "Открыть" выполняются все необходимые действия по работе со стандартным диалогом "Открытие файла" и с самим файлом данных. При выборе команды "Выход" приложение закрывается обычным образом, вызовом функции Destroy Window(). Функция обработки сообщения WMPAINT обеспечивает вывод графика на экран.
Некоторое отличие от предыдущих примеров заключается в указании элемента стиля класса окна
we. з tyI e=CS_VREDRAW ;
Указание этой константы приводит к тому, что при каждом изменении размеров окна по вертикали Windows посылает в окно сообщение WMPAINT, которое приводит к полной перерисовке всего окна (а не только области вырезки). Такой режим необходим в тех случаях, когда изображение в окне рисуется относительно его нижней границы, т. е, требуется, чтобы при перемещении по экрану нижней границы окна все изображение перемещалось вместе с ней. Если изображение рисуется относительно правой границы окна, то необходимо включать в стиль окна константу CS_HREDRAW; в этом случае окно будет перерисовываться целиком при каждом изменении его размеров по горизонтали.
Для проверки работоспособности программы 6-4 необходимо подготовить файлы данных с разумным содержимым. Ниже приводится текст программы, позволяющей это сделать. В ней вычисляются значения функции у = ft», представляющей собой сумму спадающей экспоненциальной кривой d*ехр{-*/с2) и нормальной кривой Гаусса сЗ*ехр(-с4(х-с5)2). Подбором констант с!...с5 можно в широких пределах изменять форму получающейся кривой.
Разумеется, читатель может написать свою программу для вычисления значений любой другой функции. Нужно только, чтобы в файле содержалось ровно 500 чисел и чтобы их значения не превышали размера нашего окна по вертикали.
/*флйл apectrs.cpp. Создание тестовых файлов данных*/
# incIude «windows,h>
#include <math.h>y / Ради ехр()
char fnamel]="spl.datг;//Имя создаваемого файла
short nBuftSO];//Массив с тестовыми данными для записи в файл
lilt WINAPI WinMairHHINSTANGE hlnst, HINSTANCE, LPSTR, inc) {
/*3аполиин массив тестовыми данными*/
Int Cl,c2,C3,c5;
tloat c4,x;
cl=200; C2=15O; c3=250r c4=0.01; C5=35O;
Eorfint i=0;i<500;i++){
x=i;/JПреобразуем счетчик цикла в число с плавающей точкой nBuf[il=cl*exp[-х/с2)+сЗ*ехр(-с4*(х-с5)*(х-с5)) ; //Вычисляем значения ) /•Создали» новий файл к запишем в него тестовой массив*/
HANDLE hFiIe=CreateFile(fname,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,NULL >;
DWORD nCnt,- //Счетчик записанного
WriteFile(hFile,nBuf,2*500,bnCnt,NULL|,//Запись в файл
return О,-
)
2 12 Win32. Основы программирования
Стандартный диалог "Открытие файла"
Вывод на экран стандартного диалога Windows "Открытие файла" осуществляется вызовом функции GetOpenFileName(), для успешного выполнения которой необходимо подготовить структуру типа OPENF1LENAME (переменная ofn в программе). Эта структурная переменная объявлена в начале функции OnCommand() обработки сообщения WMCOMMAND от команд меню. Там же объявлена символьная строка-фильтр szFilter, в которую в определенном формате записываются строки, которые будут выведены в окно "Тип файлов" стандартного диалога, вместе с шаблонами спецификаций отбираемых файлов. В программе предусмотрена возможность показа файлов с расширением .DAT (шаблон *.dat), а также всех файлов (шаблон +.*). При желании список возможных типов имен файлов можно расширить. Элементы строки-фильтра разделяются двоичными нулями, которые в символьной строке обозначаются как \0. Двоичный нуль помешается также и в конец строки (которая, таким образом, завершается двумя нулями).
Структура ofn обнуляется, после чего некоторые элементы получают инициализирующие значения. Назначение большинства элементов структуры должно быть ясно из текста программы. В элемент ofti.lpstrFile записывается адрес буфера, куда функция GetOpenFileNameQ вернет полную спецификацию выбранного пользователем файла; принятое нами значение элемента ofn.Flags указывает, что при вводе пользователем неправильного имени каталога или файла на экран будет выведено предупреждающее сообщение вида "Такого пути нет" или "Невозможно найти файл1*.
Функция GetOpenFileNameQ вызывается в операторе if, условный блок которого выполняется, только если функция GetOpenFileName() отработала успешно и в буфере szFile появилась законная спецификация выбранного файла. Это дает возможность правильно завершить этот фрагмент программы и в том случае, когда пользователь, открыв стандартный диалог, затем закрыл его, так и не выбрав в нем файл,
Внутри условного блока выбранный файл открывается 32-разрядной функцией Сге-ateFileQ (это не опечатка - обобщенная функция CreateFileQ служит и для создания и для открытия файла) только для чтения {параметр GENERIC_READ). Эта функция требует в качестве параметров указатель на адрес буфера с именем файла и в случае успешного выполнения возвращает дескриптор файла типа HANDLE, который в дальнейшем будет служить для нас идентификатором данного файла (работа с файлами в 32-разрядных приложениях Windows будет подробнее описана в гл.] I). Если файл открыть не удалось, функция CreateFile() возвращает значение INVALID_HANDLE_VALUE, поэтому предложение
if(hFile==INVALID_HANDLE_VALUE)break;
позволяет обойти все последующие строки, если по какой-то причине файл не был открыт.
Тридцатидвухразрядная функция ReadFtleQ читает из файла в буфер nBuf заданное количество байтов (500 данных по 2 байта), после чего файл закрывается функцией CloseHandlef).
После выполнения всех операций с файлом предложением
bDataOK=TRUE;
устанавливается флаг bDataOK, который говорит о том, что буфер nBuf заполнен данными и, следовательно, есть что выводить на экран. В дальнейшем, в функции OnPaint(). вывод содержимого nBuf будет осуществляться, только если этот флаг установлен. Это полезная предосторожность, которая предотвратит вывод на экран "мусора" в ответ на первое сообщение WMPA1NT, которое поступает в приложение при открытии главного окна, когда буфер nBuf еще не заполнен. Правда, мы объявили nBuf глобальной пере-
Г лава 6. Ресурсы: меню и диалоги 213
менкой, а такие переменные автоматически инициализируются нулями, однако и это неприятно: после запуска программы на экране появится прямая линия, соответствующая нулевым значениям всех данных.
Итак, буфер nBufзаполнен данными, которые надо вывести на экран. Однако, как было показано в гл. 5, вывод в окно любых изображении допустим только в ответ на сообщение WMPAINT, так как лишь в тгом случае при всяких естественных для Windows манипуляциях с окном (изменение размеров окна, свертывание его в пиктограмму и обратное развертывание, "вытаскивание" окна из-под других окон и т. д.) изображение в окне будет перерисовываться правильно. Если изображение в окне носит статический, неизменяемый характер, как это было в предыдущих примерах, то никаких проблем не возникает, так как при появлении на переднем плане закрытой ранее части окна Windovvs сама посылает в приложение сообщение WM_PAINT и приложение перерисовывает закрытое ранее окно. Если же изображение должно формироваться динамически, например в ответ на выбор команды меню, как это имеет место в рассматриваемой программе, то мы должны иметь возможность, подготовив данные для вывода изображения, сами инициировать сообщение WM^PAINT, в функции обработки которого изображение и будет построено. Для этой важнейшей операции в Windows предусмотрено несколько функций, самой распространенной из которых является invalidateRectQ.
Инициирование сообщения WM_PAINT
Функция InvalidateRect(), с использованием которой мы уже столкнулись при рассмотрении программы 6-1, объявляет указанную в ней прямоугольную область определенного окна поврежденной (invalid - недействительный, не имеющий законной силы), что заставляет Windows послать в это окно сообщение WMPAINT, которое приводит к перерисовке содержимого окна. Таким образом, в Windows предусмотрены два механизма инициирования сообщения WMPAINT. Сама Windows посылает в приложение это сообщение каждый раз, когда скрытое ранее окно или его часть появляется на экране и, соответственно, требует перерисовки; кроме того, программа в любой момент может вызовом функции lnvalidateR.ect() объявить окно поврежденным, что также приведет к посылке в приложения сообщения WMPAINT.
Функиия lnvatidateRect() имеет следующий прототип:
BOOL InvalidateRect(HWND hWnd,CONST RECT* lpRect,B0OL bErase!;
Первый параметр определяет дескриптор того окна, которое требует перерисовки. При наличии в главном окне приложения внутренних, порожденных окон, мы имеем возможность инициировать перерисовку любого из них. для чего надо иметь их дескрипторы. С этой целью при создании каждого порожденного окна функцией CreateWindowf) следует позаботиться о сохранении возвращаемого этой функцией дескриптора окна.
Второй параметр дает возможность указать координаты прямоугольной области, которая добавляется к так называемой области обновления, требующей перерисовки. Windows накапливает информацию обо всех областях, входящих в область обновления, и, когда приложение вызывает функцию BeginPaintQ, Windows передает в программу координаты этой области. Практически, однако, вызов функции [rival idateRect() сразу приводит к посылке в окно сообщения WMPAINT и о накапливании недействительных областей в области обновления говорить не приходится.
Если значение этого параметра равно NULL, то областью обновления считается вся рабочая область. Перерисовка занимает заметное время и к тому же может приводить к мерцанию изображения. Поэтому во всех случаях желательно определять область обновления минимального размера. В нашем случае изображение графика может занимать весь экран и приходится перерисовывать всю рабочую область.
214 Win32. Основы программирования
Третий параметр является флагом перерисовки фона. Если его значение равно TRUE, фон окна перерисовывается, что приводит к стиранию прежнего изображения. Если флаг равен FALSE, новое изображение рисуется поверх старого. В нашем случае, когда на экран выводятся отдельные точки в произвольных местах, перерисовывать весь фон, т. е. стирать точки старого графика, необходимо. В тех случаях, однако, когда на экран выводятся прямоугольные элементы изображения, которые целиком перекрывают старое изображение (например, знакоместа с буквами или цифрами), можно обойтись без перерисовывания фона, что сэкономит процессорное время и, главное, предотвратит мерцание изображения, возникающее при перерисовке окна.
Вывод па i a/hi a графика
Вывод в главное окно приложения точечного графика осуществляется в функции OnPmnt() обработки сообщения WM_PAINT. Здесь после получения с помощью функции BeginPainlO дескриптора контекста устройства вызывается функция GetClienlRectO, которая возвращает в структурную переменную г типа RECT координаты рабочей области главного окна, точнее говоря, ее размеры, так как координаты начала рабочей области всегда считаются равными нулю. Размер рабочей области по вертикали нам нужен для того, чтобы выводимый на экран график строился относительно нижней границы окна (значения r.bottorn), независимо от текущего размера окна.
Вывод графика осуществляется в цикле из 500 шагов при условии наличия прочитанных из файла данных (переменная bDataOK=TRUE) функцией отображения точек SetPi\e!(). В качестве параметров этой функции указывается дескриптор контекста устройства, х- и у-координаты выводимой точки и ее цвет (с помощью макроса RGB()). Ордината точки с х-координатой i вычисляется как разность между нижней текущей границей окна г.bottom и высотой выводимой кривой в этой точке nBuffi]. Из полученного значения вычитается 5, чтобы поднять график на 5 пикселов над нижней границей окна.
Немодальный диалог
Как уже отмечалось ранее, немодальные диалоги отличаются от модальных тем, что при их выводе на экран остальные элементы управления окна не блокируются, что позволяет одновременно работать и с немодальным диалогом, и с другими средствами управления приложением. Для иллюстрации процедуры создания немодального диалога и работы с ним добавим к программе предыдущего примера немодальное диалоговое окно, позволяющее с помощью содержащихся в нем элементов управления выполнять следующие действия;
масштабирование выводимого графика;
переключение вида графика - отдельные точки или точки, соединенные линиями (огибающая).
/*Программа €-7. Немодяльяый ляалог с -элемеитами управления*/
/*Фанл 6-7.Ь*/
/*Констан ят */
♦define MI_OPEN 101
#define MI_EXIT 102
#define MI_SETTINGS 103
#de£ine ID_DOTS 2D1
tdefine ID_CURVE 202
♦define ID_SCALE 203
/'^Определение нового типа GraphModes*/
emm G raphModes(DOTS=ID_D0TS,CURVE■ID_CURVE};
/*Лрототипи функций для главного окна*/
LRESULT CALLBACK WndProc [HWKD, UINT, WPARAM, LPAHAM) ;
