Структура приложения Windows — КиберПедия 

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

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

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

2020-03-31 249
Структура приложения Windows 0.00 из 5.00 0 оценок
Заказать работу

 

Итак, еще раз посмотрим на приложение 1a.cpp и вспомним, что надо сделать для написания приложения:

· написать оконную функцию;

· зарегистрировать эту функцию в Windows;

· создать окно, принадлежащее данному классу;

· обеспечить работу приложения, обрабатывая поступающие окну сообщения.

В рассматриваемом нами примере выполняются все эти действия. Исходный код содержит следующие функции: WinProc, init_instance и WinMain. Здесь WinProc является оконной процедурой, init_instance регистрирует класс окон (оконную процедуру), а WinMain создает окно и обеспечивает работу всего приложения.

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

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

2. Управление передается специально написанному разработчиками компиляторов startup–коду, который инициализирует приложение, получает необходимую информацию (как, например, командная строка, хендл копии приложения и пр.), запускает конструкторы статических объектов.

3. startup–код вызывает функцию WinMain, передавая ей полученные от операционной системы данные. Функция WinMain разрабатывается для каждого приложения.

4. WinMain обычно осуществляет регистрацию класса окон.

5. Далее WinMain создает и отображает главное окно приложения.

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

7. Оконная процедура выполняет обработку сообщений, направленных окну, обеспечивая реакцию задачи на действия пользователя.

8. Приложение работает, пока пользователь не закроет главное окно этого приложения. В момент закрытия этого окна оконная процедура принимает специальные меры для завершения цикла обработки сообщений, организованного WinMain.

9. Когда цикл обработки сообщений завершается, WinMain продолжает исполнение своего кода, выполняя, при необходимости, деинициализацию и уничтожение созданных объектов, после чего завершает работу.

10.Возврат управления из функции WinMain происходит опять–же в промежуточный exit–код, созданный разработчиками компиляторов. Он запускает деструкторы статических объектов, деинициализирует приложение и возвращает управление в систему.

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

Теперь, перед тем как перейти к рассмотрению непосредственно кода приложения, надо сделать последнее замечание. Windows API разрабатывался тогда, когда систем виртуальной памяти на персональном компьютере еще не существовало. Первые версии Windows могли работать на XT с 640K оперативной памяти. Из–за очень ограниченного объема памяти приходилось идти на различные ухищрения. Так один из способов экономии памяти связан с тем, что код приложений обычно не изменяется. Если запустить две копии одного и того–же приложения (скажем, два Notepad’а для редактирования двух разных файлов), то код этих приложений будет одинаковым. В этом случае его можно загружать только один раз, но для необходимости как–то различать разные копии приложения и возникло понятие хендл копии приложения (instance handle, HINSTANCE).

Функция WinMain

 

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

 

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
      //...
}

 

Описание главной функции ключевым словом PASCAL указывает на применение соглашений языка Pascal при передаче аргументов и вызове функции (так делается в большинстве функций Windows, потому что вызов pascal–декларированной функции осуществляется чуть быстрее и занимает меньше места, чем C–декларированной).

Рассмотрим ее аргументы:

HANDLE hInstance — этот параметр является хендлом, указывающим конкретную копию приложения. Знание этого хендла потребуется для связи тех или иных данных с конкретной копией.

HANDLE hPrevInstance — описывает хендл предыдущей копии приложения. Если данная копия является первой, то эта переменная содержит NULL. Использование этой информации несколько специфично:

Во–первых, Windows связывает некоторые данные с конкретной копией приложения (например: экспортированные функции, окна и пр.). При связывании необходимо указывать хендл копии приложения.

Внимание: при использовании C++ иногда удобно описать статический объект. Однако в этом случае может потребоваться информация о hInstance для конструктора статического объекта. По странной причине мы ее не можем узнать до вызова WinMain — эта информация известна с самого начала (еще до вызова каких–либо конструкторов): startup–код в самых первых инструкциях обращается к процедуре INITTASK, которая возвращает системную информацию, в том числе hInstance. После этого hInstance копируется в статическую переменную, используемую startup– и exit– кодом, однако эта переменная является локальной (?!) и недоступна для остальных модулей приложения. Причина такого поступка со стороны разработчиков компиляторов остается непонятной.

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

В–третьих, иногда нам надо получить данные от предыдущей копии приложения (например, если наши приложения организуют обмен данными между собой). С помощью hPrevInstance мы можем сделать это (только в Windows API, в Win32 API это не получится)[8].

В–четвертых, 32х битовые приложения Win32 API всегда предполагают, что запущена только одна копия приложения (так как в виртуальном адресном пространстве приложения кроме нее других приложений, в том числе копий, не находится). При этом hInstance указывает начальный адрес загрузки модуля (и для большинства приложений он совпадает), а hPrevInstance всегда равен NULL.

LPSTR lpszCmdLine — как и обычная C–программа, приложение Windows может получать командную строку. Параметр lpszCmdLine является указателем на эту строку.

int nCmdShow — этот параметр указывает, в каком виде должно быть изображено окно приложения. Для описания значений этой переменной существует целый набор #define’ов, начинающихся с префикса SW_. Например, значение nCmdShow равное SW_SHOWMINNOACTIVE указывает на то, что окно должно быть отображено в минимизированном состоянии, а значение SW_SHOWNORMAL указывает на необходимость отображения окна в нормальном состоянии. Пользователь может указать, в каком виде показывать главное окно приложения, настраивая характеристики ярлыка (shortcut).

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

 

После аргументов функции WinMain мы начнем рассматривать непосредственно тело этой процедуры. В самом начале мы должны зарегистрировать класс нашего окна, убедившись в том, что это первая копия приложения. Для этого мы должны заполнить структуру WNDCLASS и передать ее функции RegisterClass:

 

WNDCLASS WC;

if (!hPrevInstance) {
      WC.style=    NULL;
      WC.lpfnWndProc= WinProc;
      WC.cbClsExtra= NULL;
      WC.cbWndExtra= NULL;
      WC.hInstance= hInstance;
      WC.hIcon=    LoadIcon(NULL, IDI_APPLICATION);
      WC.hCursor=  LoadCursor(NULL, IDC_ARROW);
      WC.hbrBackground= GetStockObject(WHITE_BRUSH);
      WC.lpszMenuName= NULL;
      WC.lpszClassName= "Hello application";
      if (!RegisterClass(&WC)) return NULL;
}

 

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

Структура WNDCLASS содержит следующие поля:

Поле style содержит комбинацию CS_xxx констант, задающих некоторые характеристики класса. Часто этот параметр равен NULL. Для более подробного знакомства с этим полем рекомендуется посмотреть описание функции RegisterClass и возможные стили класса.

Например, стиль CS_HREDRAW говорит о том, что окно должно перерисовываться целиком при изменении его горизонтального размера, CS_VREDRAW — при изменении вертикального размера, CS_DBLCLK — окно будет реагировать на двойные нажатия на клавишу мыши. Особенно надо отметить стиль CS_GLOBALCLASS. Обычно зарегистрированный класс используется только лишь данным приложением (и всеми его копиями), но недоступен для других приложений. Стиль CS_GLOBALCLASS разрешает использование класса другими приложениями.

Если Вы захотите объединить несколько стилей класса, используйте оператор ПОБИТОВОЕ ИЛИ, например: CS_HREDRAW|CS_VREDRAW|CS_DBLCLK.

lpfnWndProc является указателем на оконную функцию. Вы не можете указать его 0, эта функция должна быть написана Вами.

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

Для этого Windows может зарезервировать специальное добавочное пространство в этом блоке, размер этого пространства указывается параметром cbClsExtra. Это пространство может быть использовано вами по своему усмотрению, поэтому вы должны задать его размер. Если Вы не собираетесь использовать это пространство, укажите его размер 0 (NULL).

Позже, при создании окна, Windows создаст другой блок, описывающий окно, и в нем выделит дополнительное пространство размером cbWndExtra байт. Оно также предназначено для использования Вами. Если оно Вам не требуется, укажите размер 0 (NULL). В таких полях удобно хранить данные, уникальные для каждого окна — скажем, хендл редактируемого файла, если это окно редактора.

 

Рисунок 2. Структуры данных, используемые Windows для описания окон.

 

Вы можете использовать при желании двенадцать функций для чтения/записи данных, находящихся в структурах описания окна и класса:

 

UINT GetWindowWord(hWnd, nIndex);
UINT SetWindowWord(hWnd, nIndex, wNewValue);
LONG GetWindowLong(hWnd, nIndex);
LONG SetWindowLong(hWnd, nIndex, dwNewValue);
int GetWindowText(hWnd, lpWinName, nMaxCount);
int  GetWindowTextLength(hWnd);
void SetWindowText(hWnd, lpszWinName);

 

“Текст окна” (...WindowText...) является обычно заголовком окна, а для окон специального вида (например, кнопки) — текстом, написанном в этом окне.

UINT GetClassWord(hWnd, nIndex);
UINT SetClassWord(hWnd, nIndex, wNewValue);
LONG GetClassLong(hWnd, nIndex);
LONG SetClasslong(hWnd, nIndex, dwNewValue);
int  GetClassName(hWnd, lpClassName, nMaxCount);

 

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

Следующие три поля (hIcon, hCursor, hbrBackground) пояснить будет посложнее — для этого надо будет разобраться с GDI (hbrBackground) и ресурсами (hIcon, hCursor), что будет сделано в соответствующих разделах. Поэтому пока что мы сделаем несколько замечаний и рассмотрим простейший случай.

hIcon — это хендл пиктограммы (иконки), которая будет выводиться вместо окна в минимизированном состоянии. Функция LoadIcon находит соответствующую пиктограмму и возвращает ее хендл. В нашем примере LoadIcon использует стандартную пиктограмму (первый параметр равен NULL), которая используется  по умолчанию для представления приложения (второй параметр равен IDI_APPLICATION).

hCursor является, аналогично hIcon, хендлом курсора. Это курсор мыши, который будет отображаться при перемещении мыши через окно. Требуемый курсор загружается функцией LoadCursor, применение которой похоже на функцию LoadIcon. Для стандартных курсоров существуют следующие имена: IDC_ARROW, IDC_CROSS, IDC_IBEAM, IDC_ICON, IDC_SIZE, IDC_SIZENESW, IDC_SIZES, IDC_SIZENWSE, IDC_SIZEWE, IDC_UPARROW и IDC_WAIT. Наиболее часто употребляемый курсор IDC_ARROW.

Третий хендл — хендл кисти — hbrBackground. Применение кисти существенно отличается от пиктограммы и курсора, поэтому она задается иным способом. Функция GetStockObject возвращает хендл заранее созданного Windows стандартного объекта. Мы используем GetStockObject для получения хендла “белой кисти” WHITE_BRUSH. Помимо хендлов кистей эта функция может возвращать хендлы других объектов. В примере 1a.cpp функция GetStockObject не применялась — вместо хендла кисти разрешено указывать специальную константу, обозначающую системный цвет (в примере — COLOR_WINDOW). Для того, что бы система могла отличить хендл от цвета, требуется, что бы к нему была прибавлена 1 (0 означает неверный хендл, являясь в то же время корректным номером цвета).

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

Указатель на строку lpszMenuName. Если Ваше окно имеет связанное с ним меню, то Вы можете указать имя этого меню для его автоматического использования. Как это делается мы рассмотрим несколько позже. В данном примере предполагается, что меню нет — lpszMenuName равен NULL.

Последний параметр lpszClassName является именем регистрируемого класса окна. Рекомендуется включать в это имя название задачи, так как это уменьшает вероятность совпадения имени данного класса с уже зарегистрированным классом другого приложения.

ВНИМАНИЕ! Если Вы применяете русские символы в именах меню, класса и др., то Вам надо быть очень осторожными — стандартные шрифты нелокализованных для России версий Windows 3.x не содержат русских символов, и, помимо этого, компиляторы могут не воспринимать русский текст, особенно некоторые его буквы (как, например, букву “я”, код которой равен 255). Дополнительно могут встретиться осложнения из–за того, что среда программирования в Windows может использовать собственные нерусифицированные шрифты, или из–за того, что DOS–программы используют иную кодировку русских символов, чем Windows–приложения (если Вы используете какие–либо DOS–средства для разработки программ, либо Ваше приложение будет использовать DOS–программы).

Если функция RegisterClass вернула не 0, то класс успешно зарегистрирован. Этот зарегистрированный класс будет существовать до тех пор, пока приложение активно, или пока Вы не вызовете функцию

UnregisterClass(lpszClassName, hInstance)

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

Создание и отображение окна

 

После регистрации класса мы можем создавать окна. Делается это с помощью процедуры CreateWindow. Эта функция создает окно и возвращает его хендл. Если создание окна почему–либо невозможно, то возвращается значение NULL. Рассмотрим параметры этой функции:

 

HWND CreateWindow(
      lpszClassName, lpszWindowName, dwStyle,
      nX, nY, nWidth, nHeight,
      hWndParent, hMenu, hInstance, lpParam
);

 

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

lpszWindowName задает заголовок окна. Этот заголовок размещается в верхней части окна. В некоторых случаях этот текст используется иначе — например, если окно — "кнопка", то это текст на кнопке, или это может быть текст под пиктограммой, если окно минимизировано.

Параметр dwStyle содержит битовые флаги, задающие некоторые характеристики окна, как–то тип рамки, наличие заголовка, кнопок системного меню, минимизации, максимизации и т.д. Признаки окна называются стилями и описаны в windows.h с префиксом WS_. Вы должны объединять отдельные стили с помощью операции ПОБИТОВОЕ ИЛИ: WS_CAPTION|WS_SYSMENU.

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

 

Рисунок 3. Основные элементы окна

 

nX, nY эти параметры задают позицию верхнего левого угла окна относительно экрана или, для дочернего окна, относительно верхнего левого угла внутренней области родительского окна. Позиция задается в пикселях. Вместо конкретных чисел можно подставить CW_USEDEFAULT — в этом случае Windows будет определять позицию сам.

nWidth, nHeight — ширина и высота окна в пикселях. Вместо этих величин Вы можете указать CW_USEDEFAULT, для того, что бы Windows самостоятельно определил их.

hWndParent этот параметр является хендлом родительского окна. Если Вы указали NULL, то данное окно не является дочерним (используемым)[9], а если Вы задали хендл другого окна, то вновь создаваемое окно будет дочерним (используемым) по отношению к указанному.

hMenu обычно задает хендл меню, используемого окном. Однако здесь есть несколько тонкостей:

· если меню описано при регистрации класса, и этот параметр равен NULL, то будет использоваться меню, описанное в классе.

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

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

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

lpParam. Как правило, этот параметр равен NULL. Он используется сравнительно редко, обычно если окно является окном MDI–интерфейса (Multiple Document Interface) — о MDI смотри в разделе, посвященном оконным системам. Этот указатель передается оконной функции при обработке сообщения, информирующего о создании окна, и используется непосредственно самой оконной функцией. Так Вы можете передать оконной функции требуемые для создания окна данные через этот параметр.

В некоторых случаях при описании стиля окна необходимо задавать дополнительные характеристики, которые не входят в состав параметра dwStyle. Так, например, Вы можете создать окно, которое всегда находится поверх всех остальных окон (always on top, topmost). Для этого используется дополнительное двойное слово стиля (ex tended style) и, соответственно, другая функция для создания окна:

 

HWND CreateWindowEx(
      dwExStyle, lpszClassName, lpszWindowName, dwStyle,
      nX, nY, nWidth, nHeight,
      hWndParent, hMenu, hInstance, lpParam
);

 

Эта функция отличается только наличием еще одного параметра dwExStyle, задающего дополнительные стили окна. Для задания этих стилей используются мнемонические имена с префиксом WS_EX_..., например WS_EX_TOPMOST или WS_EX_TRANSPARENT.

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

Однако в WinMain не рекомендуется создавать сразу видимое окно. Это связано с тем, что приложение может быть запущено в начально минимизированном состоянии. Как надо отобразить окно мы можем узнать через аргумент nCmdShow функции WinMain, но использовать его функцией CreateWindow сложно. Поэтому окно создается невидимым и затем отображается в требуемом нам виде:

 

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

 

Функция ShowWindow указывает окну, в каком виде оно должно быть отображено на экране. В функции WinMain параметр nCmdShow берется из переданных аргументов — он определяет, каким образом пользователь хочет запустить это приложение. Во время выполнения функции ShowWindow на экране отображается рамка и заголовок окна в нужном месте и требуемого размера.

Во время выполнения функции UpdateWindow отображается внутренняя область окна. Определимся, что внутренняя область окна иногда называется клиентной (client), а рамка, заголовок, или заменяющая все пиктограмма называются внешней областью, обрамлением (frame) или неклиентной (non–client) областью окна. Для отображения внутренней области окна приложение получает специальное сообщение, обработчик которого выполняет требуемое рисование.

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

 

Сейчас нам придется обзорно рассмотреть механизм передачи сообщений в Windows 3.x (реально он гораздо сложнее, особенно в Windows–95 или Windows NT). Для начала следует выделить некоторый источник сообщений. Им может являться какое–либо устройство ввода (например, клавиатура, мышь), специальные устройства (типа таймера), либо какое–либо работающее приложение. Сообщения, полученные от источника сообщений, накапливаются в очереди сообщений (message queue). Далее сообщения будут извлекаться из очереди и передаваться для обработки конкретной оконной процедуре.

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

Процесс помещения сообщения в очередь и его извлечения из нее никак жестко не увязан по времени. Более того, может случиться так, что посланное сообщение вообще не будет обработано, например, если окно–получатель будет уничтожено прежде, чем успеет его обработать.

 

Рисунок 4. Обработка посланных сообщений в Windows

 

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

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

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

· сообщение выбирается из очереди с помощью функции GetMessage или PeekMessage

· затем сообщение транслируется с помощью функции TranslateMessage[10] (одно сообщение может порождать последовательность других или заменяться, как, например, происходит для сообщений клавиатуры WM_KEYDOWN). Часто трансляция состоит из вызова более чем одной функции, сюда могут добавляться специальные средства трансляции акселераторов и немодальных диалогов (об этом позже).

· и только после этого оно направляется окну с помощью функции DispatchMessage (это называется диспетчеризацией)

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

 

MSG    msg;

while (GetMessage(&msg, NULL, NULL, NULL)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
}

 

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

BOOL GetMessage(lpMsg, hWnd, uMsgFilterMin, uMsgFilterMax);

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

Какие же данные передаются одним сообщением?

 

typedef struct tagMSG {
      HWND hwnd;  // хендл окна-получателя
      UINT message; // номер сообщения WM_...
      WPARAM wParam;    // параметр сообщения
      LPARAM lParam;     // параметр сообщения
      DWORD time;    // время поступления сообщения
      POINT pt;        // координаты сообщения (для сообщений мыши)
} MSG;

 

Поле message структуры MSG задает номер сообщения, посланного системой. Интерпретация параметров сообщения wParam и lParam зависит от самого сообщения. Для этого надо смотреть описание конкретного сообщения и обрабатывать параметры соответствующим образом. Так как в системе определено огромное количество разных сообщений, то для простоты использования применяются символические имена сообщений, задаваемыми с помощью #define в заголовочном файле. В качестве примера можно привести сообщения WM_CREATE, WM_PAINT, WM_QUIT.

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

uMsgFilterMin и uMsgFilterMax обычно установлены в NULL. Вообще они задают фильтр для сообщений. GetMessage выбирает из очереди сообщения, номера (имена) которых лежат в интервале от uMsgFilterMin до uMsgFilterMax. Нулевые значения исключают фильтрацию.

Функция GetMessage возвращает во всех случаях, кроме одного, ненулевое значение, указывающее, что цикл надо продолжать. Только в одном случае эта функция возвратит 0 — если она извлечет из очереди сообщение WM_QUIT. Это сообщение посылается только при окончании работы приложения.

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

И остается еще одно дело: так как WinMain возвращает результат, то мы должны вернуть какое–либо значение. В Windows принято, что возвращаемое значение является параметром wParam сообщения WM_QUIT, завершившего цикл обработки сообщений. Таким образом мы пишем:

return msg.wParam;

 

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

 

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

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

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

 

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
      //...
}

 

В качестве аргументов мы получаем параметры переданного сообщения. Обычно оконные функции оформляются примерно по такой схеме:

 

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
      // описание внутренних переменных

   switch (uMsg) {
      case WM_...:
            // обработка нужного сообщения
            break;

   // обработка других сообщений...

   default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
      }
      return 0L;
}

 

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

Для упрощения написания оконной функции Windows предлагает специальную функцию

 

LRESULT DefWindowProc(hWnd, uMsg, wParam, lParam);

 

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

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

 

Сообщение WM_CREATE

 

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

Стандартная обработка этого сообщения необязательна — функция DefWindowProc просто возвращает 0 в ответ на это сообщение.

Параметр wParam не используется, а параметр lParam содержит указатель[11] на структуру CREATESTRUCT. В этой структуре передается основная информация о создаваемом окне.

typedef struct tagCREATESTRUCT {
      void FAR* lpCreateParams; // указатель на дополнительные данные,
                                        // переданный как параметр lpParam в вызове
                                        // функции CreateWindow или CreateWindowEx
      HINSTANCE hInstance; // хендл копии приложения, создавшей окно
      HMENU hMenu;           // хендл меню (или NULL, если нет)
      HWND hwndParent;   // хендл родительского окна (или NULL)
      int  cy, cx;        // размеры окна
      int  y, x;           // положение окна
      LONG style;         // стили окна
      LPCSTR lpszName; // заголовок окна
      LPCSTR lpszClass;  // имя класса, к которому принадлежит окно
      DWORD dwExStyle;     // расширенные стили окна (см. CreateWindowEx)
} CREATESTRUCT;

 

Поля x, y, cx и cy в момент обработки сообщения WM_CREATE могут быть еще не определены. При необходимости получить информацию о размере или положении окна надо пользоваться функциями GetWindowRect или GetClientRect, которые возвращают корректный результат

Возвращаемое обработчиком значение:

не 0 — возникла ошибка, окно надо уничтожить (далее, при уничтожении, будет получено сообщение WM_DESTROY), функция CreateWindow или CreateWindowEx вернет NULL.

0 — окно успешно создано; функция CreateWindow или CreateWindowEx вернет хендл окна.


Сообщения WM_DESTROY и WM_QUIT

 

Еще одно сообщение, интересующее нас — WM_DESTROY. Это сообщение посылается окну в момент его уничтожения. Это одно из последних сообщений, которое получает окно — можно считать, что после этого оно уже не существует, причем в момент получения этого сообщения окно уже невидимо. В этот момент вы можете выполнить необходимые операции по освобождению выделенных при создании окна ресурсов. Оба параметра сообщения WM_DESTROY не используются.

Как и WM_CREATE сообщение WM_DESTROY является информационным — реальное уничтожение окна осуществляется функцией DestroyWindow, а не обработчиком сообщения WM_DESTROY. Независимо от способа обработки этого сообщения окно будет уничтожено.

Если ваша оконная функция обслуживает главное окно приложения, то в момент уничтожения окна вы должны принять меры для завершения работы всего приложения в целом. Для этого вы должны послать сообщение WM_QUIT, при извлечении которого из очереди закончится цикл обработки сообщений. Если этого сообщения не послать, то цикл обработки сообщений продолжит работу дальше после закрытия всех имеющихся окон. На практике это означает что приложение вообще перестанет получать сообщения и “зависнет” на функции GetMessage, которая будет ждать, пока не придет новое сообщение. В случае Windows 3.x есть единственный способ удалить такое приложение — перезапуск всего Windows (в Windows–95 или Windows NT такое приложение можно снять самому с помощью менеджера задач). Сообщение WM_QUIT посылается с помощью функции:

void PostQuitMessage(nExitCode);

Параметр nExitCode будет передан как wParam сообщения WM_QUIT и позже возвращен функцией WinMain в качестве параметра завершения.

Обработчиков для сообщения WM_QUIT писать не надо, так как:

Во–первых, функция GetMessage, получив это сообщение просто вернет FALSE, так что цикл обработки сообщений будет закончен без трансляции и диспетчеризации этого сообщения.

Во–вторых, это сообщение адресовано не окну, а приложению. То есть, даже если воспользоваться функцией PeekMessage для извлечения сообщения WM_QUIT из очереди (в отличие от GetMessage это получится), оно не будет отправлено никакому окну, так как хендл окна–получателя равен NULL.

Сообщение WM_PAINT

 

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

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

Для этого вводится специальное сообщение WM_PAINT, которое посылается окну тогда, когда часть окна (или все окно) нуждается в перерисовке. Часто перерисовка окна занимает значительное время, а обработку сообщений рекомендуется выполнять как можно быстрее; поэтому Windows особым образом обрабатывает WM_PAINT, что бы задержки в обработке этого сообщения не оказывали такого влияния на работу системы.

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

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

Основы рисования в окне

 

Еще раз вспомним, каким образом появляется сообщение WM_PAINT: это происходит когда все окно, или только его часть становятся видимыми. Та часть окна, которая нуждается в перерисовке, является неверной — ее содержимое не соответствует требуемому. Эта часть получила название неверный прямоугольник (invalid rectangle).

Соответственно, после перерисовки она перестает быть неверной — то есть становится верным прямоугольником (valid rectangle).

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

Неверный прямоугольник возникает, если:

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

· облас<


Поделиться с друзьями:

Автоматическое растормаживание колес: Тормозные устройства колес предназначены для уменьше­ния длины пробега и улучшения маневрирования ВС при...

Типы сооружений для обработки осадков: Септиками называются сооружения, в которых одновременно происходят осветление сточной жидкости...

Наброски и зарисовки растений, плодов, цветов: Освоить конструктивное построение структуры дерева через зарисовки отдельных деревьев, группы деревьев...

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



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.145 с.