// - "Talk to me like I'm a 3 year old!" Programming Lessons -
//
// $Author: Ben Humphrey digiben@gametutorials.com
//
// $Program: Color
//
// $Description: Changes text color and the window's background.
//
// $Date: 7/20/00
//
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
#include <windows.h>
#include <stdio.h>
#include <time.h>
RECT windowRect = {100, 100, 400, 400};
/* Создадим прямоугольник, который определяет желаемое положение окна.
Структура RECT работает вот так: {left, top, right, bottom}
(лево, верх, право, низ)
Помните, когда мы инициализируем переменную, мы можем заполнять
таким образом структуру. Если мы не установим здесь значения
переменных структуры, позже нам придется сделать это вот так:
windowRect.left = 100; windowRect.top = 100; windowRect.right = 400;
windowRect.bottom = 400;
*/
// Эта функция обрабатывает все оконные сообщения.
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
// Итак, вот наша "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, // стиль окна
windowRect.left, // начальное положение x (НОВОЕ)
windowRect.top, // начальное положение y (НОВОЕ)
windowRect.right, // начальное положение x (НОВОЕ)
windowRect.bottom, // начальное положение y (НОВОЕ)
NULL, // ссылка на родительское окно
NULL, // ссылка на меню окна
hInstance, // ссылка на экземпляр программы
NULL); // для WndProc
/* "Window Class 1" - имя класса окна.
Это говорит фунции CreateWindow() использовать наш класс, определенный выше.
"My First Window" - заголовок окна.
Это имя появится в заголовке окна.
WS_OVERLAPPEDWINDOW - стиль окна.
Это значение говорит windows создать типичное окно
(с опциями resize, minimize, close, и т.д.).
windowRect.left - начальное положение x.
Значение Х левого верхнего угла окна в экранных координатах.
Чтобы установить положение окна, мы используем наш прямоугольник.
windowRect.top - начальное положение y.
Значение Y левого верхнего угла окна в экранных координатах.
Чтобы установить положение окна, мы используем наш прямоугольник.
windowRect.right - начальное положение x.
Значение Х правого нижнего угла окна в экранных координатах.
Чтобы установить положение окна, мы используем наш прямоугольник.
windowRect.bottom - начальное положение y.
Значение Y правого нижнего угла окна в экранных координатах.
Чтобы установить положение окна, мы используем наш прямоугольник.
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, что означает "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)
{
static TEXTMETRIC textInfo; // Переменная хранит размеры шрифта.
static int length=0; // Переменная хранит длину строки.
char szHello[]="Hello World!"; // Создаем наш текст.
PAINTSTRUCT paintStruct; // Необходима в WM_PAINT для BeginPaint() и EndPaint().
HBRUSH hBrush; // Содержит цвет, в который закрашивается фон.
HDC hdc=NULL; // Инициализируем hdc (Handle to a Device Context) для работы с графикой.
int textWidth = 0; // Ширина строки в пикселах.
int textHeight = 0; // Высота шрифта в пикселах.
int x, y; // Используются для позиционирования текста.
switch (iMsg) // Проверяет, какое сообщение поступило.
{
case WM_CREATE: // Это сообщение посылается, когда создается окно.
srand( time (NULL) ); // Здесь мы "смещаем" rand() с помощью таймера, чтобы
// получать случайные значения.
hdc = GetWindowDC(hwnd); // Функция возвращает ссылку на контекст устройства.
// Думайте о нем, как о ссылке на видеокарту.
GetTextMetrics(hdc, &textInfo); // Здесь мы получаем информацию о шрифте
// (ширину, высоту и т.д.)
length = strlen(szHello); // Узнаем длину "Hello World!" в символах (12).
ReleaseDC(hwnd, hdc); // Освобождаем hdc, чтобы избежать утечки памяти.
break;
case WM_SIZE: // Это сообщение посылается, когда изменяется размер окна.
GetClientRect(hwnd, &windowRect); // Получаем "Client" прямоугольник окна.
// Посмотрите краткий урок по экранным и "Client"
// координатам внизу файла.
break;
case WM_LBUTTONDOWN: // Это сообщение посылается при нажатии левой клавиши
// мыши. Когда клавиша отпускается, посылается
// сообщение WM_LBUTTONUP.
InvalidateRect(hwnd, &windowRect, TRUE); // Эта функция очищает прямоугольник окна.
// Это приводит к вызову сообщения WM_PAINT.
break;
case WM_PAINT: // Это сообщение посылается в WndProc, когда необходимо
// перерисовать окно. Это может быть в случае передвижения,
// изменения размеров окна, или перекрытия его другим окном.
hdc = BeginPaint(hwnd, &paintStruct); // Получаем HDC для рисования.
SetTextColor(hdc, RGB(rand(), rand(), rand())); // Здесь мы устанавливаем случайный цвет текста.
SetBkColor(hdc, RGB(rand(), rand(), rand())); // Эта функция устанавливает случайный фон текста.
// Нам необходимо создать кисть, чтобы заполнить прямоугольник.
// Кисть содержит нужный цвет.
hBrush = CreateSolidBrush(RGB(rand(), rand(), rand()));
// А теперь мы заполняем CLIENT прямоугольник окна выбранным цветом.
FillRect(hdc, &windowRect, hBrush);
// Мы хотим вычислить случайное положение Х, но
// при этом быть уверенными, что оно внутри окна.
// Чтобы сделать это, нам нужен размер строки в пикселах.
textWidth = textInfo.tmAveCharWidth * length; // Умножим ширину символа
// шрифта на число символов (у нас 12).
textHeight= textInfo.tmHeight; // Получаем высоту текущего шрифта.
// Мы хотим получить случайное положение Х текста, поэтому мы вычитаем размер строки
// из ширины окна, чтобы убедиться, что текст появится в окне.
x = rand() % ((windowRect.right - windowRect.left) - textWidth);
y = rand() % ((windowRect.bottom - windowRect.top) - textHeight); // Вычитаем высоту текста из высоты окна.
TextOut(hdc, x, y, szHello, length); // Печатаем "Hello World!" в случайном месте, но внутри окна.
DeleteObject(hBrush); // Освобождаем память, связанную с "hBrush"
EndPaint(hwnd, &paintStruct); // А теперь мы должны освободить память, выделенную для paintStruct.
break;
case WM_DESTROY: // Сообщение посылается, когда пользователь закрывает окно.
PostQuitMessage(0); // Вы должны вызвать эту функцию, иначе придется вручную
// закрывать программу с помощью control-alt-delete.
// 0 = WM_QUIT.
break;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam);
// Функция DefWindowProc вызывает процедуру по умолчанию,
// чтобы обеспечить обработку любых сообщений, которые
// не обрабатывает приложение. Эта функция позволяет
// быть уверенными, что каждое сообщение обрабатывается.
} // Конец WndProc
// Краткий урок по CLIENT и SCREEN координатам.
// Ok, SCREEN координаты начинаются с верхней части компьютерного экрана.
// CLIENT координаты начинаются сразу после заголовка окна.
// Когда мы создаем окно, windowRect.top и windowRect.left это SCREEN координаты.
// Когда мы вызываем GetClientRect(...), мы получаем CLIENT координаты.
// И когда мы вызываем InvalidateRect(), она использует client координаты.
// Если бы мы использовали GetWindowRect() вместо GetClientRect(), случайные цвета фона
// начинались бы с положения окна, в SCREEN координатах.
// Попробуйте, и вы поймете, что я имею в виду. Измените GetClientRect() на GetWindowRect() в WM_SIZE.
// Будет смотреться забавно. И текст будет не всегда внутри окна.
// Когда мы размещаем текст и картинки внутри окна, нам все равно, где находится окно.
// Поэтому мы используем CLIENT координаты.
// © 2000-2002 GameTutorials
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
|