// - "Talk to me like I'm a 3 year old!" Programming Lessons -
//
// $Author: Ben Humphrey digiben@gametutorials.com
//
// $Program: Window
//
// $Description: Create a simple window
//
// $Date: 7/20/00
//
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
#include <windows.h>
#include <stdio.h>
/* Строка ниже это прототип функции. Если вы помните, прототип
функции это объявление функции, которое говорит компилятору,
чего следует ожидать. Он будет знать, что ему нужно выделить
память для функции с параметрами HWND, UINT, WPARAM, LPARAM;
Обратите внимание, что в функции нет имен переменных,
например WndProc(HWND Hwnd, UINT UInt, WPARAM wParam,
LPARAM lParam). Вы НЕ обязаны указывать имена переменных в
прототипе функции, хотя в ваших функциях вам следует это делать.
Как я говорил, компилятору нужно знать, из чего состоит
функция, имена переменных для него не важны.
LRESULT в начале функции означает:
Из функции callback или window процедуры будет возвращено
32-битное значение. По-русски это значит, что мы хотим, чтобы
значение, возвращаемое из функции было 32-битным.
Вы будете использовать LRESULT на CALLBACK функции. Значение
CALLBACK используется для функций windows и позволяет функции
быть чем-то вроде указателя, чтобы вы могли присвоить
функцию переменной CALLBACK. Сейчас это не играет большой
роли, но позже, когда вы увидите, как это используется,
это будет иметь значение.
*/
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
/* Эти функции обрабатывают все оконные сообщения.
Помните "main()"? Это функция main() для Windows.
Она называется WinMain().
WINAPI это "соглашение вызова Win32 API". Просто примем это
пока что как должное. Пока достаточно знать, что мы должны
ставить WINAPI перед WinMain(). А также мы указываем, что
WinMain возвращает целое число. Функции WinMain() не нужен
прототип, поэтому мы можем начинать писать для нее код.
С этого места, почти что каждая переменная будет новой для вас.
А также, к чему-то нужно будет привыкнуть. Позвольте мне объяснить
значения этих перменных. Когда я изучал их, я не знал по настоящему,
что они из себя представляют, я просто знал, что они нужны для
работы программы.
HINSTANCE hInstance - содержит информацию об экземпляре приложения.
Это потому что одновременно могут быть запущены 2 копии (экземпляра)
той же самой программы.
HINSTANCE hPrevInstance - содержит информацию о предыдущем
экземпляре приложения, и ее значение всегда равно NULL (нет
информации) для win32 приложений.
PSTR szCmdLine - содержит информацию из командной строки.
Например, как вы можете набрать "<имя программы> -l *.*"
или что-то вроде того в DOS.
Это если вы хотите передавать в ваше приложение командные
строки DOS. PSTR это просто строка.
int iCmdShow - содержит информацию о состоянии окна, например
Maximized, Minimized, Hide и т.п.
*/
// Итак, вот наша "main()" для windows. Она обязательно должна быть в windows приложении.
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HWND hwnd;
// HWND это ссылка на окно (handle to a window). Она используется,
// чтобы отслеживать конкретное окно. Программы могут иметь много
// окон. В этой у нас будет только одно.
MSG msg;
// MSG - переменная сообщений (MeSsaGe), чтобы хранить посылаемые
// окну сообщения (если окно было щелкнуто мышью, закрыто, передвинуто и т.п.)
WNDCLASSEX wndclass;
// WNDCLASSEX - эта переменная содержит всю информацию об окне
// (имя, значок, курсор, цвет, меню...)
wndclass.cbSize = sizeof (wndclass);
// Здесь мы устанавливаем размер wndclass. Вы увидите это в windows программах
// много раз. Мы используем функцию "sizeof()", чтобы сообщить windows
// размер нашего класса.
wndclass.style = CS_HREDRAW | CS_VREDRAW;
// Стиль, который мы хотим - это перерисовка по вертикали и горизонтали
wndclass.lpfnWndProc = WndProc;
// В этом месте мы сообщаем о нашей CALLBACK функции. Помните, выше
// нашу функцию "WndProc"? Эта переменная просто говорит windows,
// которую функцию нужно вызвать для проверки сообщений окна.
wndclass.cbClsExtra = 0;
// Мы не хотим выделять для класса дополнительные байты (переменная бесполезна для нас).
wndclass.cbWndExtra = 0;
// Еще одна бесполезная для нас вещь. Я полагаю, эти две в любом случае
// нужно инициализировать в 0.
wndclass.hInstance = hInstance;
// Мы присваиваем hInstance нашему окну. И вновь, может быть запущено несколько
// экземпляров той же программы, а эта переменная будет отслеживать текущий.
wndclass.hIcon = LoadIcon (NULL, IDI_WINLOGO);
// Мы вызываем функцию LoadIcon, которая возвращает информацию о значке,
// который мы хотим. Я выбрал логотип windows. NULL это вместо hInstance.
// В данном случае нам она не нужна.
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
// Мы вызываем функцию LoadCursor, которая возвращает информацию о курсоре,
// который мы хотим. Я выбрал стрелку. NULL это вместо hInstance.
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
// Здесь мы устанавливаем цвет фона. GetStockObject() возвращает void,
// поэтому мы должны преобразовать тип в HBRUSH, для совместимости
// с переменной "hbrBackground".
wndclass.lpszMenuName = NULL;
// У нас нет меню, поэтому установим это в NULL.
wndclass.lpszClassName = "Window Class 1";
// Здесь мы устанавливаем имя нашего класса, чтобы он отличался от других.
// Это имя понадобится нам позже, когда мы будем создавать окно.
wndclass.hIconSm = LoadIcon (NULL, IDI_WINLOGO);
// Мы хотим значок windows logo. Это тот значок, который
// появляется в левом верхнем углу окна.
RegisterClassEx (&wndclass);
// Нам нужно зарегистрировать наш оконный класс в системе windows.
// Нам также нужно передать адрес wndclass в памяти, поэтому
// мы используем "&".
// А теперь мы в действительности создадим окно.
// CreateWindow() возвращает ссылку на окно,
// которую мы сохраняем в нашей переменной "hwnd" типа HWND.
hwnd = CreateWindow ("Window Class 1", // имя класса окна
"My First Window", // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, // начальное положение x
CW_USEDEFAULT, // начальное положение y
CW_USEDEFAULT, // начальное положение x
CW_USEDEFAULT, // начальное положение y
NULL, // ссылка на родительское окно
NULL, // ссылка на меню окна
hInstance, // ссылка на экземпляр программы
NULL); // для WndProc
/* "Window Class 1" - имя класса окна.
Это говорит фунции CreateWindow() использовать наш класс, определенный выше.
"My First Window" - заголовок окна.
Это имя появится в заголовке окна.
WS_OVERLAPPEDWINDOW - стиль окна.
Это значение говорит windows создать типичное окно
(с опциями resize, minimize, close, и т.д.).
CW_USEDEFAULT - начальное положение x.
Значение Х левого верхнего угла окна в экранных координатах.
В данном случае мы позволяем windows выбрать за нас.
CW_USEDEFAULT - начальное положение y.
Значение Y левого верхнего угла окна в экранных координатах.
В данном случае мы позволяем windows выбрать за нас.
CW_USEDEFAULT - начальное положение x.
Значение Х правого нижнего угла окна в экранных координатах.
В данном случае мы позволяем windows выбрать за нас.
CW_USEDEFAULT - начальное положение y.
Значение Y правого нижнего угла окна в экранных координатах.
В данном случае мы позволяем windows выбрать за нас.
NULL - ссылка на родительское окно.
Так как у нас нет родительского окна, мы устанавливаем NULL.
NULL - ссылка на меню окна.
У нас нет меню, поэтому устанавливаем в NULL.
hInstance - ссылка на экземпляр программы.
Мы просто передаем нашу hInstance из WinMain().
Кстати, система Windows передает информацию в WinMain().
NULL - если бы мы хотели передать переменную или структуру
в "WndProc", мы бы сделали это здесь. (Я предпочитаю использовать
глобальные переменные). Просто передаем NULL.
*/
ShowWindow (hwnd, iCmdShow);
// Эта функция показывает наше окно. Мы передаем в нее ссылку на наше окно, в которой
// теперь есть вся информация, и переменную iCmdShow функции WinMain().
UpdateWindow (hwnd);
// Эта функция выводит наше окно на экран.
// Это наш главный цикл. Он будет продолжаться до тех пор, пока функция
// GetMessage не вернет сообщение WM_QUIT, закрывающее программу.
// Это произойдет, когда мы закроем окно.
while (GetMessage (&msg, NULL, 0, 0))
// Нам нужно передать адрес "msg", потому что GetMessage заполнает структуру "msg".
// Мы передаем NULL для HWND, потому что тогда GetMessage проверяет ВСЕ окна,
// которые используют нашу WndProc. И в конце мы передаем нули, они не важны.
{
TranslateMessage (&msg);
// Функция TranslateMessage() преобразовывает сообщения virtual-key в символьные.
// В основном это означает, что функция преобразовывает их, чтобы windows
// смогла их понять.
DispatchMessage (&msg);
// Функция DispatchMessage() передает сообщения в процедуру окна.
// Это значит, что она обрабатывает сообщения, например если было сообщение
// закрыть окно, она закрывает окно.
}
UnregisterClass("Window Class 1", hInstance);
// Нам нужно "отрегистрировать" наш оконный класс из системы Windows.
// Делая это, мы освобождаем память, выделенную для регистрации оконного класса.
return msg.wParam ;
// Мы возвращаем wParam структуры "msg". Вот почему мы указали, что WinMain()
// возвращает int. wParam/lParam это 32-битный параметр сообщения.
// В нем хранятся данные о сообщении. Позже вы увидите, как он используется.
}
// "Большинство того, что вверху, никогда не меняется."
// "WndProc это то, с чем обычно и приходится иметь дело."
/* Это WndProc, что означает "Window Procedure" (оконная процедура).
Эта функция обрабатывает оконные сообщения. Весь код выше обычно
не меняется, когда мы пишем программу для windows, но WndProc
это то, что вы действительно меняете, и она никогда не является
той же самой. Поэтому ее важнее всего понять.
У нас указано LRESULT CALLBACK. Еще раз, это означает, что функция
возвращает 32-битное целочисленное значение, и является CALLBACK.
Call back функция это все равно что указатель на функцию.
Фактически это и есть указатель на функцию. Мы передаем адрес
функции классу окна, и windows заполняет за нас все параметры.
HWND это наша ссылка на окно, iMsg это сообщение, посылаемое
windows. WPARAM и LPARAM хранят информацию о сообщении.
*/
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
// Это проверка, какое передается сообщение. Ниже идут некоторые сообщения,
// которое windows может возвратить. Конечно же, их существует СОТНИ.
// Единственное сообщение, с которым вы обязаны иметь дело, это WM_DESTROY.
// Остальные вы можете не проверять. Я просто показываю примеры того,
// как проверять сообщения.
switch (iMsg)
{
// Это сообщение передается, когда создается окно.
case WM_CREATE:
// Сюда мы можем написать код инициализации.
break;
// Это сообщение передается, когда мы изменяем размер окна.
case WM_SIZE:
// Сюда мы можем поместить код для изменения размера текста или картинки.
break;
// Это сообщение передается в WndProc, когда окно нужно перерисовать.
// Это может понадобиться если мы переместили окно, изменили его размер,
// развернули его на весь экран или поместили поверх другое окно.
case WM_PAINT:
// Помещаем сюда код, рисующий то, что должно быть в окне.
break;
// Это сообщение передается, когда пользователь закрывает окно.
case WM_DESTROY:
// Здесь мы можем обработать деинициализацию, освободить память и т.д.
PostQuitMessage(0);
// Вы обязаны вызвать эту функцию или вам придется нажать control-alt-delete
// и закрыть программу вручную. 0 = WM_QUIT.
// Эта функция помещает сообщение в очередь сообщений.
// SendMessage() посылает сообщение, которое обрабатывается немедленно.
// Это вам просто для информации.
break;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam);
// Функция DefWindowProc вызывает оконную процедуру по умолчанию, чтобы обеспечить
// обработку по умолчанию тех оконных сообщений, которые не обрабатывает программа.
// Эта функция позволяет быть уверенным, что каждое сообщение обрабатывается.
// DefWindowProc вызывается с теми же параметрами, которые получает WndProc.
} // Конец WndProc
/* Вот и все что нужно для того чтобы создать и отобразить окно. Достаточно просто, правда?
Конечно, есть много вещей, к которым вам нужно будет привыкнуть, новые переменные и др.
И скорее всего вы просто будете копировать этот код и вставлять в него то, что вам
будет нужно. Это нормально, но со временем вам нужно будет выучить все это. Когда я
писал эту обучалку, было не так много вещей, которые я забыл или не знал, например
что такое LRESULT. Просто смотрите такие вещи в MSDN. Будет здорово, когда вы сможете
просто смотреть и полностью понимать все это. Тогда вам не понадобится ничего копировать.
(Ну хорошо, я все еще копирую, вы думаете мне охота было все это набирать?)
Вот 5 шагов в программировании win32:
1) Инициализировать класс
2) Зарегистрировать класс
3) Создать окно
4) Получить сообщения из WndProc.
5) Преобразовать и обработать сообщения.
Есть МНОГО сообщений, которые вы можете проверять, я выбрал те, которые вы скорее
всего будете использовать.
Несколько других примеров: WM_MOUSEMOVE, WM_LBUTTONCLICK, WM_KEYDOWN и др. У
некоторых сообщений вы будете проверять LPARAM и WPARAM. Это то, что содержит
информацию о координатах мыши, или о том, какая клавиша нажата.
В этой обучалке я не стал все это объяснять, потому что я не хочу вас распугать.
Я подожду до следующей обучалки :)
Я знаю, что сейчас для вас все это выглядит непонятным. Поверьте, пройдет
совсем немного времени, и вы будете разговаривать как ботаник. "И затем я
задал wndproc в качестве callback, и зарегистрировал класс..."
Удачи!
*/
// © 2000-2002 GameTutorials
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
|