Событийно-управляемое программирование
В основу Windows положен принцип событийного управления. Это значит, что и сама система, и приложения после запуска ожидают действий пользователя и реагируют наних заранее заданным образом. Любое действие пользователя (нажатие клавиши на клавиатуре, щелчок кнопкой мыши, перемещение мыши) называется событием. Структура программы, управляемой событиями, изображена нарис. 14.1.
![]() |
Рис. 14.1. Структура программы, управляемой событиями
Событие воспринимается Windows и преобразуется в сообщение — запись содержащую необходимую информацию о событии (например, какая клавиш была нажата, и каком месте экрана произошел щелчок мышью). Сообщения могут поступать не только от пользователя, но и от самой системы, а также о активного или других приложений. Определен достаточно широкий круг стандартных сообщений, образующий иерархию, кроме того, можно определять собственные сообщения.
Сообщения поступают в общую очередь, откуда распределяются по очередям приложений. Каждое приложение содержит цикл обработки сообщений, который выбирает сообщение из очереди и через операционную систему вызывает подпрограмму, предназначенную для его обработки (рис. 14.2). Таким образом Windows-приложение состоит из главной программы, обеспечивающей инициализанию и завершение приложения, цикла обработки сообщений и набора обработчиков событий.
Рис. 14.2. Структура Windows-приложения
Среда Visual Studio.NET содержит удобные средства разработки Windows-npи ложений, выполняющие вместо программиста рутинную работу — создание шаб лонов приложения и форм, заготовок обработчиков событий, организацию цик ла обработки сообщений и т. д. Рассмотрим эти средства.
Шаблон Windows-приложения
Создадим новый проект (File ► New ► Project), выбрав шаблон Windows Application (рис. 14.3). После более длительных раздумий, чем для консольного приложения, среда сформирует шаблон Windows-приложения. Первое отличие, которое бросается в глаза, — вкладка заготовки формы Form1.cs[Design], расположенная в основной части экрана. Форма представляет собой окно и предназначена для размещения компонентов (элементов управления) — меню, текста, кнопок, списков, изображений и т. д.
Рис. 14.3. Выбор шаблона проекта
Среда создает не только заготовку формы, но и шаблон текста приложения. Перейти к нему можно, щелкнув в окне Solution Explorer (View ► Solution Explorer) правой кнопкой мыши на файле Form1.cs и выбрав в контекстном меню команду View Code. При этом откроется вкладка с кодом формы, который, за исключением комментариев, приведен в листинге 14.1. Представлять себе, что написано в вашей программе, весьма полезно, поэтому давайте внимательно рассмотрим этот текст.
Листинг 14.1.Шаблон Windows-приложения
Что означает программа управляемая событиями
При использовании ООП все объекты являются в некотором смысле обособленными друг от друга, и возникают определенные трудности в передаче информации от объекта к объекту. В ООП для передачи информации между объектами используется механизм обработки событий.
События лучше всего представить себе как пакеты информации, которыми обмениваются объекты и которые создаются объектно-ориентированной средой в ответ на те или иные действия пользователя. Нажатие на клавишу или манипуляция мышью порождают событие, которое передается по цепочке объектов, пока не найдется объект, знающий, как обрабатывать это событие. Для того чтобы событие могло передаваться от объекта к объекту, все объекты программы должны быть объединены в группу. Отсюда следует, что прикладная программа должна быть объектом-группой, в которую должны быть включены все объекты, используемые в программе.
Таким образом, объектно-ориентированная программа – это программа, управляемая событиями. События сами по себе не производят никаких действий в программе, но в ответ на событие могут создаваться новые объекты, модифицироваться или уничтожаться существующие, что и приводит к изменению состояния программы. Иными словами все действия по обработке данных реализуются объектами, а события лишь управляют их работой.
Принцип независимости обработки от процесса создания объектов приводит к появлению двух параллельных процессов в рамках одной программы: процесса создания объектов и процесса обработки данных.
Это означает, что действия по созданию, например, интерактивных элементов программы (окон, меню и пр.) можно осуществлять, не заботясь о действиях пользователя, которые будут связаны с ними.
И наоборот, мы можем разрабатывать части программы, ответственные за обработку действий пользователя, не связывая эти части с созданием нужных интерактивных элементов.
Событие.
Событие с точки зрения языка С++ – это объект, отдельные поля которого характеризуют те или иные свойства передаваемой информации, например:
Объект Event состоит из двух частей. Первая (what) задает тип события, определяющий источник данного события. Вторая задает информацию, передаваемую с событием. Для разных типов событий содержание информации различно. Поле what может принимать следующие значения:
Методы обработки событий.
Следующие методы необходимы для организации обработки событий (названия произвольны).
GeEvent – формирование события;
Execute реализует главный цикл обработки событий. Он постоянно получает событие путем вызова GeEvent и обрабатывает их с помощью HandleEvent. Этот цикл завершается, когда поступит событие «конец».
HandleEvent – обработчик событий. Обрабатывает каждое событие нужным для него образом. Если объект должен обрабатывать определенное событие (сообщение), то его метод HandleEvent должен распознавать это событие и реагировать на него должным образом. Событие может распознаваться, например, по коду команды (поле command).
ClearEvent очищает событие, когда оно обработано, чтобы оно не обрабатывалось далее.
Обработчик событий (метод HandleEvent).
Получив событие (структуру типа Event), обработчик событий для класса DerivedClass обрабатывает его по следующей схеме:
Обработчик событий группы вначале обрабатывает команды группы, а затем, если событие не обработано, передает его своим элементам, вызывая их обработчики событий.
ClearEvent очищает событие, присваивая полю event.What значение evNothing.
Главный цикл обработки событий (метод Execute)
Главный цикл обработки событий реализуется в методе Execute главной группы-объекта «прикладная программа» по следующей схеме:
Метод HandleEvent программы обрабатывает событие «конец работы», вызывая метод EndExec. EndExec изменяет значение private – переменной EndState. Значение этой переменной проверяет метод–функция Valid, возвращающая значение true, если «конец работы». Такой несколько сложный способ завершения работы программы связан с тем, что в активном состоянии могут находиться несколько элементов группы. Тогда метод Valid группы, вызывая методы Valid своих подэлементов, возвратит true, если все они возвратят true. Это гарантирует, что программа завершит свою работу, когда завершат работу все ее элементы.
Если событие осталось не обработанным, то вызывается метод EventError, которая в простейшем случае может просто выдать сообщение.
Пример обработки событий
Рассмотрим простейший калькулятор, воспринимающий команды в командной строке.
Параметр – целое число
Класс-событие
Объект-калькулятор, работающий с целыми числами.
Рассмотрим возможную реализацию основных методов.
Пространства имен и приведение типов
Пространство имен
Спецификатор namespace определяет пространство имен функций, классов и переменных, находящихся в отдельной области видимости. Вообще namespace определяет группу символов, представляющих собой названия или имена функций, классов или переменных. Примером такого определения области видимости является область видимости членов класса.
Аргументом спецификатора namespace (то есть name ) является идентификатор пространства имен.
Однако, при многократном использовании члена member такая запись становится слишком громоздкой. Инструкция using заставляет компилятор признавать дальнейшее использование этому члену пространства namespace_name без дополнительного определения имени этого пространства:
Инструкция using namespace заставляет компилятор признавать все члены пространства имен namespace_name :
В приведенном ниже примере глобальные переменные Cary и Hugh объявляются в пространстве имен grants :
Теперь имя Cary может использоваться в последующих инструкциях без дополнительного определения. Чтобы иметь возможность ссылаться на обе переменные без определения имени пространства, достаточно написать
Операторы приведения типов
Операторы приведения типов (cast operators) позволяют изменять тип выражения: они вычисляют значение выражения, изменяют его тип и присваивают значение нового типа результату. В некоторых случаях это может привести к изменению внутреннего формата данных (изменению вида данных в памяти компьютера). Ниже приведены основные операторы приведения типов и способы их использования.
Операторы приведения типов языка C поддерживают все эти операции, однако синтаксис является другим. Оператор приведения dynamic_cast является новым элементом ANSI C++.
Таким образом, ANSI C++ предоставляет четыре разных оператора приведения, каждый из которых обладает специфическими возможностями. Операторы C оставлены в C++ для обеспечения преемственности.
Оператор приведения старого стиля
Оператор приведения типа старого стиля также можно определить, используя альтернативный синтаксис:
Приведенный ниже пример позволяет вывести на печать текущее значение целого i в формате с десятичной точкой:
В целом существует очень мало ситуаций, когда оператор приведения типов может как-то повлиять на поведение программы. Одна из таких ситуаций показана в следующем примере. Присваивая значения и вызывая функции, компилятор автоматически переводит целые числа в действительные, поэтому в таком случае явное выполнение приведения типов не нужно.
Следующая пара операторов, которые подавляют предупреждение компилятора, также выполняют одно и те же действие:
Удаление модификатора const
В следующем примере функция display_num использует в качестве параметра указатель, но не данные, на которые он указывает (*p):
Так как заданные посредством указателя p данные не изменяются, должна быть возможность передачи в качестве параметра const-указателя. Правила C++ запрещают это из-за несоответствия типов указателей:
При использовании оператора const_cast необходимо быть уверенным, что данные, на которые указывает указатель, не изменяются. При задании оператора const_cast и одновременной попытке изменить данные результаты выполнения программы будут непредсказуемы.
Оператор dynamic_cast предоставляет возможность проверить, что указатель базового класса указывает на объект определенного производного класса. Оператор dynamic_cast проверяет тип объекта во время выполнения программы, используя информацию RTTI.
Проверка типа во время выполнения программы
Для этого оператора приведения типов существует единственное ограничение: при использовании его для приведения к указателю на производный класс (что является его главным назначением), класс выражения expr должен иметь, по крайней мере, одну виртуальную функцию. По этой причине оператор dynamic_cast часто называется оператором полиморфного приведения типа (polymorphic cast). Также возможно применение оператора dynamic_cast для приведения к указателю базового класса без каких-либо ограничений. Если типы не связаны вообще, компилятор не допускает преобразования.
Наиболее полезным в операторе dynamic_cast является то, что указатель базового класса может указывать на производные классы. Некоторые из этих классов поддерживают функции, которых нет в других классах.
Приведение типа указателя
Оператор reinterpret_cast используется для приведения указателя к другому типу. Новый тип не обязательно должен быть связан со старым, этот же оператор позволяет приводить типы между указателями и целыми числами.
Хотя возвращенное значение функции malloc не обязательно переводить в другой тип указателя немедленно, такое приведение должно быть выполнено перед первым обращением к выделенной области памяти. Одним из преимуществ оператора new перед функцией malloc является отсутствие необходимости приведения типов.
Оператор reinterpret_cast может быть также полезен при определении функции, когда функция получает указатель типа void * в качестве параметра.
Преобразование типа между родственными объектами или указателями
Хотя численное значение результата не изменяется, при выполнении этого оператора может быть изменено внутреннее представление данных.
Оператор static_cast используется в следующих случаях:
В основном оператор static_cast используется для приведения сложных типов к простым: при работе с простыми типами он подавляет предупреждение компилятора:
Этим как бы говорится: «Да, я на самом деле хочу это сделать». При этом компилятор не выдает предупреждения о возможной потере данных. При этом необходимо быть уверенным в том, что данные не будут слишком велики для приведения в другой тип.
Таким же образом возможно преобразование типов из указателя базового класса в производный без каких-либо ограничений. Но так как никакой проверки во время выполнения программы не делается, то забота о поддержке данных в должном виде возлагается на программиста (производный класс содержит все члены базового плюс члены производного класса). Например, в следующем примере B является базовым классом класса D :
Без использования этой цепочки преобразований (и при отсутствии функции преобразования A в тип int ) преобразование oa в целое было бы невозможным.
На первый взгляд кажется, что при использовании оператора static_cast программисту придется выполнять больше работы, чем при программировании старыми методами. Однако при этом становятся доступными возможности ANSI C++ по разделению операторов приведения типа, что значительно облегчает просмотр больших программ.
Оператор static_cast также полезен при применении различных форматов данных или при необходимости вывода данных в формате, отличном от того, который определен для данного типа по умолчанию. Например, нужно вывести целое число i в формате с плавающей точкой:
Программа, управляемая событиями, также известна как приложение, управляемое событиями.
Techopedia объясняет программу, управляемую событиями
Идея в программировании на основе событий заключается в том, что программа предназначена для реагирования.
Он реагирует на определенные виды ввода от пользователей, будь то нажатие кнопки команды, выбор из раскрывающегося списка, запись в текстовое поле или другие виды пользовательских событий.
Другие языки программирования могут содержать пользовательские события, которые в основном доставляются через интерфейс командной строки или какой-либо другой тип пользовательского интерфейса. Противоположностью управляемого событиями программирования было бы программирование, которое написано, чтобы действовать независимо от пользовательского ввода.
Например, приложения для отображения, такие как приложения для обновления погоды или спортивных результатов, могут содержать меньше программ, управляемых событиями, что присуще другим типам программ. Тем не менее, почти все программное обеспечение полагается на пользовательские события для функциональности, и было бы легко утверждать, что программирование на основе событий является заданием по умолчанию для почти всех видов проектов.
Это связано с тем, что в общем случае приложения и программные модули написаны для реагирования на действия человека, что является частью основной концепции работы людей с машинами. Тем не менее, выявление событийных аспектов программ может быть полезным при анализе проекта.
Что такое разрастание сервера и что я могу с этим сделать?
Управление информационными событиями безопасности (siem): в нем надолго
Управляемые событиями программы
Дата добавления: 2015-01-16 ; просмотров: 1615 ; Нарушение авторских прав
Основной чертой всех Windows-приложений является то, что они поддерживают оконный интерфейс, используя при этом множество стандартных элементов управления (кнопки, переключатели, линейки, окна редактирования, списки и т. Д.). Эти элементы поддерживаются с помощью динамических библиотек (DLL), которые являются частью операционной системы (ОС). Именно поэтому элементы доступны любым приложениям, и ваше первое приложение имеет почти такой же облик, как и любое другое. Принципиально важным отличием Windows-приложений от приложений DOS является то, что все они — программы, управляемые событиями (event-driven applications). Приложения DOS — программы с фиксированной последовательностью выполнения. Разработчик программы последовательность выполнения операторов, и система строго ее соблюдает. В случае программ, управляемых событиями, разработчик не может заранее предсказать последовательность вызовов функций, и даже выполнения операторов своего приложения, так как эта последовательность определяется на этапе выполнения кода.
Программы, управляемые событиями, обладают большей гибкостью в смысле выбора пользователем порядка выполнения операций. Характерно то, что последовательность действий часто определяется операционной системой и зависит от потока сообщений о событиях в системе. Большую часть времени приложение, управляемое событиями, находится в состоянии ожидания событий, точнее сообщений о них. Сообщения могут поступать от различных источников, но все они попадают в одну очередь системных сообщений. Только некоторые из них система передаст в очередь сообщений вашего приложения. В случае многопотокового приложения сообщение приходит активному потоку (thread) приложения. Приложение постоянно выполняет цикл ожидания сообщений. Как только придет адресованное ему сообщение, управление будет передано его оконной процедуре.
Наступление события обозначается поступлением сообщения. Все сообщения Windows имеют стандартные имена, многие из которых начинаются с префикса WM_ (Windows Message). Например, WM_PAINT именует сообщение о том, что необходимо перерисовать содержимое окна того приложения, которое получило это сообщение. Идентификатор сообщения WM_PAINT — это символьная константа, обозначающая некое число. Другой пример: при создании окна система посылает сообщение WM_CREATE. Вы можете ввести в оконную процедуру реакцию на это сообщение для того, чтобы произвести какие-то однократные действия.
Программист может создать и определить какие-то свои собственные сообщения, действующие в пределах зарегистрированного оконного класса. В этом случае каждое новое сообщение должно иметь идентификатор, превышающий зарезервированное системой значение WM_USER (0x400). Допустим, вы хотите создать сообщение о том, что пользователь нажал определенную клавишу в тот момент, когда клавиатурный фокус находится в особом окне редактирования с уже зарегистрированным классом. В этом случае новое сообщение можно идентифицировать так:
#define WM_MYEDIT_PRESSED WM_USER + 1
Каждое новое сообщение должно увеличивать значение идентификатора по сравнению с WM_MYEDIT_PRESSED. Максимально-допустимым значением для идентификаторов такого типа является число 0x7FFF. Если вы хотите создать сообщение, действующее в пределах всего приложения и не конфликтующее с системными сообщениями, то вместо константы WM_USER следует использовать другую константу WM_APP (0x8000). В этом случае можно наращивать идентификатор вплоть до 0xBFFF.
Рассмотренная модель выработки и прохождения сообщений поможет понять структуру, принятую для всех Windows-приложений. Простейшее из них должно состоять как минимум из двух функций:
1. функции WinMain, с которой начинается выполнение программы и которая «закручивает» цикл ожидания сообщений;
2. оконной процедуры, которую вызывает система, направляя ей соответствующие сообщения.
Каждое приложение в системе, основанной на сообщениях, должно уметь получать и обрабатывать сообщения из своей очереди. Основу такого приложения в системе Windows представляет функция WinMain, которая содержит стандартную последовательность действий. Однако обрабатывается большинство сообщений окном — объектом операционной системы Windows.
C точки зрения пользователя, окно — это прямоугольная область экрана, соответствующая какому-то приложению или его части. Вы знаете, что приложение может управлять несколькими окнами, среди которых обычно выделяют одно главное окно-рамку. С точки зрения операционной системы, окно — это в большинстве случаев конечный пункт, которому направляются сообщения. С точки зрения программиста, окно — это объект, атрибуты которого (тип, размер, положение на экране, вид курсора, меню, значок, заголовок) должны быть сначала сформированы, а затем зарегистрированы системой. Манипуляция окном осуществляется посредством специальной оконной функции, которая имеет вполне определенную, устоявшуюся структуру.
Функция WinMain выполняется первой в любом приложении. Ее имя зарезервировано операционной системой. Она в этом смысле является аналогом функции main, с которой начинается выполнение С-программы для DOS-платформы. Имя оконной процедуры произвольно и выбирается разработчиком. Система Windows регистрирует это имя, связывая его с приложением. Главной целью функции WinMain является регистрация оконного класса, создание окна и запуск цикла ожидания сообщений.
Рассмотрим более подробно структуру традиционного Windows-приложения.
// Стандартный включаемый файл Windows
// Прототип функции обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Функция вызывается автоматически, когда программа запускается
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
// Настройка класса окна
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.lpszClassName = “Window Class”; // Имя класса
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// Регистрация класса окна
// Сбой программы, выход
«Window Class», // Имя класса
«Приложение Windows», // Текст заголовка
// Обработка сообщений, пока программа не будет прервана
while(GetMessage(&msg, NULL, 0, 0))
// Функция обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
// Вызывается, когда пользователь отпускает левую кнопку мыши
MessageBox(hWnd, TEXT(«Вы кликнули!»), TEXT(«событие»), MB_OK);
// Вызывается, когда окно обновляется
Ellipse(hDC, 50, 20, 200, 100);
// Вызывается, когда пользователь закрывает окно
return DefWindowProc(hWnd, iMsg, wParam, lParam);
В двух первых строках кода указываются включаемые заголовочные файлы и приводятся прототипы функций. В программировании для Windows необходим только один заголовочный файл с именем windows.h.
Для нашего примера требуется единственный прототип функции для обработчика сообщений. Любая программа для Windows должна содержать функцию, которая будет вызываться для обработки сообщений и должна соответствовать следующему прототипу:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
Имя функции может отличаться, но параметры должны оставаться неизменными. Это вызвано тем, что Windows автоматически вызывает данную функцию и не сможет работать правильно, если вы измените параметры.
При вызове функции WinMain система передает ей параметры:
hInstance — описатель экземпляра приложения. Это адрес приложения, загруженного в память. В Windows NT/2000 этот адрес для всех приложений имеет одно и то же значение 0x00400000 (4 Мбайт);
hPrevlnstance — описатель предыдущего экземпляра приложения. Этот параметр устарел и теперь не используется в приложениях Win32;
szCmdLine — указатель на командную строку. Мы не будем использовать этот параметр;
iCmdShow — состояние окна при начальной демонстрации.
Ранее в Win16 второй параметр использовался в целях экономии ресурсов, но в Win32 — это NULL, так как каждый экземпляр приложения теперь выполняется в своем собственном виртуальном адресном пространстве процесса емкостью 4 Гбайт. Все экземпляры процесса загружаются, начиная с одного и того же адреса в этом пространстве.
Первый элемент, упоминаемый в функции WinMain() – это объект, используемый для создания окна. Объект является структурой типа WNDCLASSEX и очень важен для создания окна Вашей программы.
Ниже приведен прототип структуры WNDCLASSEX:
typedef struct _WNDCLASSEX <
Первая переменная типа UINT называется cbSize. Она используется для указания размера структуры данных. Обычно для инициализации этого элемента структуры используется выражение sizeof(WNDCLASSEX). Инициализацию этого элемента данных вы можете увидеть, взглянув на приведенный выше листинг.
Второй элемент структуры также имеет тип UINT и называется style. Как указывает имя, элемент style используется для задания стиля создаваемого окна. Удобство данного элемента данных в том, что вы можете указывать комбинации одних стилей с другими, используя несколько флагов, объединенных поразрядной операцией ИЛИ (|). Некоторые стили приведены в таблице.
Таблица 2.1.1 – Некоторые стили окон
| Значение | Действие |
| CS_DBLCLKS | Простой стиль. Когда он указан, Windows будет посылать окну сообщение о двойном щелчке каждый раз, когда пользователь выполняет двойной щелчок кнопкой мыши в пределах области окна. Это может показаться странным, но многие приложения самостоятельно запоминают время каждого щелчка кнопки мыши, чтобы определить был ли выполнен двойной щелчок. |
| CS_HREDRAW | Этот стиль заставляет перерисовывать все окно в случае изменения его ширины. |
| CS_NOCLOSE | Запрещает выполнение закрытия окна через системное меню. |
| CS_VREDRAW | Заставляет перерисовывать все содержимое окна в случае изменения высоты окна. |
Третий элемент структуры имеет тип WNDPROC, является указателем на функцию и называется lpfnWndProc. Помещаемый сюда указатель должен указывать на функцию обработки сообщений Windows, которую окно использует, чтобы принимать сообщения. Это очень важно, и функция, на которую ссылаются здесь, должна полностью соответствовать прототипу, приведенному в коде.
Четвертый элемент структуры относится к типу int и называется cbClsExtra. Это целое число задает количество байт, которые будут выделены сразу за структурой данных класса окна. Практически всегда это значение устанавливается равным нулю.
Пятый элемент также относится к типу int и называется cbWndExtra. Это целое число задает количество байтов, которые будут выделены сразу за экземпляром окна. Работа с ним аналогична обращению с предыдущим элементом структуры.
Шестой элемент структуры имеет тип HANDLE и называется hInstance. Дескриптор, который вы задаете здесь, является дескриптором экземпляра, к которому относится окнонная процедура класса. В большинстве случаев можно задать значение дескриптора hInstance, получаемого функцией WinMain() в одном из параметров.
Седьмой параметр называется hIcon и имеет тип HICON.
Вы, конечно, обратили внимание на обилие новых типов данных, которые используются в приложениях Win32. Многие из них имеют префикс Н, который является сокращением слова Handle — дескриптор, описатель. Описатели разных типов (HWND, HPEN, HBITMAP и т. Д.) являются посредниками, которые помогают найти нужную структуру данных в виртуальном мире Windows. Объекты Windows или ее ресурсы, такие как окна, файлы, потоки, перья, кисти, области, представлены в системе структурами языка С, и адреса этих структур могут изменяться. В случае нехватки реальной памяти Windows выгружает из памяти ненужные в данный момент времени объекты и загружает на их место объекты, требуемые приложением. В системной области оперативной памяти Windows поддерживает таблицу, в которой хранятся физические адреса объектов. Для поиска объекта и управления им сначала следует получить у системы его дескриптор (место в таблице, индекс). Важно иметь в виду, что физический адрес объекта — понятие для Windows, а не для программиста. Описатель типа HANDLE можно уподобить номеру мобильного телефона, с помощью которого вы отыскиваете объект, перемещающийся в виртуальном мире Windows.
Тип HICON это ни что иное, как тип HANDLE. Данный дескриптор указывает на класс значка, используемого окном. Класс значка в действительности является ресурсом значка (иконкой). Функция LoadIcon() загружает ресурс значка из исполняемой программы. Хотя ресурсы компилируются внутрь исполняемого файла программы (с расширением exe) для Windows, все равно необходимо загружать их, поэтому и требуется вызов данной функции. Вот как выглядит ее прототип:
У функции всего два параметра — HINSTANCE и LPCTSTR. Первый параметр с именем hInstance, содержит дескриптор экземпляра модуля, чей исполняемый файл содержит значок, который вы желаете использовать. Если параметру присвоить значение NULL, то поиск ресурса, в данном случае иконки, будет осуществляться среди стандартных. Второй параметр является указателем на строку, содержащую имя загружаемого значка. Взглянув на разбираемый пример, можно увидеть, что для инициализации данного параметра используется константа IDI_APPLICATION. Ее значение соответствует значку, используемому по умолчанию для приложений, который можно видеть во многих программах для Windows. Некоторые другие иконки перечислены в таблице.
Таблица 2.1.2 – Некоторые стандартные иконки
| Значение | Описание |
| IDI_APPLICATION | Значок, используемый по умолчанию для приложений. Он применяется и в рассматриваемом примере. В большинстве случаев его можно использовать, если только не требуется нестандартный значок для приложения. |
| IDI_ASTERISK | Значок в виде небольшого овала с буквой «i» внутри. |
| IDI_ERROR | Красный круг с крестом внутри. |
| IDI_EXCLAMATION | Желтый треугольник с восклицательным знаком внутри. |
| IDI_QUESTION | Значок с вопросительным знаком. |
| IDI_WINLOGO | Небольшой логотип Windows. |
Восьмой элемент структуры данных WNDCLASSEX очень похож на седьмой, за исключением того, что он задает используемый окном курсор. Его тип HCURSOR, а имя — hCursor. Тип HCURSOR это еще один замаскированный дескриптор. Обычно здесь указывается значение дескриптора класса курсора, который будет использован в программе для ее собственного курсора. Функция LoadCursor() похожа на функцию LoadIcon() за исключением того, что она загружает ресурсы курсора, а не ресурсы значка. Вот ее прототип:
Первый параметр называется hInstance, и содержит дескриптор экземпляра модуля, чей исполняемый файл содержит курсор, который вы собираетесь использовать. Второй параметр — это указатель на строку, содержащую имя загружаемого курсора. Как можно видеть, в рассматриваемом примере я параметр имеет значение IDC_ARROW. Оно соответствует стандартному курсору Windows в виде стрелки. Некоторые другие курсоры перечислены в таблице.
Таблица 2.1.3 – Стандартные курсоры
| Значение | Описание |
| IDC_APPSTRING | Курсор в форме стандартной стрелки с присоединенными к ней песочными часами. Обычно, данный курсор устанавливается, когда программа занята. |
| IDC_ARROW | Стандартный курсор Windows. |
| IDC_CROSS | Создает курсов, выглядящий как перекрестье прицела. |
| IDC_HELP | Этот курсор выглядит как стандартная стрелка с присоединенным к ней вопросительным знаком. Его хорошо использовать, когда пользователю предоставляется возможность задать вопрос. |
| IDC_IBEAM | Курсор в форме буквы «I». Обычно используется в режиме ввода и редактирования текста. |
| IDC_NO | Курсор в виде перечеркнутого круга. Его можно использовать, когда пользователь наводит курсор на область, которая не реагирует на щелчки кнопок мыши. |
| IDC_SIZEALL | Курсор с перекрещенными стрелками. Применяется, когда пользователь изменяет размер окна или графического элемента. |
| IDC_SIZENESW | Еще один курсор для изменения размера. В отличие от предыдущего курсора, у которого стрелки направлены во все четыре стороны, здесь стрелки направлены только на северо-восток и юго-запад. |
| IDC_SIZENS | То же, что и предыдущий курсор, но стрелки направлены на север и на юг. |
| IDC_SIZENWSE | То же, что и предыдущие два курсора, но стрелки направлены на северо-запад и юго-восток. |
| IDC_SIZEWE | Еще один курсор со стрелками. В данном случае они направлены на запад и на восток. |
| IDC_UPARROW | Курсор в виде стрелки, направленной вверх. |
| IDC_WAIT | Курсор в виде песочных часов. |
Девятый элемент структуры hbrBackground имеет тип HBRUSH и определяет цвет фона окна. Возможно, задать непосредственно цвет, использую константы вида:
И другие начинающиеся с COLOR_.
Или, как показано в примере, использовать дескриптор стандартной кисти, полученный с помощью функции GetStockObject().Функция GetStockObject() часто используется для того, чтобы получить дескриптор одной из встроенных кистей, шрифтов, палитр или перьев. Дело в том, что в Windows есть несколько предопределенных типов, которые могут быть использованы. Прототип функции следующий:
HGDIOBJ GetStockObject(int fnObject);
Ее единственный параметр представляет собой целое число, идентифицирующее предопределенный объект операционной системы. В примере используется встроенный объект DKGRAY_BRUSH. Он окрашивает фон окна в темно-серый цвет.
Десятый элемент структуры данных WNDCLASSEX — это завершающаяся нулевым символом строка с именем lpszMenuName. Она содержит имя ресурса меню, используемого окном. NULL указывает на отсутствие меню в программе.
Одиннадцатый элемент структуры данных также является строкой, которая должна завершаться нулевым символом. Его имя — lpszClassName. Как сказано в имени, эта строка используется для задания имени класса окна. Имя класса является уникальным идентификатором типа класса. Поэтому очень важно, чтобы заданное здесь имя не использовалось для других классов окон программы.
Двенадцатый, и последний, элемент структуры WNDCLASSEX — это переменная с именем hIconSm. Она аналогична элементу данных hIcon, за исключением того, что здесь задается используемый программой маленький значок.
Класс окна должен быть зарегистрирован. Ниже приведен фрагмент кода, выполняющий это действие.
// Сбой программы, выход
Вызов функции RegisterClassEx() необходим, чтобы потом мы смогли создать окно. Эта функция регистрирует класс в системе Windows. Если класс не зарегистрирован, то его невозможно использовать его для создания окна.
Прототип функции следующий:
CONST WNDCLASSEX *lpwcx
Первый и единственный необходимый для нее параметр является указателем на структуру данных WNDCLASSEX. Функция возвращает значение типа ATOM, которое можно сравнить с NULL. Если возвращаемое функцией RegisterClassEx() значение не равно нулю, ее выполнение завершилось успешно.
Итак, класс окна зарегистрирован, и программа переходит к действительному созданию окна.
Для создания окна используется функция CreateWindowEx(). Ее можно применять для создания дочерних, всплывающих или перекрывающихся окон. При создании окна указывается используемый класс, имя приложения и некоторые другие параметры. Прототип функции выглядит следующим образом:
Первый параметр имеет тип DWORD и называется dwExStyle. Он похож на определяющий стиль элемент структуры WNDCLASSEX, но задает дополнительные стили окна.
WS_EX_OVERLAPPEDWINDOW является достаточно распространенным стилем, благодаря чему скомпилированная и запущенная программа выглядит как большинство приложений Windows.
LPCTSTR lpClassName – имя класса для создаваемого окна (это имя использовалось при регистрации класса).
LPCTSTR lpWindowName – имя окна.
DWORD dwStyle – стиль окна.
Int x – позиция по горизонтали верхнего левого угла окна.
Int y – позиция по вертикали.
Int nWidth – ширина окна.
Int nHeight – высота окна.
HWND hWndParent – используется для создания «дочернего окна» («child window»). Сюда передается дескриптор «родительского окна» («parent window»).
HMENU hMenu – дескриптор меню (если hMenu равно нулю, используется меню класса, указанного в lpClassName).
HINSTANCE hInstance – экземпляр приложения.
LPVOID lpParam – указатель на пользовательский параметр окна. Этот указатель со всеми остальными параметрами функции CreateWindow будет занесен в структуру CREATESTRUCT. В сообщениях WM_CREATE или WM_NCCREATE параметр lParam будет содержать указатель на эту структуру.
Функция CreateWindow возвращает уникальный дескриптор окна HWND. Если функция вернула ноль, значит, во время создания окна произошла ошибка.
Создание окна не приводит к его отображению. Чтобы окно было действительно выведено на экран необходимо вызвать функцию ShowWindow().Ее прототип:
Первый параметр задает дескриптор отображаемого окна. Это действительно просто, поскольку дескриптор уже подготовлен функцией, создавшей окно. Второй параметр — это целое число, определяющее как будет отображаться окно. Можно использовать константы вида SW_MINIMIZE (запуск в свернутом состоянии) или отобразить, как рекомендуется операционной системой, передав в функцию параметр, полученный функцией WinMain от Windows.
Чтобы приложения Windows знали когда их окна закрываются, перемещаются или изменяют размеры, они должны принимать сообщения Windows. В общем случае приложения для Windows должны всегда иметь цикл сообщений. Чтобы проверить наличие ожидающих обработки сообщений вызывается функция GetMessage():
В первом параметре функция ожидает указатель на объект MSG. Структура данных MSG содержит всю информацию о любом обнаруженном сообщении.
Второй параметр указывает для какого окна проводится проверка наличия сообщений. Он необходим потому, что ваша программа может управлять несколькими окнами. В рассматриваемом примере есть только одно окно, поэтому просто передается дескриптор окна, созданного функцией CreateWindow(). Можно указать NULL, что будет означать обработку сообщений всех окон программы.
Третий параметр задает нижнюю границу кодов получаемых сообщений. Мы хотим получать все сообщения, поэтому присваиваем данному параметру значение 0.
Четвертый параметр позволяет задать верхнюю границу кодов получаемых сообщений. Поскольку мы хотим получать все сообщения, значение этого параметра также равно 0.
Перед тем, как отправить сообщение в вашу очередь сообщений, его необходимо транслировать в символьные данные. Это делает функция TranslateMessage(). Для нее требуется единственный параметр — указатель на транслируемое сообщение.
После того, как сообщение транслировано в символьные данные, его нужно поместить его в очередь сообщений с помощью функции DispatchMessage().
Подобно функции TranslateMessage(), функция DispatchMessage() требует единственный параметр. Цель этой функции — отправить прошедшее трансляцию сообщение в очередь сообщений программы. После вызова этой функции сообщение попадает в функцию обработки сообщений. Последняя строка кода функции WinMain() возвращает значение wParam последнего сообщения Windows извлеченного функцией получения сообщений. Как видно главная функция, программы завершается циклом while, который будет обрабатываться до тех пор пока GetMessage() не вернет ложное значение. Это происходить при приеме сообщения WM_QUIT, генерация которого осуществляется функцией PostQuitMessage().
Стартовая заготовка иллюстрирует стандартную последовательность действий при создании Windows-приложения на базе API-функций. Обратите внимание на то, что функция WndProc нигде явно не вызывается, хотя именно она выполняет всю полезную работу. Для проверки усвоения прочитанного ответьте на вопрос: «Когда и чем она вызывается?»
Теперь рассмотрим, как устроена оконная процедура WndProc. Ее имя уже дважды появлялось в тексте программы. Сначала был объявлен ее прототип, затем оно было присвоено одному из полей структуры типа WNDCLASSEX. Поле имеет тип указателя на функцию с особым прототипом оконной функции. Здесь полезно вспомнить, что имя функции трактуется компилятором C++ как ее адрес.
Оконная процедура должна «просеивать» все посылаемые ей сообщения и обрабатывать те из них, которые были выбраны программистом для обеспечения желаемой функциональности. Типичной структурой оконной процедуры является switch-блок, каждая ветвь которого содержит обработку одного сообщения. В первом примере оконная процедура реагирует только на три сообщения:
WM_LBUTTONUP – о щелчке отпускании пользователем левой кнопки мыши;
WM_PAINT — о необходимости перерисовать клиентскую область окна;
WM_DESTROY — о необходимости закрыть окно.
Сообщение WM_DESTROY (окно “уничтожено”) посылается системой уже после того, как окно исчезло с экрана. Мы реагируем на него вызовом функции PostQuitMessage, которая указывает системе, что поток приложения требует своего завершения, путем посылки сообщения WM_QUIT. Его параметром является код завершения, который мы указываем при вызове PostQuitMessage.
Рассмотренная структура приложения Win32 позволяет сделать вывод, что в подавляющем числе случаев развитие приложения сосредоточено внутри оконной процедуры, а не в функции WinMain. Развитие приложения заключается в том, что в число обрабатываемых сообщений (messages) включаются новые. Для этого программист должен вставлять новые case-ветви в оператор switch (msg).
Если оконная процедура не обрабатывает какое-либо сообщение, то управление передается в ветвь default. Вы видите, что в этой ветви мы вызываем функцию DefWindowProc, которая носит название оконной процедуры по умолчанию. Эта функция гарантирует, что все сообщения будут обработаны, то есть, удалены из очереди. Возвращаемое значение зависит от посланного сообщения.
Результат компиляции и запуска примера показан на рисунке 2.1.1.
Рисунок 2.1.1 – Результат выполнения примера
Контрольные вопросы







