Азбука программирования в Win32 API

       

Автор рассчитывает на то, что


А теперь - ВНИМАНИЕ! Автор рассчитывает на то, что в момент чтения этой книги читатель будет сидеть за компьютером и вслед за автором пройдет по заголовочным файлам Win32, файлам ее системы помощи, и будет экспериментировать с теми заготовками программ,

которые приводятся автором. В книге дано описание некоторых типов, применяемых в Win32, но, тем не менее, автор предполагает, что при описании переменных, типы которых не относятся к основным типам, определенным в языке C/C++, читатель проявит любознательность и найдет описание переменной в одном из заголовочных файлов Win32.

В книге неоднократно делаются ссылки на «заголовочные файлы» (header'ы) Win32. Одним из отличий Win32 от Windows 3.x является наличие в SDK не одного файла заголовков windows.h, а множества заголовочных файлов. Их объем по сравнению с Windows 3.x вырос не менее чем на порядок. Изучайте их! Ответы на множество вопросов вы найдете только там! По возможности, упоминаемые макросы и значения приведены в виде таблиц. Как правило, таблицы состоят из трех колонок - макрос, числовое значение и описание. Это сделано для того, чтобы читатель смог сам определить, в каком виде ему использовать то или иное значение - в числовом (скажем, для использования в цикле) или в виде макроса. Если в таблице пропуск, то это означает, что у автора нет полной информации о том или ином макросе.

Автор обращает внимание читателя на одну из особенностей про­граммирования в Win32 API . Windows нельзя знать частично. Даже самые первые программы уже требуют глубоких знаний. Поэтому читатель должен с пониманием отнестись к многочисленным ссылкам на последующие разделы книги типа «А об этом мы поговорим попозже». На каком-то этапе чтения книги все станет на свое место.

Под словом «Win32 API» автор понимает совокупность функций, предоставляющих программисту возможность создавать программы (или приложения, что то же самое) для Windows NT и Windows'95. Естествен­но, что эти платформы разнятся между собой. Но набор функций, состав­ляющих API, для них один и тот же. Все функции этого набора являются 32-битными, что отражено в названии интерфейса. При употреблении термина «Win32 API» подразумевается именно набор функций. Когда в тексте встречается термин «Win32», читатель должен понимать, что речь идет о совокупности платформ (Windows NT и Windows'95), поддержи­вающих 32-битный интерфейс программирования. В тех случаях, когда говорится о Windows, автор говорит о двух упоминаемых выше операци­онных системах. Случаи упоминания Windows 3.x оговорены особо.



4

GETTING STARTED - ДАВАЙТЕ НАЧНЕМ! «HELLO, WORLD!» ДЛЯ WIN32 API

После появления книги Кернигана и Рнтчи «Язык программирования С» в мире программирования одним стандартом стало больше. С их легкой руки сейчас практически каждое руководство по обучению про­граммированию начинается с написания программы, осуществляющей вывод на экран строки «Hello, world!». Я не буду нарушать эту традицию и изучение программирования дня Win32 мы начнем с программы, выводящей строку «Hello, world!» не просто на экран, а в окно.

ЧТО НЕОБХОДИМО ДЛЯ ПОЛУЧЕНИЯ ИСПОЛНЯЕМОГО МОДУЛЯ?



Для получения исполняемого модуля необходимо иметь: установленную на вашем компьютере операционную система Windows'95 или Windows NT;

систему подготовки программ для Win32 (автор для написания про­грамм, приведенных в книге, использовал Borland С++ 5.0);

отладчик для отладки программ (автор пользовался Turbo Debugger'oм для 32-битовых программ, входящим в комплект поставки Borland C++ 5.0).



Для повседневной работы было бы неплохо иметь под рукой рас­печатки стандартных файлов заголовков, которые используются при программировании в Win32. С этим связана определенная трудность. Если в Windows 3.x был один файл заголовков «windows.h», то в Win32 число файлов возросло минимум на порядок. Соответственно, вырос и объем этих файлов. Сейчас он приближается к мегабайту.

ФАЙЛЫ ПРОГРАММЫ ДЛЯ WINDOWS



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

Желаемый результат очевиден - мы хотим получить программу для Windows NT или Windows'95, осуществляющую вывод в окно строки «Hello, world!». Но если в DOS для получения исполняемого файла нужен, как минимум, один файл исходного модуля, то в Windows дело обстоит несколько иначе. Как минимум, в Windows проект состоит из двух, а иногда - из трех файлов. Этими файлами являются:

программа на C/C++. Является основным файлом программы и, как правило, имеет расширение .С или .СРР. После его успешной компиля-



ции возникает файл с расширением .OBJ, который используется линке­ром для получения исполняемого модуля;

файл ресурсов. Иногда может не присутствовать в простом проекте. Как правило, имеет расширение .RC. После успешной компиляции его компилятором ресурсов возникает файл с расширение .RES, который используется линкером для получения исполняемого модуля;

файл определения модуля. Обычно имеет расширение .DEF и компи­ляции не подлежит. Используется линкером для определения некоторых характеристик исполняемого модуля. С появлением Win32 файл опреде­ления модуля почти не используется.

Для программиста, привыкшего к DOS, все это выглядит непривычно и громоздко. Тем не менее, в самом ближайшем будущем мы увидим, какие громадные возможности предоставляют файл ресурсов и файл определения модуля!

ТИПЫ ДАННЫХ, ПРИМЕНЯЕМЫЕ В WINDOWS



При первом взгляде на программу, написанную для Windows, броса­ется в глаза странный внешний вид этой программы. В программе ис­пользуются переменные каких-то необычных типов, например, H1NSTANCE, HWND, LPSTR и так далее. Разобраться в них совсем не сложно. Все они определены в заголовочных файлах Win32, общим «предком» которых является знаменитый «windows.h». Возникает зако­номерный вопрос: для чего были определены столько новых типов? Почему для определения переменных нельзя было воспользоваться стандартными типами, определенными в С/С—? Во-первых, что станет очевидно даже при небольшом опыте программирования для Win32, это очень удобно. Использование типов, специально «изобретенных» для Windows, упрощает написание программы, не заставляя запоминать последовательности многочисленных описаний, а применять один описа­тель. Во-вторых, программы, написанные с применением такого стиля, легко читаются и ошибки, связанные с типами переменных, легче обна­ружить.

Возможно, истинная причина подобных нововведений лежит несколь­ко глубже. Дело в том, что применение такого двухступенчатого опреде­ления типов (стандартный тип->заголовки Win32-^nporpaMMa) облегчает перенос программ для Windows в другие операционные системы. При переходе на новую систему достаточно будет изменить только файл заголовков. Изменять отлаженное программное обеспечение пет необ­ходимости. Поэтому один и тот же код можно использовать в различных системах.



ВЕНГЕРСКАЯ НОТАЦИЯ



Помимо использования нестандартных описаний типов, при чтении программ для Windows можно заметить еще одну странность. Почти все идентификаторы начинаются с непонятных буквосочетаний. Но, оказыва­ется, все очень просто. В любой книге, посвященной программированию под Windows, вы найдете упоминание о том, что один из первых разра­ботчиков Windows Чарльз Симонаи, венгр но происхождению, начал использовать в своих программах способ именования переменных, кото­рый впоследствии назвали венгерской системой. Суть этой системы (крайне простой и потрясающе эффективной) можно определить не­сколькими правилами:

каждое слово в имени переменной пишется с прописной буквы и слитно с другими словами, например, идентификатор для обозначения какой то переменной может выглядеть следующим образом - MyVariable, YourVariable, VariableForSavingAnotherVariable и т. д.;

каждый идентификатор предваряется несколькими строчными буквами, определяющими его тип. Например, целая перемятая MyVariable должна выглядеть как nMyVariable (n - общепринятая для целых переменных), символьная (char) переменная YourVariable превратиться в cYourVariable. Указатель на строку символов заканчивающуюся нулевым байтом, VariableForSavingAnotherVariable, pszVanableForSavingAnotherVariable (psz - сокращение от Point то String with Zero). Примеры подобных префиксов приведены в табл. 1.

Это позволяет упростить процесс чтения и понимания программ, а также делает переменные в некотором смысле самоопределенпыми - имя переменной определяется ее типом. Говорят, когда Симонаи спрашивали о странном внешнем виде его программ, он невозмутимо отвечал, что эти программы написаны по-венгерски.

WINDOWS КАК ОБЪЕКТНО-ОРИЕНТИРОВАННАЯ СИСТЕМА



Когда вы начинаете программировать для Win32, необходимо уяснить, что хотя формально Windows не является объектно-ориентированной систе­мой, она придерживается объектно- ориентиро- ванной идеологии. Каждое окно фактически является объектом. Что такое объект? Фактически объект есть совокупность полей (данных) и методов (процедур и функций управле­ния полями). У окна есть масса полей, об этом мы еще будем говорить. Функция окна фактически является совокупностью методов.



Таблица  1. Префиксы, применяемые в венгерской нотации

Префикс

Тип данных

b сх, су

dw In h i I

n s sz

BYTE (unsigned char)

short (используются как ширина и длина объектов типа

RECT и окон)

DWORD (unsigned long)

function

HANDLE

int

EONG (long)

int или short

string

string terminated by zero

WORD (unsigned int)

short (используются как координаты)

char

«КРОВЕНОСНАЯ СИСТЕМА» ПРОГРАММЫ ДЛЯ WINDOWS



Для программиста, привыкшего к DOS, непривычной является и ор­ ганизация взаимодействия между операционной системой Windows (NT или 95) и другими программами. В DOS только программы обращаются к операционной системе. Обратной связи (вызов системой прикладной программы) в DOS нет (исключения типа перехвата прерываний не в счет). В Windows с момента создания окна и до момента его уничтожения не только программа обращается к Windows, но и самое операционная система при возникновении определенных событий обращается к окну, вызывая связанную с ним оконную процедуру, или, как говорят, посылая окну сообщение. Что значит послать окну сообщение? Это, значит, записать определенную информацию о каком-либо событии в область памяти, доступную оконной процедуре. Эта область памяти, которая вмещает в себя несколько сообщений, действует по принципу стека FIFO (First Input - First Output) и называется очередью программы. В Windows прикладная программа тоже вызывает систему не напрямую, а посылает сообщения системе. Но раз системе, как и прикладной программе, посы­лаются сообщения, то, значит, существует и общесистемная очередь сообщений! Итак, мы пришли к выводу о существовании одной обще­системной очереди сообщений и очереди сообщений у каждого окна.

Неясной остается одна деталь. Откуда система знает о том, что при­шло сообщение? Каким образом сообщение из очереди становится из-

8

вестным программе? Вероятно, как программа, так и система с какой-то периодичностью обращаются к очереди и проверяют, нет ли в очереди сообщений. Здесь мы приходим ко второму выводу - у каждой програм­мы, а также и у системы должны существовать (и существуют!) циклы, в ходе которых опрашивается очередь и выбирается инфор­мация о сообщениях в ней. Остановка цикла опроса очереди приведет к «зависанию» программы, программа «умрет», если сравнивать програм­му для Windows с человеческим организмом. Если продолжать сравне­ние, то будет видно, что сообщения протекают через функцию окна, как кровь по организму. Кровь в организме прокачивается сердцем, а сооб­щения «качаются» циклом обработки сообщений.



WINMAIN () + ФУНКЦИЯ ОКНА = МИНИМАЛЬНАЯ ПРОГРАММА ДЛЯ WINDOWS



Теперь мы уже знаем, что при запуске программы должны происхо­дить, по меньшей мере, два события - должно быть создано окно и запу­щен цикл обработки сообщений, из которого с наступлением какого-то события должен быть осуществлен выход и работа программы должна завершиться. Все это происходит, как правило, в функции WinMamQ, которая является стандартной точкой входа во все программы для Windows. (Обратите внимание - функция mainQ является точкой входа DOS'oBCKiix программ, функция WinMain() - программ, написанных для Windows). Для удобства функция окна отделена от WinMain() (к функ­ции окна постоянно обращается система). Она может находиться либо в той же программе, что и WinMain(), либо вызываться из какой-либо библиотеки. Тем самым становится возможным создавать множество окон, использующих одну и ту же оконную функцию (другими словами, объектов, использующих одни и те же методы!), но имеющих разные характеристики (но имеющих разные значения полей!). А не напоминает ли это каким-то образом полиморфизм объектов? Попутно отмечу, что совокупность окон, использующих одну и ту же оконную функцию, представляет собой класс окон. Каждое окно принадлежит какому-либо классу. Примером такого ктосса могут быть кнопки, работающие совер­шенно одинаково, но имеющие разные размеры, надписи и так далее. Так что же получается'? Мы сначала должны создать класс, а только потом создавать окно созданного класса'? Да! Попробуем резюмировать сказанное.

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

WinMain (список аргументов)

Подготовка и создание класса окон с заданными характеристиками Создание экземпляра окна только что созданного класса; Пока не произошло необходимое для выхода событие

Опрашивать очередь сообщений и передавать сообщения

оконной функции; Возврат из программы;

WindowFunction (список аргументов)

{

Обработать полученное сообщение; Возврат;

ПЕРВАЯ ПРОГРАММА ДЛЯ WINDOWS





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

#includc <windows.h>

ERESUET CAEEBACK HelloWorldWndProc ( HWND, UINT, UINT, LONG ); hit WINAPI WinMain ( HTNSTANCF, hlnstance, HINSTANCE hPrevinstance,

EPSTR IpszCmdParam, int nCmdShow )

i

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassNamef] - «HclloWorld»; /* Регистрируем создаваемый класс */ /* Заполняем структуру типа WNDCLASS */

WndClass.stylc = CS_HREDRAW | CS_VREDRAW;

WndClass.lpfnWndProc - HelloWorldWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon = Eoadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.IpsxMenuName = NUEE;

WndClass.IpszClassName = szClassName;

if ( !RegistcrClass(&WndClass))

10



McssageBox(NULL,«Cannot register class»,»Error»,MB _OK); return 0;

hWnd = CreateWindow(szClassName, «Program No 1», WS_OVERLAPPEDWINDOW, CWJJSEDEFAUET, CWJJSEDEFAUET, CWJJSEDEFAUET, CWJJSEDEFAULT, NUEE, NUEE, hlnstance, NUEE);

if(!liWncl)

MessageBox(NULL,»C'annot create window»,»F,rror»,MB_OK); return 0;

/* Show our window */

Sho\vWindow(hWnd,nCmdShow); UpdaleWindow(hWnd);

/* Beginning of messages cycle */

whi!e(GetMcssagc(&Msg, NUEE, 0, 0)) i

TranslateMessage(&Msg); DispatchMcssage(&Msg); } return Msg.wParam;

ERESUET CALLBACK HelloWorldWndProc (HWND hWnd, UINT Message,

DINT wParam, LONG IParam ) !

HOC HUG;

PAINTSTRUCT PaintSlrucl; RECT Reel;

switch(Message)

i t

case WMJ'AINT:

hDC = BcginPaint(hWnd, &PaintStnict);

GctClicntRect(hWnd,&Rect);

DrawText (hDC,»Hello. World!», -1, &Rect,

DT_SINGLELINE i DE_CENTER j DT VCENTER);

EndPainUhWnd.&PamtStruct);

return 0; case WM_DF.STROY:

PostQuitMessagc(O),



return 0;

return DefWindowProc(hWnd,Message.wParam, IParam);

11

Листинг № 1. Основной файл программы «Hello, world».

Предлагаю читателю откомпилировать основной файл программы. Для начинающих программистов проще всего воспользоваться интегри­рованной средой, например, Borland IDE. При этом не имеет значения, какой системой подготовки программ вы пользуетесь. Единственное, эта система должна позволять разработку программ для Win32. (В этой программе нет ничего, что присуще только Win32 и не присуще Windows 3.x. Но это только в первой программе.) Надеюсь, что эта программа пройдет у вас без ошибок с первого раза. Если же появятся ошибки, сверьте набранный вами текст модуля с приведенным в книге. По всей вероятности, вы допустили ошибку при наборе программы.

МИНИМУМ КОДА ПРИ МАКСИМУМЕ ВОЗМОЖНОСТЕЙ

На рис. 1 приведен результат работы этой программы. Естественно, что для простого вывода строки на экран эта программа велика. Но в том-то и состоит прелесть окна «Hello, world!», что оно обладает всеми харак­теристиками нормального окна, «умеет» изменять свой размер, миними­зироваться (отображаться в виде иконки), максимизироваться (занимать все пространство экрана). У него есть системное меню в левом верхнем углу и три кнопки (максимизации, минимизации и закрытия) вверху справа. Окно может перемещаться по экрану. При изменении размеров окна строка «Hello, world!» автоматически перемещается в новый центр окна. Попробуйте в DOS достичь того же самого программой такого же объема! В DOS для достижения таких же результатов потребуются либо месяцы упорного труда для реализации собствен ной оконной библиоте­ки, либо придется использовать чужую оконную библиотеку, что приве­дет ко многим нежелательным эффектам, типа резкого увеличения объе­ма исполняемого кода. С этой позиции объем «Hello, world!» кажется слишком компактным по сравнению с обычной программой, обладающей такой же функциональностью!

Теперь, когда мы увидели возможности «Hello, world!», попробуем разобрать ее построчно.



Практически каждая программа ( а наша программа исключением не является) начинается со строки

#include <windows.h>

Думаю, что к этому моменту строка в пояснениях уже не нуждается. В тело программы включается файл заголовков «windows.h».

Следом за строкой идет объявление оконной процедуры:

LRESULT CALLBACK HelloWorldWndProc (HWND, TJINT, UINT, LONG);

12

ЙЧЦЧМ Ho 1

Hellfl.Wort*

Рис. l. Результат работы «HelloWorld»

Его мы разберем при рассмотрении непосредственно оконной функ-

Третьей строкой является функция WinMain(), о которой мы сейчас и поговорим.

ФУНКЦИЯ WINMATNO И ЕЕ АРГУМЕНТЫ

С легкой руки автора одной из книг, посвященных программированию для Windows, функция WinMainQ называется «стандартным заклинани­ем». Без этих или подобных строк не обходится почти ни одна програм­ма. Как правило, программирующие для Windows хранят это «заклинание» в отдельном файле. В начале разработки нового проекта в этом файле просто изменяют несколько слов или строк - и функция WinMainQ вновь готова к работе! Определение WinMainQ всегда имеет вид, подобный следующему:

ii.t WINAPI WinMain (HINSTANCF. hlnstancc. HINSTANCE hPrevInstance, Ll'STK ipszCmdParam. int nCmdShow).

Сразу видно, что функция возвращает вызвавшей ее системе целое значение и ничего интересного этот момент собой не представляет. Следующая характеристика - WINAPI - определяет порядок передачи

13



параметров при вызове процедуры. Наименование характеристики гово­рит само за себя - WINdows Application Programming Interface - применя­ются соглашения о передаче параметров, принятые в системах Windows NT и Windows'95. Если вы не планируете писать приложения на ассемб­лере, вам нужно это просто запомнить.

А вот переменные hlnsbnce и hPrevInstance заслуживают более под­робного обсуждения. Так как Windows NT и Windows'95 являются мно­гозадачными системами, то очевидно, что одна и та же программа может быть запущена несколько раз. Для того чтобы различать экземпляры программ, каждому экземпляру присваивается условный номер - хэндл (handle). Справедливости ради, надо отметить, что в Win32 присваивают­ся хэндлы чему угодно - окну, меню, курсору, иконке и т. д. Фактически хэндл - это указатель на блок памяти, в котором размешен тот или иной объект. В заголовочных файлах тип HANDLE определен как void*, а тип HINSTANCE как HANDLE. Согласно венгерской нотации, идентифика­торы переменных типа HANDLE



должны начинаться с буквы h.

Уважаемый читатель! Обратите внимание на вытекающее из этого по­ложения следствие. Раз уж объект имеет хэндл, который является УКАЗАТЕЛЕМ, то, значит, этот объект сам расположен в памяти! Дру­гими словами, в тех случаях, когда мы должны получить хэндл того или иного объекта, фактически мы должны получить адрес загруженного в память объекта!

Но вернемся к hlnstance. Когда вызывается WinMain(), Windows через эту переменную сообщает программе хэндл экземпляра программы. В Windows 3.1 hPrevInstance являлся хэндлом предыдущего экземпляра программы. Если запускался первый экземпляр программы, то параметр hPrevInstance был равен нулю. Этот факт можно было использовать для того, чтобы не позволять системе запускать более одного экземпляра программы. В Win32 hPrevInstance оставлен ИСКЛЮЧИТЕЛЬНО для совместимости с предыдущими версиями Windows, он не несет никакой нагрузки и постоянно равен нулю. Так просто, как в более ранних верси­ях Windows, определить наличие ранее запущенного экземпляра про­граммы не удастся. Придется нам и этот вопрос оставить на потом, до изучения основ многозадачности Windows.

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

И последний параметр - nCmdShow - определяет, в каком виде созда­ваемое окно будет появляться на экране. Окно может появляться в максимизированном виде либо в виде иконки (минимизированном),

14



может иметь произвольный размер, определяемый программой и другие характеристики. В Win32 API определяются десять возможных значений этого параметра. Их идентификаторы начинаются с SW (вероятно, от названия функции ShowWindow, которая использует эти значения). Наиболее часто используются значения SWJSHOWNORJvIAL и SW_ SHOWMINNOACTIVE. Возможные значения этого параметра приведены в табл. 2. Большинство идентификаторов являются самоопре­деленными (вряд ли, скажем, SW_SHOWMAXIMIZED приводит к ото­бражению окна в виде иконки!). Вы можете поэкспериментировать с ними. Их полное описание можно найти в файлах системы помощи. Теперь вспомним, что перед созданием окна мы должны сначала опреде­лить его класс, поэтому у нас на очереди



Регистрация класса окна

Сразу после входа в WinMainQ нам необходимо создать класс окна и сообщить о нем системе. Класс создается и регистрируется функцией RcgistcrClassQ. Единственным аргументом этой функции является указатель на структуру типа WNDCLASS, в которой хранятся характери­стики создаваемого класса. Из этого следует, что у нас добавилось голов­ной боли - перед регистрацией класса заполнить процедуру типа WNDCLASS. В приведенной выше программе структура была определе­на следующим образом:

WNDCLASS WndClass;

Т а б л и п а 2. Возможные значения второго парамелра функции ShowWindowQ

Параметр

Значение

 

Параметр

 

Значение

 

SW HIDE

 

0

 

SW SHOWNOACTIVE

 

4

 

HIDE WINDOW

 

0

 

SHOW OPENNOACTIVE

 

4

 

SW SHOWNORMAL

 

I

 

SW SHOW

 

5

 

SW NORMAL

 

I

 

SW MINIMIZE

 

6

 

SHOW OPENWINDOW

 

I

 

SW SHOWMINNOACTIVE

 

7

 

SW SHOWMINIMIZKD

 

2

 

SW^SHOWNA

 

8

 

SHOW ICONWINDOW

 

2

 

SW RESTORE

 

9

 

SW SHOWMAXIMIZED

 

3

 

SW SHOWDEFAULT

 

Ю

 

SHOW FULL SCREEN

 

3

 

SW MAX

 

Ю

 

SW MAXIMIZE

 

3

 

 

 

 

 

15

Не забывайте, что в языке С, в отличие, скажем, от PASCALS, про­писные и строчные буквы различаются. Для того чтобы заполнить эту структуру, нам необходимо знать тип и назначение каждого ее поля. Посмотрим, как описана эта структура в заголовочных файлах (winuser.h):

typedef struct tagWNDCLASSA {

UTNT       style;

WNDPROC     IpfnWndProc;

ml         cbClsExtra;

int         cbWndExtra;

HINSTANCE   hlnstance;

HICON       hlcon;

HCURSOR     hCursor;

HBRUSH      hbrBackgroimd;

LPCSTR      IpszMcnuNamc;

LPCSTR      IpszClassName: } WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA,



FAR *LPWNDCLASSA; typedef struct tagWNDCLASSW {

UINT        style;

WNDPROC     IpfnWndProc;

int         cbClsExtra;

int         cbWndExtra;

HINSTANCE   hlnstance;

HICON       hlcon;

HCURSOR     hCursor;

HBRUSH      hbrBackgroimd;

EPCWSTR     Ips/MenuName; LPCWSTR     IpszClassName; } WNDCLASSW, *P\VNDCLASSW, NEAR *NPWNDCLASSW,

FAR "LPWNDCLASSW; tfifdef UNICODE

typcdcf WNDCLASSW WNDCLASS; typedcfPWNDCLASSWPWNDCLASS; typedef NPWNDCLASSW NPWNDCLASS; tvpedef LPWNDCLASSW LPWNDCLASS;

#clsc

typedef WNDCLASSA WNDCLASS; typedcf PWNDCLASSA PWNDCLASS; typedef NPWNDCLASSA NPWNDCLASS; typedef LPWNDCLASSA LPWNDCLASS;

#cndif/7 UNICODE

О том, почему структура объявляется так странно и что такое Unicode, мы поговорим в разделе, посвященном Unicode. А пока давайте считать, что это просто разные описания одной и той же структуры.

16



Особое внимание следует обратить на первое, второе и последнее по­ля. Почему? - Сейчас станет ясно.

Стиль окна я определяю оператором

WndCIass.style = CS HREDRAW   CS VREDRAW

В winuser.h описаны тринадцать стилей окна. Наименования их иден­тификаторов начинаются с CS, что, вероятно, означает «Class style». Для спим окна отведено 16 битов и только один из этих битой установлен в единицу. Другими словами, стили, упомянутые в winuser.h, используются как битовые флаги, т. е. с этими стилями можно производить операции логического сложения п логического умножения для получения комбинированных стилей. Перечень шагов приведен в табл. 3.

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

По причине, известной только Microsoft, отсутствуют стили со значениями 0x0010 и 0x0400. Те флаги, которые использует программа «Hello, world!», означают, что окну необходимо полностью перерисовать­ся (запомните это слово. О перерисовке мы еще не раз вспомним!) при изменении его размеров по горизонтали и по вертикали. Попробуйте поиграть с размерами окна и сделать так, чтобы строка появилась не в середине! Надеюсь, вам это не удастся. Как бы вы не дергали его, текст постоянно будет оставаться в центре экрана.



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

WndClass.lptiiWndProc - IlelloWorldWndProc;

Два следующих поля оставлены нулевыми. Дело в том, что для каждо­го класса Windows создает где-то в недрах своей памяти структуру с характеристиками класса. Другая структура создается для каждого окна. При создании этих структур может быть зарезервировано некоторое количество памяти для нужд программиста. Поля cbClsExtra и cbWndExtra указывают размер резервируемой памяти в структурах класса и окна соответственно. Эти поля и раньше использовались доста­точно редко, а с появлением Windows'95 и Windows NT будут использо­ваться еще реже.

17



Таблица 3. Перечень битовых флагов

Флаг

Значение

 

Описание

 

 

 

CSJVREDRAW

 

0x000 1

 

Перерисовать окно при изменении высоты

 

 

 

 

 

 

 

окна

 

 

 

CS_HREDRAW

 

0x0002

 

Перерисовать окно при изменении

 

 

 

 

 

 

 

ширины окна

 

 

 

CS KEYCVTWINDOW

 

0x0004

 

 

 

 

 

CS_DBLCLKS

 

0x0008

 

Посылать сообщение оконной функции

 

 

 

 

 

 

 

при двойном щелчке мышью, если курсор

 

 

 

 

 

 

 

находится в пределах окна

 

 

 

CS_OWNDC

 

0x0020

 

Для каждого окна класса выделяется

 

 

 

 

 

 

 

собственный контекст

 

 

 

CS_CLASSDC

 

0x0040

 

Один и тот же контекст устройства

 

 

 

 

 

 

 

разделяется всеми окнами этого класса

 

 

 

CS_PARENTDC

 

0x0080

 

Дочерние окна наследуют контекст

 

 

 

 

 

 

 

родительского окна

 

 

 

CS NOKEYCVT

 

0x0 1 00

 

 

 

 

 

CSJMOCLOSE

 

0x0200

 

Убрать команду «Close» из системного

 

 

 

 

 

 

 

меню

 

 

 

CS_SAVEBITS

 

0x0800

 

Сохранять часть области экрана, закры-

 

 

 

 

 

 

 

тую окном, как bitmap, при удалении

 

 

 

 

 

 

 

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

 

 

 

CSJ3YTEALIGNCLIENT

 

Ox 1 000

 

Выравнивает границу рабочей области

 

 

 

 

 

 

 

окна (в горизонтальном направлении)

 

 

 

 

 

 

 

таким образом, чтобы для отображения

 

 

 

 

 

 

 

строки требовалось целое число байтов

 

 

 

CS BYTEALIGNWINDOW

 

0x2000

 

То же, но действие затрагивает все окно

 

 

 

CS_GLOBALCLASS

 

0x4000

 

Разрешается создавать класс, не завися-

 

 

 

 

 

 

 

щий от текущего hlnstancc

 

 

 

CSJME

 

OxOOOIOOOOL

 

 

 

 

 

<


Поле hlnstance в объяснении не нуждается - классу окна сообщается хэндл программы.

Оператор

WndClass.hlcon - LoadIcon(NULL, IDI_APPLICATION);

определяет хэндл иконки, которая будет символом окна данного класса. Действие, производимое функцией LoadlconQ, очевидно из ее названия -загрузить иконку. Заметим, что программист может использовать собст­венную иконку, которую он сам разработал, а может применить одну из иконок, хранящихся в глубинах системы (они называются предопреде­ленными). В случае использования собственной иконки первый параметр функции LoadlconQ должен быть равным хэндлу программы (hlnstance). Если мы используем предопределенную иконку, первый параметр равен

пулю (забегая вперед, отметим, что если при загрузке в память какого-либо объекта хэндл программы равен нулю, то объект загружается либо из «глубин» Windows, либо из внешнего файла). Второй параметр - это идентификатор иконки. Все идентификаторы предопределенных иконок начинаются с букв IDI

(возможно, «IDentificator of Icon»). Пока еще мы не знаем, как формировать иконки, воспользуемся одной из предопре­деленных иконок.

Сказанное об иконке можно полностью отнести и к курсору мыши, которым будут пользоваться окна создаваемого класса (не путать с курсором, применяемым при редактировании текстовых файлов). Поле WndClass.hCursor определяет хэндл курсора. Все идентификаторы предопределенных курсоров начинаются с IDC (возможно, «IDentificator of Cursor»).

Поле WndCIass.hbrBackground определяет хэндл та называемой кис­ти (brush), которой будет закрашен фон окна.

К иконкам, курсорам, кистям и перьям мы еще неоднократно будем возвращаться. А сейчас неплохо было бы попробовать поменять иденти­фикаторы иконок, курсоров, кистей и посмотреть, к чему это приведет. Для этого в табл. 4 приведен список объектов этих типов, индификаторы которых я нашел.

Поле WndClass.lps/MenuName храпит указатель на строку, содер­жащую имя меню для данной программы. Наша программа с меню не работает, поэтому мы сделали его нулевым.



И последнее, завершающее поле - WndClass.Ips/ClassName. Как яв­ ствует из его названия, поле содержит указатель на строку, содержащую имя создаваемого нами класса окна. Указав это имя, мы тем самым поставили логическую точку в формировании структуры WNDCLASS.

Указатель на эту структуру передается функции RegistcrClassQ. С вызовом этой функции, данные о создаваемом нами классе становятся известными системе Windows, и с этого момента мы можем создавать окна этого класса. Пожалуйста, не забывайте в своих программах прове­рять, зарегистрировался класс или нет. Если класс не зарегистрирован, работать ваша программа не будет, как бы правильно она не была напи­сана. В нашей программе, в случае, если класс окна не зарегистрирован, просто выдается сообщение об ошибке (функция MessageBoxQ) и осуще­ствляется выход из программы. Кстати, мы уже говорили о предопреде­ленных иконках и курсорах? В Win32 API существует множество предо­пределенных классов окоп, например класс кнопок, списков и т. д. При необходимости создания окна предопределенного класса регистрировать класс окна уже не нужно.

18

19



Таблица 4. Список предопределенных объектов в Win32 АРГ

Иконка

 

Перо

 

Курсор

 

Кисть

 

IDI_APPLICATION

 

WHITE PEN

 

IDC_ARROW

 

WHITE BRUSH

 

IDI HAND

 

BLACK PEN

 

IDC IBEAM

 

LTGRAY BRUSH

 

IDI QUESTION

 

NULL PEN

 

me wait

 

GRAY BRUSH

 

IDl" EXCLAMATION

 

 

 

IDC CROSS

 

DKGRAY BRUSH

 

IDI ASTERISK

 

 

 

IDC UPARROW

 

BLACKJiRUSH

 

1DI_WINLOGO

 

 

 

IDC_SIZE

 

NULL BRUSH

 

IDI WARNING

 

 

 

IDC ICON

 

HOLLOW BRUSH

 

IDI ERROR

 

 

 

IDC SIZENWSC

 

 

 

IDI INFORMATION

 

 

 

IDC""SIZUNF,SW

 

 

 

 

 

 

 

IDC SIZEWE

 

 

 

 

 

 

 

IDC SIZENS

 

 

 

 

 

 

 

IDC SIZEALL

 

 

 

 

 

 

 

IDC~NO

 

 

 

 

 

 

 

IDC APPSTARTING

 

 

 

 

 

 

 

IDCMIELP

 

 

 

<


Создание экземпляра окна

Следующим шагом на нашем большом пути является создание экзем­пляра окна. Как и экземпляр программы, каждое окно в системе имеет свой уникальный номер хэндл (handle). Обычно окно создается посред­ством функции CrcatcWindowQ, которая и возвращает хэндл созданного окна. Если (увы!) функция CreateWindowQ вернула нуль, то по каким-то причинам окно не создано. Причины могут быть как внутри вашей про­граммы, так и в системе. Но, в отличие от регистрации класса, о том, что окно не создано, вы можете узнать, просто взглянув на экран. Теперь подошло время рассказать о каждом из одиннадцати аргументов функции Create Window().

Первый аргумент - указатель на строку с именем класса, к которому будет принадлежать создаваемое нами окно. В большинстве случаев значение этого аргумента совпадает со значением последнего поля структуры типа WNDCLASS, передаваемой RegisterClass() (Может быть целесообразно использовать одну и ту же переменную в функциях RegisterClass() и CreateWindow()?). Второй аргумент - указатель на стро­ку, содержащую тот текст, который появится в заголовке окна.

Третий аргумент определяет стиль окна (не общие характеристики всех окон класса, а индивидуальные характеристики конкретного окна). Стиль определяет, будет ли окно иметь заголовок, иконку системного меню, кнопки минимизации, максимизации, характер границы окна, определяет также взаимоотношения окон типа предок-потомок и т. д. Под

20



это поле отводится 32 бита. В файле winuser.h определены несколько десятков стилей окон. Их идентификаторы начинаются с букв WS. Как и в случае со стилями класса, эти значения используются как битовые флаги, т. е. комбинируя их с помощью логических операций, можно получить тот стиль окна, который требуется нам в программе. Рекомен­дую поэкспериментировать с различными стилями окна. Их список приведен в табл. 5.

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



Следующие четыре api- умента определяют положение окна на экране. Значение этих полей представляют измеренные в пикселах отступы левого верхнего угла окна от левого края экрана, от верхней границы экрана, ширину и высоту окна соответственно. Особых пояснений эти параметры не требуют. Используемые нами идентификаторы CW USEDEFAULT, допустимые, кстати, только для окон со стилем WS

OVERLAPPED, позволяют Win32 API установить размер окна самостоятельно.

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

Windows присваивает хэндлы чему угодно, в том числе и меню. Очередной аргумент - это хендл меню нашего окна. До разговора о меню в нашей программе меню не будет, поэтому оставим его нулевым.

Предпоследний аргумент - hlnstance - должен быть понятен из преды­дущих объяснений. Да-да, именно тот самый хэндл экземпляра програм­мы, который мы запускаем.

Последний аргумент - данные, которые используются в некоторых случаях для создания окна. Как правило, в это поле записывается указа­тель на структуры с дополнительной информацией. Для того чтобы доб­раться до него, мы потратили столько сил! А оно используется достаточно редко i i в нашем примере, естественно, остается без дела.

Напоминаю: не забудьте в программе проверить факт создания окна и огреагировать на него соответствующим образом! Справедливости ради, в одном из примеров, поставляемых с Borland C++ v. 5.0, все эти провер­ки называются параноидальными, но я пришел к выводу, что на этапе отладки программы лучше все эти проверки оставить. Когда программа заработает полностью - это дело другое.

21



Таблица 5. Список различных стилен окна

Стиль

WS OVERLAPPED WS_TABSTOP

WS_MAXIMIZEBOX WS GROUP WS_MINIMIZEBOX WS THICKFRAME

WS_SYSMENU WSJTSCROLL WSJVSCROLL WSJDLGFRAME

WSJBORUER WS_CAPTION WS_MAXIMIZE WS CEIPCHIEDREN

WS CEIPSIBLINGS



WS_DISABLED WS_VISIBLE \VS_MINIMIZE WS CHILD

WS_POPUP

WS_TILED

WS_ICONIC

\VS_SIZEBOX

WS TILEDWINDOW

\VS~OVERLAPPED-

WINDOW

WS POPUP-WINDOW WS CHILDWINDOW

Значение

OxOOOOOOOOL 0x00010000L

0x00010000L Ox00020000L Ox00020000L 0x000400001.

OxOOOSOOOOL OxOOIOOOOOL Ox00200000L Ox00400000L

OxOOSOOOOOL OxOOCOOOOOL OxOlOOOOOOL Ox02000000L

Ox04000000L

OxOSOOOOOOL OxlOOOOOOOL Ox20000000L Ox4000000()L

OxSOOOOOOOL

( iiihcohhc

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

У окна есть тонкая ограничивающая рамка WS_BORDER   WS_DLGFRAME Создается изначально максимизированное окно При прорисовке родительского окна область, занимаемая дочерними окнами, не прорисовыва­ется

Дочернее окно, имеющее yroi стиль, и перекры­вающее другое дочернее окно, при прорисовке перекрываемой области не прорисовывается Создается изначально запрещенное окно Создается изначально отображаемое окно Создается изначально минимизированное окно Создается дочернее окно, имеющее по умолчанию только рабочую область, меню окна этого стиля не имеют никогда Создастся всплывающее (popup) окно \VS_OVERLAPPED \VS_MINIMIZED WS_THICKFRAME WS_OVERLAPPEDWIINDOW WSJWERLAPPED | WS CAPTION WS_SYSMENU | WS THICKFRAME WS_MINIMIZEBOX7WS_MAXIMIZEBOX WS_SYSMENU | WS_BORDER j WSJ'OPUP

WS CHILD

Но что значит создать окно? Это, значит, создать внутренние для Win32 API структуры, которые будут использоваться в течение всего

22

периода существования окна. Но это не означает, что окно тут же появит­ся на экране. Для того чтобы увидеть окно, мы должны осуществить



Отображение окна на экране

Теперь мы подошли к функции ShowWindowQ. Функция отображает окно на экране (отметьте - отображает окно как набор атрибутов, таких как заголовок, рамка, кнопки и т. д.). Первый аргумент этой функции -хэндл созданного только что окна. Второй аргумент определяет, в каком виде окно будет отображено на экране. В нашем случае мы просто взяли и подставили значение nCmdShow, указанное при вызове WinMain(). Как правило, при первом запуске окна функции WinMain() передается значение SW_SHOWDEFAULT, при последующих запусках значение этого параметра может изменяться в соответствии со сложившимися обстоятельствами. Я не рекомендую указывать в качестве второго пара­метра функции ShowWindowQ значение, отличное от передаваемого при вызове WinMain(). Тем самым вы лишите Win32 API некоторых возмож­ностей по управлению окном.

Функция UpdateWindowQ посылает функции окна сообщение WM_PAINT, которое заставляет окно ПЕРЕРИСОВАТЬСЯ, т. е. прори­совать не набор атрибутов, за прорисовку которых отвечает Windows, a изображение в рабочей области окна, за что должна отвечать непосредст­венно программа.

Итак, класс окна зарегистрирован, экземпляр окна создан и выдан на отображение. На очереди -

Запуск и завершение цикла обработки сообщений

Мы уже говорили, что по аналогии с человеческим организмом, со­общения играют роль крови, а цикл обработки - роль сердца, которое эту кровь прокачивает.

Что такое сообщение? Сообщение - это небольшая структура, опреде­ленная в заголовочном файле следующим образом:

typcdcf struct tagMSG )

HWND        hwnd;

UINT        message;

WPARAM      wParam;

LPARAM      IParam;

DWORD       lime;

POINT       pi; } MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

23

Первое поле этой структуры вопросов не вызывает - это хэндл окна, которому адресовано сообщение. Второе поле - номер сообщения. Каж­дое сообщение имеет свой идентификатор. Все идентификаторы сообще­ний начинаются с букв WM, что, возможно, означает «Windows Message». Третье и четвертое поля содержат параметры сообщения. Для каждого сообщения они различны. Назначение поля time в объяснении не нуждается - оно очевидно из названия. В последнее поле (pt) записывает­ся позиция, на которой находился курсор в момент выработки сообще­ния. Все эти поля могут обрабатываться оконной процедурой.



Из очереди приложений сообщение выбирается с помощью функции GetMessageQ. Первый аргумент этой функции - указатель на структуру типа MSG, в которую будет записана информация о сообщении. Второй -хэндл окна, созданного программой. Сообщения, адресованные только этому окну, будут выбираться функцией GetMessageQ и передаваться оконной функции. Если вы хотите, чтобы обрабатывались сообщения для всех созданных программой окон, установите этот параметр равным NULL.

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

Функция GetMessage() всегда, за исключением одного случая, воз­вращает непулевое значение. Исключение в данном случае очевидно -при получении этого единственного сообщения работа цикла, а следова­тельно, и программы, прекращается. Это сообщение называется WM QUIT и приводит к нормальному завершению программы. Но давайте пока оставим WM__QUIT в покое и посмотрим, что происходит внутри цикла при получении им нормального - не WM_QUIT - сообщения.

Давайте на время представим себе, что для унификации процесса об­работки сообщений некоторые из них необходимо преобразовать в более удобный для обработки вид. Эти преобразования выполняет функция TranslateMessage(). К этой функции мы еще вернемся при изучении системы обработки ввода с клавиатуры.

После преобразования сообщение готово к обработке. Функция DispatchMessageQ передает сообщение на обработку в оконную процедуру.

ВСЕ! Мы сформировали структуру типа WNDCLASS, зарегистриро­вали класс окна и создали экземпляр окна этого класса. Мы выдали окно на отображение, после чего запустили цикл обработки сообщений и предусмотрели условие, при котором работа цикла закончится. Теперь все зависит от того, как сообщения будут обрабатываться оконной про-

24



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



ФУНКЦИЯ ОКНА И ЕЕ АРГУМЕНТЫ

Мы закончили изучение функции WinMain. Теперь нашей ближайшей задачей будет рассмотрение оконной процедуры.

Как обработать множество различных сообщений?

Первый вопрос, возникающий при изучении функции окна, касается реакции этой функции на огромное число сообщений. Ведь в файле заголовков прямо записано, что Майкрософт резервирует для использо­вания в Win32 все сообщения с номерами менее 1024! Неужели функция окна должна обрабатывать все эти сообщения и в программе должен быть код, обеспечивающий эту обработку? Ответ на этот вопрос достаточно парадоксален. Да, оконная функция должна обрабатывать все сообщения, приходящие в адрес окна. Нет, программист не должен писать кода для обработки всех сообщений! Дело в том, что в Windows предусмотрена обработка всех сообщений по умолчанию. В программе должен присутствовать код обработки только тех сообщений, обработка которых по умолчанию не устраивает программу. Все остальные «протекают» сквозь функцию и передаются на обработку по умолчанию. Делается это простым обращением к функции DefWindowProc (Default Window Procedure). Именно эта функция, спрятанная глубоко в «недрах» Windows, производит обработку подавляющего большинства сообщений, получаемых окном. Обратите внимание на предпоследнюю строчку текста программы. В этой строке все необработанные сообщения пере­даются процедуре обработки по умолчанию. На долю оконной процедуры остается «самая малость» - обработать сообщения, которые нуждаются в нестандартной обработке. В нашей программе их два - WM_PAINT и WM_DESTROY.

О сообщении WM PAINT стоит сказать особо. В большинстве окон­ных библиотек при создании окна в памяти формировался буфер, в который записывалось то, что отображалось на закрываемой окном части экрана. Содержимое менялось при изменении положения и размеров окна При этом, кстати, одно окно (перекрывающее) управляло отобра­жением другого (перекрываемого). И, если этот подход был приемлемым для текстового режима, то можно представить, сколько памяти «пожирали» бы подобные буферы в графических режимах! В Windows пошли по другому пути. В Windows каждое окно «знает» о том, что оно



25



должно проделать в тех случаях, когда условия его отображения измени­лись. К этим случаям я отношу изменение размеров окна, его положения на экране, восстановления на экране после полного или частичного перекрытия другим окном. В этих случаях окну посылается сообщение WM_PAINT, говорящее о том, что окно должно само перерисоваться полностью или частично, обработав сообщение WM_PAINT. Таким образом, помимо сокращения объема требуемой памяти (намного легче хранить небольшую процедуру обработки сообщения WM_PAINT, чем буфер с графической информацией), в Windows решается ее одна про­блема - выполняется одно из требований объектно-ориентированного программирования - полями объекта управляют методы того же (и только того же) объекта. Таким образом, мы пришли еще к одному важному выводу - каждое окно должно обрабатывать сообщение WM_PAINT, иначе оно не сможет восстановить изображение в своей рабочей области. Упомяну еще одно сообщение, которое в нашей программе не присут­ствует, но которое иногда бывает очень полезным. Дело в том, что это сообщение окно получает после создания, но до отображения, точнее, до прорисовки рабочей области. Оно называется WM_CREATE и обычно используется для инициализации окна. В некотором смысле оно является антиподом сообщения WM_DESTROY, применяемого для деинициали-зации окна.

Громадный switch

Фактически вся оконная процедура состоит из одного единственного оператора switch. Он пользуется недоброй славой и, наверное, заслужен­но. Иногда человек просто не в силах осмыслить многочисленные сазе'ы, внутри которых спрятаны очередные switch'n и т. д. По ходу изучения мы увидим, что написаны макрокоманды, позволяющие отказаться от гро­мадного switch первого уровня. Но пока давайте придерживаться «классического» стиля. При написании небольших программ оператор switch очень наглядно (почти графически!) показывает ход обработки сообщений. На данном этапе я не буду рассматривать оконную процеду­ру столь же подробно, как и WinMain(). Обработка сообщения WM_PAINT просто приводит к выводу сообщения на экран. До этого сообщения мы еще дойдем. Структура оконной процедуры ясна практически любому, хоть немного знакомому с языком С. Остановимся на моменте, касающемся завершения работы программы.



WMJ)ESTROYu \VM_QUIT

26



При необходимости закрыть окно, Windows дает окну возможность «осмотреться» и провести процедуру деинициализации. За счет чего это достигается? В ходе закрытия окна (я напоминаю, что окно - это не только прямоугольная область, видимая на экране, но и совокупность структур данных в глубине системы) сразу после снятия его с отображе­ния оконная функция получает сообщение WM DESTROY, которое является сигналом о необходимости произвести процедуру деинициали­зации. Получив это сообщение и произведя все необходимые действия, функция окна, как правило, вызывает функцию PostQuitMessage(), кото­рая, как следует из ее названия, посылает окну сообщение WM_QUIT, которое, в свою очередь, попав в цикл обработки сообщений, вызывает его прекращение. А посему - ура! Мы прошли путь от начала до заверше­ния программы. Мы узнали достаточно много о структуре программы для Windows, научились регистрировать классы окон, создавать экземпляры окон зарегистрированного класса, запускать и прекращать цикл обработки сообще­ний. Мы получили первоначальные знания об обработке сообщений и написа­нии оконной процедуры.

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

Давайте первым уберем камень под названием

UNICODE

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

ЧТО ТАКОЕ UNICODE



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



27

мало. Каждому известно, что при русификации компьютер приобретает символы кириллицы, но теряет некоторые стандартные символы, опреде­ленные в коде ANSI. В некоторых алфавитах, например, в японской кане, столько символов, что одного байта для их кодировки просто недоста­точно. В таких случаях приходится искусственно вводить всякие услов­ности (типа двухбайтовых наборов символов, double byte character sets -DBCS) и представлять символы то одним, то двумя байтами. К чему мог привести программиста этот кошмар! Для поддержки таких языков, и, в свою очередь, для облегчения «перевода» программ на другие языки, была создана кодировка Unicode.

Каждый символ в Unicode состоит из двух байтов. С одной стороны, это позволяет преодолеть все сложности по искусственному представле­нию символов двумя байтами. С другой стороны, это позволяет расши­рить набор допустимых символов до 65 536 символов. Разница с 256 символами ANSI достаточно ощутима, не так ли?

Windows NT - это первая операционная система, полностью построен­ная на Unicode. Если функции передается ANSI-строка, она преобразует­ся в Unicode. Если программа пользователя ожидает результат в виде ANSI-строки, то перед возвращением строка Unicode преобразуется в ANSI.

К сожалению, Windows'95, которая выросла из 16-битной Windows 3.x, поддерживает Unicode не в полной мере. Поэтому внутри Windows'95 вся обработка идет в ANSI коде, хотя допускается написание приложений, поддерживающих Unicode. Тем не менее, уважаемый читатель, если вы захотите перейти к работе с Unicode, то придется изучить эту кодировку и способы работы с ней.

UNICODE В WINDOWS NT И WINDOWS'95



Win 32 в большинстве случаев позволяет работу с символами как в традиционной ANSI кодировке, так и в кодировке Unicode. Почти все функции, получающие в качестве аргумента символы или строки, имеют ANSI и Unicode версии. Программист может выбрать, с каким набором символов будет работать его программа. Более того, программист может написать программу таким образом, что она сможет быть откомпилиро­вана для работы как с ANSI, так и с Unicode. Для этого программисту достаточно знать два-три макроса, несколько новых типов данных, и, естественно, новые наименования некоторых хорошо знакомых функций. Но обо всем по порядку.



28



РАБОТА С UNICODE ПРИ ИСПОЛЬЗОВАНИИ

C/C++ RUN-TIME LIBRARY

Все функции RTL (Run-Time Library - библиотека времени выполне­ния), работающие со строками и символами, имеют ANSI- и Unicode-Персии. Unicode- версии функций используют новый тип данных, введен­ный для описания символов Unicode. Этот тип описан следующим обра­зом:

typedef unsigned short wchar^t;

ANSI-версии, которые по старинке применяют старый добрый char, используют те имена функций, к которым привыкли программисты на языке С. В то же время, имена функций, использующих Unicode, не совпадают с привычными нам старыми именами типа printfQ, strcatQ и т. д. Для того чтобы написать приложение, которое легко адаптировалось оы к обеим кодировкам, нужно уметь манипулировать именами функций и типами данных. Принцип понятен - условная компиляция. В RTL для гого, чтобы определить, какую версию программы строить, введен сим­вол препроцессора UNICODE. В зависимости от того, определен этот символ или нет, строится та или иная версия программы.

Кроме этого, вместо файла string.h используется файл tchar.h, кото­рый обеспечивает универсальность. В нем определен громадный список макросов, которые необходимо использовать для того, чтобы препроцес­сор знал, какой набор функций ему необходимо вызывать, ANSI или Unicode. Этот список макросов приведен в приложении. Предлагаю читателю обратить внимание на то, что для написания кода, который мог бы компилироваться как для ANSI, так и для Unicode, необходимо вместо функций, приведенных в правых колонках, использовать имена, приво­димые в левых колонках.

Для того чтобы указать препроцессору, как нужно строить компили­руемый файл, применяется символ JJNICODE. Этот тип данных исполь­зуется при работе с символами Unicode. Для того чтобы писать приложе­ния, работающие как с ANSI, так и с Uncode, пррименяется другой макрос - FCHAR, который в зависимости от факта определения UNICODE определяется либо

lypcdcfwcharj ТС'НЛК;

typixief unsigned char TCHAR.



29



Таким образом, используя этот тип, мы можем объявлять данные, ко­торые будут восприниматься в разных обстоятельствах то как ANSI-, то как Unicode-строки или символы. Например, строка

TCHAR* pszMyStnng = «This is my string»;

в зависимости от того, определен ли символ ^UNICODE, будет считаться либо состоящей из символов ANSI и занимать 18 бантов, либо состоящей из символов ANSI и занимать памяти в два раза больше.

Теперь возникает проблема с компилятором. По умолчанию, компи­лятору почему-то наплевать (извините за такое слово, но я долго не мог понять, в чем же состоит моя ошибка при определении строки), что мы описываем строку как состоящую из символов Unicode. Для него если строка, так уж обязательно ANSI! Попробуйте откомпилировать следую­щую «программу»:

#define JJNICODE

#include <windows.h>

#include <tchar.h>

int WINAPI WinMain (HINSTANCE hlnstance, H1NSTANCE hPrevInstance,

LPSTR IpszCmdParam, int nCmdShow ) i

TCHAR* pszMyString = "This is my string"; return 0;

Кажется, полученный .      -

GXG

что   все   сделано   правильно,   но   попробуйте  посмотреть -файл обычным    ^ в ,,оиои е. Вы увидите (<This is my string     в   обычной кодировке,   т.   е.'  определение   _„.. никакого влияния на представление строки не оказывает. Таким of!

ГЖет",

необходимо явно объявлять строку, как состоящую из символов Попробуйте внести небольшое изменение.

TCHAR* pszMyString = L'This is my string";

Буква L перед строкой указывает компилятору, что строка состоит из символов Unicode. В .ехе-файле мы получим символы Unicode. Но в таком случае мы не можем получить ANSI-строку! Замкнутый круг! Проблема разрешается введением еще одного макроса - _ТЕХТ. Опреде­лен он примерно так:

#ifdef_UNICODE typedef _ТЕХТ(х) L ## х

#elsc typedef _TF.XT(x) x

30



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

Попробуйте записать



TCHAR* pszMyString - _TEXT("This is my string");

и откомпилировать программу с определением _UNICODE и без таково­го. Ну и как? Получилось?

К этому моменту трудности совместной работы Unicode и Run-Time Library уже преодолены. На очереди -

РАБОТА С UNICODE В WIN32 API

Предлагаю читателю обратить внимание на то, каким образом опреде­ляются типы данных и указатели в заголовочных файлах Win 32 (обратите внимание - символ UNICODE без знака подчеркивания, со знаком подчеркивания он используется в RTL):

#ifdcf UNICODE

typcdcf wcharj TCHAR; if/else

typcdef unsigned char TCHAR;

#cndif

typcdef TCHAR * LPTSTR, *LPTCH; typedef unsigned char CHAR; typedefCHAR *LPSTR, *LPCH; typcdcf unsigned wchar_t WCHAR, typedef WCHAR *LPWSTR, *LPWCH;

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

каждая функция, как и в RTL, имеет ANSI- и Unicode-версии. При этом имена функций формируются так:

к обычному имени (думаю, не нужно объяснять, что я под­разумеваю под обычным именем?) в случае ANSI-версии добав­ляется символ А (латинская буква, не путать с русской), напри­мер для функции DispatchMessageQ имя ANSI-версии будет DispatchMessageAQ;

для Unicode-версии к обычному имени функции добавляет­ся символ W. Например, для функции DispatchMessage() имя Unicode-версии будет DispatchMessageWQ;

31



создается макрос, имя которого совпадает с обычным именем функции. При определении символа UNICODE макрос разворачивается в имя Unicode-версии функции. Если символ UNICODE не определен, то макрос разворачивается в имя ANSI-версии функции. Например, для уже известной нам функции DispatchMessageWQ эти определения выглядят следующим образом:

WINUSERAPI LONG WINAPI DispatchMessageA(CONST MSG *lpMsg); WINUSERAPI LONG WINAPI DispatdiMessageW(CONST MSG *IpMsg);



#ifdef UNICODE

#defmc DispatchMessagc DispatchMessagcW

#else

#defme DispatchMcssage DispatchMcssageA

#endif// IUNICODE

Все гениальное просто! Единственное, я прошу читателя обратить внимание на то, что при компиляции программ обычно приходится определять оба символа препроцессора - UNICODE и ^UNICODE.

НЕСКОЛЬКО ДОБРЫХ СОВЕТОВ

К этим советам читатель может не прислушиваться, если не захочет. Просто мне бы хотелось поделиться тем небольшим опытом, который появился у меня при изучении Unicode. Самое главное, что должен осознать программист, это то, что, включив Unicode в свои системы Windows NT и Windows'95, Microsoft на весь мир заявила о том, что у Unicode существует мощная поддержка в лице этой фирмы. А к словам Microsoft (за которыми в большинстве случаев следуют дела), прислу­шаться стоит. Так что хотят этого программисты или не хотят, рано или поздно им придется переходить на работу с Unicode. Поэтому неплохо было бы позаботиться о возможной работе приложения с обеими коди­ровками сейчас, чтобы избежать «большой крови» потом.

Что для этого нужно? Немногое:

для символов и строк использовать типы TCHAR и LPTSTR; при определении литералов использовать макрос   TEXT; не забывать о правилах строковой арифметики.

На этом заканчивается рассмотрение вопросов, связанных с Unicode. Мне думается, я рассказал достаточно, чтобы у читателя появился «направляющий косинус» в этом вопросе. На страницах этой книги мы еще не раз встретим имя этой кодировки.

32



ОСНОВЫ РИСОВАНИЯ И КОПИРОВАНИЯ ИЗОБРАЖЕНИЙ

НЕМНОГО ЛИРИКИ



Когда я' обдумывал план этой книги, мне казалось, что за главой, в ко-трой описана структура программы, должна идти глава о взаимодейст­вии программы с пользователем. Действительно, чего тянуть? Ведь по­давляющее большинство программ для Windows написано с расчетом, что пользователь будет работать с программой в интерактивном режиме. Я начал было писать главу, посвященную меню и диалоговым окнам, но тут же вынужден был остановиться. Мне пришлось чуть ли не на каждой строчке извиняться и говорить, что все это мы узнаем чуть позже. Hitmap'bi в меню и кнопках - это была последняя капля, переполнившая чашу. Поэтому было принято следующее решение: на время несколько отклониться от нашего курса и изучить основы работы с изображениями (убрать тот самый камень с нашей дороги), после чего идти дальше. Думаю, что время, затраченное на изучение работы с изображениями, окупится сторицей. Я понимаю, что "читателю не терпится написать какую-нибудь грандиозную программу. Постараюсь, чтобы наше откло­нение от курса было недолгим, и изложение буду вести почти в конспек­тивном стиле (не в ущерб содержанию, надеюсь).



К этому моменту читатель уже представляет, как должна выгля­деть программа для Windows, знает кое-что о сообщениях и о том, что программа сама отвечает за перерисовку окна в случае необхо­димости.

Давайте, уважаемый читатель, обсудим вопрос: что должно происхо­дить в тех случаях, когда программе необходимо отобразить в рабочей области окна некоторое изображение? Для начала рассмотрим случаи, когда программе не нужно рисовать изображение, а необходимо скопи­ровать одно изображение на другое. Поговорим о том, что такое

КОНТЕКСТ УСТРОЙСТВА



Наверное, читатель уже знает о том, что с точки зрения программиста Windows является системой, не зависящей от устройств (device independent). Эту независимость со стороны Windows обеспечивает библиотека GDI32.dll, а со стороны устройства - драйвер этого устройст­ва. С точки зрения программы связующим звеном между программой и устройством является контекст устройства (Device Context - DC). Если

33



программе нужно осуществить обмен с внешним устройством, програм­ма должна оповестить GDI о необходимости подготовить устройство для операции ввода-вывода. После того, как устройство подготовлено, про­грамма получает хэндл контекста устройства, т. е. хэндл структуры, содержащей набор характеристик этого устройства. В этот набор входят:

bitmap (битовая карта, изображение), отображаемый в окне,

перо для прорисовки линий,

кисть,

палитра,

шрифт

и т. д. Программа никогда напрямую не обращается к контексту устрой­ства (кстати, эта структура не документирована Microsoft), она обращает­ся к нему опосредствованно, через определенные функции. После того, как все действия произведены, и необходимость в использовании устрой­ства отпала, программа должна освободить контекст устройства, чтобы не занимать память. Есть еще одна причина, из-за которой необходимо освобождать контекст устройства. В системе может существовать одно­временно только ограниченное число контекстов устройств. Если кон­текст устройства не будет освобождаться после операций вывода, то через несколько перерисовок окна система может зависнуть. Так что не забывайте освобождать контексты устройств!



Когда программа требует контекст устройства, она получает его уже заполненным значениями но умолчанию. Объект в составе контекста называется текущим объектом. Само слово - текущий - говорит о том, что контекст устройства можно изменить. Программа может создать новый объект, скажем, bitmap или шрифт, и сделать его текущим. Замещенный объект автоматически из памяти не удаляется, его необходимо позже удалить отдельно. Само собой разумеется, что программа может по­лучить характеристики текущего устройства. А вот изменить эти харак­теристики, увы, можно только через замену объекта (впрочем, это и так попятно).

ТИПЫ КОНТЕКСТА УСТРОЙСТВА

В Windows поддерживаются следующие типы контекстов устройств: контекст дисплея (обеспечивает работу с дисплеем); контекст принтера (обеспечивает работу с принтером); контекст в памяти (моделирует в памяти устройство вывода); информационный контекст (служит для получения данных от уст­ройства).

34



Контекст дисплея

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

Приложения, которые не очень интенсивно работают с экраном, ис­пользуют общин контекст. Контекст класса является устаревшим и поддерживается только для обеспечения совместимости с предыдущими версиями Windows. Microsoft не рекомендует использовать его при разработке новых приложении и рекомендует использовать только при-иатный контекст. Исходя из этого, я решил пожалеть свое и читателя время и не рассматривать контекст класса.

Контексты устройств хранятся в кэше, управляемом системой. Хэндл общею контекста программа получает с помощью функций GelDCQ, (JctDCEx() или BeginPaint(). После того, как программа отработает с дисплеем, она должна освободить контекст, вызвав функцию R.eleaseDC() или EndPaiutO (в случае, если контекст получался с помощью HcginPaintO). После того, как контекст дисплея освобожден, все нзмене-мня, внесенные в него программой, теряются и при повторном получении контекста все действия по изменению контекста необходимо повторять заново.



Приватный контекст отличается от общего тем, что сохраняет измене­ния даже после того, как прикладная программа освободила его. Приват­ный контекст Fie хранится в кэше, поэтому прикладная программа может не освобождать его. Естественно, что в этом случае за счет использова­ния большего объема памяти достигается более высокая скорость работы j дисплеем.

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

При работе с контекстами необходимо запомнить, что хэндлы кон-гекста устройства с помощью функции BcginPaint() необходимо получать только в случае обработки сообщения WM PAINT. Во всех ^стальных случаях необходимо использовать функции GetDCQ или iietDCEx().

35



Контекст принтера

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

Информационный контекст

Информационный контекст фактически не является контекстом уст­ройства и служит только для получения информации о действительном контексте устройства. К примеру, для того, чтобы получить характери­стики принтера, программа создает информационный контекст, исполь­зуя для этого функцию CreatelQ), а затем из него выбирает требующиеся характеристики. Естественный вопрос: а для чего нужно использовать информационный контекст? Почему нельзя те же самые данные получить из действительно контекста? Дело в том, что этот тип контекста создается и работает намного быстрее, а также занимает меньше памяти по сравне­нию с действительным контекстом. После того, как надобность в инфор­мационном контексте миновала, программа должна удалить его с помо­щью функции DeleteDCQ.



Чаще всего для вывода информации на устройство используется

Контекст в памяти

Этот контекст используется для хранения изображений, которые затем будут скопированы на устройство вывода. Сам по себе контекст в памяти не создается. Он обязательно создается как совместимый с тем устройст­вом или окном, на которое предполагается копировать информацию (вот он - совместимый контекст - переходник между программой и драйвером устройства!). Алгоритм работы с контекстом в памяти состоит из не­скольких шагов:

1. Получения хэндла контекста устройства (назовем его hDC - handle of Device Context) для окна, в которое будет осуществляться вывод изображения.

2. Получения хэндла bitmap'a, который будет отображаться в окне.

3. Получения совместимого с hDC контекста в памяти (для хранения изображения) с помощью функции CreateCompatibleDC() (обратите внимание на название функции - создать СОВМЕСТИМЫЙ контекст).

36



4. Выбора изображения (hBitmap) как текущего для контекста в па­мяти (hCompatibleDC).

5. Копирования изображения контекста в памяти (hCompatibleDC) на контекст устройства (hDC).

6. Удаления совместимого контекста (hCompatibleDC);

7. Принятия мер для того, чтобы замещенный bitmap из контекста в памяти не остался в памяти.

8. Освобождения контекста устройства (hDC).

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

Именно этот способ и используется в большинстве программ для ко­пирования изображения.

Но, как известно, лучше один раз увидеть, чем сто раз услышать (по-английски это звучит еще более категорично - seeing is believing - уви­деть, значит поверить). Поэтому давайте напишем небольшую програм­му, в которой продемонстрируем возможности Windows по манипулиро­ванию изображениями.

SEEING IS BELIEVING

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



Режимы отображения

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

Таким образом, даже при перемещении окна координаты объектов внутри окна остаются неизменными. При этом единицы, в которых измеряются координаты, зависят от режима отображения (mapping mode), установленного для данного окна. Единицы измерения, зависящие от режима отображения, называют логическими единицами, а координаты в )том случае называют логическими координатами.

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

37



Таблица 6. Илетнфикаторы, применяемые для обозначения режимов отображения

Идентификатор

Значение

 

Эффект

 

 

 

MMJTEXT

 

1

 

Логическая единица равна пикселю, начало

 

 

 

 

 

 

 

координат - левый верхний угол окна, положи-

 

 

 

 

 

 

 

тельное значение х - вправо, положительное

 

 

 

 

 

 

 

значение у - вниз (обычный отсчет)

 

 

 

MM_LOMETRIC

 

2

 

Логическая единица равна 0, 1 мм, отсчет

 

 

 

 

 

 

 

координат - обычный

 

 

 

MM_HIMETRIC

 

3

 

Логическая единица равна 0.01 мм, отсчет

 

 

 

 

 

 

 

координат - обычный

 

 

 

MM_LOENGLISH

 

4

 

Логическая единица равна 0,1 дюйма, отсчет

 

 

 

 

 

 

 

координат - обычный

 

 

 

MM_HIENGLISH

 

5

 

Логическая единица равна 0,001 дюйма, отсчет

 

 

 

 

 

 

 

координат - обычный

 

 

 

MM_TWIPS

 

6

 

Логическая единица равна 1/12 точки на

 

 

 

 

 

 

 

принтере (~ 1/1440 дюйма - «твип»), отсчет

 

 

 

 

 

 

 

координат - обычный

 

 

 

MMJSOTROPIC

 

7

 

Логические единицы и направление осей

 

 

 

 

 

 

 

определяются программистом с помощью

 

 

 

 

 

 

 

функций SctWindowExtExO и

 

 

 

 

 

 

 

SetVicwportExtExf), единицы по осям имеют

 

 

 

 

 

 

 

одинаковый размер

 

 

 

MM ANISOTROP1C

 

8

 

Логические единицы и направления осей

 

 

 

 

 

 

 

определяются так же, как и для

 

 

 

 

 

 

 

MM ISOTROP1C. но размеры единиц по осям

 

 

 

 

 

 

 

различны

 

 

 

MMJvflN

 

 

 

MM TEXT

 

 

 

MM MAX

 

 

 

MM_ANISOTROPIC

 

 

 

MM MAX FIXEDSCALE

 

 

 

MMJTW1PS

 

 

 

<


Для установки текущего режима отображения используется функция SetMappingModeQ, которая в файле wingdi.h описана следующим образом:

WiNGDIAPI int   WINAPI SetMapMode(HDC. int);

Первый аргумент этой функции - хэндл контекста устройства, для которо­го устанавливается данный режим. Второй аргумент определяет задаваемый режим отображения. В том же файле wingdi.h можно найти и идентификато­ры, использующиеся для обозначения режимов отображения (табл. 6). Наде­юсь, что после того, как была просмотрена таблица, вопросов у читателя не возникло. Теперь ясно, что иногда для решения конкретных задач (например, построения графиков) можно использовать различные режимы отображения. При создании окна по умолчанию устанавливается режим ММ_ТЕХТ, т. е. все координаты исчисляются в пикселах.

38



Пишем програмл i у

Наша программа будет отображать bitmap в окне и при необходимо-: in производить его масштабирование:

«include <wmdows.h>

LRESULT CALLBACK. DCDenioWndProc ( HWND. UINT, UINT, LONG );

ml WINAPI WinMaiiuHINSTANCL hlnstance. HINSTANCE hPrevInstance, LPSTR IpszCmdParam, in! nCmdShow )

HWNDhWnd;

WNDCLASS WndClass ;

MSU Msg;

char szClassNamcf] - "DCDcmo";

* Registering our window class */

* Fill WNDCLASS structure */

WndClass.stylc - CS_HRF.DRAW | CS_VREDRAW;

WndClass.lpfnWiulProc - DCDenioWndProc;

WndClass.cbClsExtra - 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance ~ lilnstance ;

WndClass.hlcon - Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.IpszMcnuName = "MyMcnu";

WndClass.IpszClassNamc = szClassNamc;

if ( !RcgislcrClass(&WndClass))

i i

McssagcBox(NULL,"Cannot register class","F.rror",MB_OK); return 0;

hWnd ~ CreateWindow(szClassNamc, "Program No 1",

WS OVERLAPPEDWINDOW, CW USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT. CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL); if(!hWnd) {

MessagcBox(NULL,"Cannot create window","Error",MB_OK); retuni 0;



39

/* Show our window */

ShowWindow(hWnd,nCmdShow);

UpdateWindow(hWnd);

/* Beginning of messages cycle */

while(GetMessage(&Msg, NULL, 0, 0))

! TranslatcMessage(&Msg);

DispatchMessage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK DCDemoWndProc (HWND hWnd, UINT Message,

UINT wParam. LONG IParam )

HDC hDC, hCompatibleDC; PAINTSTRUCT PaintStnict; HANDLE hBitmap, hOldBitmap; RECT Rect; BITMAP Bitmap; switch(Messagc)

{

case WM_PATNT: // Получаем контекст устройства.

hDC = BeginPaint(hWnd, &PaintStruct);

// Загружаем bitmap, который будет отображаться в окне , из файла. hBitmap = LoadImage(NULL, "MSDOGS.BMP", IMAGEJ3ITMAP, 0,0,

LR_LOADFROMFILH); // Получаем размерность загруженного bitmap'a.

GetObject(hBitmap, sizeof(BITMAP), &Bitmap); //' Создаем совместимый с контекстом окна контекст в памяти.

hCompatibleDC = CreateCompatibleDC(hDC); // Делаем зафуженный из файла bitmap текущим в совместимом контексте.

hOldBitmap - SelectObject(hCompatiblcDC, hBitmap); // Определяем размер рабочей области окна.

GctClientRect(h\Vnd,&Rect);

// Копируем bitmap с совместимого на основной контекст с масштабированием. SlrctchBlt(hDC, 0, 0, Rect.right, Rect.bottom,

hCompatibleDC, 0, 0, Bitmap.bmWidth, Bitmap.bmHcight, SRCCOPY); // Вновь делаем старый bitmap текущим.

SelcctObject(hCompatibleDC, hOldBitmap); i/ Удаляем загруженный с диска bitmap.

DeleteObject(hBitmap); // Удаляем совместимый контекст.

40



DeleteDC(hCompatibleDC);

// Освобождаем основной контекст, завершая перерисовку рабочей области окна. EndPaint(hWnd,&PaintStruct); return 0;

case WM_DESTROY: PostQuitMessage(O); return 0; } return DefWindowProc(hWnd,Mcssage,wParam, IParam);

Листинг № 2. Программа, осуществляющая отображение bitmap'a с масштабированием.

Результатом работы программы является окно, показанное на рис. 2.

Если читатель набрал программу буква в букву, его при запуске ожи­дает одна неприятность - отобразится точно такое же окно с белым фоном, как и при запуске «Hello, world!» (только без надписи в центре жрана). Дело в том, что в моей программе отображается тот bitmap, который нравится мне и, что более важно, находится на моем компьютере в доступной директории. Предлагаю читателю в тексте программы найти оператор, который начинается с «hBitmap = Loadlmage» и заменить в нем имя моего bitmap'a, «msdogs.bmp», на имя того bitmap'a, который будет отображать программа на читательском компьютере. Не забудьте при ном проверить, чтобы новый bitmap был доступен, т. е. находился бы в директории, доступной через переменную окружения PATH, или в теку­щей директории. Сделали? Теперь попробуйте еще раз запустить про­грамму. Если все выполнено правильно, то bitmap точно впишется в окно, причем можно будет заметить, что он несколько растянут или сжат в обоих направлениях. Попробуйте поизменять размеры окна. Bitmap



постоянно будет точно вписываться в окно, при этом растягиваясь или сжимаясь. А теперь давайте разберем эту программу и сопоставим опера-горы программы с шагами алгоритма работы с контекстом в памяти, о котором я говорил ранее.

Функция WinMainQ стандартна - ничего интересного в данном случае она не содержит. В оконной функции, которая называется DCDemoWndProc, интерес для нас представляет обработка сообщения \VM_PAINT, которое мы и рассмотрим. Первый шаг алгоритма -получить хэндл контекста устройства - мы выполняем посредством »мзова функции BeginPaint(hWnd, &PaintStruct). Аргумент hWnd очевиден - мы получаем контекст данного окна. Что же касается структуры PaintStruct...

41

В Program No 1



Рис. 2. Вид окна, отображаемого программой

Понятно, что окно далеко не всегда должно перерисовываться полно­стью. К примеру, только часть окна перекрывается другим окном. Есте­ственно, что и перерисоваться должна только часть окна. В этом случае полная перерисовка всего окна будет только лишней тратой времени и ресурсов компьютера. Windows «знает» о том, какая часть окна перекры­та. При необходимости перерисовки (при вызове BeginPaintQ) система заполняет структуру типа PAINTSTRUCT, адрес которой передается системе как второй аргумент BeginPaintQ. Среди всех полей структуры типа PAINTSTRUCT основным (на мой взгляд) является поле, содержа­щее координаты той области (clipping region), которая должна быть перерисована. В нашем примере информация, хранящаяся в этой структуре, не используется, но я прошу читателя обратить внимание на эту структуру и в дальнейшем использовать ее. Получив от функции BeginPaint() хэндл контекста устройства (hDC), будем считать первый шаг выполненным.

Второй шаг - получение хэндла bitmap'а, который будет отображаться в окне - мы делаем, вызывая функцию Loadlmage(). Я не случайно вос-

42



пользовался именно этой функцией. Во-первых, возможности этой функ­ции достаточно широки. Она позволяет загружать графические образы как из ресурсов, записанных в исполняемом файле, так и из файлов, содержащих только изображения. Графическим образом может быть bitmap, курсор и иконка. Кроме этого, функция позволяет управлять параметрами отображения и загрузки образа. Во-вторых, подавляющее большинство функций работают с ресурсами, сохраненными в исполняе­мом файле, и у программистов, начинающих осваивать Win32, попытки загрузить что-либо из файла сопровождаются некоторыми трудностями. (Помнится, что я сам в начале изучения программирования для Windows die Win32) несколько часов потратил на поиски в help'ax функции, по­зволяющей загрузить bitmap из файла). Но обо всем по порядку. В файле winuser.h эта функция описана следующим образом:



WINUSERAPI HANDLE WINAPI LoadImageA(HINSTANCE, LPCSTR, UINT,

int, int, UINT); WINUSERAPI HANDLE WINAPI LoadImageW(HINSTANCE, LPCWSTR,

UINT, int. int, UINT); tfifdef UNICODE (/define Loadlmage LoadlmageW T/eise

//define Loadlmage LoadlmageA #endif // IUNICODE

Первый, второй и последний аргументы этой функции работают в связке. Первый apryMeirr(hlnst) - хэндл программы. Как читатель должен помнить, если вместо хэндла программы указан нуль, то объект является предопределенным, т. е. хранится в «глубинах» системы. В противном случае, объект загружается откуда-то снаружи. Второй аргумент -IpszName - определяет загружаемый объект. Последний аргумент - fuLoad - содержит флаги, определяющие режим загрузки объекта. Среди этих флагов есть флаг LR LOADFROMFILE. Его название определяет его назначение - если этот флаг установлен, загрузка происходит из внешнего файла. От значения первого и последнего аргументов зависит, как будет интерпретирован второй аргумент. Взаимодействие этих трех аргументов объясняется в табл. 7.

Третий аргумент - тип образа, он может принимать значения IMAGE BITMAP, IMAGE_CURSOR, IMAGEJCON и IMAGE_ENHMETAFILE. Здесь комментарии излишни. Четвертый и :iv:b,i;i аргументы указывают ширину и высоту иконки или курсора и в нашем примере не используются.

43



Таблица 7. Взаимодействие аргументов функции LoadlmageQ

ШЬоас1(флаг LR_LOADFROMKILE)

 

hlnst

 

IpszName

 

Не установлен Не установлен Установлен Установлен

 

NULL не NULL NULL не NULL

 

Идентификатор предопределенного объекта Имя ресурса Имя файла, в котором содержится bitmap Имя файла, в котором содержится bitmap

 

Таблица 8. Флаги, определяющие параметры загртеки образа в память

 

Флаг

 

Значение

 

Эффект

 

LR_DEFAULTCOLOR

 

0x0000

 

Указывает, что загружаемый образ - не

 

 

 

 

 

монохромный

 

LRJVIONOCHROME

 

0x0001

 

Указывает, что загружаемый образ - черно-

 

 

 

 

 

белый

 

LR COLOR

 

0x0002

 

 

 

LR COPYRETURNORG

 

0x0004

 

 

 

LR COPYDELETEORG LR_LOADFROMFILE

 

0x0008 0x0010

 

Образ необходимо загружать из файла, а не

 

 

 

 

 

из ресурсов

 

LR_LOADTRANSPARENT

 

0x0020

 

Все пиксели, цвет которых совпадает с цветом пикселя, расположенного в левом

 

 

 

 

 

верхнем углу bitmap'a, отображаются как

 

 

 

 

 

прозрачные

 

LRJ3EFAULTSIZE

 

0x0040

 

Использовать ширину и высоту образа, определенные в системных метриках для

 

 

 

 

 

иконки и курсора, если cxDesircd или

 

 

 

 

 

cyDesired равны 0. Если этот флаг не

 

 

 

 

 

установлен, a cxDesired и/или cyDesired

 

 

 

 

 

равны 0, используются размеры образа,

 

 

 

 

 

указанные в ресурсе

 

LR_LOADMAP3DCOLORS

 

0x1000

 

Заменить следующие оттенки серого цвета: RGB(128, 128, 128)(DkGray)-na

 

 

 

 

 

COLOR 3DSHADOW,RGB(192, 192, 192)

 

 

 

 

 

(Gray) - на COLOR 3DFACE, RGB(223,

 

 

 

 

 

223, 223) (LtGray) - на COLOR JDLIGHT

 

LR_CREATEDIBSECTION

 

0x2000

 

При загрузке bitmap'a возвращает ориги­нальные значения цветов, не преобразуя

 

 

 

 

 

bitmap в совместимый с данным контек-

 

 

 

 

 

стом

 

LR COPYEROMRESOURCE LR__SHARED

 

0x8000

 

Разделять хэндл загруженного изображе­ния, в случае, если образ загружается

 

 

 

 

 

несколько раз. Нежелательно применять к

 

 

 

 

 

образам нестандартных размеров

 

<


Последний аргумент - флаги, определяющие параметры загрузки об­раза в память. Их достаточно много, только в файле winuser.h я насчитал 12 возможных идентификаторов. Все они начинаются с букв LR. Ничего сложного в них нет, и читатель сам сможет изучить их. Краткое описание угих флагов приведено в табл. 8.

Функция LoadlmageQ возвращает нам хэндл загруженного bitmap'a (hBitmap) (или NULL, если где-то что-то не так), после чегомы можем считать второй шаг нашего алгоритма пройденным.

Третий шаг - получение совместимого контекста в памяти - выполня­емся посредством вызова функции CreateCompatibleDCQ. Единственный аргумент этой функции - хэндл контекста (hDC), для которого создается совместимый контекст.

Четвертый шаг мы реализуем вызовом функции SelectObjectQ. Пер-иым аргументом указываем хэндл контекста, в котором замещается 1екущий элемент (в данном случае это хэндл только что созданного контекста в памяти hCompatibleDC), а вторым - хэндл элемента, которым замещается текущий элемент (хэндл загруженного bitmap'a hBitmap). Немаловажно, что эта функция возвращает хэндл ЗАМЕЩЕННОГО элемента (hOldBitmap), т. е. впоследствии с этим элементом могут также производиться манипуляции.

А вот на пятом шаге происходит то, ради чего мы заварили всю эту кашу с загрузкой bitmap'oB, совместимыми контекстами и прочим. Для копирования bitmap'a (с масштабированием) с одного контекста на другой, мы используем функцию StretchBlt(), одну из «могучих bit», по меткому выражению Чарльза Петцольда. К их числу, помимо StretchBltQ, относятся BitBlt() и PatBltQ.

Наверное, StretchBltQ является самой «могучей» из них. И наверное, се мощь и обусловила наличие у этой функции «всего-навсего» одинна­дцати аргументов. В файле wingdi.h эта функция описана следующим образом:

44



WINGDIAPI BOOL   W1NAP1 StretehBlt(HDC, int. int. int, int, HDC, hit, int. int. int,

DWORD);

Первые пять аргументов описывают тот прямоугольник на экране, в который будет вписан bitmap (на рис. 3 он обозначен светло-серым инешм). Ту часть bitmap'a, которая будет вписана в прямоугольник на чоране (на рисунке - пересекающаяся часть светло-серого и темно-серого прямоугольников), описывают следующие пять аргументов. И последний, одиннадцатый аргумент, так называемый код растровой операции, опи­сывает, каким образом пиксели одного bitmap'a будут взаимодействовать



45



с пикселами другого bitmap'a. Для того чтобы лучше понять аргументы функции, обратимся к рис. 3. Представим, что в окне, в регионе, обоз­наченном на рисунке светло-серым цветом, нужно отобразить bitmap (обозначен на рисунке темно-серым цветом) или часть bitmap'a, при необходимости сжав или растянув ее.



Рис. 3. Взаимодействие аргументов функции StrelchBIt

Итак, первый и шестой аргументы функции - хэндлы окна (hDC) и со­вместимого контекста (hCompatibleDC) соответственно. Второй (nXOriginDest) и третий (nYOriginDest) аргументы содержат смешение верхнего левого угла прямоугольника, в который будет вписываться bitmap, относительно левой и верхней сторон рабочей области окна (В каких единицах выражено смещение? Напомню, мы создали окно и не меняли режим отображения, т. е. текущий режим является уста-нов*ленньга по умолчанию). Четвертый (nWidthDest) и пятый (nlleightDest) аргументы - ширина и высота этого прямоугольника. Седь­мой (nXOriginSrc) и восьмой (nYOriginSrc) аргументы аналогичны вто­рому и третьему аргументам. Девятый (nWidthSrc) и десятый (ntleightSrc) аргументы содержат ширину и высоту отображаемой части bitmap'a. He нужно обладать развитым пространственным воображением, чтобы

46



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

В примере мы хотим фактически совместить наш bitmap с рабочей об-мсгыо окна, поэтому второй и третий аргументы вызываемой функции раины нулю. Четвертый и пятый аргументы равны ширине и высоте рабочей области (мы получили ширину и высоту рабочей области с помощью функции GctClientRectO). Седьмой и восьмой аргументы равны пулю (подумайте, почему?), а девятый и десятый содержат ширину и высоту bitmap'a, которые мы получили, обратившись к GetObject(). Вот, кажется, и все. Нет, не все.

Как работает одиннадцатый аргумент, определяющий взаимодействие двух bitmap'oB? Давайте закончим обсуждение нашего алгоритма, а затем вернемся к этому вопросу.



Мы прошли уже пять шагов алгоритма. Остались еще три шага - уда­ление совместимого контекста, объекта и контекста устройства - пусть чшатель сам определит, какие операторы программы их реализуют.

I (еужели мы добрались до конца нашего алгоритма? Мне он показался бесконечным! Давайте прежде чем рассматривать одиннадцатый аргу­мент упомянем об оставшихся «могучих Bit».

Функция BitBltQ тоже копирует bitmap с одного контекста на другой, но без масштабирования. Следовательно, ей не нужны девятый и десятый аргументы - ширина и высота отображаемой области - отображается все ю, что поместится в отведенную для этого область (светло-серый прямо­угольник на рис. 3).

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

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

КОДЫ РАСТРОВЫХ ОПЕРАЦИЙ



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

47



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

По той же традиции, биты, определяющие bitmap совместимого кон­текста, обозначают буквой S (source - источник, исходный), биты заливки - буквой Р (pattern - образец), а биты, на которых будет прорисовываться изображение - буквой D (destination - назначение, место назначения). Операции обозначаются следующим образом: а - побитовое И (AND), n -побитовое НЕТ (NO), о - побитовое ИЛИ (OR), x - побитовое ис­ключающее ИЛИ (XOR).



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

Обозначив знак операции как Ор, в польской нотации действия с би­тами можно записать таким образом: PSOp

Это говорит о необходимости взять пиксель патерны и прорисовывае­мого  bitmap'a  и   произвести   над  ними   операцию.   Если   в   операции участвуют три операнда, то получим: DPSOplOp2

Что мы должны сделать в этом случае? Правильно, сначала произве­сти действие, определяемое Opl, с битами патерны и прорисовываемым bitmap'oM, после этого произвести Ор2 с полученным результатом и битами действительного контекста. Ничего сложного здесь нет.

Каждый код растровой операции представляется 32-битным целым. Старшее слово кода представляет собой индекс битовой операции, млад­шее - код операции. Как определяется индекс операции?

Давайте представим, что нам необходимо определить индекс растро­вой операции, определяемой в польской нотации записью DPSxx. Попут­но можно определить и индекс операции PSx. Запишем друг под другом ОПРЕДЕЛЕННЫЕ значения Р, S

и D, а под ними - результаты побитовых операций PSx и DPSxx:

48



1

1

 

1

 

1

 

0

 

0

 

0

 

0

 

1

 

1

 

0

 

0

 

1

 

1

 

0

 

0

 

i

 

0

 

1

 

0

 

1

 

0

 

1

 

0

 

x         0

 

0

 

1

 

1

 

1

 

1

 

0

 

0

 

'Sxx 1

 

0

 

0

 

1

 

0

 

1

 

1

 

0

 

<


Т а б л и ц а 9. Краткое описание колон растровых операций

] ^именование

 

Инлскс операции

 

Польская запись

 

Эффект

 

IU.ACKNF.SS

 

0x00

 

{)

 

Заполнение действительного

 

 

 

 

 

 

 

контекста черным цветом

 

\OTSRCERASE

 

Oxl 1

 

DSoii

 

 

 

NOTSRCCOPY

 

0x33

 

Sn

 

Прорисовываемый bitmap отобража-

 

 

 

 

 

 

 

ется в негативном виде

 

OSTINVHRT

 

0x55

 

Dn

 

Изображение действительного контекста

 

 

 

 

 

 

 

проявляется негативным

 

I'ATINVERT

 

Ox5A

 

DPx

 

 

 

4RCTNVFRT

 

0x66

 

DSx

 

 

 

SRCAND

 

Ox8X

 

DSa

 

 

 

MERGEPAINT

 

OxBB

 

DSno

 

 

 

MI-RGFiCOPY

 

OxCO

 

PSa

 

 

 

SRCCOPY

 

OxCC

 

S

 

Копирование прорисовываемого

 

 

 

 

 

 

 

bitmap'a на действительный кон-

 

 

 

 

 

 

 

текст

 

SRCPAINT

 

OxEE

 

DSo

 

 

 

I'ATCOPY

 

OxFO

 

P

 

Копирование патерны на действи-

 

 

 

 

 

 

 

тельный контекст

 

''\TPAINT

 

OxFB

 

DPSnoo

 

 

 

WHITENESS

 

OxFF

 

1

 

Заполнение действительного

 

 

 

 

 

 

 

кон текста белым цветом

 

Итак, индекс операции PSx - ОхЗс, а индекс операции DPSxx - 0x96. Уважаемый читатель, я прошу обратить внимание на то, что друг под



Фугом записываются не произвольные, а строго определенные значения.

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

«сходного и целевого bitmap'oB. Теперь, когда все стало ясно, вы можете i 'опробовать попрактиковаться в определении индексов любых операций. ! !есмотря на то, что существуют 256 индексов растровых операций, на

рактике используются только некоторые из них. В файле wingdi.h для чапболее часто используемых растровых операций определены иденти-'•'пкаторы, которые приведены в табл. 9.

49

На основании данных табл. 9 я затрудняюсь объяснить, как изменяет­ся изображение при использовании разных растровых операций. Реко­мендую читателю запустить приведенную выше программу несколько раз, и каждый раз в функции StretchBltQ указывать новую растровую операцию. Seeing is believing!

Теперь и одиннадцатый аргумент PatBlt() стал ясным и понятным - я просто копирую bitmap в окно. Только и всего. Кстати, понимание логики работы с растровыми операциями можег позволить избежать трудоемких преобразований bitmap'oe перед копированием.

ПОЛОСЫ ПРОКРУТКИ



Мне кажется, что у читателя может возникнуть вопрос: что делать в тех случаях, когда нам не нужно масштабировать bitmap, но нужно иметь возможность просматривать все части изображения? Ответ заключен в названии данного раздела - использование полос прокрутки.

Давайте, уважаемый читатель, чуть-чуть изменим предыдущую про­грамму для того, чтобы продемонстрировать использование полос про­крутки, и посмотрим, как она будет работать. Текст программы с внесен­ными изменениями:

^include <windows.li*

long WINAPI DCDcmoWndProc ( HWND, UfNT, UINT, LONG );

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstaiicc, LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassName[] = "DCDemo";

/* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CSJ-IREDRAW [ CSJVREDRAW;

WndClass.lpfnWndProc - DCDcmoWndProc;



WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon - Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GctStockObjcct (WHITEJ3RUSH);

WndClass.IpszMenuName = "MyMenu";

WndClass. IpszClassName — szClassNamc;

50



il'( !RegisterClass(&WndClass) )

McssageBox(NULL,"Cannot register class","Error",MB_OK); return 0;

hWnd = CreateWindow(szClassNamc, "Program No 1", WS_OVERLAPPEDWINDOW | WS_VSCROLL| WS_HSCROLL, CWJJSEDEFAULT, CWJJSEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance.NULL);

il'(!hWnd)

MessageBox(NULL,"Cannot create window","Error",MB_OK); return 0;

/* Show our window */ ShowWindow(hWnd,nCmdShow); UpdatcWindow(hWnd);

/* Beginning of messages cycle */

whilc(GctMcssage(&Msg, NULL, 0, 0))

TranslatcMessage(&Msg); DispatchMessage(&Msg);

return Msg.wParam;

LRESULT CALLBACK DCDcmoWndProc (HWND hWnd. UINT Message,

UINT wParam, LONG IParam )

HOC hDC, hCompatibleDC;

PAINTSTRUCT PamtStruct;

static HANDLE hBitmap;

HANDLE hOldBitmap;

RECTRect;

BITMAP Bitmap;

static int nliorizDifference = 0, nVertDilTerence ~ 0;

static int nHorizPosition = 0, nVertPosition - 0;

switch(Messagc) i

case WM CREATL:

hBitmap =• LoadlmagcfNULL, "MSDOGS.BMP" IMAGE_BITMAP,

51



о, о,

LR_LOADFROMFILE); return 0;

case WM_PAINT:

hDC ^ BeginPaint(hWnd, &PaintStruct); GetObject(hBitmap, sizeof(BITMAP), &Bitmap); hCompatibleDC = CreateCompalibleDC(hDC); hOldBitmap = SelectObject(hCompatibleDC, liBitmap); GctClientRcct(hWnd,&Rcct); BitBltdiDC, 0, 0, Rect.right, Rect.bottom,

hCompatibleDC, nHorizPosition, nVertPosilion, SRCC'OPY); if( (nHorizDii'terence = (Bitmap.bmWidth - Rect.right)) > 0)

SetScrollRange(hWnd, SBJTORZ, 0, nHonzDifference, TRUE); else

SctScrollRange(liWnd, SBJIORZ, 0. 0, TRUE); if( (nVertDifference = (Bitmap.bmHeight - Rect.bottom)) > 0)



SetScrol]Range(h\Vnd, SBJVERT, 0. nVertDilTcrence, TRUE); else

SetScrollRangc(hWnd, SB_VERT, 0. 0, TRUE); SelectObject(hCompatibleDC, hOldBitmap); DeleteDC(hCompatibleDC); EndPaint(hWnd,&PaintStmct); return 0;

case WM_VSCROLL: svvitcli(LOWORD(wParam))

\

case SB LINEDOWN;

if(nVcrtPosition < nVertDiiTerencc) nVerlPosition—;

break; case SB_LINEUP:

if(nVcrtPosi(ion > 0) nVerlPosition—;

break; ease SB_THUMBTRACK:

nVcrtPosition - HIWORD(wParam);

break; I

SetScrollPosfhWnd, SB VERT, nVcrtPosition, TRUE); InvalidatcRect(hWnd. NULL, TRUE); return 0;

case \VM_HSCROLL: switch(LOWORD(wParam))

I

'caseSB_LINEDO\VN: ii'fnHorizPosition < nHorizDilTerence)

ullori/.Position—'-; break:

case SB  LINEUP: if(nHorizPosition > 0)

52

nHori/.Position--;

break;

case SBjniUMBTRACK: nHori/Posilion -- HIWORD(wParam); break;

SelScrollPos(h\Vmi, SBJIORZ, nHori/Position, TRUE); InvalidateRect(h\Vnd, NULL, TRUE); return 0;

case WM_DF,STROY: DeicteObjea(liBitmap); PostQu!lMessage(0); relnrti 0;

return DefWindowProc(h\Vnd,Messagc,wParam, Il'aram);

Пил. окна, создаваемого программой, показан на рис. 4.

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

BProgiamNol



i\  i. Пример |VK:.'ibi пилос прокрутки

53



Появились полосы прокрутки? Давайте разберемся, благодаря чему это произошло.

Во-первых, если сравнить вызовы функций CreateWindow() в этой и предыдущей программах, то можно увидеть, что у окна появились два новых стиля - WS__HSCROLL и WSJVSCROLL. Эти стили определяют наличие у окна горизонтальной и вертикальной полос прокрутки соответ­ственно. Первый шаг сделан. Этот шаг можно было бы сделать и по-другому, определив полосы прокрутки как дочерние окна, но о дочерних окнах мы будем говорить позже. Разница между полосами прокружи, являющимися частью окна, и полосами прокрутки - дочерними окнами состоит в том, что дочерние окна имеют встроенный клавиатурный интерфейс, позволяющий воздействовать на полосу прокрутки с помо­щью клавиатуры. Встроенным полосам прокрутки, к сожалению, доста­лось только управление с помощью курсора мыши.



Теперь необходимо определить диапазон прокрутки, который опреде­ляет число шагов между крайними позициями бегунка (слайдера). По умолчанию для полос прокрутки, являющихся частью окна, этот диапа­зон определен от 0 до 100. Для того чтобы изменить диапазон прокрутки, необходимо вызвать функцию SetScrollRange(), которая в файле winuser.h определена следующим образом:

WINUSERAPI BOOL WINAPI SetScrollRange(HWND hWnd, int nBar,

int nMinPos, int nMaxPos, BOOL bRedniw);

Первый аргумент функции - хэндл окна, которому принадлежат поло­сы прокрутки. Второй аргумент определяет, для какой полосы прокрутки (вертикальной или горизонтальной) устанавливается диапазон. В данном случае этот аргумент может принимать значение SB_VERT или SB_HORZ, что определяет работу с вертикальной или горизонтальной полосой прокрутки. Третий и четвертый аргументы непосредственно указывают нижнюю и верхнюю границу диапазона прокрутки. Пятый аргумент представляет собой флаг, определяющий, нужно ли перерисо­вывать полосу прокрутки после определения диапазона. TRUE - полоса прокрутки перерисовывается, FALSE - перерисовка не нужна. Заметьте, что если диапазон прокрутки определен от 0 до 0, то полоса прокрутки становится невидимой. Это свойство используется и в приведенной выше программе. В том случае, когда размеры окна превышают размеры ото­бражаемого bitmap'a, у полос прокрутки устанавливается диапазон от О до 0, следовательно, полоса прокрутки скрывается.

54



В данном случае с помощью функции SetScrollRangeQ диапазон про­крутки определен как разность между размером bitmap'a и размером окна по вертикали и по горизонтали, т. с. шаг полосы прокрутки соответствует одному пикселю.

Воздействовать на полосы прокрутки можно по-разному: во-первых, можно щелкнуть клавишей мыши на стрелах, расположенных по краям полосы; во-вторых, можно щелкнуть на полосе выше или ниже слайдера. Наконец, можно перетащить слайдер на другое место. Все эти воздейст­вия приводят к тому, что оконная функция окна, которому принадлежат полосы прокрутки, получает сообщение WM VSCROLL (если действия производились вертикальной полосой) или WM_HSCROLL (реакция на воздействие на горизонтальную полосу).



Характер воздействия оконная функция может определить по пара­метрам сообщения. Младшее слово wParam, которое и определяет харак-i ср воздействия на полосу прокрутки, может принимать значения, приве­денные в табл. 10. В таблице показано, что прокрутка при нажатии клавиши мыши в некоторых случаях производится на одну строку и одну страницу. В данном случае необходимо осознать, что понятия «строка» и «страница» ничего общего с текстовой строкой и страницей не имеют. Этими понятиями я заменит! условные единицы, на которые про­кручивается изображение в окне. К примеру, в приведенной программе строке соответствует один пиксель, а понятие страницы вовсе не опреде­лено (что есть страница для картинки?).

После ознакомления с таблицей становится ясно, какие •xl.OWORD(wParam)» должна обрабатывать прикладная программа.

Старшее слово wParam используется только в тех случаях, когда LOWORD(wParam) равен SBJTHUMBPOSITION или SB THUMBTRACK. В этих случаях оно хранит позицию слайдера. В остальных случаях это значение не используется.

В тех случаях, когда полосы прокрутки реализованы как дочерние ок­на, IParam содержит хэндл окна полосы прокрутки. Если полоса реализо­вана как часть окна, этот параметр не используется.

После того, как мы зафиксировали факт произведенного с полосой прокрутки действия и характер действия, программа должна правильно отреагировать на него и при необходимости изменить позицию слайдера в соответствии с произведенным воздействием. Делается это с помощью обращения к функции SetScrollPosQ, которая следующим образом описа­на в файле winuser.h:

WINUSERAPI int WINAPI SetScrolIPos(HWND hWnd, int nBar. int nPos,

BOOL bRcdraw);

55



Таблица 10. Идентификаторы характером воздействия на полосы прокрутки

Окончание табл. 10

Парамеф

Значение

 

Описание

 

 

 

SBJLINF.UP

 

0

 

Используется только с WM_VSCROLL;

 

 

 

 

 

щелчок мытью на стрелке вверх; приво-

 

 

 

SB_LUNELEFT

 

0

 

дит к прокрутке на одну «строку» вверх Используется только с WM   HSCROLL,

 

 

 

 

 

 

 

щелчок мышью на стрелке влево; приво-

 

 

 

SBJLINEDOWN

 

1

 

дит к прокрутке на одну «колонку» влево Используется только с \VM  VSCROLL,

 

 

 

 

 

 

 

щелчок мытью на стрелке ьни i; приводит к

 

 

 

SBJJNERIGHT

 

1

 

прокрутке на одну «строку» вниз Используется только с WM HSCROLL.,

 

 

 

 

 

 

 

щелчок мышью на стрелке вправо; приводит к

 

 

 

SB_PAGFUP

 

2

 

прокрутке на олну «колонку» вправо Используется •! ,ль„о с WMJVSCROLL,

 

 

 

 

 

 

 

щелчок мышью на полосе прокрутки выше

 

 

 

 

 

 

 

слайдера; приводит к прокрутке на одну

 

 

 

SB PAGELEFT

 

2

 

«страницу» вверх Используется ю.Т1,кос\\'М HSCROLL,

 

 

 

 

 

 

 

щелчок мышью на полосе прокрутки левее

 

 

 

 

 

 

 

слайдера; приводит к прокрутке на одну

 

 

 

SB PAGEDOWN

 

3

 

«страницу» влево Используется только с WM VSCROLL,

 

 

 

 

 

 

 

щелчок мышью на полосе прокрутки ниже

 

 

 

 

 

 

 

слайдера; приводит к прокрутке на одну

 

 

 

SB_PAGERIGHT

 

j

 

«страницу» вниз Используется только с WM  HSCROLL,

 

 

 

 

 

 

 

щелчок мышью на полоее прокрутки правее

 

 

 

 

 

 

 

слайдера. прпво.ттп к прокрутке на одну

 

 

 

SB_THUMBPOSITION

 

4

 

«страницу» вправо Перетаскивание слайдера закопчено, пользова-

 

 

 

SB_THUMBTRACK

 

5

 

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

 

 

 

 

 

 

 

приводит к перемещению содержимого

 

 

 

SB TOP

 

6

 

чкрана Используется только с вертикальными полоса-

 

 

 

SBJ.EFT

 

6

 

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

 

 

 

 

 

 

 

полосами прокрутки, реализованными как

 

 

 

 

 

 

 

дочерние окна, пользователь нажал клавишу «Ноте»

 

 

 

SB BOTTOM

 

7

 

Используется только с вертикальными полоса-

 

 

 

 

 

 

 

ми прокрутки, реализованными как дочерние окна, пользователь нажал клавишу «bnd»

 

 

 

I lapasieip

 

Значение

 

Опиеание

 

I SB  RIGHT SBJ-NDSCROLL

 

7 8

 

Используется только с горизонтальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавишу «End» Пользователь отпустил клавишу мыши после удержания се нажатой на стрелке или на полосе прокрутки

 

<


I lepiibrii аргумент - это хэндл окна, содержащего полосу прокрутки (в u)M случае, если полоса прокрутки реализована как часть окна), второй аргумент может принимать значение SBJVERT или SB HORZ (об этих значениях говорилось выше), третий аргумент определяет, в какую позицию должен быть установлен слайдер. И наконец, четвертый аргу­мент определяет, нужно ли перерисовывать полосу прокрутки после установки слайдера. Если последний аргумент равен TRUE, то полоса прокрутки будет перерисована.

Для того чтобы в соответствии с новой позицией слайдера изменилось изображение в рабочей области, окну необходимо послать сообщение \VM_PAINT, которое заставит окно перерисоваться. В программе, приве­денной выше, сообщение WM_PAINT окну посылается с помощью вызова функции InvalidateRectQ. Из этого следует, что код обработки сообщения WM

PAINT

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

II в заключение мне бы хотелось слегка посыпать голову пеплом. Лю-•fioii

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

'Кстати, когда я начинал писать этот раздел, я думал, что уложусь в две-три страницы. Зато теперь надеюсь, что у читателя не осталось никаких вопросов относительно манипулирования изображениями. Естественно, что манипулированием изображениями графические воз­можности Windows отнюдь не исчерпываются. По я достиг своей цели.

56



57



Теперь мне не придется постоянно просить читателя подождать, когда мы потом что-нибудь изучим. И теперь я готов к тому, чтобы продолжить изложение нашей «азбуки».



КОНТЕКСТ УСТРОЙСТВА И WM PAINT



Я уже говорил, что в Windows окно само отвечает за перерисовку се­бя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT. Каким образом осуществляется перерисовка?

Обычно используют один из трех методов:

рабочая область может быть восстановлена, если ее содержимое формируется с помощью каких-либо вычислений;

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

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

Думаю, что читатель догадался, что в качестве виртуального окна ис­пользуется контекст в памяти. Как его копировать, мы уже знаем. Но как рисовать на нем?

РИСОВАНИЕ ГРАФИЧЕСКИХ ПРИМИТИВОВ



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

Но что делать читателю в том случае, когда необходимо это изобра­жение создать в ходе самое программы? Как ему быть, если, к примеру, необходимо отобразить спектр радиосигнала, принятого его приемником? Или нарисовать график функции? Без краткого введения в основы рисо­вания не обойтись. Если читателю не нужно в ближайшее время созда­вать изображение в программе, он может смело пропустить этот раздел и перейти к разделу «Взаимодействие программы с пользователем», а к этому разделу вернуться только в случае надобности.

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

58



ТО, БЕЗ ЧЕГО РИСОВАНИЕ НЕВОЗМОЖНО

А невозможно рисование, во-первых, без инструментов.



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

Установка текущей позиции

Для установки текущей позиции используется функция MoveToExQ. В файле заголовков wingdi.h эта функция описывается следующим образом:

WTNGDIAPI BOOL WINAPI MoveToEx(HDC, irit, int, LPPOINT);

Первый аргумент - это контекст устройства, на котором мы будем ри­совать, второй и третий - координаты точки, в которую мы устанавливаем текущую графическую позицию. Последний аргумент - указатель на структу­ру типа POINT, в которую функция запишет координаты старой текущей позиции. Структура типа POINT описана в файле windef.h и ее описание выглядит следующим образом:

typedef struct tagPOINT

LONG x; LONG y; } POINT, *PP()INT, NEAR *NPPOINT, FAR *LPPOINT;

Если при вызове функции указатель на структуру типа POINT равен NULL, то координаты старой текущей позиции не возвращаются.

Прорисовка одного пикселя

Прорисовать один пиксель в определенной позиции мы можем с по­мощью вызова функции SetPixelQ, описанной в wingdi.h:

WINGDIAPF COLORREF WINAPI SetPixcl(HDC. int. int, COLORREF);

59



Первые три аргумента очевидны - контекст устройства вывода и ко­ординаты прорисовываемого пикселя. Но что такое COLORREF?

Здесь следует пояснить, что каждый пиксель на экране состоит из тех микроточек - красной, зеленой и синей. Каждая из этих микроточек может светиться с интенсивностью от 0 (микроточка не светится) до 255 (максимальная яркость). Например, если светится только красная состав­ляющая, то получаются цвета от темно-бордового (почти черного) до ярко красного. Комбинируя микроточки и их интенсивность, мы можем определить почти 17 миллионов цветов (будут ли они все поддерживать­ся на компьютере читателя, определяется видеоподсистемой компьютера, но это уже другой разговор). Обычно в таких случаях говорят об RGB значениях цвета (red, green, blue - красный, зеленый, голубой).



Вернемся к COLORREF. Опять обратимся к заголовочному файлу, но на сей раз не к wingdi.h, а к windef.h:

typedcf DWORD   COLORREF;

Понятно, что COLORREF - это двойное слово. Оно кодируется сле­дующим образом:

OxOObbggrr

т. е. младший байт определяет интенсивность красного, второй - зелено­го, третий - синего цвета. Старший байт должен быть нулевым. Для того чтобы облегчить жизнь пользователю, Microsoft в wingdi.h вставила макрос RGB:

«define RGBfr. g, b) ( (COLORREF) (((BYTF.)(r) | ((WORD) ((BYTEKg)) <-'X ))

(((d/aokd) (ija.ij-:) (p)) «\c)))

С первого взгляда в этом не разобраться. Поэтому приведу пример оп­ределения цвета с RGB - значениями 0, 100, 200

RGB(0, 100,200);

Стало понятнее, не правда ли?

При нормальном завершении функция возвращает предыдущее значение цвета пикселя. Если возвращаемое значение равно -1, то по говорит либо о возникновении ошибки, либо о том, что координаты пикселя вышли за пределы рабочей области окна.

60



Теперь мы готовы прорисовывать пиксели везде, где только можно. А если мы сумеем прорисовывать один пиксель, то сможем прорисовать и много. Но для того, чтобы рисовать, скажем, прямую, необходимо знать и реализовать в программе алгоритм прорисовки линий. То же можно сказать и о кругах, эллипсах и т. д. Думаю, что перспектива самостоя­тельной разработки этих алгоритмов читателю вовсе не улыбается. Но нет ничего страшного, команда разработчиков Win32 и здесь сняла проблему с программистов. Перейдем к достаточно обширной теме под названием

РИСОВАНИЕ ГРАФИЧЕСКИХ ПРИМИТИВОВ

Создание пера для рисования линий

Рисование графических примитивов производится с помощью перьев. В Windows'95 есть три предопределенных пера - черное (BLACK_PEN), белое (W1IITE_PEN) и прозрачное (NULL_PEN). При создании окна по умолчанию ему присваивается черное перо. Хэндл каждого из них может быть получен с помощью функции GetStockObjectQ. Естественно, что программиста ие может удовлетворить столь малое число перьев, поэто­му для прорисовки линий можно воспользоваться пером, созданным в программе посредством вызова функции CreatePenQ. Как всегда, обраща­емся к файлам заголовков, в данном случае - к файлу wingdi.h:



WINGDIAPI HPEN    WINAPI CreatePen(int, int. COLORREF);

Первый аргумент определяет стиль кисти. В wingdi.h эти стили описа­ны достаточно образно. Для того чтобы сохранить стиль этого описания (не путать со стилем кисти) я включил его третьим столбцом в табл. 11.

Не правда ли, «seeing is believing»?

Второй аргумент функции CreatePen() - толщина пера в логических единицах. Если этот аргумент равен 0, то толщина пера делается равной одному пикселю.

Третий аргумент - цвет чернил. Теперь для того, чтобы мы могли ис­пользовать наше перо, необходимо сделать его текущим в контексте устройства. Делается это уже давно знакомой нам функцией SclectObjectO. После того, как мы отработаем с пером, необходимо удалить его, вызвав функцию DeleteObject().

Мы создали перо. А теперь нам необходимо научиться рисовать при­митивы.

61

Таблица И. Возможные стили кисти

Стиль пера

Значение

 

Описание

 

Эффект

 

PS SOLID

 

0

 

 

 

Сплошная линия

 

PS DASH

 

1

 

 

 

Пунктирная линия

 

 

 

 

 

 

 

 

 

PS DOT

 

2

 

 

 

Линия из точек

 

PS_DASHDOT

 

3

 

 

 

Штрих-пунктирная линия (тирс-

 

 

 

 

 

 

 

точка)

 

PS DASHDOTDOT

 

4

 

 

 

Штрих-пунктирная линия (тире -

 

 

 

 

 

 

 

точка - точка)

 

PS NULL

 

5

 

 

 

Прозрачное перо

 

PSJNSIDEFRAME

 

6

 

 

 

При рисовании замкнутой фигуры

 

 

 

 

 

 

 

граница фигуры будет определяться

 

 

 

 

 

 

 

по внешнему краю, а не по середине

 

 

 

 

 

 

 

линии (если толщина пера более 1

 

 

 

 

 

 

 

пикселя)

 

<


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

Нарисовать линию можно с помощью функции LineTo(). Она описана в файле wingdi.h:

WINGDIAPI BOOL WINAPI LincTo(HDC, int, int);

Первый аргумент - контекст устройства. Второй и третий аргументы - координаты точки, ДО КОТОРОЙ ОТ ТЕКУЩЕЙ ПОЗИЦИИ будет проведена линия. При успешном завершении функция возвращает TRUE.

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

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

Прямоугольник можно нарисовать, обратившись к функции RectangleQ. Её описание содержится в файле wingdi.h:

WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);

Аргумент первый понятен без объяснений - хэндл контекста устрой­ства. Остальные аргументы - координаты верхнего левого и нижнего

62

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

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

Для рисования эллипса необходимо вызвать функцию EllipseQ, кото­рая в wingdi.h описывается следующим образом:

WFNGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);

Первый аргумент - это, как всегда, контекст устройства. Для того чтобы понять, как определяется эллипс, предлагаю читателю обратиться к рис. 5.

Как видно из рисунка, эллипс ограничен прямоугольником. Именно через координаты этого прямоугольника и определяется прорисовывае­мый эллипс. Второй и третий аргументы - координаты левого верхнего угла прямоугольника (на рисунке обозначены как UpX, UpY), четвертый и пятый аргументы - координаты нижнего правого угла (на рисунке обозначены как LowX, LowY).

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



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

UpX, UpY

EndX, EndY

StartX, StartY



~~LowX,LowY

Рис. 5. Определение аргумента функции FJlipscQ

63



Узнав, как рисуется эллипс, мы можем узнать, как рисуется прямо­угольник с закругленными углами.

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

Прямоугольник с закругленными краями рисуется с помощью функ­ции RoundRectQ. Из файла wingdi.h добываем ее описание WINGDIAPI BOOL WINAPI RoundRectfHDC, int, int, int, int. int. int);

Первые пять аргументов полностью идентичны аргументам функции Rect(). Последние два аргумента содержат ширину и высоту эллипса, определяющего дуги. После прорисовки прямоугольник закрашивается текущей кистью. В случае успешного завершения функция возвращает TRUE.

Рисование дуги и сектора эллипса

Возьмем из файла wingdi.h описание функции Агс(), которая исполь­зуется для рисования дуги:

WINGDIAPI BOOL WINAPI Arc(HDC, int, int, int, int, inl, int. int, int);

Первые пять аргументов полностью аналогичны аргументам функции EllipseQ. Непосредственно дуга определяется ещё двумя точками. Первая - начало дуги - находится на пересечении эллипса, частью которого является дуга, и прямой, проходящей через центр прямоугольника и точку начала дуги. На рис. 5 начало дуги обозначено StartX, StartY. Вторая - конец дуги - определяется аналогично. Конец дуги обозначен EndX, EndY. Таким образом, для прорисовки дуги необходимо сначала определить точки StartX, StartY и EndX, EndY, после чего прорисовывать дугу. Дуга прорисовывается против часовой стрелки.

У функции Pie(), которая применяется для рисования сектора эллипса, набор аргументов и их назначение абсолютно идентичны функции Агс().

Несколько слов о заполнении объектов

Как читатель уже знает, заполнение замкнутых графических объектов происходит с помощью текущей кисти. Программист может использовать предопределенную кисть, а может создать свою собственную, после чего сделать ее текущей с помощью функции SelectObject().



Простейшим видом кисти является так называемая сплошная кисть, которая создается с помощью функции CreateSolidBrush():

WINGDIAPI HBRUSH WINAPI CreatcSolidBrush(COI.ORRP.F):

64



Единственный аргумент этой функции - цвет кисти (может, лучше сказать не кисти, а краски?).

Штриховая кисть создается с помощью функции CreateHatchBrush():

WINUDIAPI HBRUSH WINAPI СгсаюНаюШшЩли. COLORREF);

Первый аргумент этой функции - стиль штриховки. Возможные стили приведены в табл. 12.

Второй аргумент указывает цвет штриховки.

И наконец, с помощью функции CreatePattemBrush() мы можем соз­дать кисть, которая при заполнении будет использовать bitmap. В wingdi.h она описана следующим образом:

WtNGDlAPf HHRUSH WINAPI CrcalcPattcrnBrush(HHITMAP);

Уже по типу аргумента видно, что единственным аргументом этой функции является хэндл bitmap'a.

Эти три функции при успешном завершении возвращают хэндл соз­данной кисти. В том случае, если произошла какая-то ошибка, возвра­щаемое значение равно NULL.

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

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

Т а б л и ц а  12. Стили штриховки

Спин, штриховки

Значение

 

Описание

 

Эффект

 

HS HORIZONTAL HS VERTICAL HS_FDIAGONAL

 

0 1

2

 

liill

\v\\\

 

Горизонтальная штриховка Вертикальная штриховка Наклонная слева направо

 

HSJBDIAGONAL

 

3

 

Hill

 

штриховка Наклонная справа налево

 

HS_CROSS HS DIAGCROSS

 

4

5

 

+ —— }-+ xxxxx

 

штриховка Штриховка крестиком Штриховка косым крестиком

 

65



ДЕМОНСТРАЦИОННАЯ ПРОГРАММА

Ниже приведен текст программы, которая использует основные функ­ции для вывода на экран 10 000 пикселей, поверх них несколько линий разных стилей, после которых, в свою очередь, прорисовывает прямо­угольники и эллипсы:



^include <windows.h>

LRESULT CALLBACK GraphDemoWndProc ( HWND, UINT, UINT, LONG );

hit WINAPI WinMain ( HINSTANCE hlnstancc, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassName[] = "GraphDcmo";

/* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style - CSJHREDRAW | CS_VREDRAW;

WndClass.IpfnWndProc = GraphDemoWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstancc = hlnstance ;

WndClass.hlcon = Loadlcon (NULL.IDIJVPPLTCATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground - (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.lpszMenuName = NULL;

WndClass.IpszClassName — szClassName;

if ( !RegisterClass(&WndClass))

I (

MessageBox(NULL."Cannot register class","Error",MB_OK); return 0;

hWnd = CreateWindow(szClassName, "Graph Demo",

WS_OVERLAPPEDWINDOW, CW USEDEFAULT. CWJJSEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance.NULL);

if(!hWnd) >

MessageBoxfNULL,"Cannot create window","Error",MB_OK); return 0;

66

/* Show our window */

ShowWmdow(hWnd,nCmdSho\v);

UpdateWindovv(hWnd);

/* Beginning of messages cycle */

whilc(GetMcssage(&Msg, NULL, 0, 0)) i

TranslateMessage(&Msg); DispatchMessage(&Msg); i return Msg.vvParam;

LRESULT CALLBACK GraphDemoWndProc (HWND hWnd, UtNT Message,

UINT wParam, LONG IParam )

HDC hDC, hCompatibleDC;

PAINTSTRUCT PaintStruct;

RFXT Reel;

HBITMAP hCompatibleBitmap, hOldBitmap;

HPENhOldPen;

static HPENPens[5];

HBRUSH hOklBnish;

static HBRUSH Brushcs[6];

int i;

swilch(Message)

case WM_PAINT: randomizcf); for(i = 0; i <=• 4; j-r~) {

Pcnsfil = (CreatePen(i, 1, RGB(random(255),random(255), random(255)))); Bnishesfil - (CreateHa(cliBrush(i, RCiB(random(255), random(255),

random(255))));

GetClientRect(h\Vnd. &Recl);

hDC - BeginPaint(hWnd, &PamtSiruct);

hCompatiblcDC - CreatcCompatibleDC(hDC);



GctClientRect(liWnd, &Rect);

hCompatibleBilmap = CreateCompaliblc,Bitmap(liDC, Rcct.right, Reel.bottom);

hOldBitmap - SelectObject(hCompatibleDC, hCompatibleBitmap);

PatBlt(hCompatibleDC, 0. 0, Red.right, Reel.bottom, PATCOPY); // Drawing of pixels

for(i = 0; i <- 9999; i---) SetPixel(hCompatibleDC,random(Rect.right), random(Rect.bottom),

RGB(random(255), random(255), random(255))); // Drawing of lines

67



for(i - 0; i <=- 9; i+-<-)

hOldPen = SelectObject(hCompatibleDC, Pcns[random(4)]); MoveToEx(hCompatibleDC.random(Rect.right), random(Rcct.bottom),

NULL);

LineTo(hCompatibleDC. nmdom(Rcct.right), random(Rect.bottom)); ScleclObjcct(hCompatibleDC, hOldPen); } // Drawing of rectangles

for(i - 0; i <=- 5; i++) i

hOldBrush - SelcctObject(hConipatibleDC, Bmshes[random(4)l); Rcctanglc(hCompaliblcDC, random(Rect.right).

random( Reel, bottom), random(Rcct.right), random(Rcet.bottom)); Ellipse(hCompatibicDC,random(Rcct.right),

random(Rect.bottom). random(Rect.right), random(Rect. bottom)); SelectObject(hCompatiblcDC, hOldBrush);

I BitBit(hDC, PaintStruct.rcPaint.left, PamtStruct.rcPaint.lop,

PaintStruct.rcPaint.right,

PaintStruct.rcPaint. bottom,

hCompatibleDC,

PaintStruct.rcPaint. left,

PaintStruct.rcPaint.top,

SRCCOPY); for(i = 0; i<=4; i++)

!

DeleteObject(Pens[i]); DeleteObjcct(Brushcs[il);

} SelectObject(hCompatibleDC, hOldBitmap);

DeleteObjcct(hCompatibleBitmap); DelelcDC(hCompatibleDC); EndPaint(h\Vnd, &PaintStruct); return 0;

case WM_DESTROY: PoslQuitMcssagc(O); return 0;

} return DefWindo\vProc(hWnd,Messagc,wParam, IParam);

На рис. 6 показан вид окна, создаваемого программой. Следует учесть, что положение линий, прямоугольников и эллипсов - случайное. При перерисовке их положение, размер и стиль штриховки изменяется, по-

68



этому при повторном запуске программы в окне может быть другое изображение.

Думаю, что после всего того, что мы обсудили в этом разделе, при разборе программы не встретится трудностей. Предоставляю вам воз­можность разобрать эту программу самостоятельно. У читателя может возникнуть вопрос, для чего все эти сложности с созданием контекста в памяти, копированием его на действительный контекст и прочее. Цель единственная - показать технику работы с виртуальным окном. Весь вывод осуществляется в виртуальное окно (контекст в памяти), после чего одна из «могучих Bit», BitBltQ выполняет копирование содержимого виртуального окна на действительное окно. Как я уже говорил, обычно вывод в действительное окно (т. е. копирование контекста в памяти на действительный контекст) происходит при обработке WM_PAINT.



В Giaph Demo



Рис. 6. Прорисовка геометрических объектов

69



ВЗАИМОДЕЙСТВИЕ ПРОГРАММЫ С ПОЛЬЗОВАТЕЛЕМ

НЕМНОГО О РЕСУРСАХ (ПРЕДИСЛОВИЕ К РАЗГОВОРУ)



ЧТО ТАКОЕ РЕСУРСЫ?

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

У Windows, как уже говорилось, есть некоторые предопределенные данные (вспомним предопределенные курсоры, иконки и кисти). Точно так же, почти в каждой программе для Windows есть некоторые данные, которые определяются еще до начала работы программы, особым обра­зом добавляются в выполняемый файл   и используются при работе про­граммы. Яркими примерами таких данных являются иконки и курсоры мыши. Кроме них, к числу ресурсов относятся: используемые в программе изображения; строки символов;

меню;

ускорители клавиатуры;

диалоговые окна;

шрифты;

ресурсы, определяемые пользователем.

Следует отметить, что выполняемым файлом может быть файл про­граммы .ехе, файл динамической библиотеки .dll и другие бинарные файлы. Для удобства буду их называть bin-файлами.

Помимо того, что ресурсы определяются до начала работы программы и добавляются в bin-файл, у них есть еще одна характерная черта. При загрузке bin-файла в память, РЕСУРСЫ В ПАМЯТЬ НЕ ЗАГРУЖАЮТСЯ. Только в случае, если тот или иной ресурс требуется для работы программы, программа сама загружает ресурс в память.

Возможность использования того или иного атрибута в качестве ре­сурса не означает, что программист не может создавать эти атрибуты в программе. Яркий пример тому можно найти в работе старого доброго Program Manager'a. При перетаскивании иконки с места на место курсор меняет свою форму и принимает форму, подобную перетаскиваемой иконке. Естественно, что в этом случае курсоры определяются програм­мой. Помимо .этого, вспомним drag-and-drop в Explorer'e и изменение формы курсора при этом.

70



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



РЕСУРСЫ СТАНДАРТНЫЕ И НЕСТАНДАРТНЫЕ

Все ресурсы, заранее определенные в Win32 API, называются стан­дартными. Для работы с ними существуют специальные функции. Но именно эта стандартность и ограничивает возможности программиста. Стандарт, он и есть стандарт.

Для того чтобы можно было преодолеть эти ограничения, был создан особый тип ресурсов - определяемые пользователем ресурсы. Используя именно этот тип, мы можем предоставить в распоряжение программы практически любые данные. Но, как известно, бесплатным бывает только сыр в мышеловке. В данном случае платой за универсальность является усложнение программы, так как забота о манипулировании данными из ресурсов лежит уже не на системе, а на программе, использующей эти ресурсы. Программа может только получить указатель на данные ресур­сов, загруженные в память средствами Windows. Дальнейшая работа с ними ложится ИСКЛЮЧИТЕЛЬНО на плечи программы!

ПОДКЛЮЧЕНИЕ РЕСУРСОВ К ИСПОЛНЯЕМОМУ ФАЙЛУ

Ресурсы создаются отдельно от файлов программы и добавляются в bin-файл при линковании программы. Подавляющее большинство ресур­сов содержится в файлах ресурсов, имеющих расширение .RC. Имя файла ресурсов обычно совпадает с именем bin-файла программы. Так, если имя программы MYPROG.EXE, то имя файла ресурсов - MYPROG.RC.

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



71

Я часто использую «смешанный» способ редактирования ресурсов. Например, при визуальном редактировании диалоговых окон достаточно трудно точно установить элементы диалогового окна именно так, как хочется. Устанавливаю все элементы ПРИБЛИЗИТЕЛЬНО на те места, где они должны находиться, после чего сохраняю ресурсы в виде файла с расширением RC. Затем редактирую RC-файл как обычный текстовый файл, точно указывая при этом все размеры и позиции.

При создании RC-файлов программист может столкнуться с одной тонкостью. Некоторые ресурсы, такие, как иконки, курсоры, диалоговые окна, изображения (bitmap) могут быть сохранены в отдельных файлах с расширениями .ico, .cur, .dig, .bmp соответственно. В этом случае в RC-файлах делаются ссылки на упомянутые файлы.

Файл ресурсов создан - теперь его нужно откомпилировать. Компили­руется он специальным компилятором ресурсов. Обычно имя компилято­ра ресурсов заканчивается на RC.EXE. В частности, в Borland 5.0 он называется BRC.EXE.

После компиляции файла ресурсов компилятором ресурсов создается новый файл, имеющий расширение .RES. Именно этот RES-файл исполь­зуется линкером для добавления ресурсов в bin-файл. Следует отметить, что при необходимости RES-файлы могут создаваться и редакторами ресурсов. В каком формате создавать ресурсы и как присоединять их к исполняемому файлу, зависит от потребностей и привычек создающего ресурсы программиста.

Итак, в очередной раз постараемся подвести итог сказанному. Ресурсы создаются и включаются в bin-файл посредством выполнения следующих шагов (некоторые шаги могут быть опущены в зависимости от обстоя­тельств) (табл. 13).

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

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



72



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

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

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

Таблица  13. Последовательность сомания файла ресурсов

Действие

Используемое средство

Создание RC-файла (при необходимости включающего ссылки па файлы с расширением .ico, .cur, .bmp, .dig, .innu   и т. д.) Редактирование RC-файла в текстовом виде Компиляция RC-файла - получение RES-файла

Добавление ресурсов, содержащихся в RIZS-файле, в bin-файл

Редактор ресурсов (при необходимости может быть использован текстовый редактор и графические редакторы) Текстовый редактор Компилятор ресурсов

Линкер

73



МЕНЮ И АКСЕЛЕРАТОРЫ



ПОДКЛЮЧЕНИЕ МЕНЮ К ОКНУ

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



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

Давайте представим себе главное меню программы как древовидную структуру. Основная часть - корень нашего дерева - это непосредственно главное меню. Само по себе оно представляет только структуру в памяти, не отображается на экране, не содержит ни одного элемента, но хранит указатель на список структур, описывающих подключаемые к нему элементы и всплывающие (popup) меню. Если читатель вспомнит свой опыт работы с Windows, он согласитсяс тем, что в окне в качестве основ­ного меню отображается именно набор рорир-меню. В свою очередь, рорир-меню должно указать на список структур очередного, более низко­го уровня и т. д. Конечные элементы меню никаких указателей на списки не имеют, но хранят некий идентификатор действия (назовем его иден­тификатором элемента меню), которое должна произвести программа при выборе данного элемента меню. Используя эти структуры, мы можем построить меню практически любой глубины и сложности.

Эта многоуровневая древовидная структура описывается в файле ре­сурсов. Описание меню имеет вид:

MenuName        MENU        [параметры]   //       это - главное меню

Описание всех рорир-меню и элементов меню второго уровня

В данном случае MenuName - это имя создаваемого нами меню. Слово MENU обозначает начало определения меню. Параметры мы пока, до изучения работы с памятью, расматривать не будем.

74



В Win32 API для описания меню существуют два ключевых слова. Первое - POPUP - специфицирует всплывающее меню. Второе -MENUITEM - описывает обычный элемент меню.

Всплывающие меню описывается следующим образом:

POPUP        «Имя»        [,параметры]   // <- описание рорир-меню



I i

Описание всех рорир-меню и элементов очередного уровня

У конечного элемента меню в его описании есть еще одна характери­стика - тот самый идентификатор действия:

MENUITEM      «Имя»,        MenuID        [.параметры]

В обоих случаях «Имя» - это тот текст, который будет выведен на эк­ран при отображении меню (обратите внимание - при описании главного меню выводимого на экран текста нет!). В том случае, когда вместо имени окна записано слово SEPARATOR (без кавычек!), на ме сте элемента меню появляется горизонтальная линия. Обычно эти горизон­тальные линии (сепараторы или разделители) используются для разделе­ния элементов подменю на логические группы (логика определяется только программистом и никаких особенностей не содержит).

Если в имени меню встречается символ «&», то следующий за ампер-сандом символ на экране будет подчеркнут одинарной чертой. Этот элемент меню можно будет вызывать с клавиатуры посредством одно­временного нажатия клавиши Alt и подчеркнутого символа.

Таблица 14. Параметры, описывающие элемент меню в файле ресурсов

CHECKED

ENABLED DISABLED GRAYED

MENURREAK

MENUBARBREAK

Рядом с именем элемента может отображаться небольшой значок, говорящий о том, что соответствующий флаг установ­лен

Элемент меню доступен

Элемент меню недоступен, но отображается как обычный Элемент меню недоступен и отображается серым цветом Горизонтальные меню размещают следующие элементы в новой строке, а вертикальные - в новом столбце То же, что и предыдущее, но в случае вертикального меню столбцы разделяются вертикальной линией

75



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

Параметры же описывают способ появления элемента на экране. Воз­можные значения параметров приведены в табл. 14.

Попробуем создать описание небольшого меню. Горизонтальное ме­ню (menubar) позволит выбирать подменю «File», «Examples» и конечный элемент «Help». Подменю «File» будет содержать элементы «Open » и «Exit», разделенные горизонтальной линией, а подменю «Examples» -несколько конечных элементов.



Ниже приведен текст скрипта для этого меню:

MyMenu MENU

{

POPUP "&File"

{

MENUITEM "&Open", 101

MENUITEM SEPARATOR MENUITEM "E&xit", 102 }

POPUP "&Examples"

{ POPUP "Example 1"

{

MENUITEM "1&Г, 103 MENUITEM "1&2", 104

} POPUP "Example &2"

{

MENUITEM "2&1", 105 MENUITEM "2&2", 106

MENUITEM "&Help", 111 }

Следует обратить внимание на то, что идентификаторы действия есть только у MENUITEM'ов. Popup-меню идентификаторов не содержат.

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

к проекту добавляется файл ресурсов (желательно, чтобы имя файла ресурсов совпадало с именем программы);

76



в текст программы вносится изменение - при определении класса окна полю IpszMenuNaine структуры типа WNDCLASS присваивается указатель на строку, содержащую имя меню. В данном случае WndClass. IpszMenuNaine = «MyMenu»;

производится перекомпиляция проекта.

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

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



Реакция окна на сообщения от меню

Как уже было сказано выше, элементы в меню могут быть обычными, запрещенными и «серыми». Для пользователя обычные и запрещенные элементы выглядят одинаково, а текст в «серых» элементах напечатан серым шрифтом. Но только обычные элементы позволяют пользователю произвести выбор. Запрещенные и «серые» элементы меню могут быть только подсвечены, но с их помощью произвести выбор нельзя. Кроме этого, существуют отмечаемые элементы меню. В них слева от текста может находиться какой-либо значок. Если значок есть, то считают, что флажок, определяемый этим элементом, установлен. Если флажок сбро­шен, то значок отсутствует.

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

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

Давайте попробуем поговорить о меню с точки зрения сообщений. При смене подсвеченного элемента меню (если, к примеру, пользователь «пробегает» по элементам меню с помощью клавиш со стрелками вверх и вниз) в оконную процедуру посылается сообщение WM_MENUSELECT. Это сообщение посылают все элементы меню. Когда же пользователь производит выбор (нажимает клавишу «Enter», к примеру), сообщение WM_COMMAND оконной процедуре посылают только обычные элемен­ты меню. Запрещенные и «серые» элементы меню в этом случае никаких сообщений не посылают. В элементах wParam и lParam посылаемых сообщений хранится информация, достаточная для того, чтобы програм­ма смогла определить, какие действия ей необходимо выполнить случае выбора пользователем того или иного элемента меню.

Вспомним, что помимо обычного меню у окна в большинстве случаев есть еще и системное меню. Сказанное относится и к системному меню. Отличие между обычным меню и системным состоит в том, что оконной процедуре посылаются сообщения WM SYSMENUSELECT и WM_SYSCOMMAND. Кроме этого, сообщения WM_SYSCOMMAND оконная процедура получает и в случае нажатия кнопок минимизации, максимизации и закрытия окна, которые находятся не в системном меню, а в правом углу заголовка окна.



Рассмотрим параметры сообщения WMSYSMENUSELECT более под­робно. В младшем слове wParam оконная процедура получает сведения о том, какой элемент стал подсвеченным. Если учесть, что макросы LOWORD() и HIWORDO выделяют соответственно младшее и старшее слово 32-битного аргумента, и назвать источник сообщения ultem, то можно записать:

ultem = (UINT) LOWORD(wParam);

В зависимости от обстоятельств смысл ultem различается:

если подсвеченный элемент является конечным и не влечет за собой вызов popup-меню, то ultem содержит идентификатор элемента меню;

если подсвеченный элемент при выборе влечет за собой вызов popup-меню, то ultem содержит номер (индекс) этого элемента в том меню, в котором оно находится;

В старшем слове wParam содержатся характеристики подсвеченного элемента. Аналогично предыдущему,

fuFlags - (UINT) HIWORD(wParam);

Возможные значения fuFlags приведены в табл. 15.

78



Таблица 15. Характеристики подсвеченного элемента меню

Флаг

Знамение

 

Описание

 

MFJ3ITMAP

 

Ox00000004L

 

Вместо строки в качестве элемента меню

 

 

 

 

 

применяется bitmap

 

MF CHECKED

 

OxOOOOOOOSL

 

Элемент отмечаемый (со «значком»)

 

MF DISABLED

 

Ox00000002L

 

Элемент запрещен

 

MF_GRAYED

 

0x0000000 IE

 

Элемент запрещен и отображается серым

 

 

 

 

 

цветом

 

MF HILITE

 

OxOOOOOOSOL

 

Элемент подсвечен

 

MF MOUSESELECT

 

OxOOOOSOOOL

 

Элемент выбран мышью

 

MF_OWNERDRAW

 

0x00000 100L

 

За прорисовку элемента отвечает не система,

 

 

 

 

 

а программа

 

MF_POPUP

 

0x000000 10E

 

Элемент вызывает появление рорчр-меню

 

 

 

 

 

более низкого уровня

 

MF SYSMENU

 

Ox00002000L

 

Элемент из системного меню

 

<


IParam содержит в себе хэндл того меню, которому принадлежит под­свеченный элемент. Обозначив хэндл меню как hMenu, получим:

hMenu = (HMENU) IParam;

Теперь пришла очередь рассмотрения сообщения WM_COMMAND. Как и в случае с \VM_SELECTMENU, младшее слово wParam содержит сведения об источнике сообщения. Так как сообщение WM_COMMAND посылается только конечными элементами меню, то в младшем слове wParam

содержится идентификатор выбранного элемента меню. На языке С

wID = LOWORD(wParam);

Старшее слово wParam указывает, от какого управляющего элемента пришло сообщение. Если сообщение пришло от меню, то это слово равно нулю, т. е.

wNotifyCodc = HIWORD(wParam) = 0;

IParam в случае сообщения от меню ВСЕГДА равно NULL!

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

Меню без использования ресурсов

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

Как было сказано выше, меню имеет строгую древовидную структуру, которая начинается с меню первого уровня (оно обычно называется главным меню программы или тепиЬаг'ом и располагается сразу под заголовком окна). К этому меню первого уровня могут быть присоедине­ны как конечные элементы меню, так и элементы, выбор которых приво­дит к появлению так называемых всплывающих (popup) меню, к кото­рым, в свою очередь, присоединяются элементы очередного уровня и т. д. Перед началом создания меню вся его структура должна быть тщательно продумана. Неплохо, если бы программист имел перед глазами гра­фическое представление этого меню. Если все предварительные вопросы решены, то мы готовы приступить к созданию меню.



Итак, для создания меню необходимо выполнить следующие дейст­вия:

выбрать подменю самого низкого уровня, которые содержат только конечные элементы меню, и создать их с помощью функций CreateMenuQ или CreatePopupMenuQ в зависимости от потребностей. Эти функции возвращают хэндл созданного меню. Меню создается пустым;

посредством функции AppendMenuQ добавляем в них требуемые элементы;

создаем меню следующего, более высокого уровня, и добавляем в них требуемые элементы и меню, созданные нами на предыдущем шаге;

повторяем эти шаги до тех пор, пока создание всех подменю не бу­дет закончено;

создаем главное меню программы посредством использования функции CreateMenuQ;

присоединяем созданные подменю самого высокого уровня к глав­ному меню программы с помощью функции AppendMenuQ;

присоединяем меню к окну посредством использования функции SetMenuQ;

прорисовываем меню с помощью функции DrawMenuBarQ. Если в ходе программы сложилась такая ситуация, что меню оказа­лось не присоединенным к окну, перед выходом из программы обязательно уничтожаем его, вызывая функцию DestroyMenuQ (присоединенное к окну меню уничтожается автоматически при уничтожении окна).

Для того чтобы проиллюстрировать сказанное, давайте разберем не­большую программу. Я постарался написать эту программу так, чтобы в ней имелись основные типы элементов меню, и была бы возможность обработать выдаваемые меню сообщения. Чтобы придать программе функциональность, информация о получении сообщения будет выдавать­ся в строку состояния - небольшую область внизу окна (возможности строки состояния мы будем изучать позже). При запуске у окна возникает меню. Внизу экрана появляется строка состояния, в которой будет ото­бражаться информация о выбранном элементе меню. Основное меню окна состоит из двух всплывающих меню, «File» и «Help», и элемента меню, в котором вместо строки отображается bitmap. В первом всплы­вающем меню находятся два элемента, «Enable exit» и «Exit», во втором -один элемент, «About», который остается запрещенным в течение всего периода существования окна. Кроме этого, элемент «Exit» при запуске объявляется «серым», т. е. из программы можно выйти только через системное меню. Однако в случае выбора элемента «Enable exit» «Exit» становится обычным, а вместо «Enable exit» возникает «Disable exit». При выборе элемента с bitmap'ом отображается окно сообщений с текстом о том, что выбран именно этот элемент. На этом возможности программы исчерпываются.



#include <windows.h>

#include <commctrl.h>

const IDM_Enable_Disable = 0;

const IDM_Exit = i;

const IDM_About = 2;

const lDP_File = 3;

const IDP_Help = 4;

char* pMcssages[] {

"Enable or disable exit", "Exit from the program", "About this program", "File operations", "Help operations", "Menu example", "System menu"

LRESULT CALLBACK MenuDemoWndProc ( HWND, UINT, UINT, LONG );

HWND hStatusWindow;

UINT wld;

HMENU hMenu,hFileMenu,hHe!pMenu;

HINSTANCE hlnst;

int APIENTRY WinMain ( HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd ; WNDCLASS WndClass ; MSG Msg;

hlnst = hinstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CS_HREDRAW | CS_VREDRAW;

WndClass.lpfiiWndProc = (WNDPROC) MenuDemoWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass. hinstance = hinstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE J3RUSH);

WndClass. IpszMenuName = NULL;

WndClass. IpszClassName = "MenuExample";

if ( !RegisterCiass(&WndClass) )

{

MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;

hWnd = CreateWindow("MenuExample", "Program No 2",

WS_O VERLAPPEDWIN DOW,

CWJJSEDEFAULT,

CWJJSEDEFAULT,

CWJJSEDEFAULT,

CWJJSEDEFAULT,

NULL, NULL,

hlnstance.NULL); if(!hWnd)

{ MessageBox(NULL, "Cannot create window", "Error", MB_OK);

return 0;

InitCommonConlrols();

hStatusWindow = CreateStatusWindow(WS_CHILD | WSJVISIBLE,

if(!hStatusWindow)

"Menu sample", hWnd,w!d);

MessageBox(NULL, "Cannot create status window", "Error", MB_OK); return 0;

/* Try to create menu */

AppendMenu( (hFileMenu=CreatePopupMenu()), MF_ENABLED, MF_STRING,

IDM_Enable_Disable, "&Enable exit");



AppendMenu(hFileMenu, MF_GRAYED  MF_STRING, IDM_Exit,"E&xil"); AppendMenu((hHelpMenu=CreatePopupMenu()),MF_DISABLEDMF_STRING,

IDM_About, "&About"); hMenu = CreateMenu(); AppendMenufliMenu, MFJZNABLED | MFJ>OPUP, (UINT) hFileMenu,

AppendMcnu(hMenu, MF_ENABLED | MFJPOPUP, (UINT) hHelpMenu,

"&Help");

SetMenufhWnd, hMcnu); /* Show our window */

ShowWindow(hWnd, nCmdShow);

UpdatcWindow(hWnd);

DrawMenuBar(hWnd);

/* Beginning of messages cycle */

while(GelMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg);

DispatchMessage(&Msg); i t

return Msg.wParam;

LRESULT CALLBACK MenuDemoWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam ) {

RECT Reel; static UINT nFlag - MFJENABLED;

char* pContentf]

i i

"Enable exit",

"Disable exit" };

static UINT nlndex = 0; static НВ1ТМЛР hBitmap; int nDimcnsion;

switch(Message) { caseWM CREATE:

nDimension = GetSystemMetrics(SM_CYMENU);

hBitmap = Loadlmage(hlnst, "msdogs.bmp", IMAGE_BITMAP,

nDimension * 2, nDimension, LR_LOADFROMFILE); AppendMenu(GetMenu(hWnd), MF^BITMAP, IDM_Bitmap, hBitmap); break;

case WM_COMMAND: switch (wParam)

{ case IDM_Enable_Disable:

EnableMenuItem(hFileMenu, IDM_Exit, MF_BYCOMMAND | nFIag);

nFlag = ( nFIag = MF_ENABLED ) ? MF_GRAYED : MFJ-NABLED;

nlndex = ( nlndex == 0) ? I : 0;

ModifyMenu(hFileMenu, IDM_Enable_Disable, MF_BYCOMMAND | MF_STRING, IDM_EnabIe_Disable, pContent[ntndex]);

break; case IDM_Exit:

SendMessage(hWnd, WM_CLOSE, NULL, NULL);

break;

} case WM_SIZE:

SendMessage(hStatusWindow, WM_SIZE, wParam, IParam);

GetClientRect(hWnd, &Rect);

return 0;

case WM_MENUSELECT: // Selection is losted

iff ((UINT) HIWORD(wParam) == Oxffff) & ((HMENU) IParam = 0))

{ SendMessage(hStatusWindow ,SB_SETTEXT, (WPARAM) 0,

(LPARAM) PMessages[5]); return 0;

I if ((UINT) HIWORD (wParam) & MF_SYSMENU)

{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,

(LPARAM) pMessages[6]); return 0;



} if ((UINT) HIWORD(wParam) & MF_POPUP)

{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,

(LPARAM) pMessages[3 + LOWORD(wParam)]); return 0;

} SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0, (LPARAM)

pMessages[LOWORD(wParam)]); return 0;

case WM_DESTROY: DeleteObject(hBitmap); PostQuitMessage(O); return 0;

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

Листинг № З. Программа, демонстрирующая возможности по мани­пулированию меню.

Вид окна, создаваемого программой, показан на рис. 7.

Как и в случае нашей первой программы для Windows, давайте рас­смотрим эту программу. Естественно, включаем файлы заголовков Win32. Включение файла «commctrl.h» обусловлено вызовом функций для работы со строкой состояния, заголовки которых находятся в этом файле. Далее идут многочисленные описания и определения, объяснять которые я не буду, они сразу же становятся понятными при разборе программы. В функции WinMain() все ясно до момента вызова функции InitCommonControls(). Но и здесь нет ничего страшного. Эта функция вызывается всегда перед использованием библиотеки общих элементов управления, к которым относится и строка состояния. К меню и нашей задаче эта функция имеет весьма далекое отношение. Интерес вызывает фрагмент, который начинается после строки комментария /* Try to create menu */.



[Menu example

ppic. 7. Окно с меню, содержащим bitmap

84



85



Таблица 16. Битовые флаги, определяющие поведение и вид элемента меню

Флаг

Назначение

MF_BITMAP

MF_CHECKED

MFJDISABLED

MF^ENABLED

MF_GRAYEU

MF_MENUBARBREAK

MF_MENUBREAK

MF_OWNERDRAW MF_POPUP

MF^SEPARATOR

MF_STRING

MF UNCHECKED

Вместо строки символов в качестве элемента меню используется изображение (bitmap) Отмечаемый элемент меню Запрещенный, но не «серый» элемент меню Разрешенный элемент меню Запрещенный «серый» элемент меню То же, что и следующее, но вертикальные столбцы отделя­ются вертикальной чертой

Очередной элемент меню размещают в новой строке (menubar) или в новом столбце За прорисовку элеме!гга отвечает программа, а не Windows Выбор элемента влечет за собой появление меню более низкого уровня Горизонтальная черта



В качестве элемента меню используется строка символов Неотмечаемый элемент меню

Как уже было сказано, сначала мы создаем меню с помощью вызова функции CreatePopupMenuQ, которая возвращает нам хэндл созданного меню hFileMenu. В это меню мы с помощью функции AppendMenuQ добавляем два элемента, «Enable exit» и «Exit». Функция AppendMenuQ заслуживает того, чтобы поговорить о ней подробнее.

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

Заметим, что некоторые флаги не могут быть установлены одновре­менно. Например, MF_BITMAP, MF_STRING и MFJPOPUP, MF_ENABLED, MF_DISABLED и MF GRAYED.

Интерпретация третьего параметра зависит от того, установлен ли во втором параметре флаг MF POPUP, т. е. является ли добавляемый эле­мент меню конечным элементом или влечет за собой вызов очередного меню. В первом случае - конечный элемент - параметр содержит иденти­фикатор этого элемента. Если же добавляется меню, то параметр содер­жит хэндл добавляемого меню.

И последний параметр тоже интерпретируется в зависимости от уста­новленных флагов. Установлен флаг MF_BITMAP - параметр содержит хэндл bitmap'а. Установлен флаг MF_STRING - параметр содержит указатель на строку символов. Установлен MF_OWNERDRAW - пара­метр содержит информацию, используемую программой при прорисовке элемента.

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

Что означает «создать меню»? Это, как и в случае создания окна, оз­начает всего лишь создание и последующее заполнение некоторых струк­тур в памяти. После создания меню не принадлежит никакому окну и до поры до времени бесполезно блуждает в глубинах памяти. Для того чтобы меню могло выполнять свои функции, оно должно быть «закреплено» за одним из окон. Функция SetMenuQ и привязывает меню к конкретному окну. Аргументы этой функции очевидны - хэндл закреп­ляемого меню и хэндл окна, к которому меню прикрепляется. После вызова этой функции указатель на меню включается в структуру окна и может нормально использоваться.



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

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

При этом оконная функция самостоятельно обрабатывает только четыре сообщения - WM_COMMAND, WMJVlENUSELECT, WM_SIZE и WMJ3ESTROY. WMJ5ESTROY мы уже рассматривали. WM_SIZE в этом примере используется для того, чтобы при изменении размеров окна строка состояния могла бы нормально перерисоваться, т. е. сейчас это сообщение нас не интересует. В настоящий момент интерес представляют только сообщения WM_MENUSELECT и WM COMMAND.

Начнем с рассмотрения WM_MENUSELECT. У этого сообщения есть одна особенность. Если поле fuFlags, т. е. старшее слово wParam, равно Oxffff и при этом поле hMenu (т. е. IParam) равно NULE, то это означает, что меню закрылось (не стало выделенных элементов), так как пользователь нажал Escape или щелкнул мышкой где-нибудь вне меню. В этом случае в строке состояния появляется текст, описывающий назначение программы в целом («Menu example»). При получении сообщения от системного меню в строке состояния возникает «System menu». Во всех остальных случаях текст в строке состояния описывает назначение под­свеченного элемента меню. Непосредственно ход обработки этого сооб­щения ясен из листинга и особых пояснений не требует.

Но обработка WM_MENUSELECT - это всего лишь прелюдия к на­стоящей работе, которая происходит тогда, когда пользователь выбирает конечный элемент меню посредством нажатия клавиши «Enter» или щелкнет левой клавишей мышки на элементе. В этом случае оконная процедура получает сообщение WM_COMMAND. Для того чтобы опре­делить, как реагировать на сообщение, мы должны проанализировать младшее слово wParam, которое хранит идентификатор элемента меню, и в зависимости от его значения предпринимать те или иные действия. В данном случае оконная функция может получать \VM_COMMAND только с двумя идентификаторами - IDMJEnable^Disable и IDMJ3xit. При получении последнего мы осуществляем выход из программы. При обработке первого я демонстрирую использование двух функций -EnableMenuItemQ и ModifyMenuQ.



При получении WM_COMMAND, младшее слово wParam которого равно IDM_Enable_Disable, производятся следующие действия:

с помощью функции EnableMenuItemQ запрещается или делается доступным элемент «Exit»;

с помощью функции ModifyMenuQ изменяется текст элемента, вы­бор которого приводит к состоянию элемента «Exit».

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

Функция EnableMenuItemQ позволяет программисту изменять состоя­ние элемента (разрешенный, запрещенный, «серый») меню по своему усмотрению. При вызове функции, ей передаются три аргумента. Первый аргумент - хэндл того меню, которому принадлежит элемент. В нашем случае меняется состояние элемента, находящегося в меню «File», хэндл которого hFileMenu. Второй аргумент определяет тот элемент, состояние которого изменяется, но каким способом происходит определение, а также в какое состояние переходит элемент, зависит от третьего аргумен­та, который в очередной раз представляет комбинацию битовых флагов.

Возможные флаги приведены в табл. 17.

88



Таблица  17. Флаги, используемые при вызове функции EnableMenuItem()

Флаг

Значение

MF_BYCOMMAND MF_BYPOSITION

MF_ENABLF.D MF_DISABLED MF GRAYED

Изменяемый элемент меню определяется по его идентификато­ру

Изменяемый элемент меню определяется по его номеру (индексу) в меню

После вызова функции элемент становится разрешенным После вызова функции элемент становится запрещенным После вызова функции элемент становится «серым»

После изменения состояния элемента «Exit» с разрешенного на серое и наоборот, необходимо изменить текст в элементе, от которого зависит это состояние. Это изменение производится посредством вызова функции ModifyMenu(), которой передаются пять аргументов. Первые два аргу­мента функционально подобны аргументам EnableMenuItemQ, т. е. пер­вый аргумент - хэндл меню, которому принадлежит изменяемый элемент, а второй аргумент определяет непосредственно изменяемый элемент. Можно было бы сказать, что и третий аргумент функционально подобен, но он представляет собой комбинацию флагов, во-первых, определяющих элемент, подлежащий изменению (MF_BYCOMMAND или MF_BYPOSITION), а во-вторых, определяющих состояние элемента после изменения (перечень этих флагов в точности соответствует приве­денному в табл. 16). Четвертый аргумент указывает или идентификатор измененного элемента, или хэндл нового меню (если, конечно, в третьем аргументе установлен флаг MF_POPUP). И наконец, последний аргумент - новое содержание измененного элемента. В зависимости от того, какой флаг установлен в третьем аргументе (MF_BITMAP, MF^STRING или MF^OWNERDRAW), последний аргумент содержит хэндл bitmap'а, указатель на строку или информацию, используемую при прорисовке элемента.



Таким образом, с помощью только функций Win32 мы создали меню и изменили его состояние.

Надеюсь, что при чтении этого раздела и разборе приведенного при­мера читатель понял технические приемы работы с меню, и теперь может применять полученные знания при разработке собственных программ. Описанными функциями отнюдь не исчерпываются возможности Win32 по управлению меню. Например, в меню можно не добавлять, а вставлять элементы посредством функции InsertMenuQ, функция DeleteMenuQ удаляет элемент из меню, информацию о меню можно получить с помо­щью функций GetMenuQ, GetMenuStringQ, GetMenuIternCountQ и других.

89



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

Изучение работы с меню на этом не заканчивается. Нераскрытым ос­тался еще один вопрос - подключение акселераторов меню, который будет рассмотрен ниже.

АКСЕЛЕРАТОРЫ

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

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



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

TableName    ACCELERATORS

{ Keyl, MenuIDl [,тип] [,параметр]

Keyn, MenuIDn [,тип] [.параметр]

TableName - это определяемое пользователем имя таблицы акселера­торов. Key определяет клавишу или комбинацию клавиш, при нажатии

90

которой происходит ввод команды. Тип определяет, является ли клавиша стандартной (это значение применяется по умолчанию) или виртуальной. Параметр может принимать одно из следующих значений: NOINVERT, ALT, CONTROL и SHIFT. Обычно при использовании акселераторных комбинаций меню отображается так, словно мы выбрали команду обычным способом. NOINVERT означает, что при использовании акселе­ратора внешне меню на ввод команды никак не отреагирует, даже если будет активно и отображено. Присутствие ALT указывает, что для по­лучения акселераторной комбинации одновременно с указанной клави­шей необходимо нажать клавишу Alt. CONTROL говорит о том, что одновременно с клавишей должна нажиматься клавиша Control, a SHIFT требует одновременного с клавишей нажатия Shift.

В качестве клавиши можно указать либо ее символ в кавычках, либо код ASCII-символа, либо код виртуальной клавиши, определенной в файлах заголовков. При использовании ASCII-кода в качестве типа должно быть указано ASCII, а в случае применения виртуальной клавиши тип должен быть VIRTKEY. Виртуальная клавиша - это системно-независимый код, определенный для основного набора служебных кла­виш. Этот набор включает клавиши F1-F12, стрелки и т. д. Коды вирту­альных клавиш определены в заголовочных файлах. Все их идентифика­торы начинаются с букв VK (Virtual Key). Разница между виртуальной клавишей и ASCII-символом с точки зрения пользователя состоит в том, что виртуальные клавиши не различают прописных и строчных букв, в отличие от ASCII-символов.

При определении акселераторов можно пойти на небольшую хит­рость. Представим себе, что в качестве акселератора мы указали заглав­ную букву и, скажем, ALT. В этом случае нам придется одновременно нажимать три клавиши - букву, клавишу SHIFT (необходимо сделать символ заглавным!) и клавишу Alt. Таким образом, при указании в качестве основной клавиши заглавной буквы, можно определять трехкла-вишные акселераторы. Кстати, если мы хотим, чтобы для вызова коман­ды использовалась клавиша Control, то можно символ в кавычках предва­рить знаком л.



Примерами акселераторов в файле ресурсов могут служить следую­щие записи:

«a», IDM_TheJFirst_Item, ALT     // определяется комбинация Alt-a

«A», IDM_The_Second_Item, ALT // определяется комбинация

Shift-Alt-a

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

Функция LoadAcceleratorQ при вызове должна получить два аргумен­та. Первый аргумент - это хэндл экземпляра программы, а второй - имя таблицы акселераторов. В результате вызова мы получаем хэндл нового объекта - таблицы акселераторов в памяти.

Но и это еще не все. Если рассуждать логически, то каждое нажатие акселераторной комбинации должно генерировать сообщение WM_COMMAND. Для этого акселераторы и создавались. Поэтому, даже после загрузки таблицы в память программа не сможет на них правильно реагировать, если мы не будем использовать функцию TranstateAccelerator(), которая преобразует сообщения от клавиатуры в сообщения WM_COMMAND. Описание этой функции можно найти в заголовочном файле winuser.h:

WINUSERAPI int WIN API TranslateAcceleratorA(HWND hWnd,

HACCEL hAccTable, LPMSG IpMsg); WINUSERAPI int WINAPI TranslateAcceleratorW(HWND hWnd,

HACCEL hAccTable, LPMSG IpMsg); «ifdefUNICODE

#define TranslateAccelerator TranslateAcceleralorW «else

#dcfine TranslateAccelcrator TranslateAccelcratorA «endif// (UNICODE

Аргументы этой функции в достаточной степени очевидны. Первый аргумент -хэндл окна, которому принадлежит меню с акселераторами, второй - хэндл табли­цы акселераторов, с помощью которой производится генерация сообщения WM_COMMAND, третий - указатель на сообщение. TranslateAcceleratorQ воз­вращает Heir/левое значение, если нажата акселераторная комбинация и нуль в противном случае. Поэтому с учетом вызова этой функции цикл обработки сооб­щений должен выглядеть следующим образом:



while(GetMessagc(&Msg, NULL, О, О))

( i

if( !TranslaleAccclcrator(hWnd, hAccel, &Msg))

!

TranslateMessage(&Msg); DispatchMcssage(&Msg);

return Msg.wParam;

Итак, с созданием таблиц акселераторов мы разобрались. Дело за ма­лым - рассмотреть небольшой пример. В данном случае я не стал изобре­тать велосипед, и сделал следующее:

в программе, взятой из предыдущего примера, создал меню не в про­грамме, а в файле ресурсов;

определил в файле ресурсов акселераторные комбинации;

добавил в цикл сообщений обработку акселераторных комбинаций.

В результате получились файлы, которые приведены ниже:

«define IDM_Enable_Disable О «define IDMJExit 1 «define IDM_About 2 «define IDP_File 3 «define IDPJHelp 4

Листинг № 4. Файл определений:

#include «menu.h»

MyMenu MENU

POPUP "&File" {

MENUITEM "Enable exit\te", IDM_Enable_Disable, GRAYED

MENUITEM "E&xit", IDM_Exit

POPUP "&Help"

MENUITEM "About\ta", IDM About, DISABLED

MyMenu ACCELERATORS {

«x», IDM_Exit, ASCII «a», IDM_About, ASCII «e», IDM_Enable_Disable, ASCII «d», IDM_Enable_Disable, ASCII

Листинг № 5. Файл ресурсов:

«include <windows.h>

«include <commctrl.h>

«include "menu.h"

char* pMessages[] = {"Enable or disable exit", "Exit from the program", "About this program", "File operations", "Help operations", "Menu example", "System menu"};

long WINAPI HelloWorldWndProc ( FTWND, UINT, UINT. LONG );

HWND hStatusWindow;

UINT wld;

HMENU hMenu,hFileMenu,hHelpMenu;

HrNSTANCE hlnst;

int APIENTRY WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )

!

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

HACCEL hAccel;

hlnst = hinstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CSJHREDRAW | CSJVREDRAW;

WndClass.lpfnWndProc = (WNDPROC) HelloWorldWndProc;

WndClass.cbClsExtra = 0;



WndClass.cbWndExtra - 0;

WndClass.hinstance = hinstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.IpszMenuName = "MyMenu";

WndClass.IpszClassName = "MenuExample";

if ( !RegisterClass(&WndClass)) !

MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;

} hWnd = CreateWindowC'MenuExample", "Program No 2",

WS_OVERLAPPEDWINDOW,

CWJJSEDEFAULT,

CW_USEDEFAULT,

CWJJSEDEFAULT,

CWJJSEDEFAULT,

NULL, NULL,

hlnstance,NULL); if(!hWnd)

{

MessageBox(NULL,"Cannot create window","Error",MBJ3K); return 0;

InitCoinmonControlsQ;

hStatusWindow = CreateStatusWindow(WSJTHILD | WS_VISIBLE,

"Menu sample",

hWnd,w!d); if(!hStatusWindow)

{

MessageBox(NULL,"Cannot create status window","Error",MB JDK); return 0;

/* Load the accelerators table */

hAccel = LoadAccelerators(hInst,"MyAccelerators"); /* Show our window */

ShowWindow(hWnd,nCmdShow);

UpdateWindow(hWnd);

hFilcMcnu = GetSubMenu(GetMenu(hWnd),0);

/* Beginning of messages cycle */

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

if( !TranslateAccelerator(hWnd,hAccel,&Msg)) {

TranslateMessage(&Msg); DispatchMessage(&Msg);

return Msg.wParam;

long WINAPI HelloWorldWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

!

RECT Rect;

static UINT nFIag = MFJENABLED; char* pContent[]

>

t

"&EnabIe exit\te", "&Disable exit\td"

};

static UINT nlndcx = 0;

switch(Message) {

case WMJCOMMAND: switch (wParam)

)

case IDMJinable J3isable:

EnableMenuItem(hFileMenu, IDMJЈxit, MFJBYCOMMAND | nFIag); nFIag - ( nFIag -= MFJZNABLED )'.' MFJ3RAYED : MFJENABLED; nlndex = ( nlndex = 0) ? 1 : 0;

ModifyMcnu(hFileMenu, IDM_Enable_Disable, MF_BYCOMMAND | MF_STRING, IDM_Enable_Disable, pContentfnlndex]); break;

case IDM_Exit:



SendMessage(hWnd, WM_CLOSE, NULL, NULL); break;

} case WM_SIZE:

SendMessage(hSlatusWindow,WM SIZE.wParam,IParam);

GetClicnlRcct(hWnd,&Rect);

return 0;

case WM_MENUSELECT: // Selection is lostcd

if ( ((UINT) HIWORD(wParam) == Oxfffl) & ((HMENU) IParam == 0))

{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,

(LPARAM) pMessages[5]); return 0;

} if ((UINT) HIWORD(wParam) & MF_SYSMENU)

{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,

(LPARAM) pMessages[6]); return 0;

if ((UINT) HIWORD(wParam) & MF_POPUP)

(

ScndMcssage(hStatusWindow, SB^SETTEXT, (WPARAM) 0.

(LPARAM) pMessages[3 + LOWORD(wParam)]); return 0;

! SendMessage(hStatusWindow,SB_SETTEXT. (WPARAM) 0, (LPARAM)

pMcssages[LOWORD(wParam)]); return 0;

case WM_DESTROY: PostQuitMessage(O); return 0; } return DefWindowProc(hWnd,Message,wParam, IParam);

Листинг № 5. Программа, демонстрирующая возможности акселера­торов меню.

Мне бы хотелось, чтобы читатель поэкспериментировал с этой про­граммой, попробовал переопределить акселераторы, и вновь провел несколько экспериментов.

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

96



Таблица  18. Возможные значения флагов поля fVirt структуры типа ACCEL

Флаг

Значение

FALT FCONTROL

FNOFNVERT FSHIFT

FVIRTKEY

При нажатии акселераторной комбинации должна быть нажата

клавиша Alt

При нажатии акселераторной комбинации должна быть нажата

клавиша Control

Внешне меню не реагирует на нажатие акселераторной комбинации

При нажатии акселераторной комбинации должна быть нажата

клавиша Shift

Поле key определяет виртуальную клавишу. Если это поле не

установлено, то считается, что поле key содержим символ ASCII

Для создания таблицы акселераторов применяется функция CreateAcceleratorTableQ, которой в качестве аргументов передаются адрес массива структур типа ACCEL и число структур в этом массиве.

Назначение полей структуры ACCEL должно быть понятно читателю, внимательно прочитавшему текущий раздел. В файле winuser.h эта струк­тура описана следующим образом:



typedef struct tagACCEL {

BYTE   fVirt; /* Also called the flags field */

WORD   key;

WORD   cmd; ( ACCEL, «LPACCEL;

Если мы вспомним формат описания акселератора в файле ресурсов, то сразу можно догадаться о том, что поле cmd - это аналог поля Menuld, key соответствует Key, а значения поля fVirt являются комбинациями флагов (табл. 18), которые однозначно соответствуют полям Тип и Пара­метр.

И наконец, чтобы завершить тему об акселераторах, замечу, что при уничтожении окна автоматически из памяти удаляются только акселера­торы, созданные с помощью функции LoadAccelerator(). В случае, если использовалась функция CreateAcceleratorTable(), программа должна сама заботиться об удалении таблицы из памяти. Для этого применяется функция DestroyAcceleratorTableQ, в качестве аргумента которой переда­ется хэндл таблицы акселераторов.

Мы завершили рассмотрение темы, связанной с меню и акселератора­ми. К этому моменту читатель должен быть готов к тому, чтобы само­стоятельно использовать ПОЧТИ все возможности по управлению меню,

97



предоставляемые Win32. За пределами нашего внимания остался один пункт. Он связан с созданием в памяти структуры типа MENUITEMTEMPLATE и использования ее для создания меню посред­ством вызова функции EoadMenuIndirect(). В книге Чарльза Петцольда есть одна фраза, которая меня не только развеселила, но и подвигла на изучение этого совершенно бесполезного (это моя личная точка зрения, но тогда-то я еще не знал этого!) вопроса. Вот эта фраза: «If you're brave, you can try using it yourself (Если ты смелый, ты можешь самостоятельно попробовать использовать ее (функцию LoadMenuIndirectQ).» Больше времени на разбор этой функции я терять не стану. Уважаемый читатель! Если вы не желаете прислушаться к моему совету, изучите, пожалуйста, третий способ создания меню самостоятельно.

Очередное ура! Мы прошли еще одну тему! Теперь мы умеем полно­стью определять порядок взаимодействия программы с пользователем через меню. Для того чтобы завершить раздел, посвященный непосредст­венному взаимодействию пользователя с программой, осталось всего ничего - начать и завершить тему о диалоговых окнах и обо всем, что с ними связано.



ДИАЛОГОВЫЕ ОКНА И ИХ ЭЛЕМЕНТЫ

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

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

98



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

Модальные и немодальные диалоги

Диалоговые окна бывают модальными и немодальными.

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



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

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

РАБОТА С ДИАЛОГОВЫМИ ОКНАМИ

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

КНОПКИ, СПИСКИ И ПРОЧЕЕ...

Как уже было сказано, элементы управления - это ОКНА более низко­го по отношению к диалоговому окну уровня. Предлагаю отметить то, что элементы управления никогда не могут использоваться как самостоя­тельные окна. Они всегда используются на фоне какого-то окна, которое

99



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

Как и любые другие окна, элементы управления могут получать и вы­давать сообщения. Правда, это относится не ко всем элементам управле­ния, но... Стоп! Давайте прервемся на секунду.

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



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

Каждому элементу управления присваивается идентификатор. При каком-либо воздействии на этот орган управления со стороны пользова­теля диалоговое окно получает сообщение, содержащее идентификаторы элемента и типа производимого пользователем действия. Диалоговая функция обрабатывает эти сообщения и выполняет соответствующие действия. Этот процесс происходит параллельно с обработкой сообщений в оконной функции. При этом нужно заметить, что в отличие от обычного окна, «нормальное» диалоговое окно Eie имеет своего цикла обработки сообщений. Цикл обработки сообщений запускается один раз при запуске программы.

Элементами управления могут быть кнопки (buttons), которые мы уже использовали в окнах сообщений, переключатели (check boxes), селекто­ры (radio buttons), списки (list boxes), комбинированные списки (combo boxes), линейки прокрутки (scroll bars) и статические элементы (statics). Все элементы в этом перечне относятся к категории базовых, и все они присутствовали и в Windows 3.x. На их основе Microsoft разработала серию новых элементов управления (common controls), которые позволи­ли расширить возможности интерфейса с пользователем и улучшить внешний вид приложений. Мы рассмотрим как базовые, так и новые общие (как еще можно перевести на русский язык название «common controls»?) элементы управления.

100

Я уже упоминал, что основную часть работы диалогового окна вы­полняют элементы управления. Поэтому рассмотрим сначала вопрос о том, как может быть создано диалоговое окно, а потом на примерах -работу каждого типа элементов управления.

СОЗДАНИЕ ДИАЛОГОВОГО ОКНА

Диалоговое окно, как и меню, может быть создано несколькими спо­собами: во-первых, с помощью описания его в файле ресурсов и, во-вторых, во время выполнения программы. Наиболее часто используется описание диалога в файле ресурсов. Лучше всего при создании диалога воспользоваться редактором ресурсов, с помощью которого может быть создан текстовый файл, содержащий описание диалогового окна. Ресурсы диалога в этом текстовом файле задаются оператором DIALOG, который имеет следующий формат:



DialogName  DIALOG [DISCARDABLE] CAPTION «Заголовок окна» STYLE <Стили диалогового окна> FONT n, <имя шрифта>

X, Y, Width, Height

Описание элементов диалога

В данном случае DialogName - это имя диалогового окна. Опция DISCARDABLE станет совершенно ясной при рассмотрении вопроса об организации памяти в Windows. Параметры X и Y - это координаты верхнего левого угла диалогового окна, Width и Height - ширина и высота диалога. STYLE описывает стили окна. Здесь могут использоваться как стили, применяемые для описания обычных окон (об этих стилях мы говорили при создании первой программы для Windows), так и стили, применяемые только в диалоговых окнах. Эти новые стили приведены в табл. 19.

Приведенных выше сведений вполне достаточно, чтобы написать за­готовку диалогового окна в файле ресурсов. Но какой смысл описывать диалоговое окно, если в нем нет ни одного из элементов управления? Ведь даже закрыть такое диалоговое окно (если в нем, конечно, нет системного меню) невозможно! Значит, нам необходимо срочно научиться описывать эти элементы !

101



Я уже упоминал о том, что в «недрах» Win32 есть масса предопреде­ленных объектов. В частности, там находятся и некоторые предопреде­ленные классы окон. К таким классам относятся кнопки (класс «button»), списки (класс «listbox»), комбинированные списки (класс «combobox»), окна редактирования (класс «edit»), полосы прокрутки класс «scrollbar»), статические элементы (класс «static»). У каждого класса есть свой определенный набор стилей, которые определяют внешний вид и поведение элементов управления, относящихся к данному классу.

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

Приступим к изучению элементов управления. Вспомним, что мы уже неоднократно встречались с кнопками. Давайте и начнем с описания обычных кнопок (buttons).



Таблица 19. Стили диалоговых окон

Стиль

Значение

 

Эффект

 

DS^ABSALIGN

 

0x000 1L

 

Положение диалогового окна исчисляется в

 

 

 

 

 

экранных координатах

 

DS_SYSMODAL

 

Ox0002L

 

Создается системное модальное диалоговое

 

 

 

 

 

окно

 

DSJDLOOK

 

Ox0004L

 

Создается диалоговое окно, имеющее

 

 

 

 

 

зрительную иллюзию трехмерности

 

DS_FIXEDSYS

 

OxOOOSL

 

Вместо SYSTEM FONT используется

 

 

 

 

 

SYSTEM_FIXED^FONT

 

DS_NOFAILCREATE

 

0x00 10L

 

Диалоговое окно создается, несмотря на то,

 

 

 

 

 

что при его создании произошли ошибки

 

DS LOCALEDIT

 

Ox0020E

 

В 32-битных приложениях не используется

 

DS_SETFONT

 

Ox0040L

 

Определяется шрифт, который будет

 

 

 

 

 

применяться в диалоговом окне

 

DS MODALFRAME

 

OxOOSOL

 

Создается модальное диалоговое окно

 

DS NOIDLEMSG

 

OxOlOOL

 

 

 

DS^SETFOREGROUND

 

Ox0200L   .

 

Поместить диалоговое окно на передний

 

 

 

 

 

план

 

DS CONTROL

 

Ox0400L

 

 

 

DS_CENTER

 

OxOSOOL

 

Диалоговое окно помещается в центр

 

 

 

 

 

рабочей области

 

DS CENTERMOUSE

 

OxlOOOL

 

 

 

DS_CONTEXTHELP

 

Ox2000L

 

 

 

102

Кнопки

Перед тем, как начать рассказ о кнопках, хочу предостеречь читателя. Дело в том, что можно использовать кнопки и в обычных окнах. Но они, как и большинство элементов управления, проектировались для исполь­зования именно в диалоговых окнах. Использование кнопок в обычных окнах не рекомендуется, ибо это увеличивает риск того, что программа будет работать неправильно.



Кнопка - это имитация на экране обычной кнопки или переключателя. В этом разделе под кнопками я также подразумеваю не только PushButtons (обычные нажимаемые кнопки), но и Check Boxes (обычно это небольшие квадратики, в которых можно установить или не устано­вить пометку) и Radio Buttons (небольшие кружочки, В ОДНОМ из которых стоит точка). Пользователь может установить курсор мыши на кнопку, щелкнуть клавишей мыши - и кнопка пошлет диалоговому окну сообщение WM_COMMAND. To же произойдет и в случае, если пользо­ватель сначала выберет кнопку клавишей Tab, а потом нажмет Enter.

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

При этом необходимо отметить, что обычная кнопка (её называют PushButton) не помнит того, что с ней делали, т. е. она на короткое время становится нажатой, а затем возвращается в исходное состояние. Можно нажать кнопку десять раз подряд, и все десять раз она пошлет диалогово­му окну одно и то же сообщение, если, конечно, кнопка не сделана за­прещенной. CheckBox помнит о том, что он находится в одном из двух состояний - установленном или не установленном, некоторые CheckBox'bi могут находиться еще и в неопределенном состоянии.

Говорить о состоянии одной RadioButton бессмысленно. Дело в том, что RadioButton'bi предназначены для осуществления выбора одного из нескольких взаимоисключающих вариантов, поэтому можно говорить о группе (иногда говорят кластере) RadioButton'ов. В частности, для объе­динения RadioButton'ов в кластеры служит такой элемент управления, как группа (GroupBox). Обычно группы используются для группирования органов управления только для улучшения дизайна диалоговых окон. Что же касается RadioButton'oB, то без обрамляющей группы существование кластера не имеет смысла.

Формат описания кнопок в окне диалога достаточно прост:



CONTROL «Заголовок», ButlonlD, class, styles, X, Y, Width, Height

103



X, Y, Width, Height - это все ясно. Все то же самое, что и при описании непосредственно диалогового окна. «Заголовок» - надпись на кнопке или рядом с кнопкой. ButtonID - идентификатор кнопки, т. е. значение, кото­рое посылается диалоговому окну при нажатии кнопки в качестве LOWORD (wParam). Через HIWORD(wParam) диалоговое окно получает код нотификации, т. е. код того действия, которое произвел пользователь. Примерами действия пользователя могут служить нажатие клавиши Enter, двойной щелчок правой или левой клавишей мыши и так далее. А источник, т. е. хэндл инициировавшего сообщение окна, сообщения содержится в IParam (я напомню, что если сообщение приходит от меню, то IParam всегда равен 0, а если от акселератора - 1). Все легко и просто. Сложности начинаются при рассмотрении класса, определяющегося полем class, типа кнопки и стиля кнопки, которые определяется парамет­ром style.

Для кнопок, вне зависимости от того, PushButton ли это, RadioButton или CheckBox, класс всегда определяется как «button».

Читатель, наверное, уже привык к тому, что ответы на большинство вопросов можно найти в файлах заголовков и файлах помощи Win32. Поэтому и сейчас, как всегда, смотрим в заголовочный файл winuser.h, выбираем оттуда стили кнопок, которые начинаются с букв BS_, и сво­дим их в табл. 20. Надоело изучать эти таблицы? Ничего, тяжело в учении - легко в бою! А.В. Суворов, «Наука побеждать».

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



В программе создается диалоговое окно, имеющее кластер RadioButtons, состоящий из трех кнопок, три CheckBox'a, а также две PushButton. Состояния RadioButtons и CheckBoxes до отображения диало­гового окна могут быть определены через меню. Затем проявляется обратная связь - состояния RadioButtons и CheckButtons определяют состояния элементов меню после закрытия диалога. PushButton с надпи­сью «Cancel» приводит к закрытию диалогового окна.

104



Т а б л и ц а 20. Стили кнопок

Флаг-

Значение

Эффект

BS_ PUSHBUTTON BS DEFPUSHBUTTON

BS_CHECKBOX

BS^AUTOCHECKBOX

BS^RADIOBUTTON BSJSTATE

BS^AUTO3STATE

BS_GROUPBOX BSJJSERBUTTON

BS_AUTORADIOBUTTON

BS_OWNERDRAW BS_LEFTTEXT

BSJTEXT BSJCON BS^BfTMAP BS_LEFT

BS_RtGHT BS CENTER

OxOOOOOOOOL 0x00000001L

Ox00000002L

Ox00000003L

Ox00000004L OxOOOOOOOSL

Ox00000006L

Ox00000007L OxOOOOOOOSL

Ox00000009L

OxOOOOOOOBL Ox00000020L

OxOOOOOOOOL Ox00000040L OxOOOOOOSOL 0x000001OOL

Ox00000200L Ox00000300L

Создается обычная кнопка Создается обычная кнопка, которая срабатывает при нажатии «Enter» даже тогда, когда не выбрана Создастся CheckBox, при нажатии состояние автоматически не изменяет­ся, забота об этом ложится на про­грамму

Создается CheckBox, который автоматически меняет свое состояние при нажатии

Создается Radio Button, автоматически состояние не меняется То же, что и BS_CHECKBOX, но имеет три состояния - включенное, отключенное и неопределенное, автоматически состояние не меняет То же, что и предыдущее, но состояние меняется автоматически Группа

Устаревший стиль, необходимо использовать BS_OWNERDRAW То же, что и RadioButton, но при нажатии состояние меняется автома­тически

За прорисовку кнопки отвечает программа, а не система Текст помещается слева от RadioButton'a или CheckBox'a, то же, что и BS_RIGHTBUTTON Внутри или рядом с кнопкой отобра­жается текст

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



105



Окончание табл. 20

Флаг

BSJTOP

BS_BOTTOM

BSJVCENTER

BS_PUSHL1KE

BSJvfULTILINE

BS_NOTIFY

BS_FLAT

BS RIGHTBUTTON

Значение

Ox00000400L OxOOOOOSOOL OxOOOOOCOOL

0x0000IOOOL Ox00002000L Ox00003000L

OxOOOOSOOOL Ox00000020L

Эффект

Размещает текст у верхнего края прямоугольника, выделенного для размещения кнопки Размещает текст у нижнего края прямоугольника, выделенного для размещения кнопки Размещает текст по вертикали в центре прямоугольника, выделенного для размещения кнопки Делает Checkbox или RadioBulton внешне похожими на PushBulton При необходимости текст разбивается на несколько строк Разрешает посылку родительскому окну нотификационных сообщении BN_DBLCLK, BN KILLFOCUSи BN^SETFOCUS Не добавляется имитация трёхмерности изображения элемента управления

RadioButton или CheckBox размеща­ются справа от надписи (то же, что и BS LEFTTEXT)

При работе программы любое изменение состояния кнопок приводит к выдаче сообщения в строке состояния диалогового окна.

Ниже приволен файл описаний, использующийся при работе демонст­рационной программы:

#dcfineIDM EXIT

101

 

#defmeIDM RadioButtonl

 

102

 

#defmc IDM_RadioButton2

 

103

 

tfdcfine IDM_RadioButton3

 

104

 

#dcfine IDM CheckButton 1

 

105

 

#define IDM_CheckButton2

 

106

 

#define IDM_CheckButton3

 

107

 

Adeline IDM DisplayDialog

 

108

 

#dcimclDC BUTTON!

 

201

 

«define IDC BUTTON2

 

202

 

#ddine IDC GROUPBOX 1

 

203

 

#defineIDC RADIOBUTTON 1

 

204

 

#define IDC RADIOBUTTON2

 

205

 

106

#defme IDC

#defme IDC

#defme IDC~

#define IDC

#defme IDC~

#define IDC

RADIOBUTTON3

GROUPBOX2

^CHECKBOX!

CHECKBOX2

CHECKBOX3

STATUSBAR

206

207

208

209

210

301

А теперь - основной файл программы:

#includc <windows.h> ^include <commctrl.h>



#iiiclude "buttons.h"

HINSTANCE hlnst; HWND hWnd ; int nRadioButtonld;

UINT uCheckBoxesState[3] - {MFJJNCHECKED, MF_UNCHECKED, MFJJNCHECKED};

long WINAPI ButtonsExampleWndProc ( HWND, UINT, UINT, LONG ); BOOL CALLBACK ButtonsExampleDialogProc(HWND, UINT, WPARAM,

LPARAM);

int WINAPI WinMain ( HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )

WNDCLASS WndClass ;

MSG Msg;

char szClassName[] = "ButtonsExample";

hlnst — hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CSJHREDRAW | CS_VREDRAW;

WndClass.IpfnWndProc = ButtonsExampleWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);'

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.IpszMenuName = "ButtonsExampleMenu";

WndClass.IpszClassName = szCIassName;

if ( !RegisterClass(&WndClass))

'i

MessageBoxfNULL,"Cannot register class","Error",MB_OK); return 0; ~~ } liWnd ^ CreatcWindow(szClassName, "Button Use Example",

107

WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CW_USEDEFAULT, NULL, NULL, Mnstance,NULL);

ifl[!hWnd)

MessageBox(NULL, "Cannot create window", "Error", MB_OK); return 0;

}

InitCommonControls(); /* Show our window */

ShowWindow(hWnd,nCmdShow);

UpdateWindow(hWnd); /* Beginning of messages cycle */

while(GetMessage(&Msg, NULL, 0, 0))

{

TranslateMessage(&Msg); DispatchMessage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK ButtonsExampleWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

{

static BOOL bFlag = FALSE; static HMENU hMenu I , hMenu2; int i;

switch(Message)

{

case WM_CREATE:

hMenu I =GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 0); hMenu2 = GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), I); CheckMenuRadioItem(hMenu 1 , IDM_RadioButton 1 , IDM_RadioButton3 ,



IDM_RadioButtonl, MF_BYCOMMAND); nRadioButtonld = IDM_RadioButtonl + 102; for(i = 0; i < 3; i++)

ClieckMenuItem(hMenu2, IDM_CheckButton 1 + i, MF_UNCHECKED); break;

case WM_COMMAND: switch(LOWORD(wParam)) {

case IDM^EXIT:

SendMessage(hWnd, WM_CLOSE, 0,0); break;

case IDM_RadioButton 1 : case IDM_RadioButton2: case IDM_RadioButton3: nRadioButtonld = LOWORD(wParam) + 102;

108



Cht-ckMenuRadiohem(hMenuI, lpM_RadioButtonI, IDM_RadioButton3,

LOWOR6(wParam), MF_BYCOMMAND); break;

case IDM_CheckButtonl: case IDM_CheckButton2: case IDM_CheckButton3: i - LOWORD(wParam) - 105; uCheckBoxesState[i] = uCheckBoxesState[i] == MF_CHECKED ?

MF_UNCHECKED : MF_CHECKED; ChcckMenuItcm(liMenu2, LOWORD(wParam), MF_BYCOMMAND

uCheckBoxesState[i]); break;

case IDM_DisplayDialog:

DialogBox(hInst, "ButtonsExample", hWnd, ButtonsExampleDialogProc); break; }

break;

case WMJ5ESTROY: PostQu i tMcssage(O); return 0; i return DefWindowProc(hWnd,Message,wParam, IParam);

BOOL CALLBACK. ButtonsExampleDialogProc(HWND hDIg, UINT Message,

WPARAM wParam, LPARAM IParam)

f \

int i;

char cMyMcssage[SO];

switch(Messagc)

f

case WMJNITDIALOG: // Set slates of controls

ScndDlgltemMcssageOiDlg. nRadioButtonld, BM SETCHECK,

BST_CHECKED, 0);

lbr(! -- [DC CHECKBOX!; i <- IDC_CHECKBOX3; i++) if(uCheckBoxesState[i - 208])

SeiidDlgItemMessagc(hDlg, i, BM_SETCHECK, BST_CHECKED, 0); return TRUE: case WM_COMMAND: swilch(LOWORD(wParanv)

case IDC_RADIOBUTTON1: case IDC_RADIOBUTTON2: case IDC_RADIOBUTTON3:

sprintf(cMyMcssage,"Message from RadioButton%d", LOWORD(wParam) - 203);

109



SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT,

(WPARAM) 0, (LPARAM) cMyMessagc);

CheckMenuRadioItem(GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 0), IDM_RadioButton 1, IDM_RadioButton3, LOWORD(wParam)- 102, MF_BYCOMMAND); return FALSE; caseHXJCHECKBOXl: case IDC_CHECKBOX2: caseIDC_CHECKBOX3: sprintf(cMyMessage,"Message from CheckBox%d",

LOWORD(wParam) - 207); SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT,



(WPARAM) 0, (LPARAM) cMyMessage); i = LOWORD(wParam) - 208; uCheckBoxesState[i] = uCheckBoxesState[i] == MF_CHECKED ?

MF_UNCHECKED : MF_CHECKED;

CheckMenuItem(GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 1), LOWORD(wParam) - 103. uCheckBoxesState[i]); return FALSE; caseIDC_BUTTONl:

SendDlgItemMessage(hDlg,roC_STATUSBAR,SB_SETTEXT, (WPARAM) 0,

(LPARAM) "Message from PushButton"); return TRUE; case IDCJ3UTTON2: // Save the state of RadioButtons

i = IDC_RADIOBUTTON 1; while(!SendDlgItemMessage(hDlg, i, BM_GETCHECK, 0, ()))

nRadioButtonld = i; // Save the state of CheckButtons

for(i = RXJCHECKBOX1; i <= IDC_CHECKBOX3; i++) uCheckBoxesState[i - 208] = SendDlgltemMessagefhDlg, i, BM_GETCHECK, 0, 0) = 0 ? MF_UNCHECKED : MF_CHECKED; EndDialog(hDlg,0); return TRUE;

i /

break; return FALSE;

При линковании программы необходимо использовать фага ресурсов:

#include "buttons.h"

ButtonsExample DIALOG 50, 50, 154, 108

110



STYLE DS_MODALFRAME | DSJDLOOK | DS_CONTEXTHELP |

WS_POPUP | WS_VISIBLE | CAPTION "Buttons Example" FONT 8, "MS Sans Serif

WS CAPTION  WS SYSMENU

CONTROL "PushButton", IDC_BUTTONl. "button", BS_PUSHBUTTON |

BS_CENTER | BS_NOTIFY | WS_CHILD | WSJVISIBLE |

WS_TABSTOP, 8, 72, 64, 16 CONTROL "RadioButtonl", IDC_RADIOBUTTON 1, "button",

BS_AUTORADIOBUTTON | WS „CHILD | WS_VISIBLE |

WSJTABSTOP, 8, 12,64, 16 CONTROL "RadioButton2", IDCJRADIOBUTTON2, "button",

BS_AUTORADIOBUTTON | BS_FLAT | WS_CHILD

WSJVISIBLE | WSJTABSTOP, 8, 28, 64, 16 CONTROL "RadioButton3", IDC__RADIOBUTTON3, "button",

BS_AUTORADIOBUTTON | BS_LEFTTEXT | WS_CHILD |

WSJVISIBLE I WSJTABSTOP, 8, 44, 64, 16 CONTROL "Groupl", IDCJ3ROUPBOX1, "button", BS_GROUPBOX |

WS^CHILD | WSJVISIBLE | WS_GROUP, 4, 4, 72, 60 CONTROL "CheckBoxl", IDC_CHECKBOX1, "button", BS_AUTOCHECKBOX |

WS^CHILD | WS_VISIBLE | WS_TABSTOP, 82, 12, 64, 16 CONTROL "CheckBox2", IDC_CHECKBOX2, "button", BS_AUTOCHECKBOX |



WS_CHILD | WSJVISIBLE | WSJTABSTOP, 82, 28, 64, 16 CONTROL "CheckBox3", IDC_CHECKBOX3, "button", BS^AUTOCHECKBOX |

WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 43, 64, 16 CONTROL "Group2", IDC_GROUPBOX2, "button", BS_GROUPBOX |

WS_CHILD | WS_VISIBLE  WS_GROUP, 78, 4, 72, 60 CONTROL "Cancel", IDC_BUTTON2, "button", BS_PUSHBUTTON |

BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 72,

64, 16 CONTROL "StatusWindow 1", IDC^STATUSBAR, "msct!s_statusbar32", 3 |

WS_CHILD | WS_VISIBLE, 0, 116, 154, 12 }

ButtonsExampleMenu MENU f

POPUP "&File" ;

MENUITEM "E&xit", IDM_EXIT \

POPUP "&Dialog"

f t

POPUP "Initialize &RadioButtons"

f (

MENUITEM "Set RadioButton&l", IDM_RadioButton I MENUITEM "Set RadioButton&2", IDM_RadioButton2 MENUITEM "Set RadioButton&3", IDM_RadioButton3

i

POPUP "Initialize &CheckButtons"

111

\

MENUITEM "Set CheckButton&l", IDM_CheckButtonl MENUITEM "Set CheckButton&2", IDM_CheckButton2 MENUITEM "SetCheckButton&3", IDM_CheckButton3

MENUITEM SEPARATOR

MENUITEM "Displa&y Dialog", IDM_DisplayDialog

Buttons Example





Рис. 8. Диалоговое окно с кнопками различных стилей

На рис. 8 показано диалоговое окна, которое создается данной про­граммой. Функция WinMainQ полностью стандартна и ничего нового не содержат.

При обработке сообщения WM^CREATE мы узнаем о новой возмож­ности, связанной с меню. С помощью функции CheckRadioMenuItem() можно заставить группу элементов меню работать как кластер RadioButtons. В этом случае установка отметки у одного элемента меню приводит к сбросу отметки у всех других элементов меню, входящих в состав группы. Характерно, что при определении группы мы должны указать минимальный и максимальный идентификаторы элементов меню, включаемых в группу. Элементы, включаемые в меню, должны иметь идентификаторы, попадающие в определенный интервал, а не произволь­но определенные. Этим мы будем пользоваться при определении и ото­бражении в меню состояния кластера RadioButtons.



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

112



Наверное, читателю небезынтересно узнать, что эти две функции для отметки элементов по умолчанию используют различные значки. Реко­мендую читателю обратить внимание на то, какие значки используются для отметки в обеих функциях.

Но эта программа написана отнюдь не для того, чтобы рассказывать о новых возможностях меню. При обработке сообщения от элемента меню с надписью «Display Dialog» создается диалоговое окно, в котором и содержатся те кнопки, о которых мы говорили. В зависимости от того, какое диалоговое окно должно быть создано, могут быть использованы функции DialogBox() и CreateDialogQ. Функция DialogBoxQ создает модальное диалоговое окно. Немодальное диалоговое окно создается с помощью функции CreateDialogQ. Мы используем функцию DialogBoxQ.

В файле winuser.h эта функция описана следующим образом:

WINUSERAPI int WINAPI DialogBoxParamA(HINSTANCE hlnstance,

LPCSTR IpTcmplateName, HWND hWndParent, DLGPROC IpDialogFunc, LPARAM dwInitParam); WINUSERAPI int WINAPI DialogBoxParamW(HINSTANCE hlnstance,

LPCWSTR IpTemplatcName, HWND hWndParent, DLGPROC IpDialogFunc, LPARAM dwInitParam);

#ifdef UNICODE

#define DialogBoxParam DialogBoxParamW

#elsc

#dcfine DialogBoxParam DialogBoxParamA

#endif// IUNICODE

#dcfine DialogBoxA(hInstance, IpTemplatc, hWndParent, IpDialogFunc) \

DialogBoxParamA(hInslance, IpTemplate, hWndParent, IpDialogFunc, OL)

//define DialogBoxW(hInstance, IpTemplate, hWndParent, IpDialogFunc) \

DialogBoxParamW(hInstance, IpTemplate, hWndParent, IpDialogFunc, OL)

//ifdef UNICODE

//define DialogBox DialogBoxW

//else

//define DialogBox DialogBoxA

#endif // IUNICODE

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



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

113



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

Третий аргумент - хэндл родительского окна. О том, что такое роди­тельское окно, нужно сказать особо.

Во-первых, если мы представим окна, начиная с Desktop'a, как распо­лагающиеся друг над другом (в так называемом Z-порядке), то дочернее окно обязательно будет над родительским, от этого зависит порядок обработки сообщений. Во-вторых, сообщения о действиях с диалоговым окном (нотификационые сообщения) система будет посылать именно родительскому окну. В-третьих, при закрытии родительского окна дочернее окно закрывается автоматически. Возможно, конечно, создание диалоговых окон без родителя, но тогда придется писать огромное ко­личество кода, обеспечивающее нормальное функционирование окна.

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

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

В дополнение к этому заметим, что последний аргумент функции DialogBoxParamQ - это какой-то параметр, определяемый программи­стом, который может быть передан функции диалогового окна.

Итак, мы рассмотрели функцию основного окна и остановились на функции окна диалога. На очереди - ее рассмотрение.

Диалоговая функция очень напоминает функцию окна, но имеет ряд отличий. Во-первых, обычная оконная функция возвращает значение типа LRESULT. Диалоговая функция возвращает значение типа BOOL. Во-вторых, обычная функция окна передает сообщения, обрабатывать которые не нужно, процедуре обработки по умолчанию (DefWindowProc()). Диалоговая функция в том случае, если она обрабо­тала сообщение, возвращает TRUE, а в противном случае FALSE. Дру­гими словами, диалоговая функция должна вернуть FALSE в том случае, если ей необходимо передать сообщение для дальнейшей обработки в «недрах» Windows.



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

114



ходимо, чтобы система продолжила обработку сообщения WIVMNITDIALOG, то после обработки этого сообщения программа должна вернуть TRUE. Рекомендую читателю немного поэксперименти­ровать с программой и попробовать после обработки WMJNITDIALOG вернуть вместо TRUE значение FALSE. Думаю, разница будет заметна сразу (по моему мнению, она бросается в глаза).

При обработке сообщения WM_DIALOG в программе производится установка состояний RadioButtons и CheckButtons. Здесь мы встречаемся еще с одной интересной функцией. Давайте вспомним, что любой эле­мент управления является окном. Для управления окнами используются сообщения, а для того, чтобы послать сообщение окну, нужно знать хэндл окна - адресата. При создании диалога мы определяли идентифика­тор элемента управления. Win32 позволяет определить хэндл окна -элемента управления. Для этого предназначена функция GetDlgltemQ, возвращающая искомый хэндл. Теперь мы можем послать сообщение окну с помощью функции SendMessageQ. Таким образом, нам нужно написать что-то типа SendMessage(GetDlgItem(...));

Для того чтобы облегчить жизнь программистам, в Win32 включена функция SendDlgltemMessageQ, объединяющая две упомянутые выше функции в одну. Прототип этой функции можно найти в файле winuser.h:

WINUSERAPI LONG WINAPI SendDlgItemMessageA(HWND hDIg,

int nlDDlgltem, UINT Msg, WPARAM wParam, LPARAM IParam);

WINUSERAPI LONG WINAPI SendD!gItemMessageW(HWND hDIg,

int nlDDlgltem, UINT Msg, WPARAM wParam, LPARAM IParam);

tfifdefUNICODE

#define SendDlgltemMessage SendDlgltemMessageW



#else

#defme SendDlgltemMessage SendDlgltemMessageA

#endif //! UNICODE

Первый аргумент - хэндл диалогового окна, функция диалога по­лучает его от системы при вызове. Второй аргумент - идентификатор элемента управления. Третий аргумент - посылаемое элементу сообще­ние. Для управления кнопками служат сообщения, идентификаторы которых начинаются с ВМ_. Все определенные в winuser.h идентифика­торы приведены в табл 21.

115



Т а б л и ц а 21. Сообщения, посылаемые кнопкам

Идснтифнкачор

Значение

 

Описание

 

ВМ_ОЕТСНЕСК.

 

OxOOFO

 

Получить состояние отметки CheckBox'a или

 

 

 

 

 

RaJioButton'a

 

ВМ SETCHECK

 

OxOOFl

 

Установить отметку в CheckBox'a или RadioButton'a

 

ВМ GETSTATE

 

OxOOF2

 

Получить состояние кнопки

 

BM_SETSTATE

 

ОхООРЗ

 

Установить состояние подсветки кнопки (имитация

 

 

 

 

 

удержания нажатой клавиши мыши на кнопке)

 

ВМ SETSTYLE

 

OxOOF4

 

Изменить стиль кнопки

 

BPv-fcLICK

 

OxOOF5

 

Симуляция нажатия кнопки мыши

 

BM_GETIMAGE

 

OxOOF6

 

Получить хэндл изображения (иконки или bitmap'a),

 

 

 

 

 

связанною с кнопкой

 

BM_SETIMAGE

 

OxOOF?

 

Связать изображение с кнопкой

 

Т а б л и ц а 22. Состояния кнопок и их описание

Идентификаюр

 

Значение

 

Описание

 

BST_ UNCHECKED BST_CHECKED BSTJNDETERMINATE BST PUSHED BST_BST_FOCUS

 

0x0000 0x000 1 0x0002 0x0004 0x0008

 

Checkbox или RadioButton делается неот­меченной (может устанавливаться) ChcckBox или RadioButton делается от­меченной (может устанавливаться) Состояние CheckBox не определено (может устанавливаться) Кнопка нажата (только в ответ на запрос о состоянии кнопки) Кнопка имеет клавиатурный фокус (только в ответ на запрос о состоянии кнопки)

 

<


Четвертый и пятый аргументы - это wParam и IParam посылаемого со­общения. Для каждого сообщения они определяются отдельно. Надеюсь, что читатель разобереться с параметрами этих сообщений самостоятель­но по файлам помощи. Состояния кнопок имеют идентификаторы, начинающиеся с BST_ (табл. 22).

После того, как мы разобрали функцию SendDigIteinMessage(), все ос­тальное в программе вызывать каких-либо трудностей не должно.

Окно сообщений

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

116

Окно сообщений, которое мы неоднократно использовали, является простейшим типом диалогового окна. Его назначение определяется его названием. Ни для чего другого, кроме вывода сообщения пользователю и предложения нажать одну из имеющихся кнопок, эти окна не предна­значены. Тем не менее, работу с этим типом диалогов я хотел бы рас­смотреть очень подробно, так как она очень часто используется не только для выдачи сообщений пользователю, но и для отладки. Функция, с помощью которой создастся окно сообщений, называется MessageBoxQ.

В файле winuser.h эта функция описана следующим образом:

WINUSERAPI int WINAPI McssagcBoxA(HWND hWiid. LPCSTR IpTcxt,

LPCSTR IpCaption, UINT uTypc);

WINUSERAPI int WINAPI MessageBoxW(HWND liWnd . LPCWSTR IpText,

LPCWSTR IpCaption, UINT uTypeV

#ifdcfUNICODE

#define MessageBox MessageBoxW

#clse

#defmc MessageBox McssaucBoxA

#cndi(7/ IUNICODF.

Первый аргумент этой функции - liWnd - хэндл родительского окна, т. е. того окна, которому будут посылаться сообщения от окна сообщений (извините меня, уважаемый читатель, за тавтологию. В данном случае я прошу не путать окно сообщений   ДЛЯ ПОЛЬЗОВАТЕЛЯ с сообщения­ми ДЛЯ ОКОН). Второй аргумент - IpText - указатель на строку, содер­жащую отображаемый внутри окна текст. Перед отображением этот текст может быть отформатирован с помощью функции sprintfQ. Третий аргу­мент - IpCaption -заголовок окна сообщений. (Мне, например, исполь­зующему окна сообщений в основном для вывода отладочных сообще­ний, нравится давать окну заголовки типа «Hurray!» или «At last...».) Четвертый аргумент - иТуре - определяет тип окна сообщений, т. е.: перечень кнопок, отображаемых в окне сообщений; иконку, отображаемую в окне сообщений; кнопку, считающуюся кнопкой по умолчанию; модальность окна сообщений.



Наверное, вы неоднократно видели на экране окно сообщений с раз­личным набором кнопок. Этот набор состоит из кнопок «OK», «Retry», «Abort» и др. Наличие каждой из таких кнопок определяется флагами, установленными в четвертом аргументе. Возможные значения флагов иТуре можно найти в файле winuser.h. Все они начинаются с букв MB (табл. 23).

117



Таблица 23. Типы окон сообщений

Флаг

 

Значение

 

Эффект

 

MB OK

 

OxOOOOOOOOL

 

Кнопка «OK»

 

MB OKCANCEL

 

0x0000000 1 L

 

Кнопки «OK» и «Cancel»

 

MB ABQRTRETRYIGNORE

 

Ox00000002L

 

Кнопки «Abort», «Retry», «Ignore»

 

MB YESNOCANCEL

 

Ox00000003L

 

Кнопки «Yes», «No», «Cancel»

 

MB YESNO

 

0x000000041.

 

Кнопки «Yes», «No»

 

MB RETRYCANCF.L

 

OxOOOOOOOSL

 

Кнопки «Retry», «Cancel»

 

Т а б л и ц а 24. Идентификаторы иконки, появляющиеся в окне сообщений

Флаг

 

Значение

 

Эффект

 

MB ICONHAND

 

0x000000 10L

 

Иконка с изображением знака «Stop»

 

MBJCONQUESTION

 

Ox00000020L

 

Иконка с изображением вопроситель-

 

 

 

 

 

ного знака

 

MBJCONEXCLAMATION

 

0x0000003 OL

 

Иконка с изображением восклицатель-

 

 

 

 

 

ного знака

 

MB ICONASTERISK

 

Ox00000040L

 

Иконка с изображением буквы i

 

MB ICONINFORMATION

 

Ox00000040L

 

Иконка с изображением буквы i

 

MB ICONSTOP

 

0x000000 10L

 

Иконка с изображением знака «Stop»

 

Таблица 25. Идентификаторы кнопки по умолчанию

Флаг

 

Значение

 

Эффект

 

MB DF.FBUTTON1 MB DEFBUTTON2 MB DEFBUTTON3

 

OxOOOOOOOOL 0x00000 100L Ox00000200L

 

Первая кнопка работает но умолчанию Вторая кнопка работает по умолчанию Третья кнопка работает по умолчанию

 

<


Т а б л и ц а 26. Идентификаторы, определяющие модальность окна сообщений

Флаг

 

Значение

 

Эффект

 

MB

 

_APPLMODAL

 

OxOOOOOOOOL

 

Разрешаются переключения на другие

 

MB

 

_SYSTEMMODAL

 

0x00001000

 

приложения Не разрешаются переключения на другие

 

MB

 

TASKMODAL

 

0x00002000

 

приложения Применяется в случае отсутствия роди-

 

 

 

 

 

 

 

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

 

 

 

 

 

 

 

другие окна

 

118



Т а б л и ц а 27. Значения, возвращаемые фунунией iVIessageBoxf)

Нажатая клавиши

Числовое -jiia'K'Miic

 

Возвращаемое функцией значение

 

ок

 

1

 

IDOK

 

Cancel

 

2

 

IDCANCEL

 

Abort

 

3

 

IDABORT

 

Retry

 

4

 

IDRETRY

 

Ignore

 

5

 

IDIGNORF,

 

Yes

 

6

 

IDYES

 

No

 

7

 

IDNO

 

 

 

8

 

IDCLOSE

 

 

 

9

 

IDHELP

 

Кроме этого набора кнопок, uType определяет также и иконку (одну из предопределенных в Win32), которая будет отображаться в окне сообщений. Таблица 24 содержит флаги, определяющие иконку, появ­ляющуюся в окне сообщений.

Следующие флаги определяют, какая из кнопок будет считаться кноп­кой по умолчанию (табл. 25). Модальность окна сообщений определяют флаги, приведенные в табл. 26.

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



Итак, выдавать сообщения мы научились. Не хватает самой малости, выяснить, как приложение узнает о том, какую кнопку нажал пользова­тель. Но здесь дело обстоит просто. Возвращаемое функцией MessageBoxQ значение напрямую определяется тем, какую кнопку нажал пользователь (табл. 27).

К сожалению, два последних значения, которые я нашел в заголо­вочном файле winuser.h, в файлах помощи фирмы Microsoft не описаны Об их назначении можно только догадываться по их названиям.

Мы закончим рассмотрение темы об окнах сообщений очередной де­монстрационной программой. Для того чтобы увидеть, что возвращает функция MessageBoxQ, я воспользовался обычным окном сообщений из интегрированной среды Borland C++ 5,0. В демонстрационной программе я не стал мудрствовать лукаво и выдал на отображение олно-

119



единственное окно сообщений с одной иконкой и тремя кнопками, «Abort», «Retry» и «Ignore». При нажатии клавиш «Retry» и «Ignore» в окне Message появляются сообщения о том, какая клавиша нажата. При нажатии клавиши «Abort» работа программы прекращается:

#include <windows.h>

int   WINAPI   WinMain(HINSTANCE   hlnstance.HINSTANCE   hPrevInstance,    LPSTR IpszCmdLine, int nCmdShow)

{

int nRcsult = IDRETRY; int nlndex; char* pcMessage[] {

"Retry key pressed", "Ignore key pressed"

while( (nRcsult = MessagcBox(NULL, "And what do you want to see?", "See in Message window, please", MB ABORTRETRYIGNORE | MBJCONASTERISK)) != IDABORT7)

{ switch(nResult)

{ case IDRETRY:

nlndex — 0;

break; case IDIGNORE:

nlndex = 1 ;

break;

} OutputDebugString(pcMessage[nIndex]);

} return 1;

На рис. 9 показано окно сообщений, создаваемое программой.

See in Message window, please



Рис. 9. Окно сообщений с тремя кнопками

120



Единственное, что осталось неясным в этой программе - это функция OutputDebugString(). Ее единственным аргументом является указатель на строку, которая должна быть передана отладчику. В случае отсутствия отладчика, содержимое строки появляется в окне сообщений интегриро­ванной среды.



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

СПИСКИ

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

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

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

Список может быть создан вместе с диалоговым окном в качестве ре­сурса, а также посредством использования функции CreateWindow(). В последнем случае в качестве имени класса необходимо указывать «LISTBOX». В подавляющем большинстве случаев списки создаются в ресурсах, поэтому мы остановимся именно на этом способе. При работе в редакторе ресурсов ничего сложного в создании списка нет. Некоторые



121



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

CONTROL  «Заголовок»,   ListboxID,   «listbox»,  styles,  X,   Y,   Width,

Height

Здесь я заменил класс «button» на класс «listbox», ибо именно к этому классу относятся списки. Возможные стили списков мы получим, обра­тившись к файлу winuser.h (табл. 28).

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

Сообщения, обеспечивающие добавление и удаление элемента

Для того чтобы добавить элемент в список, необходимо просто по­слать списку сообщение LB ADDSTRING. При этом wParara должен быть равным нулю, a IParam должен указывать на добавляемый к списку объект. Этот элемент совсем необязательно должен быть строкой. Если у списка не установлен стиль LBS_HASSTRING, то IParam, указывает на объект, связанный с элементом. Для того чтобы получить или изменить эти данные, можно воспользоваться сообщениями LB_GETITbMDATA и LB_SETITEMDATA.

Если у списка установлены стили LBS^SORT и LBS HASSTRING, то строка добавляется в список, после чего происходит сортировка. Если стиль LBS SORT не указан, строка добавляется в конец списка. Если, наоборот, указан стиль LBS SORT, но не указан LBS HASSTRING, то список посылает родительскому окну одно или несколько сообщений WM_COMPAREITEM, которые позволяют определить, где должен быть расположен включаемый элемент. Возвращает это сообщение либо номер, под которым элемент включен в список, либо одно из двух значений, говорящих об ошибке: LB_ERR - встретилась ошибка; LB_ERRSPACE - не хватило памяти для размещения элемента.

Элемент может быть добавлен в список и другим способом. Отличие сообщения LBJNSERTSTRING от предыдущего состоит в том, что wParam этого сообщения содержит номер (считается от нуля) элемента, ПОСЛЕ которого нужно включить данный элемент. Кроме этого, сорти­ровка элементов в этом случае не производится. Возвращаемые значения точно такие же, как и в предыдущем случае.



122



Особо нужно рассмотреть случай, когда необходимо создать список файлов в текущей директории, Для того чтобы облегчить жизнь про­граммисту, в систему было включено сообщение LB_DIR. В качестве wParam этого сообщения записываются атрибуты файлов, имена которых необходимо добавить в список. Возможные значения этого параметра приведены в табл. 29.

Т а б л и ц а 28. Стили окон списков

Флаг'

Значение

Описание

LBS NOTIFY

LBS SORT

LBS NOREDP.AW

LBS MULTIPLESEL

LBS OWNERDRAWFIXED

LBS OWNERDRAWVARIABLE

LBS HASSTRING LBSJJSETABSTOPS

LBS NOINTEGRALHEIGHT

LBS_MULTICOLUMN LBSJVVANTKEYBOARDINPUT

LBS_EXTENDEDSEL LBS DISABLENOSCROLL

LBSJMODATA LBS_NOSEL

LBS STANDARD

0x0001L

Ox0002L Ox0004L

Ox0008L 0x001OL

Ox0040L Ox0080L

OxOIOOL

Ox0200L Ox0400L

OxOSOOL OxlOOOL

0x20001 Ox4000L

Посылает сообщение родительскому окну о щелчке или двойном щелчке клавишей мыши

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

В списке создается несколько колонок, он скроллируется по горизонтали Позволяет приложению обрабатывать ввод с клавиатуры тогда, когда список удержи­вает фокус ввода

Позволяет списку с множественным выбором использовать для выделения клавишу Shift совместно с мышью или другие клавиатурные комбинации Показывать запрещенную линейку прокрутки тогда, когда в списке недоста­точно элементов для прокрутки Устаревший стиль

Элементы списка видны, но выделение запрещено

LBSJvlOTIFY | LBS_SORT | WSJVSCROLL | WS_BORDER

123



Таблица 29. Атрибуты файлов, добавляемых в окно списка

Параметр

Значение

 

Описание

 

DDL_READWRITE

 

0x0000

 

Включить только файлы, доступные для чтения и

 

 

 

 

 

•записи, без дополнительных атрибутов

 

DDLJIEADONLY

 

0x000 1

 

Включить в список только файлы, доступные для

 

 

 

 

 

чтения

 

DDL HIDDEN

 

0x0002

 

Включить в список скрытые файлы

 

DDL SYSTEM

 

0x0004

 

Включить в список системные файлы

 

DDL DIRECTORY

 

0x00 1 0

 

Включить в список поддиректории

 

DDL ARCHIVE

 

0x0020

 

Включить в список архивные файлы

 

DDL POSTMSG

 

0x2000

 

 

 

DDL DRIVES

 

0x4000

 

Включить в список имена дисководов

 

DDL EXCLUSIVE

 

0x8000

 

Включать в список файлы только с указанными

 

 

 

 

 

атрибутами

 

<


IParam сообщения LB_DIR указывает на строку, которая определяет, какие файлы необходимо добавить в список. Строка формируется по правилам, принятым ещё в MS DOS, то есть, к примеру, для того, чтобы отобразить все файлы в директории MyDir на диске С: необходимо записать «c:\VMyDir\\*.*»

Удаление элемента из списка производится посредством посылки списку сообщения LB DELETESTRING. В wParam этого сообщения необходимо указать номер удаляемого элемента. При анализе возвра­щаемого значения необходимо учесть, что при нормальном удалении возвращается число оставшихся элементов списка. Значение LB ERR должно указать программисту на то, что он неверно указал номер уда­ляемого элемента.

Вторая большая группа сообщений - это

Сообщения, обеспечивающие навигацию в списке

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

Наверное, наиболее часто для определения места выделенного эле­мента в списке будет использоваться сообщение LB_GETCURSEL. Никаких параметров это сообщение не использует, и wParam, и IParam должны быть равны 0. Если возвращаемое значение равно LB_ERR, в списке нет выделенных элементов.

Сделать элемент выделенным позволяет сообщение LB_SETCURSEL, wParam которого должен содержать номер элемента, который должен

124



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

Узнать, какая строка или какие данные хранятся в элементе списка, можно с помощью сообщения EB_GETTEXT. wParam должно хранить индекс интересующего нас элемента, a IParam должно указывать на буфер, в который будут записаны строка или указатель на ассоциирован­ные данные.

Число элементов в списке может быть определено посредством сооб­щения LB GETCOUNT. Параметры этого сообщения не используются и должны быть установлены в 0, а возвращает оно число элементов в списке. Одна тонкость - число элементов всегда на 1 больше индекса последнего элемента списка. Например, в списке один элемент. Его помер будет равным нулю, но EB_GETCOUNT вернет 1.



И последней группой сообщений, на которых мы остановимся, являются

Нотификациопные сообщения

Если у списка установлен стиль EBS_NOTIFY, то список будет опо­вещать родительское окно о том, какие события с ним произошли по­средством нотификационных сообщений. Нотификационные сообщения в случае списка - это сообщения WM_COMMAND, у которых младшее слово wParam содержит идентификатор окна списка, старшее слово wParam - код нотификации, a IParam - хэндл окна списка.

Кодов нотификации всего шесть (табл. 30).

Таблица 30. Коды нотификационных сообщений, посылаемых окнами списков

Код нотификации

 

Описание

 

LBN ERRSPACE LBN SELCHANGE LBN DBLCLK LBN SELCANCEL LBN SETFOCUS LBN KILLFOCUS

 

Не хватает памяти Выделенным стал другой элемент Пользователь сделал двойной щелчок клавишей мыши Пользователь снял выделение Список получил клавиатурный фокус Список потерял клавиатурный фокус

 

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

125



Для нормальной компиляции программы требуется файл ресурсов:

#include "list.h"

ListBox DIALOG 50, 50, 150, 140

STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP |

WS_POPUP | WS_VTSIBLE  WS_CAPTION  WS_SYSMENU CAPTION "ListBox Example" FONT 8, "MS Sans Serif

{

PUSHBUTTON "Display", ID_OK, 15, 100, 50, 14

PUSHBUTTON "Cancel", ID_Cancel, 85, 100, 50, 14

CONTROL "Families", ID_MyListBox, "listbox", LBS_STANDARD |

WS_CHILD | WS_VISIBLE | WS_TABSTOP, 15, 16, 120, 65 CONTROL "Families", -1, "static", SSJLEFT | WS_CHILD | WS_VISIBLE, 16, 6,

120, 10 CONTROL "StatusBar", ID_StatusBar, "msctls_statusbar32", 3 | WS_CHILD



WS_VISIBLE, 0,129, 150,12

ListBoxMenu MENU

{ POPUP "&File"

{ MENUITEM "E&xit", IDM_Exit

} MENUITEM "&Display Dialog", IDM_DisplayDialog

}

Далее следует файл заголовков, также используемый в программе:

#defme IDM_Exit 101

#defme IDM_CanceI 102

#define IDM_DisplayDialog    103

#defmeID_OK 201

#defme ID_Cancel 202

«define ID_MyListBox 203

#defme ID_StatusBar 204

И, наконец, основной файл программы:

#include <windows.h>

#include "list.h" ^include <commctrl.h>

HINSTANCE hlnst;

LRESULT CALLBACK ListBoxExampleWndProc(HWND, U1TMT, UINT, LONG);

126

BOOL CALLBACK ListBoxExampleDialogProc(HWND, UINT, WPARAM,

LPARAM);

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,

LPSTR IpszCindParam, int nCmdShow ) {

HWND hWnd ; WNDCLASS WndClass ; MSG Msg; char szClassName[] = "ListExample";

hlnst= hlnstance;

InitCommonControls(); /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style - CS_HREDRAW | CS_VREDRAW;

WndClass.IpfnWndProc = ListBoxExampleWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION),

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.IpszMenuName = "ListBoxMenu";

WndClass.IpszClassName = szClassName;

if ( !RegisterClass(&WndClass)) !

MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0; } hWnd = CreateWindow(szClassName, "ListBox Example Program",

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL); if(!hWnd) {

MessageBox(NULL,"Cannot create window","Error",MBJ3K); return 0;

/* Show our window */

ShowWindow(hWnd,nCmdShow);

UpdateWindow(hWnd);

/* Beginning of messages cycle */

while(GetMcssage(&Msg, NULL, 0, 0))

TranslatcMessage(&Msg);



127

DispatchMessage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK ListBoxExampleWndProc(HWND liWnd, UINT Message,

UINT wParam, LONG IParam )

{

switch(Message) !

case WM_COMMAND: switch(LOWORD(wParam))

{ case IDM_DisplayDialog:

DialogBox(hInst, "ListBox", hWnd, ListBoxExampleDialogProc);

break; case IDM_Exit:

SendMessage(hWnd, WM_CLOSE, 0, 0);

break;

}

break;

case WM JDESTROY: PostQuitMessage(O); return 0;

v i

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

BOOL CALLBACK ListBoxExampleDiaIogProc(HWND hDlg, UINT Message,

WPARAM wParam, LPARAM IParam)

{

int i; // 1 like «Ivanov, Petrov, Sidorov ...» TV-program. ©

LPSTR pszltems[12] = {"Berdiev", "Vasilkov", "Ivanov", "Petrov", "Sidorov", "Johnson", "Jackson", "Tompson", "Pererepenko", "Khabibullin", "Novozhenov", "Mamedov"};

char cMessage[36] = "Message about ";

char cltem[12];

static HWND hListBox;

switch(Message)

{

case WMJNITDIALOG: hListBox = GetD!gItem(hDlg, ID_MyListBox); for(i = 0;i< 12;i++) SendMessage(hListBox, LB^ADDSTRING, (WPARAM) 0, (LPARAM)

pszltems[i]); return TRUE;

128



case WM_COMMAND: switch(LOWORD(wParam)) {

case ID_MyListBox:

if(HIWORD(wParam) == LBN_SELCHANGE) { SendMessagc(hListBox, LB^GETTEXT, SendMessage(hListBox,

LB_GETCURSEL, 0, 0), (LPARAM) cltcm); strcpy(cMessage + 14, cllcm); SendDlgItemMessage(liDlg, ID_StatusBar, SB_SETTEXT,

(WPARAM) 0, (LPARAM) cMessage); }

break;

case ID_Cancel: EndDialogfliDlg, 0); break; } break;

) return FALSE;

В этой программе основное меню окна предлагает отобразить диалог. При запуске диалога (при обработке сообщения WMJNITDIALOG) производится заполнение списка фамилиями. При переносе выделения с выбранного элемента окна списка на другой элемент в строке состояния отображается выбранная фамилия. Таким образом, я продемонстрировал заполнение списка и выборку информации из него, а также работу по обработке нотификашюнного сообще­ния. Вид создаваемого программой окна со списком показан на рис. 10.



ListBox Example



_%«! • families1

Berdiev Ivanov Jackson

Johnson

Mamedov Novozhenov

[Message-about КЬаЪ1Ь|Ш1Ш№Ш;Ь:-

Рис. IO. Диалоговое окно, содержащее окно списка

129



Следующим шагом в изучении органов управления, применяющихся в диалоговых окнах, является изучение достаточно интересного элемента, который называется

ОКНО РЕДАКТИРОВАНИЯ

Окно редактирования - это один из наиболее сложных (с точки зрения реализации, а не использования) и наиболее интересных элементов управления. Фактически этот элемент представляет собой небольшой текстовый редактор, который позволяет вводить текст, редактировать его, копировать в буфер, вставлять из буфера и т. д. Окна редактирования могу быть однострочными и многострочными. Однострочные окна редактирования обычно используются для ввода небольших элементов текста. Например, в Program Manager'e для запуска программы пользова­телю необходимо ввести имя и командную строку этой программы. Примером редактора, большую часть функциональности которого обес­печивается за счет многострочного окна редактирования, является Notepad, который поставляется со всеми версиями Windows.

Окно редактирования можно создать как в файле ресурсов, так и как отдельное дочернее окно, указав при этом предопределенный класс «edit». В этом разделе мы разберем оба случая использования окна редак­тирования .

Перед тем как начать изучение, давайте вспомним, что все поведение элемента управления зависит от того, какие стили мы укажем при его создании. Все стили окна упомянуты в winuser.h. Все они начинаются с букв ES_. Эти стили приведены в табл. 31.

Таблица 31. Стили окна редактирования

Стиль

Значение

 

Описание

 

esj.eft

 

OxOOOOL

 

Текст в окне редактирования выравнивается по

 

 

 

 

 

левому краю

 

F.SJTENTFR

 

0x000 IL

 

Текст в окис редактирования выравнивается по

 

 

 

 

 

правому краю

 

HS RIGHT

 

0x00021.

 

Текст в окне редактирования выравнивается по центру

 

ES MULTILINE

 

Ox()004L

 

Создается многострочнос окно редактирования

 

es uppercase

 

0x00081.

 

Вводимый текст преобразуется в прописные буквы

 

F.S  LOWERCASF.

 

0x00 101.

 

Вводимы:'] текст преобразуется в строчные буквы

 

FS  PASSWORD

 

0x0020!.

 

Все вводимые символы отображаются в виде звез-

 

 

 

 

 

дочек

 

<


130

Окончание табл. 31

(л и .'1 ь

 

Значение

 

Описание

 

ES_AUTOVSCROLL

 

Ox0040L

 

При необходимости текст в многострочном окне

 

ES_AUTOHSCROLL

 

OxOOSOL

 

редактирования скроллируется по вертикали При необходимости текст в окне редактирования

 

ES_NOHIDFSF.L

 

0x0 100 1.

 

скроллируется по горизонтали При потере окном редактирования фокуса ввода

 

 

 

 

 

выделение с текста не снимается

 

ES_OEMCONVERT

 

0x04001.

 

Вводимые символы из одного набора преобразуются

 

ES_READONEY

 

OxOSOOL

 

в символы из друг ого наоора Текст в окне редактирования можно только просмат-

 

ESJWANTRETURN

 

Ox 1 OOOL

 

ривать, но не редактировать При нажатии клавиши Enter в многострочном окне

 

ESJNUMBER

 

0x20001,

 

система вставляет в текст символ возврата каретки Разрешается осуществлять ввод только цифр

 

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

#incl''dc <windo\vs.h>

#definc ID Edit 101

HINSTANCE Must;

ERESUET CALLBACK EditDemoWndProc ( HWND, UINT, U1NT, EONG );

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,

LPSTR IpszCmdParam, int nCmdShow )

i i

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msi-

char szClassName[] - "EditDemo";

hlnst — hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.slyle - CSJIREDRAW | CS VREDRAW;

WndClass.lpfnWndProc = EditDcmoWndProc;

WndClass.cbClsExtra - 0;

WndClass.cbWndExtru ---- 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon ----- Loacllcon (NULEJDI APPLICATION),



131



WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.librBackground - (HBRUSH) GctStockObject (WHITE_BRliSH); WndClass.lpszMcnuNamc - NULL; WndClass.lpszClassNamc ~ sxClassName;

if ( !RegisterClass(&WndClass) )

f

MessageBox(NULL,"Cannot register class", "Error", MB_OK); return 0;

} liWud = C'reateWmdow(szClassName, "EditDemo",

WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT.

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, NULL, NULL,

hlnstance.NULL); if(!hWnc!)

t MessageBo,x(NULL,"Cannot create window ', "Error", MBJDK);

return 0;

/* Show our window */

ShowWindow(hWnd,nCmdSliow);

UpdateWindow(hWnd); /* Beginning of messages cycle */

while(GetMessage(&Msg, NULL, 0, 0))

!

TranslateMessage(&Msg);

DispatchMessagef&Msg); } return Msg.wParam;

LRESULT CALLBACK EditDemoWndProc (HWND hWnd, UINT Message,

UFNT wParam, LONG IParam )

'static HWND hEditWnd; RECT Rcct;

switch(Message)

!

case WMJTREATE:

GetClientRect(hWnd, &Rect);

hEditWnd = CreatcWindow("edit", NULL,

WS_CHILD | WSJVISIBLE | WS HSCROLL! WS VSCROLL| WS BORDER | ES_LEFT | ES MULTILLNE [ ES_AUTOHSCROLL | ES^AUTOVSCROLL, 0, 0, 0, 0,

132

hWnd,

(HMENU) ID_Edit, hlnst, NULL); return 0; case WM_SIZE: MoveWindow(liEditWnd, 0, 0, LOWORD(lParam), HIWORD(IParam),

TRUE); return 0;

case WM_SETFOCUS: SetFocus(hEditWnd); return 0;

case WM_DESTROY: PostQuitMessage(O); return 0; } return DefWindowProc(hWnd,Message,wParam, IParam);

Вид окна, создаваемого программой, показан на рис. 11.

Эта программа создает окно редактирования, которое располагается по­верх основного окна программы. Фактически в программе создается тексто­вый редактор, позволяющий осуществлять набор и редактирование текста, выделять части текста. Выделенные части текста могут быть перемещены в Clipboard посредством нажатия клавиш Shift-Delete, а после нажатия клавиш Shift-Insert текст из Clipboard'a может быть вставлен в окно.

В EditDemo

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



.Этот текст выдепен и подготовлен для перемещения

ib 6i

Рис. 11. Окно редактирования с невыделенным и выделенным текстом

133



Управление окном редактирования, как и всех остальных элемен­ тов управления, осуществляется посредством посылки окну сооб­щений.

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

Следующие пять сообщений не имеют параметров, а их действия очевидны из их названия:

WM_COPY - выделенная часть текста копируется в Clipboard;

WM_PASTE - содержимое Clipboard'a копируется в окно редактиро­вания (данные вставляются только в том случае, если в Clipboard'e нахо­дится текст);

WM_CUT - выделенная часть текста удаляется из окна редактирова­ния и помещается в Clipboard;

WM_CLEAR - выделенная часть текста удаляется из окна редактиро­вания и не помещается в Clipboard;

WM_UNDO - отменяется последняя операция.

Для того чтобы получить границы выделения текста, необходимо ис­пользовать сообщение EM_GETSEL. Младшее слово возвращаемого значения содержит начальную, а старшее слово - конечную позицию выделения плюс 1. Другими словами,

DWORD dwPosition = SendMessage(hEditWnd, EMJ3ETSEL,

(WPARAM) 0, (LPARAM) 0); WORD wBcginPosition = LOWORD(dwPosition); WORD wEndPosition = HIWORD(dwPosition) - 1;

Если программе необходимо выделить часть текста, то она может ис­пользовать сообщение EM_SETCEL, в IParam которого необходимо указать начальную и конечную позиции выделения:

SendMessagefhEditWnd, EM^SETSEL, 0, MAKELONG(wBeginPosition, wEndPosition));

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

SendMcssage(hEditWnd, EM_REPLACESEL, О, (LPARAM) pszNcwTcxt);

134



И наконец, посылка сообщения, с помощью которого приложение может скопировать в собственный буфер набранный пользователем текст, выглядит следующим образом:



SendMessagefhEditWnd, EM_GETLINE, (WPARAM) nLine, (LPARAM) (LPCSTR) pBufler);

В этом сообщении в качестве wParam указывается номер строки ( в случае однострочного окна номер строки игнорируется), а в качестве IParam - указатель на буфер, в который будет записана строка из окна редактирования.

На этом завершается рассмотрение стандартных элементов управления.

Должен заметить, что в Win32 включены новые, так называемые об­щие элементы управления (common controls). Многие из них уже были реализованы в приложениях, работающих в среде Windows 3.x, но до их документирования в Windows 3.x дело не дошло. Встречались попытки описать их реализацию в частности, в MSDN были описаны строка состояния, панель инструментов (toolbar), окно просмотра деревьев. Поэтому, наверное, можно сказать, что появление общих элементов управления ожидалось. И теперь мы приступаем к их изучению.

ОБЩИЕ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ

Перед тем, как мы начнем изучение работы непосредственно элемен­тов управления, мы должны научиться подключать библиотеку, реали­зующую эти элементы. Для нормальной работы программы с общими элементами управления необходимо выполнить два шага. Первым шагом является подключение к программе файла заголовков commctrl.h. Вторым шагом является подключение при линковашш непосредственно библио­теки. Она называется comctl32.dll.

Перед использованием любого из общих элементов управления необ­ходимо загрузить эту библиотеку. Это делается с помощью функции InitCommonControls(), которая описана в файле commctrl.h как

void InitCommonContorls(void);

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

135



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



Первым элементом управления, работу с которым мы рассмотрим, бу­дет строка состояния (status bar). Ранее мы использовали ее в наших демонстрационных программах, но работу с ней не изучали.

РАБОТА СО СТРОКОЙ СОСТОЯНИЯ



Для того чтобы отобразить информацию о текущем состоянии про­граммы, выполняемых операциях и режимах, в программе может исполь­зоваться элемент управления, который называют окном или линейкой состояния (status bar). Мне кажется более удачным термин «строка со­стояния». Включение окна состояния в программу в значительной степе­ни изменяет внешний вид окна и позволяет создать более понятный и удобный для пользователя интерфейс.

Строка состояния может быть включена в описание диалогового окна в файле ресурсов. Но так как окно состояния является стандартным окном, то, естественно, что для его создания могут быть использованы функции и стандартные функции CreateWindowQ и CreateWindowExQ. При этом в качестве имени класса окна необходимо задать макро «STATUSCLASSNAME». В зависимости от того, какую систему коди­ровки использует прснрамма, можно воспользоваться также и «истинным» именем класса (msctls_statusbar32). В commctrl.h эти макро и имена описаны следующим образом:

#ifdcf_WIN32

«define STATUSCLASSNAMEW

«define STATUSCLASSNAMEA

«ifdefUNICODE

«define STATUSCLASSNAME

«else

«define STATUSCLASSNAME

#cndif

#else

«define STATUSCLASSNAME

«endif

L"msctls_statusbar32" "msctls statusbar32"

STATUSCLASSNAMEW STATUSCLASSNAMEA

"msctls statusbar"

136

Тем не менее, для создания окна состояния предусмотрена и отдель­ная функция CreateStatusWindowQ. В файле commctrl.h эта функция определяется так:

WINCOMMCTRLAPI HWND WINAPI CreateStatusWindowA(LONG style,

LPCSTR IpszText, HWND hwndParent, UINT wID);

WINCOMMCTRLAPI HWND WINAPI CreatcStatusWindowW(LONG style,

LPCWSTR IpszText, HWND hwndParent, UINT wID);

#ifdcf UNICODE

«define CreateStatusWindow

«else

«define CreateStatusWindow

«cndif

CrcateStatusWindowW



CreateStatusWindowA

Как можно узнать из описания функции, она требует передачи ей четырех аргументов. Первый аргумент, style, должен определять стиль создаваемого окна. У строки состояния есть единственный собственный стиль, SBARS_SIZEGRIP, который позволяет в правый угол строки состояния добавить «ручку» (внешне, честно говоря, на ручку это совсем не похоже) для изменения ее размеров. Но наличие у строки состояния единственного стиля не мешает комбинировать его со стандартными стилями окон, например с WS^CHILD и WSJVISIBLE.

Второй аргумент - IpszText - является указателем на строку, которая будет отображена в строке состояния сразу после ее создания. Ничего особенного здесь нет.

Третий аргумент - hwndParent - тоже не требует особых объяснений. Он является хэндлом родительского окна строки состояния.

Наконец, четвертый аргумент - uID - идентификатор окна со­стояния.

Попробуйте вызвать эту функцию (не забудьте про InitCommonControls()!) - и вы увидите, что в окне появилась строка состояния с текстом, определенным во втором аргументе этой функции.

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

137



на массив целых чисел (число элементов массива должно быть равно wParam). Каждый элемент в этом массиве должен определять позицию (в координатах родительского окна) правой границы соответствующей части. Если элемент равен -I, то границей панели считается правая гра­ница строки состояния. В случае успешного завершения операции функ­ция, с помощью которой послано сообщение, возвращает TRUE. Значение FALSE должно заставить программиста поискать ошибку в собственной программе.

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



Итак, строка состояния сформирована. Но зачем она нужна без ото­браженной информации? Для того чтобы отобразить определенный текст в строке состояния, нужно послать ей сообщение SB SETTEXT. В качестве wParam этого сообщения используется результат логического сложения двух величин. Первая (iPart) - номер (считая от нуля) панели, в которой необходимо отобразить текст. Вторая (иТуре) определяет, как будет выглядеть текст. В качестве иТуре могут быть использованы значения, приведенные в табл. 32.

Таблица 32. Возможные типы строки состояния

Тип

Значение

 

Описание

 

 

 

0x0000

 

Текст кажется вдавленным в панель

 

 

 

SBT_NOBORDERS

 

0x0 I 00

 

Панель прорисовывается без ограничительны

 

\

 

 

 

 

 

линии

 

 

 

SBT POPOUT SBTJITLREADING

 

0x0200 0x0400

 

Панель прорисовывается выпуклой Используется для языков, в которых чтение и

 

дет

 

SnT_OWNERDRAW

 

Ох 1 000

 

справа налево, как, например, в арабском За прорисовку панели отвечает родительское

 

окно

 

138



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

А теперь, для того, чтобы прочитать текст в панели, необходимо стро­ке состояния послать сообщение WM_GETTEXT. wParam этого сообще­ния должен содержать номер панели, a IParam - указатель на строку, в которую будет записан текст, содержащийся в панели.

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

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

BOOL CALLBACK ButtonsExampleDialogProc(HWND liDIg,

UiNT Message, WPARAM wParam, LPARAM IParam)



int i;

char cMyMessage[80];

RECT Rect;

int nBorders[3];

switch(Message)

case WMJNITDIALOG: // Set states of controls

SendDIgItemMessage(hDlg, nRadioButtonld, BM^SETCHECK,

BST_CHECKED, 0);

for(i - IDC_CHECKBOXI; i <= IDC_CHECKBOX3; i++) if(uCheckBoxesState[i - 208])

SendDlgItemMessage(hDlg, i, BM_SETCHECK, BST_CHECKED, 0); GetClientRect(hDIg, &Rect); nBorders[0] = Rect.right / 3; nBorders[l]-Reel.right/3 * 2; nBorders[2] = -1; SendDlgItemMessage(hDlg, IDC_STATUSBAR, SB_SETPARTS, 3,

(LPARAM) nBorders); return TRUE; case WM__COMMAND: switch(LOWORD(wParam))

case IDCJIADIOBUTTONI: case IDC_RADIOBUTTON2: case IDC_RADIOBUTTON3:

sprintf(cMyMessage,"RadioButton%d", LOWORD(wParam) - 203);

SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT, (WPARAM) 0, (LPARAM) cMyMessage);

139



CheckMenuRadioItem(GetSubMenu(GetSubMenu(GetMenu(hWnd), 1),  0), IDM_RadioButtonl,IDM_RadioButton3, LOWORD(wParam) - 102, MF_BYCOMMAND); return FALSE; caseIDC_CHECKBOXl: case IDC_CHECKBOX2: case IDC_CHECKBOX3:

sprintf(cMyMessage,"CheckBox%d", LOWORD(wParam) - 207); SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT,

(WPARAM) 1, (LPARAM) cMyMcssage); i = LOWORD(wParam) - 208; uCheckBoxesStatc[i] = uChcckBoxesStale[i] == MF_CHECKED ?

MFJJNCHECKED : MF_CHECKED;

CheckMenuttem(GetSubMenu(GetSubMenu(GetMenu(hWnd), I), 1), LOWORD(wParam) - 103, uCheckBoxesStatc[i]); return FALSE; caseIDC_BUTTONl:

SendDlgItemMessage(hDlg, IDC_STATUSBAR, SB_SETTEXT, (WPARAM) 2, (LPARAM) "PushButton"); return TRUE; case IDC_BUTTON2; // Save the state of RadioButtons

i = IDC_RADIOBUTTON 1; while(!SendDlgItemMessage(hDlg, i, BM_GETCHECK, 0, 0))

i++;

nRadioButtonld = i; // Save the state of CheckButtons

for(i = IDC_CHECKBOXl; i <= IDC_CHECKBOX3; i++) uCheckBoxesState[i - 208] = SendDlgItemMessage(hDlg, i, BM_GETCHECK, 0, 0) == 0 ? MFJJNCHECKED : MF_CHECKED; EndDialog(hDlg,0); return TRUE; } break;

} return FALSE;

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



Сравните внешний вид строки состояния, приведенной на рисунке, и строки состояния из раздела о кнопках.

140



Buttons Example



-^^fffpel-r

.. ...„.      ...

Q-"R"a'df6eutton2®,

'

IBadioButtpnS.

Рис. 12. Диалоговое окно со строкой состояния

РАБОТА СО СПИНОМ



Иногда в приложениях встречаются ситуации, в которых полосы про­крутки (скроллирования) не нужны, достаточно только кнопок «вверх» и «вниз». Ярким примером такой ситуации может служить окно, открывае­мое в WinWord'e для Windows'95 при необходимости определить парамет­ры страницы. Естественно, полоса прокрутки для того, чтобы чуть уве­личить значение размера бумаги или сделать поля поменьше, не нужна. Для подобных случаев в Win32 предусмотрен новый элемент управления, называемый спином. Спин является особым видом линейки прокрутки и состоит только из кнопок со стрелками, которые находятся на концах линейки, и не включает линейки прокрутки. Обычно спин используется в одном из двух вариантов. Во-первых, он может применяться а 1а маленькая линейка прокрутки. В этом случае его называют up-down control'oM. Во-вторых, часто он используется в сочетании с другим элементом управле­ния, называемым в этом случае buddy window (приятельским окном). Как правило, этим приятельским окном оказывается окно редактирования. Кстати, в приведенном ниже примере именно окна редактирования и оказываются приятельскими окнами. В этом случае элемент управления называется spin'oM. В данном разделе под словом «спин» будем понимать как up-down control, так и собственно спин.

Спин   может   создаваться   и  в  составе  диалогового   окна   в   файле ресурсов,  и  как  обычное  окно  посредством   использования   функций или _..„.. .....__.,_..,ч. При этом в качестве имени класса

. Соответствующее описание

шдом()

[ЧеобходймЬ указывать можно встретить в

сошшсгц-и:

141



#ifdef JWIN32

#defme UPDOWN_CLASSA

#define UPDOWN_CLASSW

#ifdefUNICODE

#dcfine UPDOWN_CLASS

#elsc

«define UPDOWN_CLASS

#endif

#else



#define UPDOWN_CLASS

#endif

"msctls_updown32" L"msctls_updown32"

UPDOWN_CLASSW UPDOWN_CLASSA

"msctls_updown"

Тем не менее, для создания спина создана специальная функция CreateUpDownControlQ, описание которого мы можем найти в файле commctrl.h:

WINCOMMCTRLAPI HWND WINAPI CreateUpDownControl(

DWORD dwSlylc, int x, int у, int ex, int cy, HWND hParent. int nID, HINSTANCE hlnsl, HWND hBuddy, int nUpper, int nLower, int nPos);

Таблица 33. Стили спина

Стиль

Значение

Описание

UDS  WRAP

UDS_SETBUDDYINT UUS_ALIGNRIGHT

UDS_ALIGNLEFT UDS_AUTOBUDDY

UDS_ARROWKEYS

UDDS_MORZ

UDS NOTHOUSANDS

0x0001

0x0002 0x0004

0x0008 0x0010

0x0020

0x0040 0x0080

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

У спина есть приятельское окно Спин размещается справа от приятельского окна

Спин размещается слева от приятельского окна При изменении позиции спина текст в при­ятельском окне меняется автоматически Разрешается использование клавиатур1»1 для изменения текущего состояния спина Спин располагается горизонтально Не отображать запятую для разделения классов в числах, отображаемых в приятель­ском окне

142

Таблица 34. Сообщения, посылаемые спину

Сообщение

Значение

Описание

UDM_SETRANGE UDM_GETRANGE

UDM_SETPOS UDM_GETPOS

UDM_SETRUDDY UDM GETBUDDY

WMJJSER-HOI WMJJSER+102

WMJJSER+103 WMJJSER+I04

WM_USER+l05 WM USER+I06

Установка диапазона прокрутки, wParam = О, min значение - в старшем слове IParam, max значение - в младшем слове IParam Получение диапазона прокрутки, wParam и IParam должны быть равны 0, возвращает max значение в младшем, a min значение - в старшем слове возвращаемого значения Установка текущей позиции спина, wParam~0, IParam - значение новой позиции Получение текущей позиции спина, wParam и IParam должны быть равны 0, возвращает текущую позицию в младшем слове во (вра­щаемого значения

Определение приятельского окна, wParam хэндлу приятельского окна, IParam - О, возвращает хэндл бывшего приятельского окна



Получение хэндла приятельского окна, wParam и IParam должны быть равны О, возвращает хзндл приятельского окна в младшем слове возвращаемого значения

Параметр dwStyle определяет стиль окна. Все стили, разработанные специально для спина, начинаются с UDS_. Они приведены в табл. 33.

Как и всегда, для управления спином используются сообщения. Обычно при нажатии одной из стрелок спина родительскому окну посы­лается сообщение WM_VSCROLL. При этом IParam этого сообщения содержит хэндл спина. При обработке этого сообщения необходимо быть аккуратным и ire забывать, что сообщения WM VSCROLL могут посту­пать и от других элементов управления.

Остальные сообщения, используемые спином, приведены в табл. 34.

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

#defmeIDM_F.xit l()l

#deime IDM_Pialog 102

#defineIDM About 103

^define ID OK 104

#define IDJIdit 105

fdefine ID Spin 106

143



Кроме этого, ниже приведен файл ресурсов, используемый

#include "spin.h"

SpinDemoMenu MENU

! POPUP "&File"

I

MENUITEM "E&xit", IDM Exit }

MENUITEM "&Dialog", IDM_Dialog

POPUP "&HcIp"

{ MENUITEM "&About", IDM_About

SpinDcmoDialog DIALOG 0, 0. 100, 100

STYLE DS_MODALFRAME | DS_3DLOOK | \VS_POPUP | WS_VISIBLE |

WS_CAPTION | WS_SYSMENU CAPTION "Spin Demo Dialog" FONT 8, "MS Sans Serif f

DEFPUSHBUTTON "OK". 1D_OK. 25, 60, 50, 14

CONTROL "", ID_Edit, "edit", ES LEFT | WSJTHILD j WS_VISIBLE |

WSJJORDER ! \VS_TABSTOP | ES_NUMBER, 25, 20, 50, 12

И наконец, непосредственно текст программы:

^include <windows.li> #includc <commctrl.h> ^include "spin.h"

HINSTANCE hlnst;

LRESULT CALLBACK SpinDemoWndProcfHWND, UINT, UINT, LONG ); BOOL CALLBACK SpinDcmoDialogProc(HWND. UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hinstancc, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )



>

HWND liWnd;

WNDCLASS WndClass ;

MSG Msg;

char szClassNamef] = "SpinDcmo";

hlnst = hlnstance;

144

/* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.stylc = CS_HREDRAW j CS_VREDRAW;

WndClass. IpfnWndProc = SpinDemoWndProc;

WndCiass.cbClsExtra = 0;

WndClass. cbWndExtra = 0;

WndCIass.hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC^ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass. IpszMenuName = "SpinDemoMenu";

WndClass. IpszClassName = szCIassName;

if ( !RegisterClass(&WndClass) ) I

MessageBox(NULL,"Cannot register class", "Error", MB_OK); return 0;

hWnd = CreateWindow(szClassName, "Spin Demo",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, h!nstance,NULL); if(!hWnd) {

MessageBox(NULL,"Cannot create window", "Error",MB_OK); return 0;

InitCommonControlsO; /* Show our window */ ShowWindow(hWnd,nCmaShow); UpdateWindow(hWnd);

/* Beginning of messages cycle */

whilc(GetMessage(&Msg, NULL, 0, 0)) {

TranslateMcssage(&Msg); DispatchMcssage(&Msg); } return Msg.wParam;

LRESULT CALLBACK SpinDemoWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam ) I switch(Messagc)

145

case \VM_COMMAND: switch(wParam)

{ case IDM_Exil:

SendMessage(hWnd, WM_CLOSE, 0, 0);

break; case IDM_Dialog:

DialogBox(hInst, "SpinDemoDialog", hWnd, SpinDcmoDialogProc);

break;

i /

return 0;

case WM_DESTROY; PostQuitMessage(O); return 0;

I return DefWindowProc(hWnd,Message,wParam, IParam);

BOOL CALLBACK. SpinDemoDialogProc(HWND hDlg, UINT Message,

WPARAM wPararn, LPARAM IParam)

!

static HWND hEditWnd; static HWND hSpinWnd; switch(Message)

{

case WMJNITDIALOG: hEditWnd = GetDlgItem(hDlg,ID_Edit);

hSpinWnd = CreatcUpDownControl(WS_CHILD | WS_BORDER |

WS_VISIBLE ] UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 0, 12,50,50, hDlg, ID_Spin, hlnst,



hEditWnd, 100,0,50);

return TRUE; case WM_COMMAND: switch(LOWORD(wParam))

{

case ID_OK: EndDialog(hDlg,0); return TRUE;

} break;

!

return FALSE;

146



Spin Demo Dialog



;.]50



Рис. 13. Диалоговое окно со спином

При выборе элемента «Dialog» из меню протраммы производится ото­ бражение диалогового окна, вид которого показан на рис. 13.

Действия, приводящие к отображению спина, локализованы в той части программы, которая обрабатывает сообщение WM_INITDIALOG. Спин создается посредством вызова функции CreateUpDownControl(), при этом в качестве приятельского окна указывается окно редактирования, созданное как часть ресурса с описанием диалогового окна. Все осталь­ные действия производятся автоматически. Читатель может попробовать изменить стили спина и посмотреть, к чему это приведет.

Т а б ,i и ц а 35. Стили трекбара

Слип,

Значение

 

Описание

 

TBSJTORZ

 

0x0000

 

Определяет горизонтальную ориентацию

 

 

 

 

 

т| скбара

 

TBSJiOTTOM

 

0x0000

 

Шкала расположена пол ползунком (для

 

 

 

 

 

горизонтального трекбара)

 

TBS_RIGHT

 

0x0000

 

Шкала расположена справа от трекбара (для

 

 

 

 

 

вертикального трекбара)

 

TBS AUTOTICKS

 

0x000 1

 

Шкала трекбара создается с делениями

 

TBS VERT

 

0x0002

 

Определяет вертикальную ориентацию

 

 

 

 

 

трекбара

 

TBS_TOP

 

0x0004

 

Шкала расположена над ползунком (для

 

 

 

 

 

горизонтального трекбара)

 

TBS_LEFT

 

0x0004

 

Шкала расположена слева от трекбара (для

 

 

 

 

 

вертикального трекбара)

 

TBS BOTH

 

0x0008

 

Шкала расположена с двух сторон трекбара

 

TBS NOTICKS

 

0x0010

 

Шкала трекбара создается без делений

 

TBS ENABLESELRANGE

 

0x0020

 

Разрешается отображение диапазона

 

TBS_FIXEDLENGTH

 

0x0040

 

При изменении диапазона длина трекбара не

 

 

 

 

 

изменяется

 

TBSJMOTHUMB

 

0x0080

 

У трекбара нет слайдера

 

<


147

РАБОТА С ТРЕКБАРОМ



Очередным клоном линейки прокрутки является ползунок (trackbar или slider). Его внешний вид достаточно эффектен и интересен. Он напо­минает регулятор, используемый в аппаратуре, скажем, в качестве регу­лятора громкости. Небольшим отличием трекбара от линейки прокрутки является то, что у ползунка есть шкала, вдоль которой он движется. Честно говоря, мне очень не нравится переводить на русский слова, к которым я уже привык и которые обычно используются в качестве про­граммистского сленга. Поэтому давайте будем в данном случае под словом «трекбар» понимать весь элемент управления, а под словом «слайдер» - указатель, движущийся вдоль шкалы.

К сожалению, для создания трекбара не предусмотрено специальной функции, поэтому создавать его необходимо посредством вызова функ­ции CreateWindowQ или CreateWindowExQ. При этом в качестве имени класса следует указать макрос TRACKBARjCLASS, который описан в commctrl.h:

#ifdef JATN32

«define TRACKBAR_CLASSA

#deime TRACKBAR_CLASSW

#ifdcf UNICODE

#define TRACKBAR_CLASS

tfdefine TRACKBAR_CLASS

#endif

#else

#defme TRACKBAR_CLASS

#cndif

"msctls_trackbar32" L"msclls trackbar32"

TRACKBAR_CLASSW TRACKBAR_CLASSA

"msctls trackbar"

При создании трекбара могут использоваться стили окна, идентифи­каторы которых начинаются с TBS (табл. 35).

Что еще можно сказать об этих стилях? По-моему, здесь все ясно. Да­же понятно, что стили TBS_HORZ, IBS BOTTOM и TBS RIGHT явля­ются стилями, принимаемыми по умолчанию.

А теперь настало время рассмотреть сообщения, посредством которых осуществляется управление трекбаром. Все эти сообщения приведены в табл. 36.

148



Таблица 36. Сообщения, посылаемые трекбару

Сообщение

Значение

Описание

TBM_GETPOS TBM^GETRANGEMIN TBM_GETRANGEMAX TBMJ3ETTIC

TBM_SETTIC TBM_SETPOS

TBM_SETRANGE

TBM_SETRANGEMIN TBM_SETRANGEMAX

TBM^CLEARTICS TBM_SETSEL

'BM_SETSELSTART "BM_SETSELEND BMJ3ETPTICS BM_GETTICPOS

WMJJSER WM_USER + I WMJJSER + 2 WM_USER + 3



WMJJSER + 4 WMJJSER + 5

WMJJSER + 6

WMJJSER + 7 WMJJSER + 8

WMJJSER + 9 WMJJSER + 10

WMJJSER + 11 WMJJSER+12 WMJJSER+14 WM USER+ 15

wParam и IParam = 0, возвращает текущую позицию слайдера

wParam и IParam = 0, возвращает нижнюю границу диапазона прокрутки wParam и IParam = 0, возвращает верхнюю границу диапазона прокрутки wParam = номер (от нуля) пометки, IParam = 0, возвращает позицию в диапазоне, соответствующую указанной пометке

wParam = 0, IParam = позиции, устанав­ливает пометку в указанной позиции wParam = TRUE, IParam = новой позиции слайдера, слайдер устанавливается в новую позицию

wParam = флаг перерисовки (BOOL), IParam = MAKELONG(wMinimum, wMaximum), устанавливает диапазон для слайдера трекбара

wParam — флаг перерисовки (BOOL), IParam = wMinimum, установка нижней

•раницы диапазона для слайдера wParam = флаг перерисовки (BOOL), IParam = wMaximum, установка верхней границы диапазона для слайдера wParam = флаг перерисовки (BOOL), IParam = 0, удаляет текущую пометку wParam = флаг перерисовки (BOOL), IParam = MAKELONG(wMinimum, «Maximum, установка диапазона выделения в трекбаре wParam = флаг перерисовки (BOOL), IParam = wStart, установка нижней границы выделения wParam = флаг перерисовки (BOOL), IParam = wEnd, установка верхней границы выделения wParam и IParam = 0, возвращает указа-

-ель на массив, содержащий позиции юметок

wParam = номер (от нуля) пометки, ^aram = 0, возвращает позицию пометки оконных координатах

149

Окончание табл. 36

Сообщение

Значение

Описание

TBM_GETNUMTICS TBM_GETSELSTART TBM_GETSELEND TBM_CLEARSEL

TBM_SETTICFREQ TBM SETPAGESIZE

TBM GETPAGESIZE

TBM SETLINESIZE

TBM GETLINESIZE

tbm getthumbrect tbm"getchannelrect tbm_setthumbrect tbm getthumbrect

WMJJSER + 16 WM_USER+ 17 WMJJSER + 18 WMJJSER + 19

WM_USER + 20 WM USER+ 21

WM USER+ 22

WM USER+ 23

WM USER + 24

WMJJSER + 25 WMJJSER + 26 WMJJSER + 27 WM USER + 28

wParam = 0, IParam - 0,возвращает

число пометок

wParam = 0, IParam = 0, возвращает

нижнюю границу выделения



wParam = 0, IParam - 0, возвращает

верхнюю границу выделения

wParam = флаг перерисовки (BOOL),

IParam ~~ 0, сбрасывает текущее

выделение

wParam = частоте следования пометок,

IParam = номеру позиции, начиная с

которой расставляются пометки,

устанавливает шаг расстановки

пометок

wParam ~ 0, IParam — wPageSize,

определяет, на сколько позиций

необходимо передвинуть слайдср при

получении сообщений

ТВ_PAGEDOWN и TBJ>AGEUP,

возвращает предыдущее значение

этого параметра

wParam = 0, IParam = 0, возвращает

значение параметра,о котором

говорится в предыдущей строке

таблицы

wParam — 0. IParam — wLineSize,

определяет, на сколько позиций

необходимо передвинуть слайдер при

получении сообщений

TB_LINEDOWN и TB_LINEUP,

возвращает предыдущее значение

этого параметра

wParam = 0, IParam •- 0, возвращает

значение параметра,о котором

говорится в предыдущей строке

таблицы

150



Т а б л ч ц а 37. Коды нотификации, посылаемые трекбаром

Сообщение

Описание

TB_LINEUP

TBJLINEDOWN

TB_PAGEUP

TBJ'AGEDOWN

TB_THUMBPOSITION

TB_THUMBTRACK TB_TOP

TB_BOTTOM ТВ ENDTRACK

Нажата VKJLEFT (стрелка влево) или VK_UP (стрелка вверх)

Нажала VK_RIGHT (стрелка вправо) или VK_DOWN (стрелка вниз) Нажата VKJNEXT (PageUp) или щелчок мытью перед елайдером Нажата VK_PRIOR (PageDown) или щелчок мышью после слайдера Слайдер зафиксирован после протяжки с помощью мыши

Слайдер протягивается с помощью мыши Нажата VK_HOME (Home), слайдер устанав­ливается в положение, соответствующее верхней границе диапазона Нажата VK_END (End), слайдер устанавлива­ется в положение, соответствующее нижней границе диапазона

Слайдер зафиксирован после перемещения с помощью клавиатуры

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



Сообщениия WM TOP, WM BOTTOM, WM_LINEDOWN и WM LINEUP посылаются только в том случае, если пользователь воз­действует на трекбар с помощью клавиатуры, TB_THUMBPOSITION и TBJTHUMBTRACK посылаются в случае работы с мышью, остальные сообщения могут посылаться в обоих случаях.

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

#с!еЛпе IDM_Exit 101

#define IDM_Dialog 102

ftk-fine IDM About 103

151



«define ID_OK 104 «define ID_Edit             105 «define ID_Spin             106 «define IDJTrackbar     107 А теперь - файл ресурсов. «include "trackbar.h" TrackbarMenu MENU { POPUP "&File"

{ MENUITEM "E&xit", IDM_Exit

}

MENUITEM "&Dialog", IDM_Dialog POPUP "&Help"

{ MENUITEM "&About", IDM_About

TrackbarDialog DIALOG 0, 0, 100, 100

STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE |

WS_CAPTION | WS_SYSMENU CAPTION "Trackbar Demo Dialog" FONT 8, "MS Sans Serif

{

DEFPUSHBUTTON "OK", ID_OK, 25, 73, 50, 14

CONTROL "", ID_Edit, "edit", ES_LEFT | WS_CHILD | WSJV1SIBLE |

WS_BORDER  WSJTABSTOP | ES_NUMBER, 25, 14, 50, 12

А теперь - очередь основного файла программы. «include <windows.h> «include <commctrl.h> «include "trackbar.h"

HINSTANCE hlnst;

LRESULT CALLBACK TrackbarWndProc(HWND, UINT, UINT, LONG ); BOOL CALLBACK TrackbarDialogProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR IpszCmdParam, int nCmdShow ) {

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassName[] = "TrackbarDemo";

hlnst = hinstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.stylc = CSJHREDRAW | CSJVREDRAW;



152



WndClass.lpfnWndProc = TrackbarWndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass. hinstance = hinstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION); WndClass. hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); WndClass. IpszMenuName = "TrackbarMenu"; WndClass. IpszClassName = szClassName; if ( !RegisterClass(&WndClass) ) {

MessageBox(NULL, "Cannot register class", "Error", MB_OK);

return 0;

hWnd = CreateWindow(szClassName, "Trackbar Demo Program",

WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, h!nstance,NULL); if(!hWnd) {

MessageBox(NULL,"Cannot create window", "Error",MB_OK); return 0;

InitCommonControls(); /* Show our window */ ShowWindow(hWnd.nCmdShow); UpdateWindow(hWnd); /* Beginning of messages cycle */

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

TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;

LRESULT CALLBACK TrackbarWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam ) {

switch(Message) {

case WM_COMMAND: switch(wParam)

i \

case IDM_Exit: SendMessage(hWnd, WM_CLOSE, 0, 0);

153



break;

case IDM_Dialog;

DialogBox(hInst, "TrackbarDialog", hWnd, TrackbarDialogPruc); break;

}

return 0;

case WMJDESTROY: PostQuitMessage(O); return 0;

return

DefWindowProc(h\Vnd,Mcssage,\vParam, IParam);

BOOL CALLBACK TrackbarDialogProc(HWND hDlg, UINT Message,

WPARAM wParam, LPARAM IParam)

{

static HWND hEditWnd; static HWND hSpinWnd; static HWND hTrackbarWnd; switch(Message) j

case WMJNITDIALOG: hEditWnd = GctDlgltemfliDlg, ID_Edit); hTrackbarWnd = GctDlgltem(hDlg, ID J'rackbar); hSpinWnd = CreateUpDownControl(WS_CHILD | WSJ3ORDER |

WS_VISIBLE j UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 0, 12,50,50,

hDlg, !D_Spin, hlnst, hEditWnd, 10,0,5); hTrackbarWnd = CreatcWindow(TRACKBAR_CLASS,"Trackbar Demo",

WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_AUTOTICKS, 4,75, 142.40, hDlg, NULL, hlnst, NULL); SendMessagediTrackbarWnd. TBM_SETRANGE, TRUE,



MAKELONG(0,10));

SendMessagc(hTrackbarWnd. TBM^SETPOS, TRUE, 5); return TRUE; case WMJVSCROLL: SendMessagediTrackbarWnd, TBM^SETPOS, TRUE,

GetDlgIiemInt(hDlg, ID_Edit, NULL,!)); return TRUE; case WMJISCROLL: SetDlgltemlntOiDlg, ID_Edit, SendMcssage(hTrackbarWnd,

TBMJ3ETPOS, 0,0), TRUE); case WM_COMMAND: switch(LOWORD(wParam))

case ID OK:

154



EndDialog(hDlg,0); return TRUE;

break;

i i

return FALSE;

После того, как эта программа будет запущена и в основном меню программы будет выбран элемент «Dialog», на экране появится диалого­вое окно, вид которого показан на рис. 14.

Tiackbar Demo DialogQ!



,__...

Рис. 14. Диалоговое окно со спином и трскбаром

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

При написании этой программы использовались две новые функции. Первая, GetDlgItemInt(), описана в файле winuser.h следующим образом:

WINUSERAPI UINT WINAPI GetDlgItemInt(HWND hDlg,     int nIDDlgllem,

BOOL «IpTraiislated. BOOL bSigned);

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

Вторая функция, описанная в том же файле, имеет следующий прототип:

WINUSERAPI BOOL WINAPI SetDIg[temTextA(HWND hDlg. int ninDlgltem,

LPCSTR IpString); WINUSERAPI BOOL WINAPI SetDlgItcmTcxtW(HWND hDlg. int nlDDlgltcm,

155



LPCWSTR IpString);

#ifdefUNICODE

tfdefine SetDlgltemText SetDlgltemTextW

#e!se

#defme SetDlgltemText SetDlgltemTextA

#endif//! UNICODE

Она производит действие, обратное GetDlgItemInt(), т. е. получает в качестве аргумента целое число и возвращает его представление в виде строки. В программе она используется для того, чтобы в окне редактиро­вания отобразить номер позиции слайдера. Все просто, не так ли?



РАБОТА С ИНДИКАТОРОМ (PROGRESS BAR'OM)



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

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

Как и в случае трекбара, специальной функции для создания индика­тора нет. Для создания индикатора необходимо использовать функцию CreateWindowQ или CreateWindowExQ. Для указания имени класса необходимо использовать макрос PROGRESS_CLASS, который в файле commctrl.h описан следующим образом:

#ifdef_WIN32

tfdefme PROGRESS_CLASSA

#define PROGRESS_CLASSW

#ifdef UNICODE

#defme PROGRESS_CLASS

#else

tfdefme PROGRESSJXASS

#endif

#else

#define PROGRESS_CLASS

#endif

"msctls_progress32" L"msctls_progress32"

PROGRESS_CLASSW PROGRESS_CLASSA

"msctls_progress"

156

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

Наверное, для того чтобы использовать индикатор, необходимо опре­делить для него минимальное и максимальное значения (в приведенном примере, когда я говорил об инсталляционных программах, минимальное и максимальное значения равны 0 и 100 соответственно). С этой целью используется сообщение PBM_SETRANGE (WM_USER+1). wParam его должен быть равно 0, a IParam должно определяться как MAKELONG(wMinRange, wMaxRange), где wMinRange и wMaxRange -минимальное и максимальное значения. Функция, пославшая это сооб­щение, возвращает значения старого диапазона. Если обозначить возвра­щаемое значение как IReturn, то LOWORD(lReturn) будет определять нижнюю границу диапазона, a HIWORD(lReturn) будет содержать верх­нюю границу диапазона.



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

Сообщение РВМ DELTAPOS применяется для определения значения, на которое будет увеличена длина индикатора. wParam этого сообщения определяет приращение, a IParam должен быть равным 0. Функция, пославшая сообщение, возвращает значение предыдущей позиции.

Для определения шага, с которым будет увеличиваться длина индика­тора, используется сообщение PBM^SETSTEP, wParam которого опреде­ляет шаг, a IParam должно быть равно нулю. По умолчанию, шаг прира­щения равен 10.

Сообщение РВМ STEPIT указывает индикатору, что необходимо осуществить увеличение длины, используя при этом все текущие уста­новки, т. е. текущую позицию и текущий шаг. Оба параметра сообщения должны быть равны 0.

И, как всегда, демонстрационная программа, с помощью которой читатель сможет увидеть управление индикатором в действии.

Вся программа состоит из трех файлов. Первый - файл заголовков:

fldefinelDC MSCTLS TRACKBARI       101

Adeline IDFvf Exit 101

#dcfme IDM_Dialog 102

#definc IDM_About 103

#defincID_OK. 104

#dcilne ID_Cdit 105

#deiine ID_Spin 106

157



#define ID^ProgressBar 107

Второй файл - файл ресурсов, он также приводится ниже.

#include "ProgressBar.h"

ProgrcssBarMenu MENU

{

POPUP "&File"

!

MENUITEM "E&xit", IDM Exit

MENUITEM "&Dialog", IDM_Dia!og

POPUP "&Help"

{

MENUITEM "&About", IDM_About

ProgressBarDialog DIALOG 0, 0, 100, 100

STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WSJVISIBLE |

WS_CAPTION i WS_SYSMENU CAPTION "Progrcssbar Demo Dialog" FONT 8, "MS Sans Serif

{

DEFPUSHBUTTON "OK", ID_OK, 25, 73, 50. 14



CONTROL "", ID_Edit, "edit", ES_LEFT | WS_CH1LD | WS_VISIBLE [

WS_BORDER | WS_TABSTOP | HS_NUMBER, 25, 14, 50. 12 >

И, естественно, основной файл программы.

#include <windows.h>

#include <commctrl.h>

#include "ProgressBar.h"

HINSTANCE hlnst;

LRESULT CALLBACK ProgressBarWndProc ( HWND, UINT, UINT, LONG ); BOOL CALLBACK ProgressBarDialogProc(I!WND, UINT, WPARAM,

LPARAM);

int WINAPI WinMain ( HINSTANCE hinstancc, HINSTANCE hPrcvInstancc, LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassNamef] = "ProgressBarDemo"

hlnst = hinstancc;

158



/* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.stylc = CS_HREDRAW j CS_VREDRAW;

WndClass.IpfhWndProc = ProgressBarWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL,!DI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, 1DC_ARROW);

Wndclass.hbrBackground - (HBRUSH) GetStockObject (WHITE_BRUSH);

WndClass.IpszMenuNamc = "ProgressBarMenu";

WndClass. IpszClassName = szClassName;

if ( !RegistcrClass(&WndClass)) {

MessageBox(NULL,"Cannot register cIass","Error",MB_OK); return 0;

} hWnd = CrealeWindow(szClassName, "Progressbar Demonstration Program",

WS_OVERLAPPED\VINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, Mnstance,NULL); if(!hWnd) {

MessageBox(NULL,"Cannot create window","Error",MB_OK); return 0;

/* Show our window */ ShowWindow(hWnd,nCmdShow); Update Window(hWnd);

/* Beginning of messages cycle */

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

TranslateMessagc(&Msg); DispatchMessage(&Msg); ( return Msg.wParam;

LRESULT CALLBACK ProgressBarWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

j t

swilch(Mcssage) {

case WM_COMMAND; swilch(LOWORD(wParain))

159

case IDM_Dialog:

DialogBoxfhlnst, "ProgressBarDialog", liWnd, ProgressBarDialogProc);



break; case IDMJExit:

SendMessage(hWnd, WM_CLOSE, 0,0);

break;

}

return 0;

case WM_DESTROY: PostQuitMessage(O); retuni 0;

! return DefWindowProc(hWnd,Message,wParam, IParam);

BOOL CALLBACK ProgressBarDialogProc(HWND hDlg, UINT Message,

WPARAM wParam, LPARAM IParam)

{

static HWND hEditWnd; static HWND hSpinWnd; static HWND hProgressBarWnd; int i; switch(Message)

{

case WMJNITDIALOG: hEditWnd = GetDlgItem(hDlg, ID_Edit);

hSpinWnd = CreatcUpDownControl(WS_CHILD | WS_BORDER |

WS_VISIBLE | UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 0, 12,50,50, hDlg, ID_Spin, hlnst, hEditWnd, 10,0,5);

hProgressBarWnd = CreateWindow(PROGRESS CLASS,

"ProgressBar Demo", WS_CHILD j WSJVISIBLE, 10,75, 130,20, hDlg, NULL, hlnst, NULL); SendMessage(hProgressBarWnd, PBM^SETRANGE, 0,

MAKELPARAM(O.IO));

SendMcssage(hProgressBarWnd, PBM_SETSTEP, 1, 0); SendMessage(hProgressBarWnd, PBM_SETPOS, 5, 0); return TRUE; case WM_VSCROLL: SendMessage(hProgressBarWnd, PBM_SETPOS, GetDlgItemInt(hDlg,

ID_Edit,NULL,l),0); return TRUE; caseWM COMMAND:

160

switch(LOWORD(wParam))

{

case ID_OK: EndDialog(hDlg,0); return TRUE; } break;

} return FALSE;

Вид диалогового окна, возникающего после выбора пользователем эле­мента «Dialog» в главном меню программы, показан на рис. 15.

Piogiessbai Demo .

4XI

 

 

 

,  :  - •' •'

 

"-- ...:.,:.'4i.;-:;;v,W>»i';..-';.v"

---::     \: !^"i';,V;"«Tv::,«™. • ,,.-   ,',   •   -.;•:•*?-   •-•>-/.:

 

cm --..-'

 

••••••

 

:}'--

 

;.                  r.......0.____,:||

 

•i'ij"-.

 

 

 

V^-V""V.

 

Рис. 15. Диалоговое окно со спином и индикатором

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



Далее рассмотрим окна подсказок (Tooltip Controls) и списки изобра­жений (ImageLists). При изучении Win32 я не увидел тех моментов, когда два этих элемента управления использовались бы самостоятельно. Они являются только вспомогательными элементами.

РАБОТА С ОКНАМИ ПОДСКАЗОК



Окна подсказок - это небольшие всплывающие окна, которые содер­жат одну строку текста, объясняющую назначение какого-либо инстру­мента (tool) родительского окна. Под инструментом в данном случае понимается либо элемент управления, присутствующий в родительском

161

окне (пример - полоса инструментов в WinWord'e), либо прямоугольная область внутри рабочей области окна.

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

К сожалению, не все общие элементы управления имеют специальную функцию для своего создания ( © ). Окна подсказок тоже создаются только посредством применения CreateWindow() или CreateWindowExQ. В этом случае для их создания необходимо использовать макрос TOOLTIPS^CLASS, который в файле commctrl.h описан следующим образом:

#ifdef_WIN32

^define TOOLTIPS_CLASSW

#defmc TOOLTIPS_CLASSA

#ifdef UNICODE

ftleflne TOOLTIPS_CLASS

#else

#define TOOLTI PS CLASS

#cndif

#clse

#define TOOLTIPS (CLASS

#endif

L"tooltips_class32" "tooltips_class32"

TOOLTIPS CLASSW TOOI.TIPS_CLASSA

"tooltips_class"

При создании подсказки могут использоваться два стиля, специально разработанные для окон этого типа - TTS_ALWAYSTIP и TTS_NOPREFIX. Подсказка, имеющая стиль ITS ALWAYSTIP, появля­ется при помещении курсора на инструмент вне зависимости от того, активно или не активно родительское окно.

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



Достаточно часто подсказки используются в панели инструментов, при этом каждая кнопка в панели инструментов соответствует элементу

162

меню. При этом подсказки, как правило, совпадают с текстом, отобра­жаемым в соответствующем элементе меню. Если окно подсказки созда­ется со стилем TTS_NOPREFIX, то система автоматически удаляет знак амперсанта, если он присутствует в строке меню.

Помимо этого, подсказка сама по себе может быть активной и неак­тивной. Акпганая подсказка появляется при нахождении курсора на инстру­менте, неактивная подсказка ire отображается ни в каких случаях.

Т а б л и ц а 38. Сообщения, посылаемые "подсказкам"

С сюощсшк'

Описание

ТТМ_ ACTIVATE ТТМ SETDI-LAYTIMF.

ТТМ ADDTOOL TTM_DELTOOL

TTM_NEWTOOLRECT TTM_RELAYEVENT

ТТМ GF.TTOOLINFO TTM_SETTOOLINFO ТТМ НГГП-ST

ТТМ GF.TTEXT TTMJJPDATETIPTEXT TTMJiETTOOLCOUNT ТТМ  ENUMTOOLS

TTM_GETCURRENTTOOL ТТМ WINDOWFROMPOINT

WM USER

WM USER + 3

WM USER • 7

WM USER ~  13

WM USER + i 6

Подсказка делается активной или неактивной. wParam = TRUE - под-ска зка активна, IParam всегда = О Определяются интервал инициализа­ции, интервал отображения и интервал повторного отображения Регистрирует инструмент, с которым будет работать подсказка Удаляет ранее добавленный инстру­мент

Определяет границы окна подсказки Передаст сообщение от мыши окну подсказки для обработки Запрос информации об инструменте, с которым работает подсказка Установка информации для инстру­мента

Определение, попадает ли точка в указанный для инструмента прямо­угольник, и, если попадает, по­лучение информации об инструмен­те

Получение текста подсказки, отображаемой с инструментом Установка текста подсказки для инструмент

Получение числа инструментов, с которыми работает подсказка Позволяет программе последова­тельно перебрать все инструменты, с которыми работает окно подсказки Получение информации о текущем инструменте

Выдача окна подсказки в месте, определяемом не положением курсора, а параметрами сообщения



163



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

Для того чтобы подсказка работала с тем или иным инструментом, не­обходимо этот инструмент включить в список инструментов, с которыми работает окно подсказки. Для этого окну подсказки надо направить сообщение TTM_ADDTOOL. WParam этого сообщения должен быть равен 0, a IParam содержать указатель на структуру типа TOOLINFO. Эта структура описана в файле commctrl.h :

typedef struct tagTOOLINFOA {

UINT cbSize;

UINT uFlags;

HWND hwnd;

UINT uld;

RECT reel;

HINSTANCE hinst;

LPSTR IpszText; } TOOLINFOA, NEAR «PTOOLINFOA, FAR «LPTOOLINFOA;

typedef struct tagTOOLINFOW {

UINT cbSize;

UINT uFlags;

HWND hwnd;

UINT uld;

RECT rcct;

HINSTANCE hinst;

LPWSTR IpszText; } TOOLINFOW, NEAR *PTOOLINFOW, FAR *LPTOOLINFOW;

#ifdef UNICODE tfdcfine TOOLINFO

#define PTOOLINFO

#defme LPTOOLINFO

#else

tfdefine TOOLINFO tfdefine PTOOLINFO tfdefine LPTOOLINFO

#endif

TOOLINFOW PTOOLINFO W LPTOOLINFOW

TOOLINFOA PTOOLINFOA LPTOOLINFOA

Первое поле этой структуры - cbSize - должно содержать размер в байтах структуры типа TOOLINFO. Сам факт присутствия поля, содер­жащего такую информацию, говорит о том, что фирма Microsoft не исключает возможности изменения и/или дополнения этой структуры.

164

Таблица 39. Битовый файлы, определяющие вил и поведение подсказки

Фл;и

Значение

 

Описание

 

TTFJDISHWND

TTF CENTERTIP TTF_RTLREADING

 

0x0 1

0x02 0x04

 

Флаг установлен - поле uld содержит хэндл инструмент;!, иначе - идентификатор инструмента Центрирует подсказку под инструментом Отображает текст справа налево, как в арабском

 

 

 

 

 

Я'|ЫКС

 

TTF_SUBCLASS

 

Ох 10

 

Подсказка должна перехватывать сообщения WM MOUSEMOVE, адресованные инструменту

 

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



Поле hwnd определяет родительское окно инструмента.

Поле uld обычно содержит идентификатор инструмента. Если по­ле uFlags включает TTF_IDISHWND, то поле uld содержит хэндл окна, внутри которого находится область, используемая в качестве инструмента.

Следующее поле - reel - определяет координаты окна инструмента от­носительно левого верхнего угла клиентской области окна, определяемо­го полем hwnd. Если поле uFlags включает флаг TTF_IDISHWND, поле rect игнорируется.

В поле hinst хранится хэндл экземпляра программы, которая со­держит строковый ресурс, определяющий текст подсказки. Если это поле не равно нулю, то поле IpszText содержит идентификатор стро­кового ресурса.

Как, надеюсь, читатель уже понял, поле IpszText может интерпрети­роваться по-разному. Итак, вариант первый. Если значение поля IpszText равно LPSTR TEXTCALEBACK, то именно окно, хэндл которого указан в поле hwnd, получает нотификационное сообщение TTN__NEEDTEXT, уведомляющее о том, что родительское окно инструмента должно опре­делить, какой текст подсказки должен быть отображен. Второй вариант -поле содержит идентификатор строкового ресурса, в котором определен текст сообщения. Этот вариант используется тогда, когда поле hinst не равно 0. Кроме этого, признаком того, что поле содержит идентификатор строкового ресурса, является старшее нулевое слово. И, наконец, третье, наиболее часто использующееся поле, содержит указатель на строку, содержащую текст подсказки.

165



Таким образом, мы видим, что структура типа TOOLINFO полностью определяет, что используется в качестве инструмента - элемент управле­ния или область экрана, а также где находится текст подсказки - в ресур­сах, в строке программы или он определяется родительским окном инст­румента.

В любой момент программа может изменить текст подсказки. Для этого окну подсказки посылается сообщение ТТМ UPDATETIPTEXT, wParam которого должен быть равным 0, a IParam - указывать на структу­ру типа TOOLINFO.

Программа может получить текст, который используется для выдачи подсказки об инструменте с помощью посылки окну подсказки сообще­ния ТТМ GETTEXT. В этом сообщении wParam должен быть равным 0. IParam, как и в предыдущем случае, должен содержать указатель на структуру типа TOOLINFO, в которой определяется инструмент, под­сказка о котором запрашивается. Ноле IpszText указывает на буфер, в который будет записан текст подсказки.



Для того чтобы отобразиться, окно подсказки должно получить сооб­щение от мыши. Так как Windows посылает сообщения только тому окну, поверх которого находится курсор, программа должна использовать сообщение TTM_R.ELAYEVENT для того чтобы транслировать сообще­ние окну подсказки. wParam этого сообщения должен быть равным нулю, a IParam должен содержать указатель на структуру типа MSU, в которой хранится информация о транслируемом сообщении. При этом необходи­мо учесть, что окно подсказки обрабатывает информацию только о сообщениях, приведенных ниже: WM IJiUTTONDOWN;

" WM LBUTTONUP;

* WM]MUUTTONI)OWN: ]WM MBUTTONUP;

* WM^MOUSEMOVF;

* WM RBUTTONDOWN;

* WM RBUTTONUP.

Если инструмент представляет собой прямоугольную часть окна, то тогда никаких сложностей не появляется. Если же инструмент является системным окном (таким, например, как кнопка), то в этом случае возни­кают определенные трудности. Программа должна будет или перехваты­вать сообщения посредством использования hook'oB, или подменить оконную функцию системного окна (осуществить subclassing). К сожале­нию, рассмотрение вопросов, связанных с поок'амн и subclassing'oM выходит за рамки этой книги, поэтому я вынужден буду остановиться только на подсказках, связанных с областью окна. Уважаемый читатель!

166

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

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

При работе окно подсказки использует три определенных временных интервала. Первый из них, называемый интервалом инициализации, определяет период, в течение которого курсор должен находиться в пределах интересующей области для того, чтобы отобразилась закладка. Второй - интервал повторного отображения - определяет задержку между последовательными отображениями окна подсказки в тех случаях, когда курсор скользит по инструментам, например, по панели инструментов. Третий интервал - интервал отображения - определяет время, в течение которого подсказка находится на отображении в тех случаях, когда курсор находится внутри интересующей области или в преде­лах границы инструмента. Все чти периоды могут быть определены с помощью сообщения ТТМ SETDELAYT1ME. wParam этого сообще­ния определяет, какой интервал устанавливается. IParam определяет длительность интервала в миллисекундах. Допустимые значения wParam приведены в табл. 40.



В тол) случае, когда инструментом является область окна, размеры и/или положение которой изменились, окну подсказки необходимо послать сообщение ТТМ NEWTOOLRECT для того, чтобы подсказка появлялась в нужном месте. wParam этого сообщения всегда равен 0.

Тай .1 и и а 40 Идентификаторы временных интервалов

П„р

 

Значение

 

Описание

 

TTDT AUTOMATIC

TTDT RP.SHOW ТТГУГ"ЛЬ'ТОРОР TTDT INITIAL

 

0 1

 

Все интервалы вычисляются автоматически на основе IParam Определяется интервал повторного отображения Определяется интервал отображения Определяется интервал инициализации

 

167

IParam этого сообщения должен указывать на структуру типа TOOLINFO, поля hwnd и uld которой должны определять инструмент, а поле rect - новые границы инструмента. В том случае, когда инструмент реализован как окно, информировать подсказку о его изменении не нужно, так как подсказка определит факт нахождения курсора в границах инструмента по хэндлу окна.

Перед своим отображением окно подсказки посылает родительскому окну нотификационное сообщение TTN_SHOW, а перед скрытием -TTN_POP. В данном случае нотификационные сообщения посылаются с помощью сообщения WM NOTIFY.

Для получения информации об инструменте программа может ис­пользовать сообщения TTM_GETCURRENTTOOL и TTMJ3ETTOOLINFO. Изменить информацию об инструменте можно с помощью сообщения TTM_SETTOOLINFO. Если программе требуется, чтобы подсказка с данным инструментом больше не работала, окну подсказки нужно направить сообщение ТТМ DELTOOL. Параметры этих сообщений однотипны и ничего сложного в них нет. Рекомендую читателю изучить работу этих сообщений самостоятельно.

Как уже было сказано, сами по себе окна подсказок не используются, поэтому демонстрационной программы я не приведу. Тем не менее, в разделе, посвященном закладкам, будут даны примеры другого способа использования подсказок, применимого, к сожалению, только к общим элементам управления, которые появились в Windows NT и Windows'95. В чем состоит этот способ? Дело в том, что при создании некоторых элементов управления можно указать стиль, позволяющий этим окнам реагировать на сообщения WMJNOTIFY с нотпфикационным кодом TTNJNEEDTEXT. Скажем, для закладок этот стиль называется TCSJTOOLTIPS, для панели инструментов - TBSJTOOLTIPS и т. д. В этих случаях родительскому окну элемента управления в качестве IParam сообщения WM_ NOTIFY передается указатель на структуру типа TOOLTIPTEXT, описание которой, находящееся в файле commctrl.h имеет следующий вид:



typcdef struct tagTOOLTIPTEXTA {

NMHDR heir;

LPSTR ipszTcxt;

char s/Tcxt[8()];

HINSTANCE hinst;

UINT u Flags; ! TOOLTIPTEXTA, FAR «LPTOOLTIPTEXTA;

168



typcdef struct tagTOOLTIPTEXTW {

NMHDR hdr;

LPWSTR IpszText;

WCHAR szText[80];

HINSTANCE hinst;

UINT uFIags; ) TOOLTIPTEXTW, FAR *LPTOOLTIPTEXTW;

#ifdcf UNICODE

#define TOOLTIPTEXT Wcllne LPTOOLTIPTEXT

#else

tfdefmc TOOLTIPTEXT

#definc LPTOOLTIPTEXT

#endif

TOOLTIPTEXTW LPTOOLTIPTEXTW

TOOLTIPTEXTA LPTOOLTIPTEXTA

Первое поле этой структуры, тоже структура, но типа NMHDR, опи­сана в файле winuser.h так:

typcdef struct tagNMHDR

i

HWND hwndFrom;

UINT idFrom;

UINT code;         //NM_code }   NMHDR; typedef NMHDR FAR * LPNMHDR;

hwndFrom - хэндл элемента, пославшего нотификационное сообщение, idFrom - идентификатор этого элемента, code - код нотификационного сообщения. Вроде бы все ясно.

Второе поле стуктуры типа TOOLTIPTEXT (как бы не запутаться с этими структурами!) - IpszText - может содержать указатель на строку, выдаваемую в качестве подсказки, или, если поле hinst не равно 0, содер­жит идентификатор строкового ресурса, определяющего текст выдавае­мой подсказки.

Вместо того чтобы определять указатель на строку, можно скопиро­вать эту строку в буфер szText, который является третьим полем структу­ры типа TOOLTIPTEXT.

Поле hinst является хэндлом экземпляра, содержащего строковый ре­сурс с текстом подсказки. Если IpszText является указателем на строку подсказки, это поле должно быть равным NULL.

И наконец, последнее поле - uFIags - содержит комбинацию флагов TTFJDISHWND и TTF RTLREADING, которые были рассмотрены ранее.

169



Анализируя поля этих двух структур, можно определить элемент, для которого определяется подсказка. Для того чтобы эта подсказка появи-лась на экране, достаточно определить либо указатель на строку подсказ­ки, либо ее идентификатор в таблице строк (не забыть при этом о поле hinst!), либо скопировать эту строку в предлагаемый буфер. И все! При­мер подобного использования подсказок приведен в разделе о работе с закладками.



РАБОТА СО СПИСКОМ ИЗОБРАЖЕНИЙ



И Windows предусмотрен интересный элемент, который лично я могу назвать элементом управления с большой натяжкой. Тем не менее, этот элемент активно используется при работе с другими элементами управ­ления, например, с закладками, речь о которых еще впереди. Я имею в виду список изображений (Image List). Он представляет собой коллекцию изображении одинакового размера, к каждому из которых можно осуще­ствить доступ по его индексу. Список изображении используется для эффективного управления и манипулирования большими наборами изображении.

Наверное, этот элемент управления самостоятельно не используется еще и потому, что он не является окном. Список изображений - это всего-навсего структура в памяти, обеспечивающая простой доступ к изобра­жениям.

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

Списки изображений могут быть немаскированными и маскирован­ными. Немаскированный список представляет собой один большой цветной bitmap, который, в свою очередь, состоит из одного или несколь­ких изображений. Маскируемый список состоит из двух больших bitmap'oB, первый из которых, цветной, содержит непосредственно список изображений, а второй, монохромный, содержит список масок -по одной для каждого элемента.

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

Для создания списка изображений приложению необходимо вызвать функцию ImageList Create(), которая описана в файле commctrl.h сле­дующим образом:

WINCOMMCTRLAPI HIMAUEL1ST WINAM

lst Crcatc(int ex. int су,

UfNT Hags. int cliiitial, inl cGrow):



G этой функции первые два аргумента, сх и су, определяют размер в пикселах каждого изображения. Третий аргумент, flags, указывает тип списка изображений. Для каждого типа в файле commctrl.h предусмотрен. макрос, начинающийся с ILC . Список этих типов приведен в табл. 41.

Размер создаваемого bitmap 'а вычисляется, исходя из значения четвертого аргумента. Этот аргумент определяет, сколько изображений должен включать bitmap. Если на каком-то этапе число изображений, включенных в bitmap, достигнет предельного значения, то система авто­матически расширит bitmap, добавив место для хранения еще определен­ного числа изображений, которое определяется последним параметром функции - cGrow.

Т а Я л и ц а 41. Флаги, используемые при создании списка изображении

 

Флаг

 

Значение

 

Описании

 

 

 

ILC COLOR

 

0x1)000

 

Используется флаг ни умолчанию, обычно

 

 

 

 

 

 

 

ILC COLOR4. для старых драйверов -

 

 

 

 

 

 

 

ILC COLORDDH

 

 

 

ILC MASK

 

0x0001

 

Создается маскированный bitmap

 

 

 

ILC_COLORDDO

 

OxOOFE

 

Используется bitmap, зависящий от устройства

 

 

 

 

 

 

 

(устаревший формат)

 

 

 

ILC_COI.OR4

 

O.\0004

 

U качестве bitmap 'а. содержащею изображения,

 

 

 

 

 

 

 

используется 16-цвстнын bitmap

 

 

 

ILC COLOR»

 

0x0008

 

И качестве bitmap'a, содержащего изображения.

 

 

 

 

 

 

 

используется 256-цветный bitmap

 

 

 

II. С COLOR 1П

 

0x0010

 

I) качестве bitmap'a. содержащего изображения.

 

 

 

 

 

 

 

используется bitmap, допускающий одновременное

 

 

 

 

 

 

 

использование до 65536 цветок

 

 

 

If С COLOR24

 

0x0018

 

В качестве bitmap'a, содержащего изображения,

 

 

 

 

 

 

 

используется bitmap, допускающий до 2**24 цветов

 

 

 

ILC COLOR32

 

ОхП('.20

 

Н кичестнс hitmap'a. содержащего изображения.

 

 

 

 

 

 

 

нсполмусгси bitmap, допускающий до 2**J2 цвет он

 

 

 

ILC I'ALETIl-

 

ОхОЙОО

 

Со списком изображений используется цнстовая

 

 

 

 

 

 

 

палитра

 

<


170

171

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

Но что же происходит при создании списка изображений? Помимо создания непосредственно bitrnap'a с указанными характеристиками, функция создает контекст, совместимый с экраном, и выбирает создан­ный bitmap в качестве текущего для этого контекста. В случае маскиро­ванного bitmap'a функция создает два экранно-совместимых контекста, при этом для одного в качестве текущего она выбирает bitmap с изобра­жениями, а для другого - bitmap с масками.

Естественно, что при использовании такой техники резко уменьшает­ся время, необходимое для копирования изображения на экран. Как результат - программы, использующие список изображений, могут работать значительно быстрее тех, которые перед использованием выну­ждены загружать изображения из ресурсов.

Для того чтобы удалить список изображений из памяти (отдельные изображения, из которых состоит список, остаются на своих местах, уничтожаются только указатели, что, собственно, и делает набор изобра­жений списком), необходимо вызвать функцию ImageList_Destroy(), которая описана следующим образом:

WINCOMMCTRLAPI BOOL WINAPI ImageList_Dcstroy(HIMAGELIST himl);

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

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

Для того чтобы добавить изображение в список, необходимо вос­пользоваться функцией ImageList_Add(), описание которой имеет следующий вид:

WINCOMMCTRLAPI int WINAPI ImageList_Add(HIMAGELlST him!,

HBITMAP hbmlmage, HBITMAP hbmMask);

Первый аргумент этой функции - himl - очевиден: хэндл списка изо­бражений. Второй аргумент - hbmlmage - представляет собо хэндл добав­ляемого в список изображения. Третий аргумент - hbmMask - хэндл



172



монохромного bitmap'a, который содержит маски. В случае немаскиро­ванного списка третий аргумент игнорируется.

Описание функции ImageList_AddMasked() приведено ниже:

WINCOMMCTRLAPI int WINAPI ImageList_AddMasked(HIMAGELIST himl,

HBITMAP hbmlmage, COLORREF crMask);

Эта функция действует почти так же, как и предыдущая, но маска ге­нерируется автоматически. Для генерации маски необходимо задать цвет. Если в изображении встречается пиксель указанного цвета, то цвет этого пикселя заменяется на черный, а соответствующий бит маски - на 0. В результате, при прорисовке изображения пиксели указанного цвета становят­ся прозрачными. Аргументы этой функции также очевидны. Первый - хэндл списка изображений, второй - хэндл включаемого в список изображения, третий - цвет пикселей, которые необходимо сделать прозрачными, при прорисовке.

Для добавления в список иконки или курсора используется макрос ImageList_AddIcon(), первым аргументом которого необходимо указать хэндл списка изображений, а вторым - хэндл добавляемой иконки или добавляемого курсора. Макрос возвращает индекс добавленного изобра­жения.

При необходимости программа может создать новую иконку или кур­сор, используя изображение и маску из списка изображений. Для этой цели необходимо использовать функцию ImageList_GetIcon(). Её описа­ние приведено ниже:

WINCOMMCTRLAPI HICON WINAPI ImageList_GetIcon(HIMAGELIST himl,

int i, UINT nags);

Первый аргумент этой функции - хэндл списка изображений. Второй -индекс изображения, на основе которого будет создана иконка или кур­сор. Третий аргумент - флаги прорисовки, которые можно найти в табли­це, приведенной при описании функции ImageList_Draw().

Функция возвращает хэндл созданной иконки или курсора.

К этому моменту мы научились добавлять изображения в список. А для удаления изображения нужно вызвать функцию ImageList_Remove(), описанную так:

WINCOMMCTRLAPI BOOL WINAPI ImageList_Removc(HIMAGELIST himl,

int i);

173

Автор уверен, что даже не заглядывая дальше, читатель догадался, что первым аргументом является хэндл списка изображений, а вторым -индекс удаляемого изображения. Если вместо индекса изображения подставить -1, то функция удалит все изображения из списка, но не удалит сам список. Для удаления всех изображений из списка можно воспользоваться макросом ImageList_RemoveAH(), единственным аргу­ментом которого является хэндл списка изображений.



Для замены изображения в списке служит функция ImageList_Replace(). Её описание находим в файле commctrl.h:

WINCOMMCTRLAPI BOOL WINAPI ImagcList_Replace(HIMAGFLLIST him],

int i,

HBITMAP hbmlmage, HBITMAP hbmMask);

Аргументы этой функции вполне понятны: первый - хэндл списка изображений; второй - индекс замещаемого изображения; третий и четвертый - хэндлы нового изображения и его маски. Если список немас­кированный, четвертый аргумент игнорируется.

Очередная функция - ImageList_ReplaceIcon() - описана так:

WINCOMMCTRLAPI int WINAPI ImageList_RcplaceIcon(HIMAGELIST himl,

int i. HICON hicon);

Нужно ли описывать аргументы этой функции?

Если в функции ImageEist_ReplaceIcon() второй аргумент заменить на -1, то иконка или курсор будут не замещать старое изображение, а добав­ляться в список. Этот нюанс используется в макросе ImageList_ AddlconQ, аргументами которого являются хэндл списка изображений и хэндл добавляемой иконки или курсора.

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

Для того чтобы прорисовать изображение, хранящееся в списке, необ­ходимо использовать функцию ImageListJDraw(). Вполне понятно, что для прорисовки изображения вызывается что-то типа функции BitBlt(), которая требует два контекста устройства, координаты и прочее. Вспом­ним, что контекст устройства, в котором хранится список изображений, у нас ' есть. Координаты отдельного изображения моментально вычисляются по его индексу. И что остается? Хэндл контекста, на кото-

174



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

Функция ImageList_Draw() имеет следующий прототип:

WINCOMMCTRLAPI BOOL WINAPI ImageList_Draw(HIMAGELIST himl,

int i,

HOC hdcDst, intx, int y, UINT (Style);

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



Привлекательность использования списков изображений повышает еще одно обстоятельство. При использовании списков возможно исполь­зование специальных функций, позволяющих пользователю перемещать изображения (drag-and-drop) на экране: во-первых, с минимальными затратами на написание нового кода; во-вторых, без заметного мерцания.

Таблица 42. Флаги прорисовки отдельного изображения в списке изображений

Значение

ILD_NORMAL

ILD TRANSPARENT

ILD_BLEND25 ILD_BLEND50

ILD_MASK

ILD IMAGK

ILD OVERLAYMASK

ILD SELECTED

ILD FOCUS

ILD BLEND

0x0002 0x0004

0x0010

0x0020

OxOFOO

ILD BLEND50 ILD BLEND25 ILD~BLEND50

Обычное копирование изображения Каждый белый бит маски заставляет соответствующим бит изображения прорисовываться как прозрачный Снижение интенсивности цветов изобра­жения на 25 %

Снижение интенсивности цветов изобра­жения на 50 % Прорисовка маски Прорисовка изображения

175



Операция перемещения изображения начинается вызовом функции ImageList_BeginDrag(), прототип которой выглядит следующим образом:

WINCOMMCTRLAPI BOOL WINAPI ImageList_BcginDrag(IMAGELIST himlTrack,

int iTrack, int dxHotspot, int dyHotspot);

В число аргументов этой функции входят хэндл списка изображений, индекс перемещаемого изображения и координаты «горячего пятна» (hot spot'a - о проблемы перевода!) внутри изображения. Горячее пятно - это пиксель, по которому определяется точное положение изображения на экране. Обычно горячее пятно определяется таким образом, чтобы оно совпадало с горячим пятном курсора мыши. С этим горячим пятном нам еще предстоит помучиться.

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

Следом за функцией ImageList_BeginDrag() обычно используется функция ImageList_DragEnter(). Описание этой функции можно найти в файле commctrl.h:



WINCOMMCTRLAPI BOOL WINAPI ImagcList_DragEnter(IIWND hwndLock,

int x, int y);

Эта функция запрещает обновление указанного окна во время выпол­нения операции «drag-and-drop» и прорисовывает перемещаемое изобра­жение в промежуточных позициях (до того, как будет отпущена клавиша мыши). В некотором смысле можно сказать, что эта функция делает изображение курсором мыши (конечно, нельзя понимать это буквально, сходство чисто внешнее). Первый аргумент этой функции понятен -хэндл окна, обновление которого запрещается. Этим окном является то окно, в котором производится перемещение изображения. Второй и третий аргументы определяют координаты той точки, в которой необхо­димо прорисовать изображение. ВНИМАНИЕ! В данном случае необхо­димо указывать координаты оконные, а не координаты в рабочей области окна. Таким образом, до обращения к этой функции необходимо опреде­лить ширину границы окна, высоту заголовка и, при необходимости, ширину полосы меню. В демонстрационной программе это сделано при

176



функции GetSystemMetrics(). Рекомендую читателю изучить эту функцию самостоятельно.

Функция ImageListJDragMove() описана следующим образом:

WINCOMMCTRLAPI BOOL WINAPI I mage I ist_DragMove(int x, int y);

Эта функция перемещает изображение, но не прорисовывает его. По­пробуйте в демонстрационной программе убрать функцию lmageList_DragEnter() и посмотреть, что получится.

Последней функцией, обеспечивающей «drag-and-drop», является lmageList_Endnrag(). Эта функция завершает перемещение, но не разре­шает обновление окна и не производит прорисовку перемещенного изображения. Для разрешения обновления окна необходимо вызвать функцию ImageList_DragLeave(), передав ей в качестве аргумента хэндл окна, а затем прорисовать изображение, например, с помощью функции ImageList Draw().

Л теперь, как всегда, демонстрационная программа. В этой программе при создании окна производится прорисовка двух икон в левой верхней части рабочей области. Иконки могут быть скопированы в другое место при использовании операции «drag-and-drop». Думаю, читателю не соста­вит труда при необходимости изменить эту программу так, чтобы иконки не копировались, а перемещались.



При разработке программы я допустил определенного рода плагиат. Одну из иконок я «выдрал» из программы pview95, другую - из примера mixtree, поставляемых с Borland Си- 5.0. Если читатель захочет, то он легко может заменить иконки на свои.

//include <windo\vs.h> //include <commctrl.h>

//define CX_ICON 32 //define CYJCON 32

UINSTANCE hlnst;

LRLSULT CALLBACK ImageListWndProc ( HWND, UINT, UINT, LONG );

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hl'revlnstancc, LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassNamcf] ^ "ImageList";

177

hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CS_HREDRAW | CS_VREDRAW;

WndClass.lpfnWndProc = ImageListWndProc;

WndClass.cbClsExtra = 0;

WndCIass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObjcct (WHITE_BRUSH);

WndClass.lpszMenuName = "";

WndClass.lpszClassName = szClassName;

if ( !RegisterClass(&WndClass)) {

MessageBox(NULL,"Cannot register class","Error",MBJ3K); return 0;

}

hWnd = CreateWindow(szClassName, "Image List Demo Program",

WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, CWJJSEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL);

if(!hWnd)

{

MessageBox(NULL,"Cannot create window», «Error», MB_OK); return 0; }

InitCommonControlsO; /* Show our window */ ShowWindow(hWnd,nCmdSliow); UpdateWindow(hWnd);

/* Beginning of messages cycle */

whi!e(GetMessage(&Msg, NULL, 0, 0))

t i

TranslateMessage(&Msg); DispatchMessage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK ImageListWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

<

static HIMAGELIST hlmageList;

static int i;

static HOC hDC, hPaintDC;

178



PAINTSTRUCT PaintStruct;

RECT rBigRect = {0, 0, CXJCON * 2, CYJCON};



RECT rLittleRecll = {0, 0. CX_ICON, CYJCON};

static POINT Point, pHotSpot;

static BOOL bCapture = FALSE;

static int nXBordcr, nYBordcr. nYCaption;

switch(Message) {

case WM_CREATE: hDC = GctDC(hWnd);

hlmageList - IniageListJJreate(CXJCON, CYJCON, ILCJvIASK, 3, 3); ImageList_AddIcon(hImageList, Loadlmage(hlnst, "Mixtree.ico",

IMAGEJCON, 0, 0, LRJJ3ADFROMFILE)); ImageList_AddIcon(hImageList, Loadlmage(hlnst, "Pview95.ico",

IMAGEJCON, 0, 0, LRJ.OADFROMFILE)); nXBordcr = GetSystemMclrics(SM_CXBORDER); nYBorder = GetSystemMctrics(SMJCYBORDER); nYCaption = GetSystemMetrics(SMJCYCAPTION); return 0;

case WMJ'AINT:

hPaintDC = BcginPaint(hWnd, &PaintStruc(); for(i = 0; i < 2; i++) Image-ListJ)raw(hImageList, i, hPaintDC, i * CXJCON, 0,

ILD_NORMAL); EndPaint(hWnd. &PaintStruct); return 0;

case WMJ.BUTTONDOWN: Point.x = LOWORD(lParam); Point.y = HIWORD(lParam); if(PtInRect(&rBigRect, Point)) >

SetCapture(hWnd); bCapture = TRUE; if(Pt!nRect(&rLittlcRectI, Point))

i = 0; else

i= I;

pHotSpot.x = Point.x - i * CXJCON; pHotSpot.y = Point.y;

ImageListJ3eginDrag(hImageList, i, pHotSpot.x, pHotSpot.y); ImageListJJragEnterfhWnd, Point.x + nXBordcr, Point.y + nYBorder +

nYCaption); }

return 0;

case WMJVIOUSEMOVE: iffbCapture) ImageListJ>agMove(LOWORD(!Param), HIWORD(lParam) + nYBorder

-i- nYCaption); return 0;

179



case WM_LBUTTONUP: if(bCapture)

ImageListJSndDragO;

ImageList_DragLeave(hWnd);

ImageListJ>aw(hImageList, i, hDC, LOWORD(lParam)- pHotSpot.x,

HIWORD(lParam) - pHolSpot.y, ILDJMORMAL); ReleaseCapturc(); bCapture = FALSE;

}

return 0;

case WM_DESTROY: ReleascDC(hWnd, hDC); ImageList_Destroy(hImageList); PostQuitMessage(O); return 0;

\ j

return DefWindowProc(hWnd,Message,wParam, IParam); >

Вид окна до произведения операций «drag-and-drop» показан на рис. 16. На рис. 17 приведен вид этого же окна после выполнения нескольких операций копирования иконок.

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



В Image List Demo Progiam



Рис. 16. Окно с двумя изображениями in списка изображений до операции "drag-and-drop"

180



Ш Image List Demo Program

mmm\



Рис. 17. Предыдущее окно после нескольких операций "drag-and-drop"

РАБОТА С ЗАКЛАДКАМИ



Достаточно интересным элементом управления, появившимся только в Win32, являются закладки. Их появление, как и появление большинства общих элементов управления, давно ожидалось. Этот элемент действует подобно «алфавиту» в записной книжке, при выборе определенной буквы (в данном случае - определенной закладки) всплывает нужная страница (в данном случае - диалоговое окно). Читатель вспомнит, надеюсь, интер­фейс электронной таблицы Excel 5.0 и рабочие листы в ней, которые можно было перебрать с помощью переключателей в нижней части таблицы. Эти переключатели и являлись закладками (tab control'ами). Связав каждую из закладок со страницей информации, возможно разместить несколько С'фаниц информации на одном и том же месте. Специальный тип закладок действует как кнопки - при выборе закладки вместо отображения очередной страницы просто производится посылка команды.

К сожалению, и в этом случае специальной функции для создания ок­на не предусмотрено. Программист должен использовать одну из функ­ций - CreateWindowQ или CreateWindowExQ. При этом в качестве имени класса необходимо указать макрос WC_TABCONTROL, который описан в файле commctrl.h следующим образом:

#ifdef_WIN32

#define WC_TABCONTROLA

"SysTabControl32"

181



#detme WC_TABCONTROLW

#ifdef UNICODE

#define WC_TABCONTROL

#elsc

#define WC_TABCONTROL

#endif

#elsc

#dcfine WC_TABCONTROL

#cndif

L"SysTabControl32" WC TABCONTROLW WC'_TABCONTROL.A

"SysTabControl"

При создании окна с закладками могут использоваться как общие сти­ли, применяющиеся для всех окон, так и стили, специфические для закладок (табл. 43).

Таблица 43. Стили закладок

Стиль

TCS_TABS

tcs_singleline

tcs rightjustify tcs"forceiconleft



tcs_forcelabelleft tcs_buttons tcs multiline tcs_fixedwidtm

tcs raggfdright

tcs focusonbuttondown

tcsjavnfrdrawfixed

tcs_. tooltips tcs focusnever

0x0020 0x0100 0x0200 0x0400

0x0800 0x1000 0x2000

0x4000 0x8000

Закладки являются закладками, а не

кнопками

Закладки располагаются в одну линию и

при необходимости скроллируются с

помощью up-down control'a

Иконка сдвинута к левому краю заклад-ки, текст центрирован И текст, и иконка сдвинуты к левому краю закладки

Закладки выглядят и действуют как кнопки

Закладки при необходимости распола­гаются в несколько строк Все закладки имеют одинаковую ширину

За прорисовку закладок отвечает прикладная npoipaMMa. а не система Задержка курсора мыши на одной из закладок вызывает появление подсказки Закладка никогда не получает фокуса ввода

После создания окна с закладками, необходимо определить каждую закладку, в которой могут быть иконка, заголовок (текст) и дополнитель-

182



ные данные, определяемые приложением. Для этого в программе нужно заполнить столько структур типа ТСМТЕМ, сколько закладок планирует­ся создать. Структура ТСМТЕМ определена в файле commctrl.h:

typedcf struct JTCJTEMA

I

UINTmask; UINTlpRcservedl; UINT lpRcservcd2; LPSTR pszTcxt: inl cchTexlMax, int ilmagc:

LPARAM IParam; | TCJTEMA;

lypedef struct _TC_ITEMW {

UINT mask;

UINTlpRcservedl;

UINT lpRescrved2;

LPWSTR ps/Text;

int ccliTcxtMax;

int ilmage;

LPARAM IParam; } TCJTEMW;

#ifdefUNICODE

#define TCJTEM

#elsc

Adeline TCJTEM

#endif

TCJTEMW TC ITF.MA

Как следует из названий, поля IpReservedl и lpReserved2 не использу­ются, они зарезервированы Microsoft для применения в будущем.

В поле mask этой структуры указывается, какие данные определяют внешний вид закладки. Это поле может принимать значения, приведен­ные в табл. 44.

После того, как читатель ознакомился с этой таблицей, назначения полей pszText, ilmage и IParam, надеюсь, стали понятны. Единственное поле, оставшееся нерассмотренным, - это cehTextMax. В случае, если структура типа ТСМТЕМ используется для получения информации о закладке, в поле cehTextMax определяется размер буфера, на который указывает pszText.



Т а б л it ц а 44. Битовые флаги, определяющие внешний вид и поведение закладок

183



Флаг

Значение

 

Описание

 

TCIFJTEXT TCIFJMAGE

TCIFJITLREADING TCIF_PARAM

 

0x0001 0x0002

0x0004 0x0008

 

Поле pszText заполнено, в нем хранитея указатель на строку - заголовок закладки или па буфер, в который будет записана информация Полк ilmage заполнено, в нем хранится индекс отображаемого на закладке изображения в списке изображений или -1, если список изображений не используется Текст отображается справа налево, как, например, в арабском языке Поле IParam заполнено и содержит данные, определяемые приложением

 

Тем не менее, сделаю одно замечание. Если размер данных, опреде­ляемых приложением, не равен 4 байтам, то приложение должно опреде­лить собственную структуру и использовать ее вместо ТС_1ТЕМ. Первым полем этой структуры должна быть другая структура, типа TCJTEMHEADER. В файле commctrl.h она описана так:

typedef struct JTCJTEMHEADERA

{

UINT mask;

UINTlpRescrvedl;

UINT ]pReserved2;

LPSTR pszText;

int cchTextMax;

int ilmage: } TCJTEMHEADERA;

typedef struct JTCJTEMHEADERW

{

UINT mask;

UINTipReservedl;

UTNT lpRescrved2;

LPWSTR pszText;

int cchTextMax;

int ilmage; } TCJTEMHEADERW;

tfifdcf UNICODE

«define TCJTEMHEADER

Seise

#definc TCJTEMHEADER

#endit'

TCJTEMHEADERW TC ITEMHEADERA

Как всегда, для управления окном с закладками используются сооб­щения. Их список приведен в табл. 45.

184

Таблица 45. Сообщения, посылаемые закладкам

Сообщение

Значение

Описание

TCMJ4RST

ТСМ GETIMAGELIST

ТСМ SETIMAGELIST

ТСМ GETITEMCOUNT

ТСМ GETITEM

ТСМ SETITEM

ТСМ INSERTITEM

ТСМ DELETEITEM

ТСМ DELETEALLITEMS

TCM_GETITEMRECT ТСМ GETCURSEL

ТСМ  SETCURSEL

ГСМ  HITTEST

0x1300 ТСМ FIRST+ 2

ТСМ FIRST+ 3

ТСМ FIRST+ 4

ТСМ FIRST+ 8

ТСМ FIRST+ 9

TCMJ4RST+ 10 TCMJ4RST + 11

ТСМ FIRST + 12

ТСМ FIRST + 13

Получить хэндл используемого совместно с закладками списка изображений, wParam и IParam = О, возвращается хэндл списка изображе­ний



Связать список изображений с закладками, wParam = 0, IParam хэндлу списка изображений, возвра­щается хэндл предыдущего списка изображений

Получить число закладок, wParam = О, IParam — 0, возвращается число закладок

Получить информацию о закладке, wParam = индексу закладки, IParam указателю на структуру типа ТС JTEM, в которую будет записана информация

Установить атрибуты закладки, wParam = индексу закладки, IParam указателю на структуру типа TCJTEM, которая определяет атрибуты

Вставить закладку, wParam - индексу новой закладки, IParam = указателю на структуру тина TCJTEM, возвращает­ся индекс новой закладки или -I Удалить закладку, wParam = индексу закладки, IParam = 0, возвращается TRUE при успешном выполнении Удалить все закладки, wParam — О, IParam = 0, возвращается TRUE при успешном выполнении

Получение индекса текущей закладки, wParam = 0, IParam = О Установка заданной закладки текущей, wParam = индексу закладки, возвраща­ется индекс ранее выбранной закладки

185



Окончание таил, 45

Сообщение

 

Значение

 

Описание

 

TCM_SETITEMEXTRA

 

ТСМ FIRST + 14

 

Установка размера дополнительных

 

 

 

 

 

данных для закладки, wParam — числу

 

 

 

 

 

байт, выделяемых для дополнитель-

 

 

 

 

 

ных данных

 

ТСМ ADJUSTRECT

 

ТСМ FIRST ^40

 

 

 

ТСМ SETTTEMSIZE

 

ТСМ FIRST + 41

 

 

 

ТСМ REMOVEIMAGE

 

ТСМ FIRST -.-42

 

 

 

ТСМ SETPADDING

 

ТСМ FIRST + 43

 

 

 

ТСМ GETROWCOUNT

 

ТСМ FIRST + 44

 

 

 

ТСМ GETTOOLTIPS

 

ТСМ FIRST + 45

 

 

 

ТСМ SETTOOLTIPS

 

ТСМ FIRST + 46

 

 

 

ТСМ GETCURFOCUS

 

ТСМ FIRST + 47

 

 

 

ТСМ SETCURFOCUS

 

ТСМ FIRST + 48

 

 

 

<


Сообщение TCM_SETITEMEXTRA может в работе окна с закладками использоваться только один раз и только до момента добавления первой закладки. Некоторые сообщения в этом списке, которые могут использо­вать параметры как в Unicode, так и в ANSI-кодировках, сами являются макросами. В таких случаях в графе «Значение» оставлен пропуск. Для примера ниже приведено описание макроса TCM_GETITEM:

#defme TCM_GETITEM/\

#deime TCM_GETITEMW

ffifdef UNICODE

#defme TCM_GETITEM

#eise

«define TCM_GETITEM

#endif

(TCM_FIRST + 5)

(TCM_FIRST + 60)

TCM GETITEMW TCM GETITEMA

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

Имя каждого макроса образуется из имени сообщения:

1. От имени сообщения отбрасывается префикс ТСМ_.

2. Все слова оставшейся части изменяются таким образом, что про­писной остается только первая буква слова, а все остальные делаются строчными, например, Getltem, Insertltem и т. д.

3. К полученному добавляется префикс TabCtrl_, например, TabCtrl Getltem, TabCtrl Insertltem.

186



Сообщение, которое посылается тем или иным макросом, определяет­ся именем этого макроса. Каждый макрос может содержать один, два или три параметра. Число аргументов определяется очень просто - (число параметров сообщения, не равных нулю) + I. Если у сообщения wParam и IParam равны 0, то у макроса определен только первый аргумент, если определен только wParam - макрос требует наличия двух аргументов. Первым аргументом макроса всегда является хэндл окна, которому посылается сообщение, т. е. хэндл окна с закладками. Второй и третий аргументы (при необходимости) - это wParam и IParam сообщения соот­ветственно. К примеру, макрос TabCtrl_DeleteAHItems() имеет один аргумент, макрос TabCtrl Deleteltem() - два аргумента, TabCtrl Insertltem() - все три аргумента.

Итак, с управляющими сообщениями и макросами все ясно. А как об­стоит дело с получением информации о том, что выбрана одна из закла­док'.' Если пользователь что-то сделал с закладкой, то закладка посылает родительскому окну сообщение WMJNOTIFY, при этом wParam этого сообщения содержит идентификатор элемента управления, a



IParam

- указатель на структуру типа NMHDR. Эта структура описана в файле winuser.h и имеет вид, приведенный ниже:

typedef struct lagNMHDR i

HWND hwmlFrom;

UINT idFrom;

HINT code:         //NM_code }   NMHDR; typedcfNMHDR FAR * LPNMHLJR:

Первое поле этой структуры - hwndFrom - содержит хэндл элемента управления, который послал сообщение WMJNOTIFY. Второе поле -idFrom - идентификатор элемента управления. Третье поле - code - со­держит код нотификации, т. е. код того действия, которое было произве­дено с элементом управления. В случае закладки это может быть один из двух кодов - TCN SF.LCHANGING или TCN SELCHANGE.

Сообщение с кодом TCN SELCHANGING посылается после того, как пользователь произвел действие, но до изменения состояния закладки. Это сообщение может быть использовано, скажем, для того, чтобы со­хранить информацию, введенную пользователем в диалоговом окне, связанном с закладкой. После того, как состояние закладки изменилось, посылается сообщение TC'N_SELCHANGE. При получении этого сооб­щения программа может произвести какие-либо действия по формирова­нию вновь отображае-,'ой страницы.

187

Но использование закладок именно тем и осложнено, что каждая из закладок обычно связана с диалогом. Другими словами, при выборе новой закладки старое диалоговое окно должно исчезнуть, а на его месте должно появиться новое. Так как каждая из закладок может быть выбрана в любой момент, то с закладками должны быть связаны немодальные диалоги. Для того чтобы отобразить впоследствии диалог, связанный с невыбранной закладкой, программа должна сохранять состояние диалога. В качестве примера приведена небольшая программа, иллюстрирующая работу с закладками. Эта программа создает три закладки. При размеще­нии курсора мыши над любой из них возникает подсказка (помните, при разборе окон подсказок я обещал, что продемонстрирую их использова­ние при описании закладок?). При выборе любой из закладок отобража­ется окно диалога, связанное с этой закладкой. В программе используется файл ресурсов:



Dialog I DIALOG 2, 40, 250, 108

STYLE DSJDLOOK  DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE

FONT 8, "MS Sans Serif'

{

DEFPUSHBUTTON "OK". IDOK, 24, 65, 50, 14

CONTROL "This is a text in the first dialog", -1, "static". SSJ.EFT | WS_CHILD | WS VISIBLE, 20, 13,96,9

Dialog2 DIALOG 2, 40, 250, 108

STYLE DS_3DLOOK  DS_CONTEXTHELP

FONT 8. "MS Sans Serif"

WS POPUP I WS VISIBLE

DEFPUSHBUTTON "OK", IDOK, 24, 65, 50, 14

CONTROL "This is a text in the second dialog", -1, "static", SS_LEFT | WSJTHILD | WS VISIBLE, 20, 13, 120.9

Dialog3 DIALOG 2, 40, 250, 108

STYLE DS_3DLOOK | DSJTONTEXTHELP

FONT 8, "MS Sans Serif

WS POPUP I WS VISIBLE

DEFPUSHBUTTON "OK", IDOK, 24, 65, 50, 14

CONTROL "This is a text in the third dialog", -1, "static", SS_LEFT | WS_CHILB WS VISIBLE, 20, 13,96,9

Ниже приводится текст программы:

#includc <windows.h> ^include <commctrl.h> ^include <stdio.h>

188

HINSTANCE hlnst; HWND hWnd;

I.RESULT CALLBACK TabControlWndProc ( HWND. UINT, UINT, LONG ); BOOL CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,

LPSTR IpszCmdParam, int nCmdShow ) }

WNDCLASS WndClass ;

MSG Msg;

char szClassName[] = "TabControl";

hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.stylc = CSJTREDRAW | CS_VREDRAW;

WndClass.lpfnWndProc = TabControlWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass. hlnstance ~ hlnstance ;

WndCIass.hlcon = Loadlcon (NULL.IDI_APPLICATION);

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObjcct (LTGRAY_BRUSH);

WndClass. Ips/McmiNamc = "";

WndClass. IpszClassName = szClassName;

if ( !RegisterClass(&WndClass) ) {

McssageBox(NULL,"Cannot register class". "Error", MB__OK); return 0;

liWnd = CrcateWindow(szClassName, "Tab Control Demo Program",



WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,

CW USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hfnstance,NULL); ifllhWnd) {

Message I.iox(NULL, "Cannot create window", "Error", MB_OK); return 0;

fnitC'oinmonControls(); /* Show our window */

ShowWmdow(hWnd,nCmdShow); UpdateWindow(hWnd);

189



/* Beginning of messages cycle */

while(GetMessage(&Msg, NULL, 0, 0))

{

TranslateMessage(&Msg); DispatchMessage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK TabControIWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

{

HWND hTabControlWnd; RECT Rect; LPNMHDR IpNMHdr; LPTOOLTIPTEXT IpTooITipText; static HWND hDlg = 0; int nTab; TC_ITEM TCJtem;

switch(Message)

{

case WM_CREATE: GetClientRect(hWnd, &Rect);

hTabControlWnd = CreateWindow(WC_TABCONTROL, "", WS_ VISIBLE |

WS_TABSTOP | WS_CHILD | TCS_TOOLTIPS, 0, 0, Rect.right, Rect.bottom, hWnd, NULL, hlnst, NULL); TCJtem.mask = TCIFJTEXT; TC_Item.iImage = -1 ; TC_Item.pszText = "The first dialog"; TabCtrl_InsertItem(hTabControlWnd, 0, &TC_Item); TC_Item.pszText = "The second dialog"; TabCtrl_InsertItem(hTabControlWnd, 1, &TCJtem); TC_Item.pszText = "The third dialog"; TabCtrl_InsertItem(hTabControlWnd, 2, &TC_Item); hDlg = CreateDialog(hInst, "Dialog 1", hTabControlWnd, DialogProc); return 0;

case WMJMOTIFY: IpNMHdr = (LPNMHDR) IParam; switch(lpNMHdr->code) {

case TTN_NEEDTEXT: IpToolTipTcxt = (LPTOOLTIPTEXT) IParam; sprintf(lpToolTipText->lpszText, "Tip about tab No %d",

lpToolTipText->hdr.idFrom); break; caseTCN SELCHANGE:

190



if(hDlg)

Destroy Window(hDlg);

nTab = TabCtrl_GetCurSel( (HWND) lpNMHdr->hwndFrom); switch(nTab)

{

case 0:

hDlg = CreateDialog(hInst, "Dialog 1", hTabControlWnd, DialogProc); break; case 1:

hDlg = CreateDialog(hInst, "Dialog2", hTabControlWnd, DialogProc); break; case 2:

hDlg = CreateDialog(hInst, "Dialog3", hTabControlWnd, DialogProc); break; } break;



x t

return 0;

case WM_DESTROY: PostQuitMessage(O); return 0;

} return DefWmdo\vProc(hWnd,Message,wParam, IParam);

BOOL CALLBACK DialogProc(HWND hDlg, UINT Message,

WPARAM wParam, LPARAM IParam) {

switch(Message) {

case WM_COMMAND: PostQuitMessage(O); return I;

} return 0;

Вид окна, создаваемого программой, приведен на рис. 18.

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

Ещё один элемент управления рассмотрен. А сколько их осталось? Рассмотрим еще один интересный и достаточно сложный элемент управ­ления, который называется окно просмотра деревьев (Tree View control).

191



В Tab Control Demo Program

The first dialog The second dialog The third dialog j



.'--•""• -This is: a text in the fiist dialog

OK

Рис. 18. Окно с закладками

РАБОТА С ОКНОМ ПРОСМОТРА ДЕРЕВЬЕВ



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

И этот элемент управления не имеет специальной функции для своего создания, т. е. для того, чтобы создать окно просмотра деревьев, про­грамма должна использовать функции CreateWindowQ или CreateWindowEx(). При этом в качестве имени класса создаваемого окна необходимо использовать макрос WC_TREEVIEW, который описан в файле commctrl.h следующим образом:

#ifdef_WIN32

#define WCJTREEVIEWA

#define WC_TREEVTEWW

#ifdef UNICODE

#define WC_TREEVir-W

#else

"SysTrecVicw32" E"SysTrecVicw32"

WC TREEVIEWW

192

tfdetinc WC_TREEVIEW

#endif

#clse

«define WC_TREEVIEW

#endif

WC TREEVIEWA

"SysTreeView"



При создании окна просмотра деревьев можно использовать несколь­ко стилей, разработанных специально для окон данного типа. Все эти стили приведены в табл. 46.

На некоторых из этих стилей остановимся более подробно. Начнем со стиля Т VS_H AS BUTTONS. Если читатель запустит Explorer, то увидит, что у директорий, содержащих внутри себя еще что-то, справа есть небольшой квадратик. Это и есть те кнопки, наличие которых и предпо­лагает стиль. Если элементы следующего для директории уровня отобра­жаются, то внутри квадратика содержится знак «+», в противном случае -знак «-». Для сворачивания и разворачивания ветви дерева необходимо щелкнуть кнопкой мыши на этой кнопке. К сожалению, этот стиль не добавляет кнопки к элементам наивысшего уровня. Для того чтобы появились кнопки и у этих элементов, необходимо комбинировать стили TVS_HASLINES, TVS_LINESATROOT и TVS_HASBUTTONS.

При использовании стиля TVSJHASLINES есть одна особенность. Линиями соединяются только родительские и дочерние элементы.

Таблица 46. Стили окна просмотра деревьев

Стиль

Значение

Описание

TVS_HASBUTTONS

TVSJ-IASLINES

TVSJJNESATROOT TVS_EDITLABELS TVSJ3ISABLEDRAGDROP TVS SHOWSELALWAYS

0x0001

0x0002

0x0004 0x0008 0x0010 0x0020

К элементам, имеющим дочерние элементы, слева добавляютея небольшие кнопки, позволяющие раскрывать и закрывать список подчиненных элементов Дочерние элементы списка соединяются с родительским элементом линиями, элементы высшего уровня не соединяются Элементы высшего уровня соединяются друг с другом

Названия элементов списка могут быть изменены

Запрещает операции drag-and-drop с элемен­тами списка

Выбранные элементы остаются таковыми даже тогда, когда окно теряет фокус

193

Таблица 47. Сообщения, посылаемые окнам просмотра деревьев

Сообщение

Опиеание

TV_FIRST

TMVJNSERTTTEM

TVM_DELETEITEM

TVM_EXPAND

TVM_GETITEMRECT

TVM_GETCOUNT

TVM_GETINDENT TVM_SETINDENT TVM GETIMAGEEIST

TVM_SETIMAGELIST TVM GETNEXTITEM

TVM_SELECTITEM

TVM_GETITEM

TVM_SETITI-M



TVMJZDITLABEL

TVM_GETEDITCONTROL

TVMJ3ETVISIBLECOUNT

TVMJ1ITTEST

TVM_CREATEDRAGIMAGE

TVM_SORTCHILDREN

TVM_ENSUREVISIBLE TVM SORTCHILDRENCB

tvm endeditlabelnow tvm' "getisearchstring

TV_F1RST -i- 1 TV_FIRST + 2 TV_FIRST + 4

TVJTRST + 5

TV_FIRST + 6 TV_FIRST + 7 TV FIRST 1-8

TVJTRST + 9 TV_FIRST+ 10

TV FIRST + 11

TV FIRST- 15 TVJ-TRST + 16

TV_FIRST+ 17 TVJFIRST- 18 TVJ4RST+ 19

TV_FIRST + 20 TV FIRST+ 21

TV FIRST + 22

Вставка элемента в список Удаление элемента из списка «Распахнуть» или «свернуть» элемент Получить ограничивающий прямо­угольник для элемента списка Вернуть количество элементов в списке

Получить значение отступа Установить значение отступа Получить хэндл списка изображений, связанного с окном просмотра деревьев

Связать список изображений с окном просмотра деревьев Получить дополнительную информа­цию об элементе, находящемся с текущим в определенных отношениях (следующий, родительский, первый дочерний и так далее) Сделать элемент' текущим Получить информацию об элементе Установить параметры элемента Изменитьтекст элемента

Получить число видимых элементов списка

Отсортировать дочерние элементы в алфавитом порядке

Отсортировать дочерние элементы в соответствии с критерием, определен­ным программой

Элементы наивысшего уровня друг с другом не соединяются, т. е. ви­зуально отображаются несколько отдельных деревьев. Если пользователь хочет, чтобы отобразилось единое дерево, необходимо указать комбина­цию стилей TVS J1ASLINES и TVS LINESATROOT.

Раз окно просмотра деревьев является окном (прошу извинить меня за тавтологию), то обмен информацией с этим окном и управление им

194

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

Как и в случае с закладками, посылка сообщений дереву просмотра деревьев может быть осуществлена с помощью макросов. Имена макро­сов для сообщений формируются точно так же, как и в случае закладок. Единственное отличие состоит в том, что в качестве префикса использу­ется не TabCtrl, a Tree View.



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

Для того чтобы вставить элемент в синеок, необходимо послать окну просмотра деревьев сообщение TVMJNSERTITEM или, что то же самое, использовать макрос TreeView Insertltem. При этом параметр wParam должен быть равным 0, a IParam должен содержать указатель на структу­ру типа TVJNSERTSTRUCT. Эта структура описана в файле commctrl.h так:

typedef struct JTVJNSERTSTRUCTA (

HTREEITEM hParent:

HTREEITEM hlnsertAt'tcr;

TVJTEMA item; } TVJNSERTSTRUCTA, FAR *LPTV_INSERTSTRUCTA;

typedef struct _TV INSF.RTSTRUCTW {

HTREEITEM hParent;

HTREEITEM hlnsertArter;

TVJTEMW item; } TVJNSERTSTRUCTW, FAR *LPTVJNSERTSTRUCTW;

#ifdef UNICODE

«define TVJNSERTSTRUCT

Adeline LPTVJNSERTSTRUCT

#clse

#deime TVJNSERTSTRUCT Adeline LPfvjNSERTSTRUCT

#endif

TVJNSERTSTRUCTW LPTVJNSERTSTRUCTW

TV INSERTSTRUCTA LPTV INSERTSTRUCTA

Поле первое - hParent - хэндл родительского элемента. Если этот эле­мент равен TVI_ROOT или NUEE, то элемент не имеет родителей и добавляется в список наивысшего уровня.

Второе поле - hlnsertAfter - определяет хэндл элемента, после которо­го вставляется новый элемент. Помимо этого, поле может принимать следующие значения:

195



TVI_FIRST - элемент вставляется в начало списка;

TVI_LAST - элемент вставляется в конец списка;

TVI~SORT - элемент вставляется в список в алфавитном порядке.

Третье поле - item - описывает непосредственно вставляемый элемент. Он представляет собой очередную структуру (структура в структуре!). Тип этой структуры - TVJTEM - описан в файле commctrl.h:

typedef struct JTVJTEMA {

UINT      mask;

HTREEITEM hltem;

UINT     state;

UINT      stateMask;

LPSTR     pszText;

int       cchTextMax;

int      ilmage;

int       iSelectedlmage;

int      cChildren;

LPARAM    IParam; } TVJTEMA, FAR "LPTVJTEMA;

typedef struct _TV_ITEMW {



UINT     mask;

HTREEITEM hltem;

UINT      state;

UINT      stateMask;

LPWSTR   pszText;

int       cchTextMax;

int       ilmage;

int       iSelectedlmage;

int cChildren; LPARAM IParam; } TVJTEMW, FAR *LPTV_ITEMW;

Таблица 48. Флаги, определяющие в каком поле структуры типа TV_ITEM содержится (или куда должны записываться) информация

#ifdef UNICODE

#defme TVJTEM

#definc LPTVJTEM

#else

#definc TVJTEM tfdefine LPTVJTEM

#endif

TVJTEMW LPTVJTEMW

TVJTEMA LPTV ITEMA

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

196



Поле

Значение

 

Описание

 

TVIF TEXT

 

0x000 1

 

Информация содержится в полях pszText и

 

 

 

 

 

cchTextMax

 

TVIF IMAGE

 

0x0002

 

Информация содержится в поле ilmage

 

TVIF PARAM

 

0x0004

 

Информация содержится в поле IParam

 

TVIF_STATE

 

0x0008

 

Информация содержится в полях state и

 

 

 

 

 

staleMask

 

TVIF HANDLE

 

0x00 1 0

 

Информация содержится в поле hltem

 

TVIF SELECTEDIMAGE

 

0x0020

 

Информация содержится в поле iSelectedltem

 

TVIF CHILDREN

 

0x0040

 

Информация содержится в поле cChildren

 

Первое поле - mask - определяет, в каком из полей этой структуры со­держится используемая информация. Это поле может принимать значения, приведенные в табл. 48.

Дело за малым - выяснить, что может храниться в каждом из этих по­лей.

hltem - хэндл элемента, информация о котором содержится в структу­ре.

Поле state определяет флаги состояние элемента, а поле stateMask -какое состояние элемента должно быть установлено или получено. Поле state может принимать значения, приведенные в табл. 49.



Т а б л и ц а 49. Флаги, определяющие внешний вид и состояние окна просмотра деревьев

 

 

Состояние

 

Значение

 

Описание

 

 

 

TVIS_FOCUSED

 

0x000 1

 

Элемент получил фокус ввода, т. е. он обрам-

 

 

 

 

 

 

 

лен стандартным прямоугольником

 

 

 

TVIS SELECTED

 

0x0002

 

Элемент выбран

 

 

 

TVIS CUT

 

0x0004

 

Элемент выбран для операции копирования

 

 

 

TVISJ3ROPHILITED

 

0x0008

 

Элемент выбран как место назначения для

 

 

 

 

 

 

 

операции drag-and-drop

 

I

 

TVIS BOLD

 

0x00 1 0

 

Текст элемента написан жирным шрифтом

 

 

 

TVISJZXPANDED

 

0x0020

 

Дочерние элементы списка видны, т. е.

 

 

 

 

 

 

 

элемент «распахнут»

 

 

 

TVIS EXPANDEDONCE

 

0x0040

 

Элемент «распахивался» минимум один раз

 

 

 

TVIS OVERLAYMASK

 

OxOFOO

 

 

 

 

 

TVIS STATEIMAGEMASK

 

OxFOOO

 

 

 

 

 

TVIS USERMASK

 

OxFOOO

 

То же, что и предыдущее

 

197



Очередное поле - IpszText - содержит указатель на строку, появляю­щуюся в элементе списка. Помимо этого, поле может иметь значение LPSTR_CALLBACK, в этом случае родительское окно отвечает за фор­мирование текста элемента.

Если структура используется для получения информации об элементе, поле IpszText содержит указатель на буфер, в который будет записан текст элемента. В этом случае поле cchTextMax определяет размер выде­ленного буфера.

Поля ilmage и iSelectedlmage определяют индексы изображений, ис­пользующихся с элементом, для прорисовки невыбранного и выбранного элементов. Если значение поля равно IJMAGECALLBACK, то за фор­мирование изображения отвечает родительское окно.



Поле cChildren имеет значение, равное единице, в том случае, когда у элемента есть дочерние элементы. В противном случае значение этого поля равно нулю.

И наконец, последнее поле - IParam - хранит данные, связанные с эле­ментом. Об этих данных мы уже говорили при обсуждении окон списков.

На этом заканчивается рассмотрение параметров сообщения TVMJNSERTITEM. Много ли еще подобных структур ждет нас? Ко­нечно^ работает все это эффективно и эффектно (самоё Wmdows'95 и Windows NT тому подтверждение!), но, по-моему, иногда фирме Microsoft неплохо было бы подумать и о тех, кто будет изучать ее творе­ния! ( © )

Мы изучили только параметры сообщения. Еще нужно узнать, что возвращает функция, с помощью которой было послано данное сообще­ние. А возвращает она при успешном завершении хэндл элемента, а в случае неудачи - NULE.

Те же действия, как читатель уже догадался, могут быть произведены с помощью макроса TreeViewJmsertItem().

После изучения структуры типа TVJTEM, рекомендую читателю са­мостоятельно изучить работу сообщений TVM GETITEM и TVM SETITEM. В них тоже используется структура этого типа, поэтому никаких сложностей встретиться не должно.

В каждый момент элемент, у которого есть дочерние элементы, может быть «свернут» или «распахнут». Элемент автоматически меняет свое состояние либо при двойном щелчке мышью на нем, либо при щелчке мышью на кнопке элемента, если, конечно, у элемента установлен стиль TVSJ-IASBUTTONS. Программа может изменять состояние элемента с помощью посылки окну просмотра деревьев сообщений TVM_EXPAND или, что то же самое, обращением к макросу TreeView_Expand(). IParam этого сообщения определяет хэндл элемента, с которым производится

198

действие, a wParam определяет, что нужно произвести с элементом. В данном случае wParam

может принимать значения, приведенные в табл. 50.

В случае изменения состояния элемента окно просмотра деревьев по­сылает родительскому окну сообщение WMJNOTIFY, посредством которого передает информацию о том, что состояние элемента каким-то образом изменилось. В случае «распахивания» или «сворачивания» элемента родительскому окну посылаются сообщения TVN_ITEMEXPANDING до «распахивания» или «сворачивания» и TVNJTEMEXPANDED - после.



В том случае, когда элемент «распахнут», дочерние элементы списка отображаются смещенными вправо относительно родительского элемен­та. Получить значение смещения или установить это значение можно с помощью сообщений TVM GETINDENT и TVM_SETINDENT.

Программа может дать пользователю возможность изменить текст элемента, послав этому элементу сообщение TVM_EDITLABEL. В этом случае родительское окно получает нотификационные сообщения TVN BEGINLABELEDIT перед началом редактирования и TVM_ENDLABELEDIT после его окончания.

При необходимости программа может отсортировать элементы списка в алфавитном порядке, послав окну сообщение TVM_SORTCHILDREN. Если необходима сортировка списка по какому-то другому критерию, то тогда окну просмотра деревьев необходимо послать сообщение TVM_SORTCHILDRENCB, указав в качестве IParam адрес процедуры сортировки.

Т а б л и ц а 50. Действия, производимые с элементом окна просмотра деревьев при посылке окну срообшепия TVM_EXPAND

wParam

 

Значение

 

Описание

 

TVR COLLAPSE

 

0x000 1

 

Элемент «сворачивается»

 

TVE EXPAND

 

0x0002

 

Элемент «распахивается»

 

TVE TOGGLE

 

0x0003

 

Если элемент «свернут», то он

 

 

 

 

 

«распахивается», и наоборот

 

TVE COLLAPSHRESET

 

0x8000

 

Элемент «сворачивается», при этом

 

 

 

 

 

дочерние элементы удаляются, действует

 

 

 

 

 

только в паре с TVE COLLAPSE

 

Как и в случае окна закладок, при смене выбранного элемента роди­тельскому окну посылаются нотификационные сообщения Т VN SELCHANGING перед сменой и TVN_SELCHANGED после смены

199

выбранного элемента. Для того чтобы сменить выбор, программа должна послать окну сообщение TVM_SELECTITEM.

Для того чтобы получить информацию об элементе, необходимо по­слать окну сообщение TVM_GETITEM. Сообщение TVM_GETNEXTITEM, вопреки своему названию, позволяет получить информацию не только о следующем за текущим элементе, но и о других элементах, находящихся в определенных отношениях с текущим.



Сообщение TVM_GETCOUNT позволяет получить число элементов списка, а сообщение TVM GETVISIBLECOUNT - число элементов списка, видимых в данный момент.

Для того чтобы связать с окном просмотра деревьев список изображе­ний, нужно воспользоваться сообщением TVM_SETIMAGEEIST. Сооб­щение TVM_GETIMAGELIST позволяет получить хэндл списка изобра­жений, связанного с окном просмотра деревьев.

Для того чтобы пояснить то, о чем шла речь в этом разделе, ниже при­ведена демонстрационная программа. В ней элементами наивысшего уровня являются десятки от 0 до 100 (0, 10, 20... 100), а элементами второ­го уровня - числа, располагающиеся на числовой оси между целыми десятками. Вот текст этой программы:

#include <windows.h>

#include <commctrl.h> ^include <stdio>

HINSTANCE hlnst;

LRESULT CALLBACK TrceVicwWndProc ( HWND, UINT, UINT, LONG );

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrcvInstancc, LPSTR IpszCmdParam, int nCmdShow )

{

HWND hWnd ; WNDCLASS WndClass ; MSG Msg; char szClassName[] = "TreeView";

hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CS_HREDRAW | CSJVREDRAW;

WndClass.IpfnWndProc = TrecViewWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.Mcon = Loadlcon (NULL,IDI_APPLICATION);

200



WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); WndClass.IpszMenuName = ""; WndClass.IpszClassNamc = szClassName;

if ( IRegisterClassf&WndClass))

>

\

MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;

hWnd = CreateWindow(szClassName, "TreeView Demo Program",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance.NULL); if(!hWnd) i

MessageBox(NULL,"Cannot create window","Error",MB_OK); return 0;

}

InitCommonControlsO; /* Show our window */



ShowWmdo\v(hWnd,nCmdSliow); UpdatcWindow(hWnd);

/* Beginning of messages cycle */

while(GctMcssage(&Msg, NULL, 0, 0)) {

TranslateMessage(&Msg); DispatchMessage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK TreeViewWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

static HWND hTreeView;

RECT Rcct;

TV_INSERTSTRUCT TV InsertStruct;

TVJTEM TVJtem;

int i. j;

charcBufferfl2]:

swileh(Message) { caseWM CREATE:

201

GetCiientRect(hWnd, &Rect);

hTreeView = CreatcWindow(WC_TREEVIKW,"",

WSJVISIBLE j WS JTABSTOP | WS_CHILD | TVSJIASLINES j TVSJiASBUTTONS | TVS_LINESATROOT, 0, 0. Rcct.right, Rcct.bottom, hWnd, NULL, hlnst, NULL);

TVJnsertStruct.MnsertAHcr = TVI_ LAST;

TVJtem.mask = TVIF_TEXT;

lor(i"0; i< 100; i+= 10)

!

TVJnsertStruct.hParent - TVIJIOOT; TVJtcm.pszText = itoa(i, cBuffer, 10); TVJnsertStruct.itcm = TVJtcm; TV_InscrtStruct.hParcnt = TreeViewJnsertItem(hTrceView,

&TV_InscrtStrucl); for(j = l;j < 10;j++)

{

TVJtcm.pszText = iloa(i + j, cBufTer, 10); TVJnsertStruct.itcm = TVJtcm; TrceViewJnsertItcm(hTreeView, &TVJnscrtStruct);

return 0;

case WMJ5ESTROY: PostQuitMessage(O); return 0;

return DefWindowProc(hWnd,Message,wParam. IParam);

На рис. 19 показан вид создаваемого программой окна. Как всегда, самое трудное (©) - добавить изображения в список - я ос­тавляю читателю в качестве упражнения.

РЕЕСТР

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

Надеюсь, что читатель помнит массу файлов с расширением .ini в по­давляющем большинстве случаев, которые приложения, разработанные для Windows более старых версий, использовали для хранения данных. Там хранились данные, нужные только данному приложению для работы. Для работы с ними использовались функции, имена которых содержали строку PrivateProfile. В Win32 для хранения подобных данных разработан совершенно новый механизм, получивший название реестра (registry -



202

реестр, регистратура). Этот механизм, во-первых, облегчил работу с данными приложений, и, во-вторых, упростил работу с ними. При этом следует заметить, что хотя никаких особых ограничений для хранимой в реестре информации нет, хранить в нем следует только инициализацион-ные и конфигурационные данные. В help'e no Win'32 API записано, что если данные превышают один килобайт, их целесообразно хранить в отдельном файле, а не в регистре. Мне кажется, что в большинстве случаев следует поступать именно так. Какой же должна быть программа, чтобы данные инициализации и конфигурационные данные занимали бы 1 кбайт!

В TreeView Demo Program

в о

•: Ш- 10

: и 20 , и зо

:: Ш    40 ; Й-50

В 60 . Ш- 70

:ш 80 I а эо

Рис. 19. Окно просмотра деревьев с одним "распахнутым" и девятью "нераспахнутыми" элементами

СТРУКТУРА РЕЕСТРА



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

203

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

РАБОТА С РЕЕСТРОМ



СОЗДАНИЕ И ОТКРЫТИЕ КЛЮчЕЙ

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

WINADVAPI LONG APIENTRY RegCrcatcKcyExA (HKEY hKey,

LPCSTR IpSubKcy, DWORD Reserved, LPSTR IpClass, DWORD dwOptions, REGSAM samDesircd,



LPSECURITY^ATTRIBUTESIpSecurityAttnbutes, PHKEY phkResult, LPDWORD IpdwDisposition); WINADVAPI EONG APIENTRY RegCreateKeyExW (HKEY hKey,

LPCWSTR IpSubKey, DWORD Reserved, EPWSTR IpClass, DWORD dwOptions, REGSAM samDesircd,

LPSECURITY_ATTRIBUTESlpSecurityAttributcs, PHKEY phkResult, LPDWORD IpdwDisposition);

#ifdef UNICODE

#defme RegCreateKeyEx RegCreateKeyExW

#elsc

#define RegCreateKeyEx RegCreateKeyExA

#cndif// iUNICODE

Опять функция с массой аргументов! Первый аргумент - hKey -хэндл ранее открытого ключа или одно из следующих значений: HKEY_CLASSES_ROOT; HKEY_CURRENT_USER; HKEY_LOCALJVIACHINE; HKEY USERS.

204



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

При инсталляции Windows создаются четыре ключа. Их имена совпа­дают со значениями, приведенными выше. Другими словами, эти ключи являются основой для создания иерархии ключей.

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

Ключи, находящиеся по иерархии ниже HKEY_CLASSES_ROOT, оп­ределяют типы (или классы) файлов и свойства, ассоциированные с этими классами. Свойства классов определяются только программистом. Обычно эти свойства применяются при работе приложений, использую­щих внедрение и связывание объектов, а также приложений, использую­щих среду Windows (shell applications). К примеру, при открытии файлов в Explorer'e используются свойства файлов, записанные в реестре.

Ключи, подчиненные HKEY USERS, определяют конфигурацию по умолчанию при подключении нового пользователя на локальной машине и конфигурацию текущего пользователя.

И наконец, ключи, подчиненные HKEY_CURRENT_USER, определя­ют установки, сделанные текущим пользователем, касающиеся перемен­ных окружения, данных о принтерах, сетевых подключениях и т. д. Кроме этого, в этой ветви дерева хранятся установки, сделанные кон­кретными приложениями.



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

Вторым аргументом - IpSubKey - является указатель на строку, содер­жащую имя создаваемого ключа. Создаваемый ключ будет подчиненным ключа, хэндл которого указан в первом аргументе.

Третий аргумент - Reserved -зарезервирован и должен быть равным нулю.

Четвертый аргумент - IpClass - указатель на строку, определяющую класс создаваемого ключа.

205



Очередной, пятый аргумент - dwOptions, определяет опции создавае­мого ключа. Этот аргумент может принимать одно из значений -REGJ3PTION VOLATILE или REG_OPTIONNON_VOLATILE. ' В Windows'95 первое значение не используется. Второе значение указыва­ет, что при перезагрузке системы значение этого ключа сохраняется, т. е. информация сохраняется в файле, а не в памяти.

Следующий, шестой аргумент - samDesired, определяет маску доступа к ключу. Этот параметр представляет собой битовую шкалу и может быть комбинацией флагов, приведенных в табл. 51.

Седьмой аргумент - IpSecurityAttributes - указатель на структуру типа SECURITY_ATTRIBUTES, которая определяет атрибуты безопасности создаваемого ключа. К сожалению, Windows'95 не поддерживает безо­пасность, поэтому этот параметр игнорируется.

Туда, куда указывает восьмой аргумент - phkResult - записывается хэндл созданного ключа.

Т а б .ч и ц а 51. Флаги, составляющие маску доступа к ключу

Флаг

Значение

Описание

KF.Y_QUERY_ VALUE

KEY_SET_VALUE

KEY_CREATE_SUB_KEY

KEY_ENUMERATE_SUB_KEY

KEY_NOTIFY

KEY_CREATE_LINK

KEY READ



KEY WRITE

KEY_EXECUTE KEY ALL ACCESS

0x0001 0x0002 0x0004 0x0008 0x0010 0x0020

Права 'запрашивать данные подключен

Права устанавливать данные подключен

Права создавать подключи

Права перебирать подключи

Права изменять нотификацию

Права создавать символическую связь

(STANDARD_RIGHTS_READ |

KEY_QUERY_VALUE

KEYJiNUMERATE SUBJCEYS |

KEY_NOTIFY) & (-SYNCRONIZE)

(STANDARD_RIGHTS_WRITE

KEY_SET_VALUE |

KEY_CREATE_SUB_KEY) &

(-SYNCRONIZE)

KEY READ & (-SYNCRONIZE)

(STANDARD_RIGHTS_ALL j

KEY_QUERY_VALUE|

KEY_SET_VALUE [

KEY_CREATE_SUB_KEY |

KEY_ENUMERATE_SUB_KEYS |

KEY_NOTIFY j KEY_CREATE_LINK) &

(-SYNCRONIZE))

206

И наконец, последний, девятый аргумент - IpdwDisposition - указывает место, куда будет записана информация о том, что произошло с ключом. Дело в том, что если с помощью этой функции производится попытка создать ключ, который уже существует, то ключ не создается, а просто открывается. Поэтому приложению необходимо знать, что произошло при создании ключа. Если ключ был создан, то в поле, определяемое IpdwDisposition, записывается значение REG_CREATEDJNEWJCEY. В том случае, если ключ существовал и был открыт, записываемое значение равно REG OPENED EXISTING KEY. Это поле может быть использо­вано и для того, чтобы узнать, не открыт ли ключ другим приложением. Открытый ключ доступен только тому приложению, которое создало его. Таким образом, если приложение открывает заведомо существующий ключ и получает в ответ значение REG_CREATED_NEW KEY, то можно сделать вывод о том, что ключ занят другим приложением.

Функция возвращает значение ERROR SUCCESS в том случае, если ключ создан или открыт удачно. Любое другое значение является свиде­тельством того, что при создании или открытии ключа встретилась ошибка.

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

WINADVAPI LONG APIENTRY RcgOpenKeyExA (HKEYhKey,



LPCSTR IpSubKey, DWORD ulOptions, REGSAM samDcsired, PHKEY phkResult);

WINADVAPI LONG APIENTRY RegOpcnKeyExW (HKEY hKey,

LPCWSTR IpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult);

tfifdcfUNICODE

ffdeime RcgOpenKeyEx RegOpenKcyExW

#elsc

/'define- RcgOpcnKeyEx KcgOpenKeyExA

#cndil"// 'UNICODE

.Надеюсь, что читатель, сравнив описания обеих функций, разберется с аргументами открывающей ключ функции самостоятельно (небольшая подсказка - полю Reserved функции RegCreateKeyExQ соответствует поле ulOptions).

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

207

ЗАКРЫТИЕ КЛЮЧЕЙ И СОХРАНЕНИЕ ПРОИЗВЕДЕННЫХ

В НИХ ИЗМЕНЕНИЙ

Закрывается ключ с помощью функции RegCloseKeyO, описание ко­торой, приведенное ниже, можно встретить в файле winreg.h:

WINADVAPI LONG APIENTRY RegCloseKey (HKEY hKey);

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

WINADVAPI LONG APIENTRY RegFlushKey (HKEY hKey);

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

ДОБАВЛЕНИЕ ДАННЫХ К КЛЮЧАМ И УДАЛЕНИЕ

ДАННЫХ ИЗ КЛЮЧЕЙ

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



WFNADVAPI LONG APIENTRY RegSetValueExA (HKEY hKcy,

LPCSTR IpValueName, DWORD Reserved, DWORD dwType, CONST BYTE* IpData, DWORD cbData); WINADVAPI LONG APIENTRY RegSetValueExW (HKEY hKey,

LPCWSTR IpValueName, DWORD Reserved, DWORD dwType, CONST BYTE* IpData,

DWORD cbData);

#ifdef UNICODE

#define RegSetValueEx RegSetValueExW

#else

#defme RegSetValueEx RegSetValueExA

#endif// IUNICODE

208

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

Таблица 52. Типы сохраняемой в реестре информации

I lapaMCip

Значе­ние

Описание

REG_NONE

REG_SZ

REG_EXPAND_SZ

REG_BINARY REGJDWORD REG_LINK REG MULTI SZ

REG_RESOURCE_LIST

REG FULL RESOURCE_DESCRIPTOR

REG_RESOURCE_REQUIREMENTS LIST

REG_DWORD_LITTLE_ENDIAN

REG DWORD BIG ENDIAN

10

4

Тип данных не устанавливается Строка, оканчивающаяся нулем Строка со ссылками на перемен­ные окружения (типа %РАТН%) Бинарные данные в любой форме Двойное слово Символическая связь Массив из нескольких строк, заканчивающихся нулями, который, в свою очередь, за­канчивается двумя нулями Список драйверов устройств Список ресурсов в виде частей аппаратуры

То же, что и REG_DWORD То же, что и REGJ5WORD, но наиболее значащим в слове является младший байт

Пятый аргумент является указателем непосредственно на данные, которые будут сохранены. И наконец, шестой аргумент определяет размер данных, на которые указывает пятый аргумент. Все легко и просто, не так ли?

А удалить данные можно с помощью обращения к функции RegDeleteValueQ. Её описание приведено ниже:

WINADVAPI LONG APIENTRY RegDeleteValueA (HKEY hKey,

LPCSTR IpValueName);

WINADVAPI LONG APIENTRY RegDeleteValueW (HKEY hKey,

LPCWSTR IpValueNamc);

#ifdcfUNICODE

#define RegDeletcValue RegDeleteValueW «else



209

#defme RegDek-teValue RegDeleteValucA

#cndif// IUNICODE

Аргументы этой функции очевидны - хэндл ключа и указатель на строку с именем данных.

Но если данные записываются в реестр, то, наверное, их можно и нужно считывать из реестра. Поэтому сейчас мы рассмотрим вопрос о том, как происходит

ВЫБОРКА ДАННЫХ ИЗ РЕЕСТРА

Если прикладной программе нужно осуществить выборку данных из реестра, то для начала программа должна определить, из какой ветви дерева регистрации ей нужно выбрать данные. Естественно, что никаких функций для этого нет. При написании программы программист должен сам позаботиться об этом. После того как решение принято, начинается второй этап. Программа должна перебирать все ключи в этой ветви до тех пор, пока не найдет нужный ключ. Для этого приложение может восполь­зоваться функцией RegEnumKeyEx(). Как и всегда, обратимся к заголо­вочному файлу winreg.h для того, чтобы найти описание этой функции. Оно приведено ниже:

WINADVAPI LONG APIENTRY RegEnumKeyExA (HKEY hKey,

DWORD dwlndex, LPSTR IpName, LPDWORD IpcbName, LPDWORD IpReserved, LPSTR IpClass, LPDWORD IpcbClass, PFLETIME IpftLastWriteTime);

WINADVAPI LONG APIENTRY RegEnumKeyExW (HKEY hKey,

DWORD dwlndex, LPWSTR IpName, LPDWORD IpcbName, LPDWORD IpReserved, LPWSTR IpClass, LPDWORD IpcbClass, PFLETIME IpftLastWriteTime);

#ifdef UNICODE

#defme RegEnumKeyEx RegEnumKeyExW

#else

«define RegEnumKeyEx RegEnumKeyExA

#endif// 'UNICODE

Функция   перебора   объектов   нам   встречается   впервые.   Давайте сначала рассмотрим аргументы этой функции, а потом поговорим о том,

210

что происходит при переборе ключей. Многие аргументы этой функции уже должны быть знакомы читателю. Первый аргумент - это хэндл ключа, подчиненные ключи которого будут перебираться в поисках нужного ключа. Второй аргумент - dwlndex - является индексом требуе­мого подключа. Третий аргумент - IpName - указывает на буфер, в кото­рый будет записано имя ключа. Четвертый аргумент - IpcbName - опреде­ляет размер этого буфера в байтах. Пятый аргумент, как следует из его названия - IpReserved - зарезервирован для использования в будущем и должен быть равным NULL. Шестой аргумент - IpClass - должен указы­вать на буфер, в котором после завершения работы функции будет со­держать имя класса подключа. Если это имя программе не требуется, то этот аргумент должен быть равным NULL. Размер этого буфера опреде­ляется седьмым аргументом - IpcbClass. И последний, восьмой аргумент -IpftLastWriteTime - после завершения работы функции содержит время последнего обновления данного подключа.



Знать функцию и ее аргументы - это хорошо. Но какой от функции и аргументов прок, если мы не умеем пользоваться функцией? Для того чтобы перебрать подключи, приложение должно сначала вызвать функ­цию RegEnumKeyExQ со вторым аргументом (dwlndex), равным нулю (поиск начинается с начала дерева). Если искомый ключ найден с первой попытки, то приложению повезло. В противном случае необходимо dwlndex увеличить на единицу и снова обратиться к функции. Так необ­ходимо делать до тех пор, пока не будет найден искомый ключ или функция не вернет значение ERROR_NO_MORE ITEMS. Естественно, что поиск можно производить и в обратном порядке. Для того чтобы поиск мог быть нормально осуществлен, ключ, хэндл которого указан первым аргументом, должен быть открыт с правом доступа KEY__ENUMERATE_SUB_KEYS. Если функция выполнена успешно, то она возвращает значение ERROR SUCCESS. Любое другое возвращен­ное значение является кодом ошибки. Кстати, получить полную инфор­мацию о подключе можно с помощью функции RcgQueryInfoKey().

Давайте считать, что с помощью способа, описанного выше, мы пере­брали подключи и нашли нужный нам подключ. Теперь в этом подключе нам необходимо найти нужные данные. Способ поиска точно такой же, как и в предыдущей функции. Для поиска необходимо перебрать все данные, связанные с подключом. Чтобы произвести этот перебор, обычно используется функция RegEnumVa!ue(). описание которой, приведенное ниже, можно найти в файле winreg.h:

WINADVAPI LONG APIENTRY KegEnumValueA (HKEY hKey,

DWORD dwlndex.

211

LPSTR IpValueName, LPDWORD IpcbValueName, LPDWORD IpReserved, LPDWORD IpType, LPBYTE IpData. LPDWORD IpcbData); WINADVAPI LONG APIENTRY RegEnumValueW (HKEY hKey,

DWORD dwlndex, LPWSTR IpValueName, LPDWORD IpcbValueName, LPDWORD IpReserved, LPDWORD IpType, LPBYTE IpDala, LPDWORD IpcbData); flifdefUNICODE

#defme RegEnumValue RegEnumValueW

#else

#defme RegEnum Value RegEnum ValueA

#endif// IUNICODE

Порядок использования этой функции полностью совпадает с поряд­ком использования функции RegEnumKeyEx(), поэтому я не стану на нем останавливаться. Опишу только аргументы этой функции. Понятно, что hKey - это хэндл ключа, которому принадлежит подключ, индекс которо­го представлен вторым аргументом - dwlndex. Следующий аргумент -указатель на буфер, в который будет записано имя подключа. IpcbValueName определяет размер этого буфера. Аргумент IpReserved зарезервирован и должен быть равным NULL. Последние три аргумента определяют класс подключа, указатель на буфер, в который будут запи­саны эти данные и размер буфера. После возврата функции предпослед­ний аргумент содержит число записанных данных.



Теперь я, наконец, могу сказать, что у читателя есть полное представ­ление о том, как использовать реестр. Как всегда, рассмотрение темы заканчивается демонстрационной программой:

#includc <windows.h>

#include <commctrl.h>

#defme hKeyMin 0x80000000

#dcfi:ie hKeyMax 0x80000006 HINSTANCE hlnst; HWND hTrccChild; TVJNSERTSTRUCT InscrtStruct;

LRESULT CALLBACK RegistryWndProc ( HWND, UINT, UINT, LONG ); void FillTree(H\VND, HTREEITEM);

212



void FillBranch(ULONG, DWORD, HWND, HTREEITEM); void FillSubBranch(HKEY, char*, HWND, HTREEITEM);

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevhistance. LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd;

WNDCLASS WndClass ;

MSG Msg;

char s/ClassNamef] = "Registry";

hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.stylc = CS_HREDRAW | CSJVREDRAW;

WndClass.lpfnWndProc - RegistryWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL.IDI_APPLICATION1;

WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);

WndClass.hbrBackground = (HBRUSH) GetStockObjcct (WHITE_BRUSH);

WndClass.IpszMenuName = "";

WndClass.IpszClassName = szClassName;

if ( !RcgisterClass(&WndClass))

!

MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;

hWnd - CreateWindow(szClassName, "Registry Demo Program",

WS_POPUPWINDOW | WS_VISIBLE | WSJTAPTION,

100, 100,300,400,

NULL, NULL,

hlnstancc.NULL); if(!hWnd)

i

>

MessageBox(NULL,"Cannot create window" ."Error", MB_OK); return 0;

InitCommonControls(); /* Show our window */

ShowWindow(hWnd, nCmdShow); UpdatcWindow(hWnd);

/* Beginning of messages cycle */

while(GctMessage(&Msg, NULL, 0, ()))

213



TranslatcMessage(&Msg); DispalchMcssage(&Msg);

} return Msg.wParam;

LRESULT CALLBACK RegistryWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

»

RECT Rcct;



static HWND hTreeChild;

static HTREEITEM hParentltem;

switch(Message)

{ case WM_CREATE:

GetC'licntRcct(hWnd, &Rect);

hTreeChild = CreateWindow(WC_TREEVIEW,"",

WS_VISIBLE | WS_TABSTOP j WS_CHILD |

TVS_HASLINES | TVS J.INESATROOT |

TVS_HASBUTTONS | WS_DLGFRAME,

0, 0, Rect.right, Rcct.bottom,

hWnd,

NULL,

hlnst,

NULL);

InsertStruct.item.mask = TVIF_TEXT;

InscrtStruct.item.hltem = NULL;

InsertStruct.item.pszText = "Registry Keys";

InsertStruct. item. cchTextMax = 14;

InscrtStmct.hParcnt = TVI_ROOT;

InsertStruct.hlnsertAfter = TVI_LAST;

hParentltem = TreeView_InscrtItem(hTreeChild, &InsertStruct);

FillTrce(hTreeChild, hParentltem);

TreeView_Expand(hTreeChild, hParentltem. TVE_EXPAND);

TreeView_SclcclItem(hTreeChild, hParentltem);

return 0; case WM_SIZE:

Move\Vindow(hTreeChild, 0. 0, LOWORD(lParam), HIWORD(lParam), TRUE);

return 0; case WM_DESTROY:

PostQuitMcssage(O);

return 0;

!

return DeAVindovvProc(hWnd, Message, wParam, ll'aram);

214



void FillTrce( HWND hTrceWnd, HTREEITEM hParentltem) {

ULONG i; TVJTEM Item;

TVJNSERTSTRUCT IiisertSmict; LPSTR IpszKeysf?] •--= {"HKEY CLASSES ROOT",

"HKEY~CURRENf USER",

"HKEY" LOCAL_MACHINE".

"HKEYJJSERS",

"HKEY PERFORMANCE_DATA",

"HKEY CURRENT_CONFIG",

"HKEY  DYN_DATA"j; char cClass[80] - ""; DWORD dwSizc ~ 80, dwSubKeys, dwMaxLength, dwMaxClass, dwValucs,

(IwMaxValuc, dwMaxData, dwSec; FILETIME ItFilcTime; HTREEITEM hNewParentltem;

for(i - hKcyMin, i <-•' hKeyMax; i-i-~)

// Add the highest items

illERROR^SUCCESS -=-- RegQiierylnl'oKeyffHKEY) i, cClass, &dwSizc,

NULL.

&dwSubKeys, &dwMaxLength, &dwMaxClass, &dwValues, &dwMaxValue, &dwMaxData, &dwSec, &ftF)leTime))

Uem.mask = TVIFJTEXT;

Itcin.pszTcxt - lps/Keys[i - hKcyMin];

InsertStruct.item ~ Item;

InsertStruct.hParcnl = hParentltem;

hNewParentltem = TreeView_Insertltem(hTrecWnd, &InsertStruct);



FillBrauclUi. dwSubKeys, hTrceWnd, liNewParcntltcm);

void FillBranch( ULONG i, DWORD dwSubKeys, IIWND hTrceWnd, HTREEITEM hNewParentltem)

int j;

DWORD dwClassNameSizc = 80;

char cChissName[80] - "";

if (dwSubKeys ^= 0)

return; else

for(j = 0, j < dwSubKeys; j—)

215



RegEnumKey((HKEY) i, (DWORD) j, cClassName. dwClassNameSize);

dwClassNameSize = 80;

FillSubBranch((HKEY) i, cClassName, hTreeWnd, hNewParentltem);

I

void FillSubBranch(HKEY hKcy, char* cClassName, HWND hTreeWnd, HTREEITEM hParentltem)

)

HKEY hNewKey; char cClass[80], cNewCIass[80]; DWORD dwClassSize = 80, dwSK, j; TVJTEM Item;

TVJNSERTSTRUCT InscrtStruct; HTREEITEM hNewParentltem;

Item.mask = TVIFJTEXT;

Item.pszText = cClassName;

InsertStruct.hParent = hParentltem;

InsertStruct.hlnsertAfter = TVI_SORT;

InsertStruct.item = Item;

hNewParentitem = TreeView_InsertItem(hTreeWnd, &InsertStruct);

RegOpenKey(hKey, cClassName, &hNewKey);

RegQueryIntbKey(hNewKey, cClass, &dwClassSize, NULL, &dwSK, NULL,

NULL, NULL, NULL, NULL, NULL, NULL); dwClassSize = 80; if(dwSK != 0)

for(j = 0; j < dwSK; j+

RegEnumKey(hNewKey, j, cNewClass, dwClassSize); FillSubBranch(hNewKey, cNewClass, hTreeWnd, hNewParentltem);

} RegCloseKey(hNewKey);

Вид, создаваемого программой окна, показан на рис. 20.

Я не большой специалист в рисовании ( © ), поэтому подключение изображений, как всегда, оставляю на долю читателя.

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

216

Ш Registry Demo Program

Й- HKEY_CLU.SSES_ROOT Ш HKEY_CURRENT_USER Ш HKEY_LOCAL_MACHINE Й- HKEY_USERS

В- .Default i       Ep AppE vents



И Console

: Ш Control Panel :          i-- InstallLocationsMRU :         Ш keyboard layout i       Ш Network :        :•••• RemoteAccess ;          И Software В HKEY_CURRENT_CONFIG i   В Display

i    !•• • Fonts '•     i    '-•• Settings :    0 System

; В CurrentControlSet H HKEY_DYN_DATA Ш Config Manager В PerfStats

Рис. 20. Окно с деревом реестра, созданное программой

КОЕ-ЧТО О МНОГОЗАДАЧНОСТИ В WINDOWS

Одним из основных отличий Win32 от его предшественников явилась многозадачность. "Как это - разве в Windows 3.x не была реализована истинная многозадачность?" - может спросить кто-нибудь из неискушен­ных пользователей. "НЕ БЫЛА!" - отвечу я. И вот почему.

Мне Windows 3.x представляется чем-то вроде однорукого натурали­ста. У этого натуралиста есть зверинец, в каждой клетке которого сидит хищник - программа. Каждому хищнику (программе) натуралист подает корм (сообщение) рукой. Как только хищник (программа) съест корм (обработает сообщение), корм (сообщение) получает очередной хищник. И так далее по кругу. Но иногда один из хищников (программ) мертвой хваткой вцепляется в руку (зависает и не возвращает управление) и натуралист умирает (Windows зависает), после чего на смену умершему смотрителю зверинца приходит новый однорукий натуралист (производится перезагрузка системы). И все начинается сначала. Разве

217



вам не знакома эта ситуация, уважаемый читатель? В Windows 3.x была реализована ПСЕВДОМНОГОЗАДАЧНОСТЬ, т. е. управление передава­лось программе, возвращалось системе и передавалось следующей про­грамме, т. е. фактически программы работали последовательно, друг за другом. В случае зависания одной из задач средств завершить ее, не перезагружая систему, практически не было. Кроме этого, фактически все задачи разделяли одни и те же системные ресурсы, например, память. Не было никаких проблем для одной задачи затереть содержимое памяти, выделенной другой.

В Windows'95 и Windows NT дело обстоит не так. В этой системе реа­лизована истинная многозадачность, т. е. каждой программе системой выделяется квант времени, в течение которого программа обрабатывает поступившие в ее адрес сообщения. Вне зависимости от состояния про­граммы СИСТЕМА забирает управление у программы и передает его другой программе. Если программа зависла, то система от этого не по­страдает. Управление в любом случае будет передано другой программе. Кроме этого, в Windows'95 и Windows NT введено понятие процесса. Грубо говоря, процесс - это совокупность выполняющейся программы и выделенных ей системных ресурсов. Случай, при котором программа может вырваться из рамок своего процесса и повредить еще чьи-то ресурсы, практически не возможен.



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

Таким образом, в системе Windows реализованы два типа многоза­дачности - процессная и потоковая. Рассмотрим оба типа многоза­дачности, после чего отдельно остановимся на вопросе синхронизации работы в многозадачной среде.

Остановимся на одной детали. Оттого, что мы назвали Windows мно­гозадачной системой, физический смысл этой многозадачности не изме­нился. На однопроцессорном компьютере в каждый конкретный момент выполняется одна задача. Если при запуске двух-трех маленьких про­грамм временная задержка субъективно не заметна, то при запуске не­скольких программ, требующих колоссальных ресурсов (к примеру, WinWord или Borland C++ 5.0), задержка при выполнении программ становится достаточно заметной. На многопроцессорных системах за каждым процессором может быть закреплен свой ноток, поэтому на таких системах выполнение программ осуществляется действительно в многозадачном режиме.

218

ЗАПУСК ПРОЦЕССА



Давайте, уважаемый читатель, все же более точно определим, что есть процесс. В Windows 3.x, да иногда и в Win32 процесс определяют как копию (экземпляр) выполняющейся программы. Так оно и есть, но при этом забывают, что копия - понятие статическое. Другими словами, процесс в Win32 - это объект, который не выполняется, а просто «владеет» выделенным ей адресным пространством, другими словами, процесс является структурой в памяти. А вот в адресном пространстве процесса находятся не только код и данные, но и потоки - выполняющие­ся объекты. При запуске процесса автоматически запускается поток (он называется главным). При остановке главного потока автоматически останавливается и процесс. А так как процесс без потока просто бесцель­но занимает ресурсы, то система автоматически уничтожает ставший ненужным процесс. Первичный процесс создается системой при запуске, точно так же при создании первичного процесса в нем создается и поток.



Приложение тоже может создать процесс с главным потоком, исполь­зуя для этой цели функцию CreateProcessQ. Её прототип, находящийся в файле winbase.h, при первой встрече с ним внушает легкий ужас:

WINBASEAPI BOOL WINAPI CreateProcessA(LPCSTR IpApplicationName,

LPSTR IpCommandLinc,

LPSECURITY_ATTRroUTESlpProcessAttributes,

LPSECUR ITY_ATTRIBUTES IpThreadAttributes,

BOOL blnheritHandles,

DWORD dwCreationFlags,

LPVOID IpEnvironment,

LPCSTR IpCurrentDirectory,

LPSTARTUPINFOA IpStartupInfo,

LPPROCESSJNFORMATIONlpProcessInformation); WINBASEAPI BOOL WINAPI CreateProcessW(LPCWSTR IpApplicationName,

LPWSTR IpCommandLinc,

LPSECURITY_ATTRIBUTESlpProcessAttributes,

LPSECURITY_ATTR]BUTESlpTlireadAttributcs,

BOOL blnheritHandles,

DWORD dwCreationFlags,

LPVOID IpEnvironment,

LPCWSTR IpCurrentDirectory,

LPSTARTUPINFOW IpStartupInfo,

LPPROCESSJNFORMATIONlpProccssInformation);

#ifdcf UNICODE

#defme CrcateProccss CrcateProcessW

#else

#defme CreateProcess CreateProcessA tfendif/'IUNICODE

219



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

Я уже говорил, что процесс - это структура в памяти. Таким образом, в начале работы функция выделяет память для этой структуры, а потом выделяет память (виртуальную, естественно) для адресного пространства процесса. Если выделение памяти прошло без ошибок, в адресное про­странство процесса загружается код исполняемой программы и исполь­зуемых программой динамических библиотек. Только после этого созда­ется главный поток процесса. Если функции удастся произвести все эти действия без ошибок, то возвращаемое значение будет равно TRUE. FALSE явится индикатором того, что по каким-то причинам процесс не создан.

Перейдем к рассмотрению аргументов функции CreateProcessQ.

АРГУМЕНТЫ ФУНКЦИИ CREATEPROCESSQ

С моей точки зрения, взаимодействие первых двух аргументов не со­всем продумано.



Первый аргумент - IpApplicationName - определяет имя исполняемого файла (обязательно указывать имя и расширение файла, автоматически расширение .ехе не подставляется), для которого создается процесс.

Второй аргумент - IpCommandLine определяет передаваемую этому файлу командную строку. Если IpApplicationName равен NULL, то пер­вый (до первого пробела) элемент IpCommandLine считается именем исполняемого файла.

Таким образом, имя исполняемого файла можно передавать как в пер­вом, так и во втором аргументе. Но здесь нужно быть внимательным и не допустить, скажем, такой ситуации, когда IpApplicationName равен «Wordpad.exe», a IpCommandLine - «Wordpad.exe MyFile.doc». Нетрудно догадаться к чему это приведет.

Третий и четвертый аргументы определяют атрибуты доступа к про­цессу и потоку соответственно. Я намеренно употребил слово «должны», ибо Windows'95 системы разграничения доступа не имеет. В Windows'95 эти значения, как правило, равны NULL.

Очередное поле - dwCreationFlag - является комбинацией битовых флагов. Одна группа битовых флагов определяет способ создания процесса.

220



Флаги способа создания процесса

Флаг DEBUG_PROCESS (0x00000001) устанавливается в тех случаях, когда родительский процесс должен осуществлять отладку порождаемого процесса и всех его потомков. Система будет оповещать родительский процесс о возникновении определенных событий в порождаемом процес­се и его потомках.

Флаг DEBUG_ONLY_THIS_PROCESS (0x00000002) почти эквивален­тен предыдущему, разница состоит в том, что система будет оповещать о событиях только в порождаемом процессе, но не в его потомках.

Флаг CREATE_SUSPENDED (0x00000004) указывает, что главный поток порождаемого процесса создается, но не выполняется до вызова функции ResumeThreadQ. Этот флаг обычно используется в отладчиках.

Флаг DETACHED PROCESS (0x00000008) запрещает создаваемому консольному процессу использовать консоль родительского процесса. Порождаемый процесс вынужден будет вызвать функцию AllocConsoleQ для получения собственной консоли.



Флаг CREATE NEW CONSOLE (0x00000010) указывает на необхо­ димость создания новой консоли для порождаемого процесса. Этот флаг не может использоваться вместе с предыдущим.

Флаг CREATE_NEW_PROCESS_GROUP (0x00000200) создает группу консольных процессов, которые будут одновременно реагировать на нажатие клавиш Ctrl-C и Ctrl-Break.

Флаг CREATE_UNICODE_ENVIRONMENT (0x00000400) оз­начает, что данные, на которые указывает IpEnvironment, используют символы Unicode. По умолчанию считается, что используется ANSI-кодировка.

Флаг CREATE_SEPARATE_WOW_VDM (0x00000800) используется только при запуске 16-битовых Windows-приложений и указывает, что приложению необходимо выделить отдельную виртуальную машину (Virtual DOS Machine, VDM) (по умолчанию, все 16-битовые Windows-приложения используют одну разделяемую виртуальную машину). Преимуществом выделения отдельной машины является то, что прило­жение почти не влияет на остальные. Даже зависнув, оно приведет к краху только своей VDM. Недостаток - каждая виртуальная машина требует большого объема памяти.

Флаг CREATE_SHARED_WOW_VDM (0x00001000) используется при запуске 16-битовых Windows-приложений и указывает на необходимость создания для процесса разделяемой VDM.

221



Таблица 53. Флаги класса приоритета процесса

Флаг

Значение

 

Эффект

 

NORMAL PRIORITY CLASS IDLE_PRIORITY_CLASS

 

0x00000020 0x00000040

 

Нормальный приоритет Потоки этого процесса выполня-

 

 

 

 

 

ются только тогда, когда система

 

HIGH_PRIORITY_CLASS

 

0x00000080

 

простаивает Приоритет выше нормального, но

 

 

 

 

 

ниже приоритета реального

 

REALTIME PRIORITY CLASS

 

0x00000100

 

времени Самый высокий возможный

 

 

 

 

 

приоритет

 

Флаг CREATE_DEFAULT_ERROR_MODE (0x040000) указывает, что порождаемый процесс не наследует режим обработки ошибок своего родителя. Ему при создании устанавливается режим обработки ошибок, принятый по умолчанию.



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

Флаги класса приоритета процесса

При создании процесса можно указать и класс его приоритета (табл. 53). Если при создании процесса не указан ни один из флагов, приведен­ных ниже, класс приоритета порождаемого процесса по умолчанию устанавливается равным IDLE PRIORITY CLASS, если этот класс установлен у процесса родителя, и NORMAL_PRIORITY_CLASS во всех остальных случаях.

Тем не менее, присвоение класса приоритета вновь создаваемому по­току не рекомендуется - Windows сама присвоит потоку класс приоритета по умолчанию.

Следующий аргумент функции CreateProcessQ - IpEnvironment -обычно равен NULL. Это означает, что порождаемый процесс наследует переменные окружения родительского процесса. Если этот аргумент не равен NULL, то он должен содержать указатель на блок памяти, содер­жащий те переменные окружения, которыми будет пользоваться порож­даемый процесс.

Наименование следующего аргумента функции - IpCurrentDirectory -говорит само за себя. Этот аргумент позволяет установить текущие диск и директорию для порождаемого процесса. Если этот аргумент равен NULL, порождаемый процесс наследует текущие диск и директорию

222

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

Очередной аргумент - указатель на структуру типа STARTUPINFO. Эта структура, служащая для описания свойств окна, создаваемого в новом процессе, описана в winbase.h следующим образом:

typedef struct _STARTUPINFOA {DWORD   cb; LPSTR   ipReserved;

LPSTR   IpDesktop; LPSTR  IpTitle; DWORD   dwX; DWORD   dwY; DWORD  dwXSize; DWORD   dwYSize; DWORD  dwXCountChars; DWORD   dwYCountChars; DWORD   dwFillAttribute; DWORD   dwFlags; WORD    wShowWindow; WORD    cbReserved2; LPBYTE lpReserved2; HANDLE hStdlnput; HANDLE hStdOutput; HANDLE hStdError;



} STARTUPINFOA, *LPSTARTUPINFOA; lypedef struct _STARTUPINFOW {DWORD   cb; LPWSTR IpReserved;

LPWSTR IpDesktop; LPWSTR IpTitle; DWORD  dwX; DWORD  dwY; DWORD   dwXSize; DWORD   dwYSize; DWORD   dwXCountChars; DWORD   dwYCountChars; DWORD  dwFillAttribute; DWORD  dwFlags; WORD    wShowWindow; WORD    cbReservedl; LPBYTE lpReserved2; HANDLE hStdlnput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFOW, «LPSTARTUPINFOW;

#ifdef UNICODE

typedef STARTUPINFOW STARTUPINFO;

typedef LPSTARTUPINFOW LPSTARTUPINFO;

#else

typedef STARTUPINFOA STARTUPINFO;

typedef LPSTARTUPINFOA LPSTARTUPINFO;

#endif//UNICODE

Рассмотрим поля этой структуры.

223

Поля структуры типа STARTUPINFO

Первое поле - cb - размер этой структуры. Оно должно быть равно sizeof(STARTUPrNFO).

Второе поле - IpReserved - зарезервировано и должно быть равно NULL.

Третье поле - IpDesktop - в Windows'95 просто игнорируется.

Четвертое поле - IpTitle - определяет заголовок консольного приложе­ния. Для GUI или приложений, не создающих новой консоли, должен быть равным NULL.

Поля с пятого по восьмое включительно определяют положение окна и его размеры (dwX, dwY - координаты верхнего левого угла окна в пикселах, dwXSize, dwYSize - ширина и высота окна в пикселах).

Таблица 54. Флаги, определяющие, в каких полях структуры типра STARTUPINFO содержится информация

Флаг

Значение

Эффект

STARTFJJSESHOWWINDOW STARTF JJSESIZE STARTFJJSEPOSITION STARTFJJSECOUNTCHARS

STARTF_USEFILLATTRIBUTE

STARTF_RUNFULLSCREEN STARTF FORCEONFEEDBACK

0x00000001 0x00000002 0x00000004 0x00000008

0x00000010

0x00000020 0x00000040

STARTF_FORCEOFFFEEDBACK STARTFJJSESTDHANDLES

STARTF USEHOTKEY

0x00000080 0x00000100

0x00000200

Если флаг не установлен, поле

wShowWindow игнорируется

Если флаг не установлен, поля

dwXSize и dwYSize игнорируются

Если флаг не установлен, поля dwX

и dwY игнорируются

Если флаг иг установлен, поля

dwXCountChars и dwYCounChars

игнорируются

Если флаг не установлен, поле

dwFillAttribute игнорируется



Курсор становится « песочными часами» на две секунды, за которые должно произойти обращение к GUI, после чего за 5 секунд должно быть создано окно и еще за 5 секунд оно должно перерисоваться При создании процесса форма курсора не меняется Если флаг установлен, то использу­ются потоки, хэндлы которых определены полями hStdlnput, hStdOutput, hStdError

224

Девятое и десятое поля (dwXCountChars, dwYCountChars) определяют ширину и высоту окна консоли в символах (не пикселах!). Одиннадцатое поле - dwFillAttribute - определяет атрибуты консольного окна.

Двенадцатое поле - dwFIags - используется для того, чтобы опреде­лить, какие поля структуры типа STARTUPINFO будут использоваться при создании окна порождаемым процессом. Это поле представляет собой комбинацию битовых флагов (табл. 54).

Тринадцатое поле - wShowWindow - определяет, каким образом окно будет отображено (помните функцию ShowWindow())? Значение этого поля игнорируется, если только в dwFIags не установлен флаг STARTF USESHOWWINDOW. Возможные значения этого поля - те же константы, начинающиеся с SW__, которые используются в функции ShowWindowQ.

Четырнадцатое и пятнадцатое поля, - cbReserved2 и lpReserved2 заре­зервированы. Должны инициализироваться нулем и NULL соответственно.

Шестнадцатое, семнадцатое и восемнадцатое поля - hStdlnput, hStdOutput и hStdError - определяют хэндлы стандартных потоков ввода-вывода.

Нерассмотренным остался только один аргумент функции CreateProcess() - IpProcessInformation, указывающий на структуру типа PROCESS INFORMATION, в которую записывается информация о порожденном процессе после его создания. Структура описана в файле winbase.h следующим образом:

typedcf struct J>ROCESS_rNFORMATION {

HANDLE hProcess; HANDLE hThrcad; DWORD dwProcessId; DWORD dwThreadld;

PROCESSJNFORMATION, *PPROCESS INFORMATION, *LPPROCESS_INFORMATION;

В первое поле - h Process - система записывает хэндл созданного про­цесса, во второе - hThread - хэндл потока. Поля dwProcessId и dwThreadld являются уникальными идентификаторами процесса и потока соответст­венно. Рекомендую обратить особое внимание на последние два поля. Дело в том, что Win32, если идентификатор освобожден, может повторно использовать его. К примеру, пусть процессу присвоен идентификатор 0x00001111. После завершения процесса идентификатор освобождается и какому-нибудь новому процессу может опять быть присвоен тот же



225

идентификатор 0x00001111. Это необходимо учитывать при написании программ.

Итак, аргументы функции рассмотрены. Основные результаты - хэнд­лы и идентификаторы процесса и потока - получены. А какое значение возвращает функция? Возвращаемое функцией значение TRUE говорит о том, что процесс создан и функция завершилась нормально. А при по­лучении значения FALSE программисту придется искать ошибку.

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

ЗАВЕРШЕНИЕ ПРОЦЕССА



Процесс может быть завершен вызовом одной из двух функций -ExitProcess() пли TerminateProcess(). Рассмотрим более подробно каждую из этих функций.

ФУНКЦИЯ EXITPROCESSQ

В обычных условиях процесс завершается тогда, когда один из при­надлежащих ему потоков вызывает функцию ExitProcessQ, которая описана в файле winbase.h следующим образом:

WINBASEAPI VOID WINAPI ExitProcess(UINT uExitCode);

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

Если говорить более конкретно, то при завершении процесса произво­дятся следующие действия:

вызываются функции деинициализации всех подключенных библио­тек динамической компоновки, т. е. происходит нормальное завершение всех подключенных DLL;

закрываются и/или уничтожаются все объекты, открытые и/или соз­данные процессом;

состояние процесса изменяется на «освобожденный» (signaled), что является сигналом для всех потоков, ожидающих завершения процесса;

состояние всех потоков изменяется на «освобожденный» (signaled), что является сигналом для всех потоков других процессов, которые ожидают завершения потоков текущего процесса;



226

код завершения меняется со STILL ACTIVE на код, записываемы!! в uExitCode;

счетчик числа пользователей процесса уменьшается на единицу (заметим, что данные процесса удаляются из памяти, но сам объект остается в памяти до того момента, пока счетчик пользователей не дос­тигнет нулевого -.шачения, или, другими словами, пока не будут закрыты все хэндлы процесса. Определить, завершен ли процесс можно с помо­щью функции GetExitProcessCode(), которая в случае незавершенности процесса возвращает STILL ACTIVE).

Необходимо отметить, что завершение процесса не приводит к завер­шению порожденных им процессов.

Сразу после деинициализации и выгрузки библиотек из памяти, по до своего завершения, функция заносит в параметр uExitCode код заверше­ния. После этого процесс можно считать полностью завершенным.

ФУНКЦИЯ TERMINATEPROCESSO

Эта функция является аварийным средством завершения процесса и её рекомендуется использовать только в крайнем случае. Она описана в том же winbase.li:

WINBASHAPI BOOL WINAPI Termm;UeProcess(HANDLE hProccss,

UlNTuExilCodc);

Функция используется только тогда, когда иными средствами завер­шить процесс не удается. С этой целью извне (!), а не изнутри процесса вызывается функция TerminateProcess(). которая и завершает процесс. Но в данном случае не освобождаются используемые процессом DLL, хотя все используемые объекты освобождаются. Освобождается также и память, занимаемая процессом. Число пользователей процесса также уменьшается.

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

PROCESS INFORMATION Processlnformation;

BOOL hMyProcess;

if ((hMyProcess = CreateProcess(...... &ProcessInformation))

{

CloseHandle(ProcessInfonnation.hThread);

CloseHandle(ProcessInformation.hProcess);



227

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

ДЕМОНСТРАЦИОННАЯ ПРОГРАММА

В этой программе не происходит ничего интересного. Просто при вы­боре элемента меню «Создать процесс» создается процесс, в котором запускается обычный Notepad (надеюсь, он у всех в доступной директо­рии? Если нет, то вы можете заменить Notepad любой другой програм­мой). Максимум может быть запущено 10 процессов. По команде «Kill process» процессы уничтожаются в порядке, обратном их созданию. Предлагаю читателю обратить внимание на то, что я завершаю процесс посредством вызова TerminateProcess(), а не ExitProcessQ. Для того чтобы завершить процесс обычным способом, пришлось бы писать программу, которая вызывала бы функцию ExitProcessQ изнутри процесса, а мне бы не хотелось рассеивать внимание читателя. Результаты создания процес­са, взятые из структуры типа PROCESS_ INFORMATION, отображаются в окне сообщений. Если кого-то раздражает необходимость постоянно убирать окно сообщений с отображения, рекомендую воспользоваться программой pview95.exe, которая поставляется с SDK.

Текст демонстрационной программы приведен ниже:

#include <windows.h>

#include <stdio.h>

#include "proc.h"

LRESULT CALLBACK ProcessesWndProc ( HWND, UINT, UINT, LONG );

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstancc, LPSTR IpszCmdParam, int nCmdShow )

HWND hWnd ;

WNDCLASS WndClass ;

MSG Msg;

char szClassName[] = "Processes"; /* Registering our window class */ /* Fill WNDCLASS structure */

WndClass.style = CS_HREDRAW  CS_VREDRAW;

WndClass. IpfnWndProc = ProcessesWndProc;

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra - 0;

WndClass. hlnstance = hlnstance ;

WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);

228



WndClass.hCursor= LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground - (HBRUSH) GetStockObject (WHITE_BRUSH); WndClass.IpszMcnuName = "ProcessesMeivu"; WndClass.IpszClassName = szClassName;



if( !RegisterClass(&WndClass)) {

MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;

hWnd = CrcateWindow(szClassName, "Processes Demo",

WS^OVERLAPPEDWINDOW, CWJJSEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL);

if(!hWnd)

t t

MessageBox(NULL,"Cannot create window", "Error",MB_OK); relurn 0;

/* Show our window */

ShowWindow(hWnd,nCmdShow);

UpdateWindow(hWnd);

/* Beginning of messages cycle */

while(GetMessage(&Msg, NULL, 0, 0))

{

TranslateMessage(&Msg);

DispatchMessage(&Msg); i return Msg.wParam;

LRESULT CALLBACK ProcessesWndProc (HWND hWnd, UINT Message,

UINT wParam, LONG IParam )

const Max ~ 10;

STARTUP1NFO Startuplnfo;

static int ProccssNumber = 0;

static PROCESS INFORMATION ProccssInformationfMax];

static char cMyMessage[80]; static HMENU hSubMcnu;

switch(Message)

i

case WM CREATE: hSubMemi = GetSubMenu(GetMcnu(hWnd).0);

229



return 0;

case WM_COMMAND: switch( LOWORD(wParam))

case IDM_New_Process: if(ProcessNumber < Max)

t

Startuplnfo.cb = sizeof(STARTUPINFO);

StartupInfo.lpReservcd = NULL;

StartupInfo.lpDesktop = NULL;

StartupInfo.lpTitlc - NULL;

StartupInfo.dwFlags = STARTFJJSESHOWWINDOW;

StartupInfo.wShowWindow = SW_SHOWNORMAL;

StartiipInfo.cbReserved2 = 0;

StartupInfo.lpReserved2 = NULL;

if(CreateProcess(NULL."Notepad.exe",

NULL, NULL, FALSE, 0,

NULL, NULL, &StartupInfo,

&(ProcessInformation[ProcessNumber])))

ProcessNumber—;

wsprintffcMyMessage,"hProcess is %x.\nhThread is

%x.\ndwProcess!d is %x.\ndwThrcad!d is %x.", ProcessInformation[ProcessNumber - Ij.hProcess, ProcessInrormation[ProcessNumber - IJ.hThread, ProcessInformation[ProcessNumber - IJ.dwProcessId, ProcessInformation[ProccssNumber - IJ.dwThrcadld);

MessageBox(hWnd, cMyMessage, "Process is created", MB_OK);

EnableMenuItem(hSubMenu, IDMJCill_Process,

MF_BYCOMMAND | MF_ENABLED);

else

MessageBox(hWnd, "Cannot create process", "Process creation", MB_OK);



else

!

MessageBoxfhWnd, "Too many created processes...", "Process creation", MB_OK);

break;

case IDM_Kill_Process: if(ProcessNumber > 0)

if(TenninatcProcess(ProcessIntbrmation[ProcessNumber-l].hProcess, 0))

ProcessNumber-; if(! ProcessNumber) Enab!eMenuItem(hSubMenu, IDM Kill_Process,

MF BYCOMMAND | MF_GRAYED);

230



else McssageBox(hWnd. "Cannot terminate process",

"Process termination", MB OK)' }

else MessageBox(hWnd, "No more processes", "Process termination"

MB_OK); break;

case IDM_Exit:

SendMessage(hWnd, WM_CLOSE, 0, 0); break; !

return 0;

case WM_ DESTROY: PostQuitMessage(O); return 0; » return DefWindowProc(hWnd,Mcssage,wParam, IParam),

В этой программе используется файл описаний:

#define IDM_ About 104

#define IDM_Exit 103

#define IDM_KiIl_Process 102

#define IDM_New_Proeess 101

Кроме этого, в программе используется файл ресурсов:

^include "proc.h"

ProcessesMcnu MENU i

POPUP "&Processes"

{

MENU1TEM "&New process", IDM_New Process

MENUITEM "&Kill process", IDM_Kill_Proccss GRAYED

MENU1TEM SEPARATOR

MENUITEM "E&xit", IDM Exit

POPUP "AHelp"

{

MENUITEM "&About", IDM About

231



СОЗДАНИЕ ПОТОКА



Создание потока в большей степени (внешне, конечно) напоминает программу для Windows, чем создание процесса. Дело в том, что для создания потока используется функция CreateThread() (аналог WinMain()), одним из аргументов которой является указатель на функцию потока (аналог оконной функции). Но давайте обо всем по порядку.

Итак, начнем по уже сложившейся традиции, с прототипа функции. Она описана в файле winbase.h:

WINBASEAPI HANDLE WINAPI CreateThrcadf

LPSECURITY_ATTRIBUTES IpThrcadAttributes, DWORD dwStackSizc,

LPTHREAD_STARTJlOUTINEIpStartAddress, LPVOID IpParameler. DWORD dwCreationFlags, LPDWORD IpThreadld);

При вызове этой функции происходит следующее:

в памяти создаются все необходимые для управления потоком струк­туры (назовем их объектом «поток»);



код завершения потока инициализируется значением STILL_ACTIVE;

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

создается стек потока;

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

Рассмотрим аргументы этой функции.

АРГУМЕНТЫ ФУНКЦИИ CREATETHREADQ

Первый аргумент - IpThreadAttnbutes - является указателем на струк­туру типа SECURITY_ATTRIBUTES. Так как в Windows'95 атрибуты безопасности не используются, то обычно этот аргумент равен NULL.

Второй аргумент - dwStackSize - определяет размер выделяемого по­току стека. Если в качестве этого параметра указан 0, то поток будет иметь стек такого же размера, как и у породившего его потока.

Третий аргумент этой функции - IpStartAddress - собственно и опреде­ляет поток, так как является адресом точки входа функции потока. Функ-

232

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

DWORD WINAPI ThreadFunctionfLPVOID IpParameter);

Я не случайно дал аргументу этой функции и четвертому аргументу функции CreateThreadQ одинаковые имена. Четвертый аргумент функции CreateThreadQ - это параметр, передаваемый функции потока. Что и каким образом передается в этом параметре, совершенно неважно. Это могут быть всевозможные данные, которые функция потока может использовать для своей работы.

Если следующий аргумент - dwCreationFlags - равен нулю, то выпол­нение потока начнется немедленно. Если этот аргумент будет равен CREATE_SUSPENDED, то начало выполнение потока будет задержано до определенных событий, например, до вызова функции ResumeThreadQ.

И наконец, в значение, определяемое последним аргументом, IpThreadld, записывается идентификатор созданного потока. А значение, возвращаемое функцией, является хэндлом этого потока.



Раз у потока есть начало, то должно быть и

ЗАВЕРШЕНИЕ ПОТОКА



Как и процесс, поток может быть завершен двумя способами - вызо­вом функции ExitThreadQ и обращением к функции TenninateThread(). Отличаются они друг от друга примерно тем же, что и функции ExitProcessQ и TerminateProcessQ. Первая функция, ExitThreadQ, исполь­зуется для нормального завершения потока. Естественно, что она вызы­вается изнутри потока. Она описана в файле winbase.h:

WINBASEAPI VOID WINAPI ExitThread(DWORD dwExitCode);

Единственным ее аргументом является двойное слово, в которое будет помещен код возврата этой функции.

Функцию TerminateProcess(), описанную в том же файле winbase.h следующим образом,

WINBASEAPI BOOL WINAPI TerminateThreadfHANDLE hThread,

DWORD dwExitCode);

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

233



аргументами являются хэндл завершаемого потока и двойное слово, в которое будет записан код завершения потока.

Осталось только узнать, что происходит при завершении потока. Во-первых, освобождаются или удаляются все занятые или созданные объекты. Это действие является стандартным и ничего особенного собой не представляет. Во-вторых, поток получает статус незанятого (signaled). В-третьих, код завершения процесса меняется со STILL ACTIVE на указанный при вызове завершающей поток функции. В-четвертых, уменьшается счетчик пользователей потока. Если пользователей потока больше не осталось, и поток является единственным потоком процесса, то завершается и процесс. Все легко, просто и логично.

СИНХРОНИЗАЦИЯ



К этому моменту читатель уже знает, что в программе один поток, главный, запускается автоматически при запуске. Следовательно, созда­вая новые потоки, мы тем самым делаем программу многопотоковой. Хорошо, конечно, если эти потоки работают независимо. А как быть тем потокам, которые зависят друг от друга? Например, осуществляют дос­туп к одному и тому же файлу или продолжение работы одного зависит от выполнения какого-то условия в другом? Для решения этих проблем в Win32 предусмотрен механизм синхронизации, который позволяет, что следует из его названия, синхронизировать работу потоков.



Обычно поток, работа которого зависит каким-то образом от другого потока, сообщает системе о том, какое событие он ожидает. После этого выполнение этого потока приостанавливается до наступления ожидаемо­го события. Обычно для синхронизации используются четыре типа объектов - семафоры, исключающие семафоры (объекты типа mutex), события и критические секции. Далее мы поговорим об этих объектах.

СЕМАФОРЫ

Семафор действует как обычный флажок, и используется для того, чтобы определить, свободен или нет в настоящее время требующийся потоку ресурс. Тем не менее, фактически семафор является не просто флагом. Для того чтобы создать семафор, приложение должно вызвать функцию CreateSemaphore(), описание которой находится в файле winbase.h и выглядит следующим образом:

WINBASEAPI HANDLE WINAPI CreateSemaphoreA(

LPSECURITY_ATTRIBUTESlpSemaphoreAtlributes, LONG ITnitialCount. LONG IMaximumCount, LPCSTR IpName);

234



WINBASEAPI HANDLE WINAPI CreateSemaphoreWf

LPSECURITY_ATTRIBUTESlpSemaphoreAttributes, LONG UnitialCount, LONG IMaximumCount. LPCWSTR IpNamc);

#ifdef UNICODE

#define CreateSemaphore CreateSemaphoreW

#else

#dcfine CreateSemaphore CreateSemaphoreA

#endif// IUNICODE

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

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



Последний, четвертый аргумент, - это указатель на строку, содержа­щую имя семафора.

При успешном завершении функция возвращает хэндл созданного сема­фора. Возвращение NULL сигнализирует о том, что произошла ошибка.

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

Перед завершением потока, использующего семафор, последний дол­жен быть освобожден. Это делается с помощью функции ReleaseSemaphoreQ, описание которой, взятое из файла winbase.h, приве­дено ниже:

WINBASEAPI BOOL WINAPI ReleaseSemaphore(HANDLE hSemaphore,

LONG IReleaseCount, LPLONG IpPreviousCount);

Первый аргумент - это хэндл семафора, полученный с помощью функции CreateSemaphore(). Второй аргумент определяет, какое значение

235



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

Таблица 55. Флаги доступа к семафору

Параметр

Описание

SEMAPHORE_ALL_ACCESS

SEMAPHORE_MODIFY_STATE

SYNCHRONIZE

Устанавливает все возможные флаги доступа к семафору

Разрешается изменение счетчика ресурсов в функции ReleaseSemaphoreQ Разрешается использование в любой из ожидаю­щих функций сигнала об изменении состояния семафора

Поток, которому заведомо известно, что семафор уже создан, может не создавать семафор, а открыть его с помощью функции OpenSemaphoreQ. Ниже приведено описание этой функции, взятое из файла winbase.h:

WINBASEAPI HANDLE WINAPI OpenSemaphoreA(DWORD dwDesiredAccess,

BOOL blnheritHandle, LPCSTR IpName);

WINBASEAPI HANDLE WINAPI OpenSemaphoreW(DWORD dwDesiredAccess,

BOOL blnheritHandle, LPCWSTR IpName);

#ifdefUNICODE

#defme OpenSemaphore OpenSemaphoreW

#else

#defme OpenSemaphore OpenSemaphoreA

#endif// ! UNICODE

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



Второй аргумент определяет, наследуют ли этот семафор другие про­цессы, создаваемые функцией CreateProcessQ. Значение TRUE говорит о том, что семафор является наследуемым.

Главным аргументом в этой функции является третий аргумент, опре­деляющий имя открываемого семафора. Если функция выполняется успешно, то она возвращает хэндл открытого семафора.

Созданный или открытый семафор можно использовать с помощью функции WaitForSingleObject(), описание которой, приведенное ниже, можно найти в файле winbase.h:

236



WINBASEAPI DWORD WINAPI WaitForSingleObject(HANDLE hHandle,

DWORD dwMilliseconds);

Первый аргумент функции очевиден - хэндл семафора. Второй аргу­мент определяет время ожидания наступления события в миллисекундах. Если это значение равно 0, то функция сразу же прекращает ожидание и возвращает управление. Если время ожидания определено как INFINITE, то ожидание наступления события не прекращается. Функция может вернуть значения, приведенные в табл. 56.

Алгоритм работы с семафорами выглядит следующим образом:

поток   создает   или   открывает   семафор   с   помощью   функций CreateSemaphore() или OpenSemaphore() соответственно;

поток вызывает функцию WaitForSingleObjectQ (или WaitForMultipleObjects()) Для того, чтобы определить, свободен ли тре­бующийся потоку ресурс. В зависимости от результата, возвращаемого этой функцией, определяются дальнейшие действия;

при завершении поток вызывает функцию ReleaseSemaphoreQ, осво­бождающую семафор.

СОБЫТИЯ

События являются самой примитивной разновидностью объектов синхронизации. Они используются для того, чтобы оповестить поток о том, что наступило ожидаемое событие. Эти объекты обычно использу­ются для того, чтобы синхронизировать потоки, которые работают по принципу конвейера. К примеру, один поток опрашивает датчики и загружает считанные значения в буфер. Другой поток считывает эти данные из буфера и производит их обработку. Первый поток может сигнализировать второму о том, что событие - заполнение буфера -наступило. Второй поток может сигнализировать первому о том, что наступило другое событие - данные из буфера считаны, ожидается новая порция данных. Событие может иметь два состояния - занятое (nonsignaled) и свободное (signaled).



Таблица 56. Значения, возвращаемые функцией WaitForSingleObjectQ

Параметр

Значение

 

Описание

 

WAIT OBJECT 0 WAITJTIMEOUT

WAIT_ABANDONED WAIT_FAILED

 

0x00000000 0x00000102

0x00000080 OxFFFFFFFF

 

Объект перешел в состояние свободного Объект за указанное время не перешел в состояние свободного Объект mutex стал свободным из-за отказа от него Произошла ошибка

 

237



Для того чтобы использовать событие, его нужно создать. Делается это посредством функции CreateEvent(), описание которой, приведенное ниже, взято из файла winbase.h:

WINBASEAPI HANDLE W1NAPI CreateEventA(

LPSECURITY_ATTRIBUTESlpEvc[itAttributes,

BOOL bManualResel,

BOOL blnitialState,

1.PCS1 R IpName); WINBASEAPI HANDLE WINAPI CreatcEvcntW(

LPSnCURITY_ATTRIBUTES IpEveiuAtlribules,

BOOL bManualReset,

BOOL blnitialState,

LPCWSTR IpName);

#ifdef UNICODE

#definc CreateEvcnt CreateEvcntW

«else

Adeline CreateEvent CreateEventA

#endif// IUNICODE

С первым аргументом этой функции - указателем на структуру, со­держащую атрибуты доступа, мы уже знакомы.

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

Третий аргумент определяет начальное состояние создаваемого собы­тия. Значение TRUE определяет, что создается событие в СВОБОДНОМ состоянии. Если поток намерен создать событие в занятом состоянии, то он должен установить этот параметр в FALSE.

И наконец, последний, четвертый аргумент, определяет имя создавае­мого объекта - события.

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

Для того чтобы сигнализировать о наступлении события, в потоке должна присутствовать функция SetEventQ, переводящая событие в свободное состояние, описанная в winbase.h следующим образом:



WINBASEAPI BOOL WINAPI Se(Event(HANDLE hEvent);

Единственный аргумент этой функции очевиден - хэндл события, соз­данного посредством CreateEventQ.

238



Каким образом прикладная программа может узнать о наступлении события? Да с помощью уже знакомой нам функции WaitForSingleObjectf).

Для того чтобы сбросить событие в занятое состояние, необходимо вызвать функцию ResetEvent(), описанную в том же winbase.h следую­щим образом:

WINBASEAPI BOOL WINAPI ResetEvent(HANDLE hEvent);

В качестве аргумента функции передается хэндл события.

Достаточно часто встречаются случаи, когда после установки события с помощью SetEventQ тут же следует вызов ResetEventQ. Для этих целей предусмотрена функция PulseEvent(), описанная так:

WINBASHAPI BOOL WINAPI PulscEvcnt(HANDLE hEvent);

Аргументом является хэндл события. После выполнения функции объект-событие остается в занятом состоянии.

Алгоритм использования объекта-события полностью аналогичен ал­горитму использования семафора.

Надеюсь, что после того, что сейчас узнал читатель, разобрать кри­тические секции и объекты типа mutex ему не составит труда.

ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ

Думаю, что, посмотрев на размер исполняемого файла, полученного после компиляции нашей первой программы «Helloworld», многие были поражены. Как! Столько всего умеет делать программа при таком малом размере! Даже на ассемблере невозможно написать библиотеку такого размера и обладающую такими возможностями. Как же это сделано'? Ответ прост - большая часть кода, обеспечивающего возможности про­граммы, находится вне самое программы, в библиотеках. Это естественно и понятно. Но с исполняемым файлом эти библиотеки соединяются не на стадии липковаиия, как обычные библиотеки, а НА СТАДИИ ВЫПОЛ-НР.НИц! Это одно из принципиальных положений, отличающих все версии Windows от ее главного в прошлом конкурента MS DOS.

Библиотеки динамической компоновки представляют собою одного из тех китов, на которых базировались Windows всех версий, в том числе и Windows'95. Все функции API, с которыми мы работаем, находятся в библиотеках динамической компоновки - DLL (dynamic iink libraries). Основу Windows составляют три библиотеки: kerne!32dll, user32.dll и gdi32.dll. Первая отвечает за управление памятью, процессами и потока­ми, вторая - за систему окон с подсистемой сообщений, третья - за графи­ку и вывод текста (само название - GDI - является аббревиатурой выра­жения Graphical User Interface - графический интерфейс пользователя).



239



Это, так сказать, самое Windows. Многочисленные DLL, которые можно найти в директории Windows, являются ее расширениями. Но, естествен­но, пользователи об этом и не догадываются.

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

СПОСОБЫ ПРИСОЕДИНЕНИЯ DLL К ПРОГРАММЕ



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

Решить эту задачу можно двумя способами. Первый способ - это не­явное линкование с DLL. Второй способ - явная загрузка DLL.

НЕЯВНОЕ ЛИНКОВАНИЕ С DLL

При неявном линковании программа не знает о том, какую библиоте­ку ей необходимо присоединить. Для того чтобы неявно прилинковать библиотеку, необходимо на этапе подготовки проекта произвести неко­торые действия: создать файл с расширением .lib, содержащий ссылку на DLL и перечень находящихся в ней функций. Делается это с помощью утилиты implib. Вызывается она следующим образом:

implib FileNamel.lib FileName2.dll

где FileNamel.lib - это имя создаваемого файла; а FileName2.dll - DLL. Полученный lib-файл можно прилинковать к вашей программе точно так же, как и любую другую библиотеку.

Для того чтобы проиллюстрировать процесс вызова DLL



при неявном линковании, я напишу библиотеку, в которой будет находиться всего одна функция. При обращении к этой функции будет выдаваться окно с сообщением (MessageBox) «Сейчас мы в DLL!». Наверное, трудно при-

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

#ifdef_MYDLL_

#define MY API _decispec(dllexport) #e!sc

#define MYAPI _declspec(dllimport)

#endif

MYAPI void CALLBACK MyMessagc();

Листинг № 7. Файл заголовков библиотеки dll.h: ^include <windows.h> #define _MYDLL_

MYAPI void CALLBACK MyMcssageQ {

McssageBox(NULL. "Now we are in DLL!", "Hurray!" , MB_OK); i

Листинг № 8. Основной файл библиотеки dll.cpp:

LIBRARY        MyMessage

DESCRIPTION 'Program'

EXETYPE         WINDOWS

CODE PRELOAD MOVEABLH DISCARDABLE

DATA PRELOAD MOVEABLE SINGLE

Листинг № 9. Файл определения модуля dll.def:

//include <\vindows.h> //include "dll.h"

hit WiNAPI WmMaiufHlNSTANCE hInsiance,HINSTANCE hPrevInstance,

LPSTR Ips/CmdLinc, int nCmdShow)

i

MyMessageQ; return !;

240



241



Листинг №10. Основной файл программы, осуществляющей вызов библиотечной функции, арр.срр посредством неявной компоновки:

NAME МуЛрр

DESCRIPTION 'Program'

EXETYPE         WINDOWS

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

Листинг № 11. Файл определения модуля app.def.

Для того чтобы эта связка программа-DLL заработала, проделайте следующие действия:

создайте DLL;

с помощью утилиты IMPLIB создайте lib-файл;

при создании ехе-файла прилинкуйте файл, полученный с помощью implib, к вашей программе;

запустите ехе-файл.

Надеюсь, что после всех этих действий вы увидите на экране сообще­ние о том, что произошло обращение к DLL.

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



каталоге, содержащем ехе-файл;

текущем каталоге процесса;

системном каталоге Windows;

каталоге Windows;

каталогах, указанных в PATH.

Попробуйте изменить имя DLL-файла. Вы заметите, что если файл DLL не найден, то система выдает сообщение об этом и немедленно завершает процесс

У неявной компоновки есть свои преимущества и недостатки. По мо­ему мнению, к преимуществам нужно отнести следующее:

программа может ничего не знать о том, что она использует DLL;

проверка доступности DLL производится еще до загрузки программы, т. е. в случае отсутствия DLL программа просто не запустится.

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

242



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

ЯВНАЯ ЗАГРУЗКА DLL

В этом случае все манипуляции с DLL производит вызывающая про­грамма. Для того чтобы библиотека загрузилась в память, должна быть вызвана одна из функций - LoadLibraryQ или LoadLibraryExQ.

В winbase.h эти функции описаны следующим образом:

WINBASEAPI HMODULE WINAPI LoadLibraryA(LPCSTR IpLibFileNamc);

WINBASEAPI HMODULE WINAPI LoadLibraryW(LPCWSTR IpLibFileName);

#ifdcf UNICODE

#define LoadLibrary LoadLibraryW

#else

#defme LoadLibrary LoadLibraryA tfendilV/ !UNICODE

WINBASEAPI HMODULE WINAPI LoadLibraryExA(LPCSTR IpLibFileNamc,

HANDLE hFile, DWORD dwFlags);

WINBASEAPI HMODULE WINAPI LoadLibraryExW(LPCWSTR IpLibFileName,

HANDLE hFile, DWORD dwFlags);

#ifdcf UNICODE

#dcflnc LoadLibraryEx LoadLibraryExW

#elsc

#define LoadLibraryEx LoadLibraryExA

#endif // (UNICODE

Аргументом первой функции является имя загружаемой DLL. Другая же при вызове должна получить три аргумента. Первый - то же имя загружаемой DLL. Второй аргумент зарезервирован и должен быть равен NULL. Третий аргумент должен представлять собой либо нуль, либо комбинацию ИЗ трех флагов: DONT_RESOLVE_DLL_REFERENCES, LOADJJBRARY_AS_DATAFILE, LOAD_WITH_ALTI:RED_SEARCH_PATH.



DONT_RESOLVE_DLL_REFERENCES

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

243



LOAD LIBRARY_AS_DATAFILE

Этот флаг может использоваться в нескольких случаях. Во-первых, можно загружать библиотеку, не содержащую никакого кода и содержа­щую только ресурсы. Полученное значение HINSTANCE можно исполь­зовать при вызове функций, использующих ресурсы. Во-вторых, если мы загружаем ехе-файл обычным способом, то это приводит к запуску нового процесса. А как быть в том случае, если мы хотим получить доступ к ресурсам ехе-файла, не запуская его? При загрузке ехе-файла с помощью функции LoadLibraryExQ с установленным флагом LOAD_LIBRARY_AS_DATAFILE, возможно получить доступ к ресур­сам ехе-файла.

LOAD_WITH__ALTERED_SEARCH_PATH

Ранее мы рассмотрели, какие каталоги и в какой последовательности просматриваются системой при загрузке DLL. Если установлен флаг LOAD_WITH_ALTERED_SEARCH_PATH, то просмотр каталогов начинается с каталога, указанного в первом аргументе функции LoadLibraryExQ. Далее просмотр продолжается в обычном порядке.

После загрузки DLL программа не может вызывать требующиеся ей функции. Дня того чтобы вызвать какую-либо функцию, ей необходимо сначала определить адрес этой функции с помощью GetProcAddressQ, a затем вызывать функцию через полученный адрес. После того, как на­добность в присутствии DLL в памяти отпала, программа должна выгру­зить ее с помощью функций FreeLibrary() или FreeLibraryAndExitThread(). Но сейчас разговор не об этом. Давайте попробуем рассмотреть преды­дущий пример, измененный таким образом, чтобы DLL загружалась явно.

Само собой разумеется, что все, что касается DLL, никаким изменени­ям не подвергалось. Иначе какой смысл писать DLL, если в зависимости от потребностей программиста ее нужно было бы каждый раз переписы­вать? Изменился только основной файл программы, которая вызывает функцию из DLL. Итак...



^include <windows.h> #include "dll.h"

int WINAPI WinMain(HINSTANCE hlnstance. HINSTANCE hPrevInstance, LPSTR IpszCmdLine, int nCindShow)

i

HINSTANCE hDII; FARPROC MyProcAddr;

if( (hDII = LoadLibrary("dll.dll")) != NULL)

244



MyProcAddr - GetProcAddress(hDH, "MyMcssage"); else {

MessageBox(NULL, "Sorry, cannot find requested DLL", "Sorry", MB_OK); return 0; i

(MyProcAddr)O; FreeLibrary(liDII); return 1 ;

Листинг № 12. Основной файл программы, осуществляющей вызов библиотечной функции, арр.срр посредством явной за!рузки DLL.

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

ВЫВЕРНЕМ ПРОГРАММЫ НАИЗНАНКУ



Давайте попробуем разобраться в том, что все это означает и к чему может привести.

Начнем с файла, который использует ся как библиотекой, так и прило­жением - файла заголовков. Чтобы ехе-файл мог вызывать функции из DLL, то, с одной стороны, библиотека должна объявить их как доступ­ные, или, как говорят, экспортируемые. С другой стороны, сам ехе-файл должен определить эти функции как находящиеся в DLL, т. е. как импор­тируемые. Если мы объявим эти функции по-разному в заголовочном файле и непосредственно в тексте, мы не оберемся ошибок при компилч-ции. Следовательно, выход один - условная компиляция. Если мы по­смотрим на распечатку заголовочного файла dll.li, то увидим, что я опре­деляю макро MYAPI, которое принимает одно из двух значений (_declspec(dllexport)) или (_declspec(dllimport) ) в зависимости от факта определения другого макро, _MYDLL_ . Теперь понятно, что и в заголо­вочном, и в исходных файлах можно описать функцию, находящуюся в



245



DLL, как MYAPI, но при этом в исходном файле библиотеки мы должны определить макро _MYDLL , а в исходном файле приложения ни в коем случае это макро не определять. Что и сделано. Проверьте - работает! Посмотрите в заголовочные файлы Win32. По-моему, в них используется эта техника.

Описание функций как экспортируемых требуется линкеру для того, чтобы правильно построить таблицу экспортируемых функций в dll-файле. Каждый элемент в этой таблице содержит имя экспортируемой функции, а также ее адрес. Немаловажно, что список функций сортирует­ся по алфавиту. Таким образом, если функция должна работать бмстро, то какое-то преимущество можно получить в том случае, если имя функ­ции будет начинаться с первых букв алфавита. По это преимущество сработает только в момент выполнения GetProcAddressQ, а не (увы!) при обращении к функции. Для того чтобы разобраться во внутренностях DLL, воспользуемся утилитой TDUMP, поставляемой с Borland C++ 5.0 (аналогичные утилиты есть и в других системах программирования, например, в Microsoft Visual C++ подобная утилита называется DUMPBIN). Часть распечатки таблицы экспорта для kernel32.dll приведе­на ниже:

Turbo Dump Version 4.2.15.2 Copyright (с) 1988, 1996 Borland International Display of File KERNEL32.DLL

Exports from KERNEL32.dll

680 exported name(s), 780 export addresse(s). Ordinal base is I. Ordinal RVA      Name

0049

0002d900 AddAtomA

 

0101

 

00034c99 AddAtomW

 

0102

 

0002f44b AllocConsole

 

0103

 

00021b22 AllocLSCallback

 

0104

 

0002 Ib55 AllocSLCallback

 

0105 0106

 

0002e75b AreFileApisANSl 00034d20 BackupRead

 

 

 

 

 

0774 0775 0776 0777

 

00007 Ida Istrcpyn 00007 Ida IstrcpynA 00034ccf  IstrcpynW 00007251  Istrlen

 

0778

 

00007251  IstrlenA

 

0779

 

0002b83c IstrlenW

 

246



Как можно убедиться, все функции рассортированы в алфавитном по­рядке? В первой колонке указаны ordinals - порядковые номера функций. В более ранних версиях Windows функции могли экспортироваться не только по именам, но и но порядковым номерам. Сейчас Microsoft реко­мендует пользоваться только именами функций (именно поэтому я не описываю способ экспорта и импорта функций посредством указания порядковых номеров). Для справки - средняя колонка содержит Real Virtual Addresses - адреса функций внутри DLL.



Аналогично решается и вопрос при импорте функций. Функция объ­является как импортируемая, после чего линкер строит специальный код и таблицу импортируемых функций.

Кстати, помимо TDUMP можно воспользоваться утилитой IMPDEF, которая выдает список присутствующих в DLL функций. Что же касается моего личного мнения, то я рекомендую читателю изучить форматы файлов Windows и написать самостоятельно несколько утилит, которые будут использоваться для «выворачивания программ наизнанку» и пока­зывать все те данные и в таком виде, который удобен программисту.

ИНИЦИАЛИЗАЦИЯ И ДЕИШЩИАЛЮАЦИЯ DLL



Мы разобрались, каким образом можно написать DLL и вызвать функцию из DLL. По, как правило, ьо всех нормальных (не демонстраци­онных) примерах существуют блоки, отвечающие за инициализацию и деннициализацию программы. Возникает вопрос: как это сделать в DLL? LcTb ли такая возможность? Да, есть! В каждой библиотеке может быть функция, которая вызывается строго в определенных обстоятельствах и обычно используется библиотекой для инициализации и деиницналнза-ции. В нашей микро-DLL эта функция не использовалась, однако, я счел бы тему рассмотренной не полностью, если бы обошел этот вопрос стороной

В Borland C++ v. 5.0 эта функция по умолчанию называется DllEntryPointO и в некотором смысле является аналогом связки LibMi/:n() + WEP() в Windows 3.x. Вызывается эта функция всего в четырех случаях и имеет следующий вид:

BOOL WINAPI DHEntryPoint(HINSTANCE hinstDll, DWORD fdwReason, LPVOID IpvReserved)

{ switch(fdwReason)

{ case DLL_PROCESS ATTACH:

247



/* Операторы */ case DLL_THREAD_ATTACH:

/* Операторы */ case DLL_THREAD_DETACH:

/* Операторы */ case DLL_PROCESS_DETACH:

/* Операторы */

} return(TRUE);

Лично мне именно такой способ инициализации и деинициализа-ции библиотек динамической компоновки очень импонирует. Дело в том, что он использует оператор switch - case, который применяется при обработке сообщений. Именно из-за этого вся конструкция зрительно воспринимается так же, как и оконная процедура. Мне кажется, что с такой функцией намного приятнее иметь дело, чем со связкой LibMainQ - WEP в Windows 3.x.



Первый аргумент этой функции - это хэндл библиотеки, присваивае­мый системой.

Второй аргумент указывает причину вызова этой библиотеки систе­мой.

Третий аргумент, как понятно из его названия, пока зарезервирован и обычно должен быть равным NULL.

Теперь нам необходимо до конца разобраться с причинами вызова библиотеки.

DLL_PROCESS_ATTACH

Система вызывает функцию инициализации с этим значением пара­метра fdwReason единственный раз при загрузке библиотеки. Другими словами, если один из потоков процесса, вызвавшего DLL, пытается вновь загрузить ее с помощью LoadLibraryQ, то обращение к DLL с параметром DLL_PROCESS_ATTACH не произойдет. Система увеличит счетчик пользователей этой DLL.

Значение, возвращаемое функцией инициализации, после обработки DLL_PROCESS_ATTACH, уведомляет пользователя, была ли инициали­зация успешной. В случае неуспешной инициализации функция должна возвратить FALSE, при успехе - TRUE. Это значение используется как при неявной, так и при явной загрузке DLL в память.

248



DLL PROCESS JDETACH

Вызов функции инициализации для обработки DLL_PROCESS_DETACH означает, что библиотека из памяти выгружа­ется и должна произвести действия по деинициализации самое себя. Помимо того, что необходимо освободить память и другие ресурсы, хорошим тоном считается оставить систему точно в том же состоянии, в каком ее приняла библиотека (если, конечно, изменение параметров системы не является задачей одной из функций DLL). При выгрузке библиотеки есть одна тонкость, связанная с причиной завершениия процесса, обратившегося к ней. Если DLL выгружается в связи с вызовом функции ExitProcessQ (или FreeLibraryQ, хотя это и не связано с процес­сом), вызов функции инициализации проходит нормально. Но если процесс завершается благодаря функции TerminateProcess(), функция инициализации НЕ ВЫЗЫВАЕТСЯ! Таким образом, попутно можно сделать еще один вывод - функцией TerminateProcessQ можно и нужно пользоваться только в самых крайних случаях!

Ниже приведен чуть измененный листинг библиотеки dll.c; попробуй­те загрузить ее и понаблюдать за тем, как она работает:



^include <windows.h> tfdettne _MYDLL_ #include "dll.h"

BOOL WINAPI D!IEntryPoint(HINSTANCE hinstDII, DWORD fdwReason,

LPVOID IpvReserved) {

switch(fdwReason) {

case DLL PROCESS_ATTACH: MessageBox(NULL, "We are in DLL_PROCESS_ATTACH!", "Hurray!",

MB_OK); break;

case DLL_THREAD_ATTACH: MessageBox(NULL, "We are in DLL_THREAD_ATTACH!", "Hurray!",

MB_OK); break;

case DLL_THREAD_DETACH: MessageBox(NULL, "We are in DLL_THREAD_DETACH!", "Hurray!",

MB_OK); break;

case DLL PROCESS DETACH:

MessageBox(NULL, "We are in DLL_PROCESSJ)ETACH!", "Hurray!", MBJDK);

249



break;

} retum(TRUE);

/

MYAPI void CALLBACK MyMessageQ

"I

MessageBox(Gc4DcsktopWindo\v().''nLI, is called!", "Hurray!" . MB_OK},

i j

Листинг № 13. Библиотека dll.c, включающая код функции инициали­зации.

Очередной раз - ура! Мы научились писать библиотеки динамической компоновки, добавлять в них функции инициализации и деишшиализа-ции, подключать DLL к нашим программам, используя как неявную, так и явную загрузку.

КОНСОЛИ

«Неужели для того, чтобы написать простейшую программу, которая выводит на экран несколько строк, мне необходимо городить этот огород с WinMainQ и функцией окна'.' Неужели в каждой моей программе, предназначенной для вывода текста на экран, должно присутствовать «стандартное заклинание»? Или же я вынужден вечно быть огра­ниченным рамками DOS?» Я предвижу такие вопросы со стороны тех, которым при разработке их программ не только ns нужен, но и мешает графический интерфейс.

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

ЧТО ТАКОЕ КОНСОЛЬ



Консоль - это интерфейс, обеспечивающий поддержку программ, ра­ботающих в текстовом режиме, т. е. программ, написанных в стиле MS DOS. Консоль состоит из буфера ввода и одного или нескольких экран­ных буферов. Буфер ввода включает в себя очередь, каждая запись в которой содержит информацию о вводных событиях. Под вводными событиями в данном случае подразумеваются нажатия и отжатая клавиш на клавиатуре, на мыши, движения мыши, а также действия пользователя, производимые с окном. Экранный буфер - это двумерный массив, кото-



250



рый содержит коды символов и цвета символов текстового экрана (аналог видеобуфера в текстовом режиме при работе в MS DOS).

Каждая программа, работающая в текстовом режиме, взаимодействует с Windows через консоль. Если одна программа запускается из консоли, принадлежащей другой программе (скажем, aidstest запускается из кон­соли Norton Commander'a), то запускаемая программа работает в той же консоли. Если же программа запускается самостоятельно из Windows, то ей выделяется собственная консоль (фактически ей выделяется целая виртуальная машина). Другими словами, каждая программа может по­лучить для себя эмулятор DOS-машины и считать, что весь компьютер принадлежит только ей.

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

Мы долго говорили о том, что с консолями работают программы, на­писанные в стиле MS DOS. Но в языке С точкой входа для ООЗ'овских программ является функция mainQ, а не WinMainQ, следовательно, и консольные программы должны точкой входа тоже иметь функцию mainQ, а не WinMainQ. Таким образом, основными отличиями консоль­ных программ от обычных программ для Windows являются: отсутствие графического интерфейса;

использование  в  качестве  точки  входа  функции  mainQ,   а  не WinMainQ.

ТЕХНИКА РАЗРАБОТКИ КОНСОЛЬНОЙ ПРОГРАММЫ



СОЗДАНИЕ КОНСОЛИ

Как уже было сказано, консольная программа должна иметь точкой входа не WinMainQ, a mainQ. При запуске программы она должна запро­сить для себя консоль, используя для этого функцию AllocConsoleQ. Ее прототип находится в файле wincon.h, к которому мы будем обращаться в этом разделе:



WINBASEAPI BOOL WINAPI AllocConsole(VOID);

251



Эта функция, возвращающая TRUE при успешном завершении, пре­ доставляет вызвавшей ее программе консоль. Внешне консоль выглядит так же, как и обычное окно. У него есть заголовок, системное меню, кнопки максимизации и минимизации. Если программе необходима собственная консоль, а она работает в унаследованной, то программа может перед вызовом AlIocConsole() произвести освобождение консоли, вызвав для этого функцию FreeConsole(), описание которой практически не отличается от описания предыдущей функции:

WINBASEAPI BOOL WINAPI FreeConsole( VOID );

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

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

ПРИСВОЕНИЕ КОНСОЛИ ИМЕНИ

Очередным шагом после создания консоли будет присвоение консоли имени. Это имя будет отображено в заголовке консоли. Делается это с помощью функции SetConsoleTitle(). Из wincon.h извлекаем прототип этой функции:

WINBASEAPI BOOL WINAPI SetConsoleTitleA(LPCSTR IpConsoleTitle ); WINBASEAPI BOOL WINAPI SetConsoleTitleW(LPCWSTR IpConsoleTitlc);

#ifdefUNICODE

#define SetConsoleTitle SetConsoIeTitleW

#else

«define SetConsoleTitle SetConsoleTitleA

tfcndif// IUNICODE

Аргумент этой функции - указатель на строку символов, содержащую текст, который будет отображен в заголовке окна консоли.

ВВОД И ВЫВОД В КОНСОЛИ

Основные функции вывода в окно консоли

Когда я изучал этот вопрос, функции ввода и вывода в консоли на­помнили мне вызов прерываний DOS (наверное, так и должно быть, ведь консоль эмулирует DOS-машину). Поэтому знакомые с прерываниями DOS программисты увидят в функциях ввода - вывода много «знакомого».

252



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



WINBASEAPI HANDLE WINAPI GetStdHandle(DWORD nStdHandle);

извлеченному в данном случае из файла winbase.h, мы видим, что для получения хэндла стандартного потока в консольной сессии мы в качестве аргумента функции должны указать номер того потока, хэндл которого нам нужен. Запоминать номера потоков не нужно, они опреде­лены в том же файле winbase.h как STD_INPUT_ HANDLE, STD_OUTPUT_HANDLE и STD_ERROR_HANDLE. При успешном завершении функция возвращает хэндл требующегося потока, в против­ном случае возвращаемое значение равно INVALID_HANDLE_VALUE.

Определив хэндл стандартного потока, можно попытаться вывести текст в окно консоли с помощью функции WriteConsoleQ:

WINBASEAPI BOOL WINAPI WriteConso!eA(HANDLE hConsoleOutput,

CONST VOID «IpBuffer,

DWORD nNumberOfCharsToWrite,

LPDWORD IpNumberOfCharsWritten,

LPVOID IpReserved); WINBASEAPI BOOL WINAPI WriteConsolcW(HANDLE hConsoleOutput,

CONST VOID "IpBufTer,

DWORD nNumberOfCharsToWrite,

LPDWORD IpNumberOfCharsWritten,

LPVOID IpReserved);

#ifdefUNICODE

#defme WritcConsole WriteConsoleW

#else

#defme WriteConsole WriteConsoleA

#endif// IUNICODE

Аргументами этой функции являются:

хэндл стандартного потока вывода;

указатель на выводимую строку;

длина выводимой строки в символах;

в указатель на двойное слово, в которое записывается действительное число выведенных символов;

253



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

Строка выводится, начиная с текущей позиции курсора, при этом ис­пользуются текущие цветовые атрибуты текста и фона. Курсор устанав­ливается в позицию, следующую за последним символом строки.

В случае успешного завершения функция возвращает TRUE.

Для установки позиции курсора в консоли необходимо вызвать функ­цию SetConsoleCursorPositionQ, прототип которой можно найти в wincon.h:

WINBASEAPI BOOL WINAPI SetConsoleCursorPosition( HANDLE hConsoleOulput, COORD dwCursorPosition);

hConsoleOutput - это хэндл стандартного вывода консоли, а структура типа COORD, содержащая координаты новой позиции курсора, опреде­ляется в wincon.h следующим образом:



typedef struct _COORD {

SHORT X;

SHORT Y; } COORD, *PCOORD;

X и Y - координаты новой позиции курсора.

Если функция завершена успешно, она возвращает ненулевое значение.

Последнее, что нам осталось сделать для того, чтобы мы могли полно­стью управлять выводом, - это научиться устанавливать цветовые атри­буты выводимого текста. Учиться недолго - это делается с помощью функции SetConsoleTextAttribute(). Извлечем из wincon.h ее прототип:

WINBASEAPI BOOL WINAPI SetConsolcTexlAttribute(HANDLE hConsoleOutput,

WORD wAttribute.s);

hConsoleOutput - хэндл стандартного потока вывода консоли, a wAttribues определяет цвета тона и фона текста. wAttributes должен быть комбина­цией нескольких флагов. Перечень флагов приведен в табл. 57.

254

Таблица 57. Атрибуты цветов фона и тона окна консоли

Флаг

Значение

 

Эффект

 

FOREGROUND BLUE

 

0x0001

 

Тон содержит синюю составляющую

 

FOREGROUND_GREEN

 

0x0002

 

Тон содержит зеленую составляющую

 

FOREGROUND RED

 

0x0004

 

Тон содержит красную составляющую

 

FOREGROUND INTENSITY

 

0x0008

 

Тон имеет повышенную интенсивность

 

BACKGROUNDJ3LUE

 

0x0010

 

Фон имеет синюю составляющую

 

BACKGROUND GREEN

 

0x0020

 

Фон имеет зеленую составляющую

 

BACKGROUND RED

 

0x0040

 

Фон имеет красную составляющую

 

BACKGROUND INTENSITY

 

0x0080

 

Фон имеет повышенную интенсивность

 

 

 

 

 

или текст мигает (только в полноэкранном

 

 

 

 

 

режиме)

 

В разделе, посвященном графике, упоминалось о том, что каждый пиксель на экране состоит из трех микроточек, при этом интенсивность свечения каждой точки может изменяться от нуля до 255. В тексто­вом режиме все проще. В обычных условиях (подчеркиваю - обычных условиях!) тон символа (не пикселя - символа!) тоже определяется как состоящий из трех компонентов, однако их интенсивности могут быть 0 и 127 (флаг интенсивности не установлен), 0 и 255 (флаг интенсивности установлен). Таким образом, всего возможно 16 цветов тона символов.



Точно так же обстоит дело и с фоном символов. Однако в зависимости от некоторых условий (обсуждение возможностей и регистров видео­адаптера явно выходит за рамки этой книги), установленный фла! интен­сивности тона обеспечивает либо повышенную интенсивность фона, либо мигание символа. Но все это касается только программ, работающих в полноэкранном режиме (не путать максимизированную консоль с рабо­той в полноэкранном режиме!). Рекомендую читателю проверить эту установку на своем компьютере. Программы в консольном режиме, к сожалению, не могут использовать мигание фона, поэтому те символы, для которых в полноэкранном режиме установлен флаг мигания, в кон­сольном режиме не мигают, а отображаются с повышенной интенсивно­стью цвета фона символов.

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

Ввод из окна консоли

Для ввода из окна консоли текста с клавиатуры используется функция

ReadConsoleO, описанная следующим образом: WINRASEAPI BOOL WINAPI ReadConso!c.A(

255



HANDLE hConsolelnput, LPVOID IpBuffer, DWORD nNumberOfCharsToRead, I.PDWORD IpNumberOfCharsRead, LPVOID IpRcserved); WINBASEAPI BOOL WINAPI ReadConsoleW(

HANDLE hConsolelnput, LPVOID IpBuffer, DWORD nNumberOPCharsToRead, LPDWORD IpNumberOfCharsRead, LPVOID IpReserved);

#ifdefUNICODE

#defme ReadConsole ReadConsoleW

#else

#defme ReadConsole ReadConsoleA

#endif// IUNICODE

Аргументами этого файла являются: hConsolelnput - хэндл потока ввода;

IpBuffer - указатель на символьный массив, в который будет записа­на строка символов;

nNumberOfCharsToRead - максимальное число вводимых символов; IpNumberOfCharsRead - число фактически считанных символов; IpReserved - зарезервировано для дальнейшего использования и должно быть равно NULL.

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



Демонстрационная программа

Перед тем, как привести текст демонстрационной программы, мне бы хотелось обратить внимание читателя на одну тонкость. Программы, которые мы рассматривали до сих пор, являлись оконными программами. Я не давал никаких пояснений по поводу их компиляции. Но та програм­ма, которую мы будем разбирать сейчас, не имеет собственного гра­фического интерфейса, т. е. оконной программой не является, а следова­тельно, и компилировать ее нужно несколько иным способом. Предлагаю читателю обратиться к руководству по той системе, с которой он работа­ет, и выяснить, каким образом можно скомпилировать консольную программу. Если читатель паче чаяния работает с Borland C++ 5.0 в IDE, то ему при создании нового проекта в TargetExpert необходимо изменить TargelMode с GU! на Console.

256



Сделано? Тогда листинг демонстрационной программы перед вами:

#include <windows.h>

#includc <stdio.h> mainQ {

HANDLE hStdlnputHandle, hStdOutputHandle;

COORD Coord;

char cMyString[255] = "This is our first console program! It's working !";

DWORD dwResult;

FreeConsoleQ;

AllocConsoleQ;

SetConsoleTitle("ConsoIe Demonstration program");

hStdlnputHandle = GetStdHandle(STD_lNPUT HANDLE);

hStdOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

Coord.X = (80 - strlen(cMyString)) / 2;

Coord. Y= 12;

SetConsoleCursorPosition(hStdOutputHandle, Coord);

SetConsoleTextAttribute(hStdOutputHandle, FOREG ROUNDJIED |

BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN FOREGROUNDJNTENSITY | BACKGROUNDJNTENSITY);

WriteConsole( hStdOutputHandle, cMyString, strlen(cMyString), &dwResult, NULL); SetConsoleTextAttribute(hStdOutputHandlc, 0); getchar();

SetConsoleCursorPosition(hStdOutputHandle, Coord); WriteConso!e(hStdOutputHandle, cMyString, strlen(cMyString),

&dwResult, NULL); Coord.X = 0; Coord.Y= 12;

SetConsolcCursorPosition(hStdOutputHandle, Coord); SetConsoleTextAttribute(hStdOutputHandle, FOREGROUND_RED [

FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY); WriteConsolefhStdOutputHandle, "Type some letters and press Enter, please: ",



strlen(cMyString), &dwResult, NULL); SetConsoleTextAttributefhStdOutputHandle,

FOREGROUND_RED | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUNDJ3REEN | FOREGROUNDJNTENSITY);

ReadConsolefhStdlnputHandle, cMyString, strlen(cMyString), &dwResult, NULL); return 0;

257



Окно, отображаемое при запуске программы, показало на рис

5 Сонм» yemomuation pcogi



Рис. 21. Окно-консоль, в которое иронзвелен выно.; счроки

Ничего особенного в этой программе не происходи1! Программа за­прашивает собственную консоль и устанавливает заголовок этой консоли, после чего получаст хэндл' потоков ввода и вывода. Курсор устанавлива­ется с таким расчетом, чтобы выводимая фраза размещалась в центре экрана. Цвет выводимых символов устанавливается как ярко-красный на ярко-белом (на рисунке показана консоль программы именно после этого момента). После вывода фразы программа ждет нажатия-Enter, после чего затирает фразу и предлагает пользователю что-нибудь набрать на экране, завершив набор нажатием клавиши Enter. Завершение ввода пользователя означает и завершение программы. Ничего сложного, но используются все функции управления консолью, которые мы уже изучили. На долю читателя я оставляю возможность самостоятельно исследовать, какие функции ввода - вывода из стандартных библиотек С и C++ могут быть использованы в консольных программах вместо функций API. Попробуй­те, например, начать с исследования функции printf(). С другой стороны, приятной особенностью консольных программ является возможность вызова таких функций API, как, скажем, MessageBoxQ. Другими словами, при очень небольшой доработке большинство программ MS DOS могут быть перекомпилированы как консольные программы и могут использо­вать многие из тех возможностей, которые предоставляет

258

ОБРАБОТКА НАЖАТИЙ КЛАВИШ НА КЛАВИАТУРЕ И СОБЫТИЙ, ПРОИСШЕДШИХ С МЫШЬЮ

Мы уже знаем, что Windows - система, управляемая событиями. Кон­сольные программы не являются исключением. По есть одна деталь, которую необходимо ел метить. События с клавиатурой и мышью запи­сываются во входной буфе]) только в тех случаях, когда программа, во-первых, имеет клавиатурный фокус, и, во-вторых, указатель мыши нахо­дится в рабочей области консольного окна. Каждому событию, произо­шедшему с консолью, соответствует одна запись во входном буфере. Каждая запись о событии представляет собой заполненную структуру типа INPUT_RECORD, описание которой можно найти в файле vvincon.h:



typedcf struct _1NPUT_REC'ORD { WORD EventType; union )

KEY EVENT_RECORD KeyEvent;

MOUSE_EVENTRECORD MouseEvent;

WINDOWJ3UFFER SIZE RECORD WindowBuflcrSizcEvent;

MENU EVENT_RECORD MenuEvcnt;

FOCUS_EVENT_RECORD FocusEvent; j Event; } INPUT RECORD, *PINPUT RECORD;

Даже не особо вникая в смысл полей, видно, что консоль обрабатыва­ет пять типов событий. Их перечень, взятый из файла wincon.h, приведен в табл. 58.

Т а б л и ц а 58. События, обрабатываемые консолью

Флаг

Значение

 

Эффект

 

KEY EVENT

 

0x000 1

 

Событие с клавиатурой

 

MOUSE EVENT

 

0x0002

 

Событие с мышью

 

WINDOW_BUFFER_SIZE_EVENT

 

0x0004

 

Событие по изменению размерен

 

 

 

 

 

экрана

 

MENU EVI-NT

 

0x0008

 

Событие с меню

 

FOCUS_ EVENT

 

0x00 1 0

 

Изменение фокуса

 

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

259



WINBASEAPI BOOL WfNAPI ReadConsoleInputA(

HANDLE hConsolelnput,

PfNPUT_RECORD IpBuffer,

DWORD nLength,

LPDWORD IpNumberOfEventsRead); WINBASEAPI BOOL WINAPI ReadConsoleInputW(

HANDLE hConsolelnput,

PINPUT_RECORD IpBuffer,

DWORD nLength,

LPDWORD IpNumberOfEventsRead);

#ifdef UNICODE

#define ReadConsoIelnput ReadConsolelnputW

#else

#define ReadConsoIelnput ReadConsolelnputA

#endif// IUNICODE

Здесь:

hConsolelnput - хэндл входного потока консоли;

IpBuffer - указатель на структуру типа INPUT_RECORD, в которую будут записаны данные о событии (или массив структур, если считываются данные более чем об одном событии);

nEength - число считываемых записей о событии;

IpNumberOfEventsRead - указатель на двойное слово, в которое запи­сывается число реально считанных данных.



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

События с клавиатурой

События клавиатуры генерируются каждый раз при нажатии клавиши. При этом поле EventType структуры типа _INPUT_RECORD содержит значение   KEY_EVENT,   а   в   объединение   Event   записывается   поле KeyEvent типа KEY_EVENT_RECORD. Этот тип определен в wincon.h: typedef struct _KEY_EVENT_RECORD { BOOL bKeyDown; WORD wRepeatCount; WORD wVirtuaiKeyCode; WORD wVirtualScanCode; union {

WCHAR UnicodeChar; CHAR   AsciiChar; } uChar;

DWORD dwControlKeyState; } KEY_EVENT_RECORD, *PKEY_EVENT_RECORD;

260

Для того чтобы нормально обрабатывать события с клавиатурой, нам необходимо подробно разобрать назначение полей этой структуры. Программисты, знакомые с обработкой клавиатурных прерываний в DOS, увидят здесь множество знакомых характеристик. К сожалению, рамки этой книги не позволяют мне описать основы работы с клавиатурой. Если читатель чувствует, что у него в этой области есть пробел, рекомендую изучить этот вопрос по другим изданиям.

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

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

Виртуальный код нажатой клавиши записывается в поле wVirtualKeyCode, а виртуальный скан-код - в поле wVirtualScanCode.

Объединение uChar содержит ASCII или Unicode код нажатой клави­ши в зависимости от того, какая версия функции ReadConsolelnputQ, ASCII или Unicode, используется.

И наконец, поле dwControlKeyState указывает на состояние управ­ляющих клавиш. Их возможные значения приведены в табл. 59.

Т а б л и ц а 59. Флаги состояния управляющих клавиш

Флаг

 

Значение

 

Эффект

 

RIGHT ALT PRESSED

 

0x0001

 

Нажат правый Alt

 

LEFT ALT PRESSED

 

0x0002

 

Нажат левый Alt

 

RIGHT CTRL PRESSED

 

0x0004

 

Нажат правый Ctrl

 

LEFT CTRL PRESSED

 

0x0008

 

Нажат левый Ctrl

 

SHIFT PRESSED

 

0x0010

 

Нажат Shift

 

NUMLOCK ON

 

0x0020

 

NumLock горит

 

SCROLLLOCK ON

 

0x0040

 

ScrollLock горит

 

CAPSLOCK ON

 

0x0080

 

CapsLock горит

 

ENHANCED KEY

 

0x0100

 

Клавиша с двойным скан-кодом

 

<


Демонстрационная программа

Я думаю, что даже конспективного изложения достаточно для того, чтобы можно было начать работу с клавиатурой в консольной сессии. Разве не так, уважаемый читатель? Тем не менее, я привожу демонстра-

261

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

#include <windows.li>

#include <stdio.h>

main() {

HANDLE hStdlnputHandie, hStdOutputHandle; char cMyMessage[80] - "Do something with mouse to exit"; COORDCoord= {0,24}; DWORD dwResult; BOOL bMyFlag = TRUE; _INPUT^RECORD InputRecord; char cMyString[16]; char* cMyKeys[9] •= {" RAlt", " LAlt"," RCtrl", " LCtrl", " Shift", " NumLock",

" ScrollLock", " CapsLock"," EnhKey"); DWORD dwMyFlag;

FreeConsole();

AllocConsoleO;

SetConsoleTitle("Keyboard in console session demo program");

hStdlnputHandie = GetStdHandle(STDJNPUTJlANDLE);

hStdOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

SctConsoleCursorPosition(hStdOutputHand!e, Coord);

SetConsoleTextAttribute(hStdOutputHandIe, FOREGROUND_RF.D |

FOREGROUND_GREEN | FOREGROUND_BLUE); WriteConsole(hStdOutputHandle, cMyMcssage, strlen(cMyMessage), &dwResult,

NULL);

while(bMyFlag) t

ReadConsoleInput(hStdInputHandlc, &InputRecord, 1, &dwResult); if(dwResult>= 1) f

ir(InputRecord.EvcntType == KEY EVENT)

t »

SetConsoleCursorPosition(hStdOutputHandlc. Coord); SctConso!eTextAttribute(hStdOutputHandle,0); WritcConsole(hStdOutputHandle, cMyMcssage, strlen(cMyMessage),

&dwResull, NULL); for( int i = 0, i < 80; i Ч

cMyMessagcfi] ~ 0; C'oord.X - 0; Coord. Y= 1;

SetConsoleCursorPositioii(hStdOutput!iandle. Co»/rd); if(lnputRecord. Event. Key Event, b Key Down)

strcat(cMyMessagc, "Pressed "); else

262

strcat(cMyMcssage, "Released "); strcat(strcat(cMyMessage,

itoa(InputRccord.Event KcyEvent.wVirtualKeyCode,

cMyString, 16)),""); strcat(cMyMessage,



itoa( InputRecord.Event.KeyEvent.wVirtualScanCode,

cMyString, 16));

if(InputRccord.Event.KeyEvent.dwControlKeyState != 0) ior(int i - 0; i <= 8; i+-r) {

dwMyFlag = 1;

if(InputRecord.Event.KeyEvent.dwControlKeyState & (dwMyFlag « i)) strcat(cMyMessage, cMyKeys[i]);

(

SetConsoleTcxtAttribute(hStdOutpulHandle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE): WriteConsolc(hStdOutpu (Handle, cMyMessage. strlen(cMyMessage),

&dwResult, NULL); }

else

ifTInputRecord.EvcntType == MOUSE_EVENT) bMyFlag = FALSE;

return 0;



;Jnc. '12. (/KHo-KOiicoiib, отогбражающес лоложслие курсора мыши и сосюяимс клавиатур!-;

263

На рис. 22 показан вид окна, созданного этой программой.

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

События с мышью

События с мышью происходят в тех случаях, когда мышь двигается (при этом курсор должен находиться поверх окна консоли), либо на ней нажимается одна или более кнопок. При возникновении события с мы­шью поле EventType структуры типа INPUT RECORD содержит значение MOUSE_EVENT, В объединении Event этой структуры в этом случае будет содержаться поле MouseEvent типа MOUSE_EVENT RECORD. Для того чтобы понять, какую информацию мы можем извлечь из события с мышью, рассмотрим описание типа MOUSE_EVENT_RECORD. Его мы извлечем из заголовочного файла wincon.ii:

typedef smict _MOUSn_EVENT_RECORD {

COORD dwMousePosition;

DWORD dwButtonState;

DWORD dwControlKeyState;

DWORD dwEventFlags; ! MOUSEJEVENT_RECORD, *PMOUSE_EVENT_RECORD;



В этой структуре некоторые поля нам уже знакомы. Первое поле dwMousePosition типа COORD - координаты курсора мыши во время наступления события. Если обычно координаты курсора указываются в пикселах, то в данном случае они указываются в символах, причем начало отсчета - левый верхний угол рабочей области окна консоли. Не забудьте, экран-то текстовый!

Поле dwButtonState описывает состояние кнопок мыши на момент возникновения события. Кодируется это поле достаточно замысловато. Считается, что максимум у мыши может быть четыре кнопки (лично я таких мышей не видел и не слышал о них. Может, Microsoft боится повторить ситуацию с 640 кбайтами в DOS?). При этом младший бит определяет состояние самой левой клавиши ( 1 - клавиша нажата), сле-

264



дующий по старшинству бит определяет состояние самой правой клави­ши. Очередной бит связан со второй слева кнопкой, следующий - с третьей слева и, наконец, последний - с четвертой слева кнопкой. Для каждого из этих битов в файле wincon.h определены макросы, которые приведены в табл. 60.

Т а б л и ц а 60. Флаги, определяющие нажатую клавишу мыши

Макрос

Значение

 

FROM LEFT  1ST BUTTON PRESSED RIGHTMOST BUTTON PRESSED FROM LEFT 2ND BUTTON PRESSED FROM LEFT 3RD BUTTON PRESSED

from "left Чтн "button "pressed

 

0x0001 0x0002 0x0004 0x0008 0x0010

 

Таблица 61. События от мыши

Флаг

 

Значение

 

Эффект

 

MOUSE MOVED DOUBLE J7LICK

 

0x0001 0x0002

 

Перемещение мыши Второй щелчок кнопки (при двойном щелчке)

 

До чего же приятно работать с макросами, название которых опреде­ляет их назначение!

С полем dwControlKeyState мы познакомились при изучении работы с клавиатурой. Никаких изменений это поле по сравнению с аналогичным в структуре KEY_EVENT_RECORD не претерпело.

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



Не напоминает ли это все нотификационные события при разработке оконных программ?

Демонстрационная программа

Думаю, что все дальнейшие объяснения излишни. Вспомним о прин­ципе «Seeing is believing». Давайте разберем небольшую демонстрацион­ную программу. При написании этой программы я, чтобы не утомлять читателя, сделал одно допущение: у мыши всего две кнопки. Надеюсь, это допущение не повлияет на восприятие программы читателем:

^include <windows,h>

265



mainQ

HANDLE hSldlnputHandle. liSldOtitputHandle;

COORD Coord = {0,24};

char cMyMessage[80] = "Press any key lo exit";

DWORD dwResult;

BOOL bMyFlag = TRUE;

_INPUTJ?ECORD InputRecord;

char cMyString[l6];

char* cMyButtons[4] - {" LcftButton", " RightButton".

" Mouse moved". " Double Click"}; DWORD dwMyFlag;

FrceConsoleQ;

AllocConsole();

SctConsoleTitlc("Mouse in console session demo program");

hStdlnputHandle = GctStdHandle(STD_INPUT HANDLE);

hStdOutputHandle - GetStdHandle(STD _OUTPUT_HANDLE);

SetConsoleCursorPosition(hStdOutputHandle. Coord);

SetConsoleTextAttribute(liStdOutputHandle, FOREGROUND_RED |

FOREGROUND_GREEN | FOREGROUND^BLUE);

WriteConsole(hStdOutputHand!e, cMyMessagc. strlen(cMyMessagc). &dwRcsult,NULL);

while(bMyFlag)

( i

ReadConsolclnput(hStd!nputHandle, &lnpulRecord, 1, &dwRcsult);

irflnputRccord.EventType -= MOUSE EVENT)

SelConsoleCursorPosition(hStdOtitputHandlc, Coord); SelConsolcTcxtAttribute(liS(dOulputHandle,0); WritcConsole(hSldOutput Handle. cMyMcssage, slrlen(cMyMessage),

&d\vResult, NULL); for( int i = 0; i < 80; i — )

cMyMessagcfi] = 0; Coord. X - 0; Coord. Y - I ;

SclConsolcC'ursorl'ositioiKhStdOiitpul Handle, Coord); slrcaUcMyMcssage. "P(!situ;n - "): strcatfcMy Message.

itoa( input Record. I'.vciit.MouseEvent.dwMuuscPosition.X,

cM>'String. 10)): sircat(cMyMessai:e. ". "); strcaKcMyMessagc.

itoat InputRecord. Event. Mouse Event. dwMouscPosition.Y, cMySlrin». 10));



266



strcat(cMyMessage, " "); l'or(int i = 0; i <= I; i-r+) {

dwMyFlag = 1; if(InputRecord.Event.MouseEvcnt.dwButtonStatc & (dwMyFlag « i))

strcaI(cMyMcssage, cMyButtons[il);

if(InputRecord.Event.MouseEvent.dwEvcntFlags & (dwMyFlag « i)) strcat(cMyMessage, cMyButtons[i+2]);

i t

SetConsoleTextAttributc(hStdOutputHamIle, FOREGROUND_RED |

FOREGROUND^GREEN FOREGROUND_BLUE); WriteConsolc(hStdOulpulHandle, cMyMessagc, strlen(cMyMcssagc),

&dwRcsull,NULL); }

else

if(lnputRecord.EvenlType =-=- KEY_EVENT) bMyFlag = FALSE;

return 0;

И, как всегда, вид окна, созданного программой (рис. 23).

1НШН1 т on»li ibcbob diB» рмшю



Рис  2~<. Омю-конс'оль, см поражающее по южсние и состояние мыши

267



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

ЗАКЛЮЧЕНИЕ



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

Пусть читатель не судит меня строго. Я сделал все, что мог.

Теперь я должен расстаться со своим читателем и мне немного груст­но. Я не знаю, что я должен сказать: «До свидания» или «Прощайте». Надеюсь, что до свидания, мой читатель.

268



ПРИЛОЖЕНИЕ



Список макросов, используемых для создания программ, способных работать как в кодировке ANSI, так и в кодировке Unicode

Макрос in tchar.h

Функция

 

Макрос из tchar.h

 

Функция

 

tWinMain

 

WinMain

 

puttchar

 

pu(char

 

targv

 

_argv

 

tputenv

 

pu(env

 

_tenviron

 

_environ

 

pulls

 

puts

 

tfullpath

 

fullpath

 

tremove

 

remove

 

tmakepath

 

makepath

 

(rename

 

rename

 

_tpopen

 

popen

 

trmdir

 

rmdir

 

tsplitpath

 

splitpath

 

tscanf

 

scanf

 

tstrdate

 

_strdate

 

tsetlocale

 

setlocale

 

tstrtimc

 

strtime

 

tsopen

 

sopen

 

_taccess

 

access

 

tspawnl

 

spawnl

 

tasctime

 

asctime

 

tspawnle

 

spawnle

 

Jtoi

 

atoi

 

tspawnlp

 

spawnlp

 

Jtol

 

atol

 

tspawnlpe

 

spawnlpe

 

tchdir

 

chdir

 

tspawnv

 

spawnv

 

tchmod

 

chmod

 

tspawnve

 

spawnve

 

_tcreat

 

creat

 

(spawn vp

 

spawnvp

 

tctime

 

ctime

 

(spawnvpe

 

spawnvpe

 

_texecl

 

execl

 

stprintf

 

sprin(f

 

texecle

 

execle

 

s(scanf

 

sscanf

 

texeclp

 

execlp

 

_tsla(

 

stat

 

_texeclpe

 

execlpe

 

_tcsca(

 

strcat

 

texecv

 

execv

 

tcschr

 

strchr

 

texecve

 

execve

 

icscmp

 

strcmp

 

_texecvp

 

execvp

 

_tcscoll

 

strcoll

 

texecvpe

 

execvpe

 

tcscpy

 

strcpy

 

_tfdopen

 

fdopen

 

(cscspn

 

strcspn

 

_fgettc

 

fgets

 

Jcsdec

 

s(rdec

 

fgettchar

 

fgetchar

 

tcsdup

 

strdup

 

_fgettc

 

fgetc

 

tcsftime

 

s(rf(ime

 

tfmdfirst

 

findfirst

 

_tcsicmp

 

stricmp

 

tfmdnext

 

findnext

 

tcsinc

 

strinc

 

_tfopen

 

fopen

 

_tcslen

 

strlen

 

ftprintf

 

fprintf

 

tcslwr

 

strlwr

 

_fputtc

 

fputc

 

_tcsncmp

 

s(rncmp

 

fputtchat

 

fputchar

 

_(csnbcnt

 

stmcnt

 

fputts

 

fputs

 

tcsncoll

 

strncoll

 

tfreopen

 

freopen

 

tcsncpy

 

stnicpy

 

_ftscanf

 

fscanf

 

(csnextc

 

strnextc

 

tfsopcn

 

fsopen

 

tcsnicmp

 

strnicmp

 

_gcttc

 

getc

 

tcsninc

 

strninc

 

<


269



СОДЕРЖАНИИ



 

 

 

 

 

 

 

II      IC lcm...

 

.......................   3

 

Макрос и ilchar.h

 

Функция

 

Макрос in tdiar.h

 

 

 

 

 

 

 

gctlchar Igelcwd

 

gctchar gctcwd

 

_tcspbrk tcsrchi

 

strrchr

 

(;.••' riM.STARTh;) • ДЛВЛ1ГП-: If Ч'ПГ.-М' ..НИ U< WOKUr

!; ; -.- -.• '4 v  -. <>]

 

..    .      ^

 

igelcnv

 

getcnv

 

_tcsrcv

 

strrcv

 

 

 

 

 

„getts

•     (     |

istaltiunt Jstalpha

istascii

• , -.,.,.,.1 istcnirl

istdigit Jsigraph _istlowcr

t     •   (

_istpnni

 

isalnum isalpha isascii iscntrl isdigit

:,..„„]

isgrapn islower isprint

 

_tcsset Jcsspn _tcsspnp

"tcstod tcstok Jcstol Jcstoul

tcsxtrm (system

 

slrset strspn strspnp strslr strtod strtok strtol strtoul stnipr strxfrm system

 

'.'.-•,•-•.    ••• h-  .'!>! 110 iviL-i.-лч i'm,.:     •.  л-..-'i    .-.I i:

:-;       ..... '1 n;,i-   i ,-• i .1*   I'pr-KMHCMi-ik. H ^.V'.n':.! •                  ...

•i,.!': :;l '              •         '    ' . v:i > IIPOI p.IMMI..  '           .'. "•  '•     . • ' !..-[ или 'ч • •     i'.      •   ' .•-; Vy 1,1, jo'.   .

'    N'l         !                         ....                                                            .                              .              . 'hi-   1,.- ...                       :

 

••>

 

istupper istxdigit ilot

 

isupper isxdigit itoa

 

, ti _tot lower

 

tempnam tolower

 

•;.•• .'--I     '••' ' .ii.--,f!!iM ii л>.;;ч!'' ••.  • .•:•. и с .ч 'л<н i!;r.i ....

 

....       Г,

 

Hot

 

Itoa

 

Jottipper

 

toupper

 

 

 

. . . ....  .....    ^

 

tmain

 

 

 

_ultot

 

ultoa

 

 

 

.   .    ....   .....    .     -17

 

tmkdir

 

mkdir

 

_ungettc

 

 

 

 

 

 

 

 

 

mlctemp

 

 

 

unlmK

 

Кодекс; -и: inoi.rina и WM   PAINT .. .                .     .    ...       ....................

 

.............. ..... ...   5Х

 

Jopen

 

open

 

Jutime

 

 

 

''ПСОН'М IIi;' Г; !Ui')J[4ecKII\ JlpIIN'II 1 МНОВ

 

.....  .........  ....  ..58

 

tporror tcrintf

 

pcrror printf

 

vftprintf vtprintf

 

viprmtr vprinlf

 

IflAliMOJlLfiCTIiill- ПРОГРАММЫ С :U):i!,4i)!!\rr:irM .   .......

 

70

 

 

 

putc

 

.    • .1-vstpnntl

 

vsprintf

 

 

 

.... ... ....  ..    .. ...   70

 

 

 

 

 

 

 

 

 

 

 

.........   . . .......... ,   74

 

 

 

 

 

 

 

 

 

 

 

.........................   98

 

 

 

 

 

 

 

 

 

ОБ1ЦП1- ОЛГМЕНТЫ УПРАВЛЕНИЯ ... ...... ........... ...   ,. .................

 

.........    ....  ......    135 .........................    136

 

 

 

 

 

 

 

 

 

1>абога со скипом       .                .   . ...          ... .

 

.... . .........  ....       141

 

 

 

 

 

 

 

 

 

Работа с тпскоаром

 

........................    14Х

 

 

 

 

 

 

 

 

 

 

 

...........   ......       156

 

 

 

 

 

 

 

 

 

 

 

..............   ......   161

 

 

 

 

 

 

 

 

 

 

 

...     ...........    170

 

 

 

 

 

 

 

 

 

P'iOTI"! С Т1КЛЧ ЧКЧМН

 

............ ....  ...  ,   IX!

 

 

 

 

 

 

 

 

 

Работа с окном ппосмо; па депсш.св    ............................    .   ........

 

....................   192

 

<


РПЕГТР ......    ..............     .......   ....   ..........................................................   202

Структура реестра.. .........

Работа '.' pl-ос гром .   .. . ...

..................................   203

...................   ...............     204

271



КОЕ - ЧТО О МНОГОЗАДАЧНОСТИ В WINDOWS

Запуск процесса .........

Завершение процесса. Создание потока .........

Завершение потока ....

Синхронизация ...........

217

219

226

232

233

234

ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ ................................................. 239

Способы присоединения DLL к программе .......

Вывернем программы наизнанку .........................

Инициализация и деинициализация DLL ............

240

245

247

КОНСОЛИ ...........................................................................................................................   250

Что такое консоль .............................................

Техника разработки консольной программы

Заключение .. Приложение .

250

251

268

269

272




Содержание раздела