Основной
особенностью программ на чистом Windows является
крайне непривычная логика построения их архитектуры.
Понять ее изначально трудно, нужно запоминать. Если постоянно работать с
подобными программами, то все становится понятно, и, даже, местами логично. Но
стоит забросить работу на пол года – год и от логики в голове остается ровным
счетом ничего.
Тем
не менее, не представляя принципов архитектуры программ под Windows
(или не зная где можно эти принципы подсмотреть), очень трудно реализовать
некоторые задачи, возникающие при программировании под эту операционную
систему.
Подробнее с основами программирования можно ознакомится сдесь.
Для
получения справки по основным функциям API возьмите сдесь архив
справочника, сохраните его на свой рабочий диск и разархивируйте. Ознакомьтесь с основными функциями.
Структура данной лабораторной работы:
1.
Приводится пример, он же шаблон, простейшей программы. Разбираться и
вдумываться в него на данном этапе не имеет смысла. Зато рекомендуется набрать
его самостоятельно, а не просто копировать и вставить.
2.
Приводится описание шаблона и его составных частей. Эту часть можно просмотреть
бегло. Все равно без постоянной практики программирования на чистом АPI ничего в памяти не остается.
3.
Приводятся теоретические сведения об оконной процедуре. Эту часть теории стоит
прочитать вдумчиво, так как это ключевая часть программы под Windows.
Именно здесь находится более 90% кода, придуманного программистом. Эта часть
позволяет оживить программу. И без понимания ее Вы самостоятельно не сможете
выполнить задания лабораторной работы.
4.
Теоретическая информация о функциях, выполнение которых будет явно заметно – что то запищит и вылезет какое то окно. Структуру самих функций запоминать не обязательно. Приводятся лишь для того чтобы можно было понять, что программа что то
делает.
5.
Приводятся некоторые сообщения Windows и пример обработки
некоторых сообщений. Это так же ключевой момент лабораторной работы.
6.
Задание на лабораторную работу и контрольные вопросы.
1. Шаблон
простейшей программы под Windows
// Включаем необходимый заголовочный
файл для Windows-программ
#include <windows.h>
// Объявление функции окна (оконной
процедуры)
LRESULT CALLBACK WndProc(HWND
hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
HINSTANCE hInst;
// Идентификатор приложения
// Указатель на константную строку
символов - имя программы и класса окна
LPCSTR AppName
= "MyProgramm";
// Точка входа в программу - функция WinMain
int
WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd; //
Уникальный идентификатор окна (handle)
MSG msg; //
Объявление структуры типа MSG, для работы с сообщениями
hInst = hInstance; // Сохраняем идентификатор приложения
// Заполняем структуру WNDCLASS
WNDCLASS wc;
// Инициализируем выделенную для
структуры память нулями
ZeroMemory(&wc, sizeof(wc));
wc.style =
CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(hInst, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground =
(HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = AppName;
RegisterClass(&wc); // Создаем и регистрируем оконный класс
// Создаем окно программы
hWnd
= CreateWindow(
AppName, // Имя класса окна
AppName, // Заголовок окна
WS_OVERLAPPEDWINDOW,
// Стиль окна
CW_USEDEFAULT,
0, // Горизонтальная и вертикальная позиции окна
300,
300, // Ширина и высота окна
NULL,
// Хендл родительского окна
NULL,
// Хендл меню
hInst, // Идентификатор приложения
NULL); // Дополнительные данные окна
ShowWindow(hWnd, SW_SHOW); // Отображаем окно
UpdateWindow(hWnd); // Перерисовываем окно
//
Стандартный цикл обработки сообщений
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
// Оконная процедура
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam,
lParam);
}
return 0;
}
Что бы транслировать
и собрать эту программу достаточно в консоли выполнить
"C:\Program Files\Borland\CBuilder6\Bin\bcc32.exe" -W имя_исходного_файла
Примеры программ можно взять из этого архива.
WinMain - это главная функция в
Windows-приложениях. Именно с нее и начинается выполнение программы.
Аналогичной функцией в консольных приложениях является функция main. Разумеется, функция WinMain
должна быть ровно одна - не больше и не меньше.
В WinMain передается 4 параметра. Первые два из них имеют тип
HINSTANCE. Этот тип - это Windows-тип который означает экземпляр приложения. Первый параметр при
этом содержит указатель (в смысле handle) на текущий
экземпляр приложения, второй - всегда равен NULL. Ранее (в Windows
3.1) второй параметр содержал handle предыдущего
запущенного экземпляра этого же приложения (например, если вы запускали второй
экземпляр блокнота). Третий параметр представляет из себя
указатель на строку, заканчивающуюся нуль-символом (стандартное представление
строк в C/C++). Эта строка - это параметры командной строки, передаваемой в
приложение. Параметры командной строки включают и имя программы. И, наконец,
четвертый параметр определяет способ показа главного окна нашего приложения -
т. е. показывать ли его развернутым на целый экран,
или в нормальном виде, или в сложенном и др.
Двигаемся дальше. В
самой функции WinMain мы последовательно проходим
несколько этапов. Сначала мы создаем класс окна, потом регистрируем его в Windows, после чего создаем на основе этого класса окно и
показываем его. На последнем этапе мы запускаем цикл обработки сообщений (так
называемый message pump).
Вот эти этапы более
подробно.
Создание класса
окна начинается с объявления переменной типа WNDCLASS:
...WNDCLASS w;...
Далее мы заполняем
поля этой переменной. Она имеет структурный тип, так что мы должны заполнить
все поля. Так как большинство из них можно поставить в ноль, то сначала мы
обнуляем их всех:
...memset(&w, 0, sizeof(WNDCLASS));
...
а потом задаем для
остальных подходящие значения:
... w.style = CS_HREDRAW | CS_VREDRAW; w.lpfnWndProc = WndProc; w.hInstance = hInstance; w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);w.lpszClassName = "My Class";
...
Тут мы
последовательно задавали стиль окна, установили, что переменная hInstance нашей структуры должна содержать handle на текущий экземпляр приложения (это первый параметр
функции WinMain), установили цвет фона и задали имя
класса окна.
Далее мы
регистрируем наш класс в системе Windows:
...RegisterClass(&w);
...
После этого мы,
наконец-то, создаем окно с помощью функции CreateWindow:
...hwnd = CreateWindow("My Class", "My title", WS_OVERLAPPEDWINDOW,
300, 200, 200, 180, NULL, NULL, hInstance, NULL);
...
Параметры этой
функции задают внешний вид нашего окна. Эта функция возвращает в качестве
значения handle на созданное окно. Это у нас
переменная hwnd типа HWND. Далее все операции с окном
мы делаем через этот handle. В частности, мы
показываем окно и обновляем его:
... ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); ...
И, наконец, после
того, как окно создано и показано, мы запускаем цикл обработки сообщений:
...
while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } ...
Окнонная процедура у нас названа WndProc. Это название произвольное - в принципе мы могли
назвать ее как угодно. Основное предназначение оконной процедуры - это
обработка сообщений Windows. Например, вы нажимаете
левую кнопку мыши и ваша программа получает сообщение Windows WM_LBUTTONDOWN. Вы изменяете размер окна и ваша программа получает сообщение WM_SIZE. Так вот, в
оконной процедуре мы и обрабатываем сообщения Windows.
Для этого мы пишем большой (как правило) switch, внутри которого пишем несколько case'ов.
Если мы хотим, например, чтобы наша программа обращала внимание на изменение
размера окна, то мы должны написать
switch (Message)
{...case WM_SIZE:
//Обработчик сообщения WM_SIZE.
...
Если же мы такого
не напишем, то наша программа никак реагировать на изменение размеров окна не
будет. При этом соответствующее сообщение Windows
программа все равно получит.
Мы писали
минимальную программу, так что у нас обработчик только одного сообщения Windows - WM_DESTROY. Это сообщение окно получает при своем
уничтожении. В этом обработчике мы только делаем некоторые действия, связанные
с уничтоженим нашего окна.
Обратите внимание
на ветку default:
...
default: return DefWindowProc(hwnd, Message, wparam, lparam);...
Тут мы вызываем
API-функцию DefWindowProc. Основное предназначение
этой API-функции - это обработка сообщений Windows,
которые не обрабатываются в нашей программе (т. е. тех, для которых нет своего case). При этом ничего
особенно это функция не делает, но очередь из сообщений при этом двигается. И
это самое важное - если у нас не было обработчика по умолчанию, то сообщения, не
обрабатываемые нашей программой, забили бы очередь, и она бы встала - и даже те
сообщения, для которых есть обработчики, никогда бы не были обработаны. Таким
образом, основное предназначение функции DefWindowProc
- это обработка ("проглатываение") тех сообщений,
которые не обрабатываются в каком-нибудь case'е.
В оконную процедуру
передаются 4 параметра:
LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam){ ...
Первый из этих
параметров - это окно, в которое мы передаем сообщение Windows.
Второй - это само сообщение. Третий и четвертый параметры - это дополнительные
параметры для конкретного сообщения. Эти параметры будет разными
для разных сообщений (и для некотрых сообщений Windows могут вообще не использоваться). Например,
для сообщения WM_LBUTTONDOWN в дополнительных параметрах передаваются
x и y той точки, в которой мы щелкнули, информация и том, были ли при этом нажати кнопки shift, atl и ctrl и другое, для
сообщения WM_SIZE - новые размеры окна и т. д.. Еще обратите внимание, что эти
же параметры у нас передаются в функцию DefWindowProc.
Конкретно, что означают эти параметры для некоторого сообщения Windows, надо смотреть в справке по этому сообщению.
4. Некоторые API функции Windows.
Функция MessageBeep служит для издания одного из стандартных
звуковых сигналов Windows. Что за сигнал будет
звучать, определяется единственным параметром этой функции, который может
принимать следующие значения:
|
Значение |
Описание
звука |
|
-1
или 0xFFFFFFFF |
Звуковой
сигнал через встроенный динамик компьютера. |
|
MB_ICONEXCLAMATION |
Восклицание
(системный) |
|
MB_ICONHAND |
Предупреждение
(системный) |
|
MB_ICONQUESTION |
Вопрос
(системный) |
|
MB_OK |
Стандартный
(системный) |
|
MB_ICONASTERISK |
Звездочка
(системный) |
Функция возвращает
значение типа BOOL - если функция вызвалась успешно, то возвращается ненулевое
значение, в случае неуспеха возвращается ноль.
Вот пример
использования этой функции:
...MessageBeep(0xFFFFFFFF); //Обычный звуковой сигнал
Функция MessageBox предназначена для вывода стандартного окна
сообщений. Оно может выглядеть, например, так:

Эта функция имеет
несколько параметров. Первый определяет то, к какому окну принадлежит наш MessageBox. При этом доступ к этому окну будет закрыт до
тех пор, пока мы не закроем MessageBox. Если первый
параметр поставить в NULL, то MessageBox не будет
принадлежать никакому окну (и мы сможем переключиться на любое окно нашей
программы).
Второй параметр
функции MesageBox определяет, что за надпись появится
в нем, третий же параметр определяет заголовок MessageBox'а.
Последний,
четвертый параметр самый интересный. Он определяет много чего. Например, этим
параметром вы можете задать, что за кнопки будут в MessageBox'е,
какая из них будет кнопкой по умолчанию, что за картинка будет слева от
сообщения, модальность окна.
Вот несколько
констант, которые определяют, что за кнопки будут в MessageBox'е:
MB_OK - только кнопка OK, MB_OKCANCEL - кнопки OK и Cancel, MB_YESNOCANCEL
- кнопки Yes, No и Cancel. Разумеется, есть и другие константы.
Пример констант,
определяющих значок в MessageBox'е: MB_ICONQUESTION - вопросительный знак, MB_ICONEXCLAMATION - восклицательный
знак, MB_ICONERROR - красный круг с
белым крестом.
А вот константы,
задающие кнопку по умолчанию: MB_DEFBUTTON1
- первая кнопка, MB_DEFBUTTON2-
вторая кнопка, MB_DEFBUTTON3 -
третья кнопка.
И, наконец,
последнее, что надо сказать о MessageBox'е. Это
функция и это означает, что она возвращает значение. Это значение определяет,
что за кнопку пользователь нажал - OK или Cancel или Yes или еще что-нибудь. Для этого существует целый ряд констант и все эти константы названы по одному принципу - их
название начинается на ID, после которого идет надпись на кнопке (прописными
буквами). Например, IDOK - для
кнопки OK, IDCANCEL - для кнопки Cancel, IDIGNORE -
для кнопки Ignore. Вот так, например, можно
проверить, что нажата кнопка NO:
if(IDNO==MessageBox(hwnd, "Сохранить изменения?", "", MB_YESNO|MB_ICONQUESTION))
...
Обратите внимание,
что при этом константы в четвертом параметре мы соединяем побитовым
или (|).
На этом уроке мы
посмотрим, что мы должны добавить в нашу программу для того, чтобы при закрытии
появлялось стандартное окно сообщений с вопросом, надо ли из программы
выходить. Если пользователь ответит Да, то программа
закроется, если Нет, то не закроется.
Вот текст, который
необходимо добавить в оконную процедуру:
...switch (Message){
case WM_CLOSE:if(IDOK==MessageBox(hwnd, "Выходим?", "", MB_OKCANCEL|MB_ICONQUESTION))
SendMessage(hwnd, WM_DESTROY, NULL, NULL);break;
...
Как вы видите, тут
мы добавили обработчик для сообщения Windows
WM_CLOSE. Именно такое сообщение получит окно, когда пользователь нажимает на
крестик в правом верхнем углу программы или нажимает Alt+F4. Если пользователь
нажал на кнопку Да (Yes), то
самому окну посылается сообщение WM_DESTROY.
Это мы делаем посредством вызова API-функции SendMessage.
Схема действий для
добавления обработчика любого сообщения Windows
следующая - мы просто должны добавить соответствующую ветку в case оконной процедуры. В частности, если мы хотим, чтобы
наша программа обращала внимание на нажатие левой кнопки мыши, то мы должны
добавить строку "case WM_LBUTTONDOWN:":
...switch (Message){
//Если нажали на левую кнопку мыши, case WM_LBUTTONDOWN: //то раздается звуковой сигнал. MessageBeep(0xFFFFFFFF); break; ... }
Теперь при запуске
нашей программы при нажатии левой кнопки мыши в окне мы услышим звуковой
сигнал. Аналогично мы можем писать обработчики для остальных сообщений Windows. Обратите внимание на break
в конце обработчика. Хотя синтаксически он не обязателен, но в подавляющем
числе случаев он нужен (чтобы программа не "проваливалась" на следующий обработчик).
Вообще для
программ, написанных на голом
API, как правило, мы имеем следующую ситуацию - функция WinMain
у них различается мало, а вот оконная процедура - весьма существенно. И именно
тем, какие сообщения в оконной процедуре обрабатываются, программы и отличаются
друг от друга. И, как правило, оконная процедура достаточно большая - ведь
настоящая программа умеет много чего. Хотя в нашем случае она пока очень
маленькая.
Как вы уже знаете,
окно может получать различные сообщения. Эти сообщения могут исходить из разных
источников - от действий пользователя, от самой Windows,
от другой программы. Вообще говоря, сообщений Windows
несколько сотен. Может быть когда-нибудь мы и
рассмотрим большую часть из них, но не сейчас. Сейчас мы перечислим только
самые распространенные из сообщений.
Вот они:
Задание на лабораторную работу
Напишите программу, на основе приведенного выше шаблона, которая обрабатывала 2 или более сообщений Windows, реагируя на них либо звуковым сигналом, либо MessageBox с произвольным текстом.
Контрольные
вопросы
1. С какой функции
начинается выполнение программы под
Windows.
2. Какой принцип связи
программ под
Windows
с "внешним миром".
3. Что такое
API
функции.
4. Что такое "сообщение
Windows".
5. Что вы поняли из выше
написанного
J
6. Что делает ваша программа
J