Windows GDI. Рисуем графические объекты
Язык программирования: C++Платформа: Win32
По данной теме написана не одна сотня книг (многие из которых на русском языке). А так как данная статья не претендует на полноту изложения, настоятельно рекомендуем прочесть хотя бы пару из них. Не факт, что всё усвоишь, но основные моменты всё равно запомнишь.
Содержание
- Windows GDI. Рисуем графические объекты
- Windows GDI
- Типы, используемые в GDI
- Контекст устройства (device context, DC) является основным понятием при работе с GDI.
- Работа с объектами GDI
- Пример приложения, выводящее в окне текст и точки
- Источники
Windows GDI
- Применяется только при создании оконных приложений.
- Аббревиатура расшифровывается как Graphics Device Interface (Интерфейс графических устройств).
- До появления DirectX, являлась единственной библиотекой для разработки графических приложений в ОС MS Windows.
- Значительно уступает DirectX по быстродействию. В то же время, функции GDI активно используются совместно с функциями DirectX в примерах, поставляемых вместе с DirectX SDK (например те, что относятся к работке со шрифтами ).
- Является одним из трёх базовых компонентов ОС семейства MS Windows. Два других компонента (Kernel и User) отвечают за управление памятью, файловый ввод/вывод, взаимодействие с пользователем и т.д.
- Является частью Microsoft ® Windows ® APIg (англ. application programming interface - прикладной программный интерфейс).
- Предоставляет стандартные блоки, используемые прикладными программами, написанными для Windows ®, включая семейство Windows Server 2003, Windows XP, Windows 2000, Windows NT ®, Windows 95, Windows 98 и Windows Me.
- Представляет собой часть операционной системы, реализованную, как и большинство её компонентов, в виде динамически подключаемой библиотеки (файла с расширением .dll).
- Обеспечивает аппаратно-независимую графику для приложений в ОС семейства MS Windows. Через драйверы устройств графика отображается на множестве аппаратно-различных устройств (в основном это видеоадаптеры и мониторы). Таким образом, программе нет необходимости знать или учитывать особенности того или иного устройства, так как в Windows реализован так называемый HAL (Hardware Abstraction Level - Уровень аппаратной абстракции).
- В своё время GDI критиковали за медлительность, что является прямым следствием его универсальности (+ непрямой доступ к видеопамяти). Но это актуально лишь для некоторых функций GDI.
- Представляет собой одну из самых мощных и массивных составляющих Windows API. Только для работы с текстом предусмотрено более 40 функций.
- Для своей работы требует подключение к проекту заголовочного файла windows.h .
- изменение цвета пикселей;
- рисование прямых, окружностей, прямоугольников;
- вывод надписей с поддержкой различных шрифтов;
- копирование участков изображения, создание регионов, отсечений;
- отображение сплайнов Безье;
- и многое другое.
Типы, используемые в GDI
В файле windef.h (расположен в директории с установленной MSVC++ или Windows Platform SDK) определено несколько типов, активно используемых при работе с GDI. Рассмотрим несколько из них. Тип POINT определяет координаты точки на плоскости и имеет вид:typedef struct tagPOINT { LONG x; LONG y; } POINT;
Тип _RECT определяет координаты прямоугольника путём указания его левого верхнего и правого нижнего угла:
... typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; }RECT; ...
Для работы с этими двумя основными типами существует около десятка функций. Вот наиболее полезные из них:
Таблица 1. Функции для работы с типами POINT и RECT
ФУНКЦИЯ | ОПИСАНИЕ |
---|---|
BOOL PtInRect(CONST RECT *lprc, POINT pt); | Возвращает TRUE, если точка, переданная в параметре pt, лежит внутри прямоугольника, на который указывает параметр lprc. |
BOOL SetRect(LPRECT lprc, int xLeft, int yTop, int xRight, int yBottom); | Позволяет установить поля структуры прямоугольника, адресуемой указателем lprc, четыремя значениями xLeft, yTop, xRight, YBottom. Использование этой функции исзбавляет программиста от написания четырёх операторов присваивания при попытке записать новые значения в уже существующую структуру прямоугольника. |
BOOL EqualRect(CONST RECT *lprc1, CONST RECT *lprc2); | Позволяет сравнить 2 структуры прямоугольников, адреса которых передаются ей в качестве параметров. |
BOOL CopyRect(LPRECT lprcDst, CONST RECT *lprcSrc); | Позволяет копировать содержимое одной структуры прямоугольника внутрь другой структуры такого же типа. |
BOOL OffsetRect(LPRECT lprc, int dx, int dy); | Позволяет переместить прямоугольник, адрес которого указан в первом параметре, на указанное расстояние. |
BOOL InflateRect(LPRECT lprc, int dx, int dy); | Позволяет увеличить или уменьшить прямоугольник на заданные величины смещений. |
GDI также активно использует тип SIZE, который позволяет хранить высоту и ширину прямоугольника. |
typedef struct tagSIZE { LONG cx; LONG cy; } SIZE;
Контекст устройства
Контекст устройства (device context, DC) является основным понятием при работе с GDI.
- Представляет собой структуру, которую Windows поддерживает для опеределённого участка памяти. Этот участок является своего рода "окном" в реальное устройство (например монитор или принтер).
С контекстом устройства связан набор графических объектов. Каждый из них имеет свой набор характеристик, влияющих на результаты работы графических функций. К таким объектам относятся:
Таблица 2. Графические объекты для работы со структурой контекста устройства
ОБЪЕКТ | ОПИСАНИЕ |
---|---|
Перо (тип HPEN) | Влияет на внешний вид отображаемых линий и графических объектов. |
Кисть (тип HBRUSH) | Влияет на цвет и способ заливки объектов. |
Растр (тип HBITMAP) | Представляет собой текущее визуальное содержимое контекста устройства, созданного в памяти, а не полученного каким-либо другим способом. Когда выполянется рисование в контексте памяти, поверхностью для рисования служит растр, выбранный в контекст в данный момент. |
Палитра (тип HPALETTE) | Определяет набор допустимых цветов для данного контекста. Используется только с палитровыми режимами (16 и 256 цветов). |
Регион (тип HRGN) | Позволяет выполнить отсечение при выводе, копировании и других операциях. |
Путь ( тип PATH) | Позволяет записывать последовательности операций рисования. |
Шрифт (тип HFONT) | Определяет внешний вид выводимого текста. |
Все эти типы также определены в windef.h .
Работа с объектами GDI
Все графические объекты из Таблицы 2 являются объектами GDI. Любой из них в данный момент может:- быть помещён внутрь какого-то контекста (говорят "выбран в контекст"); Объекты, выбранные в данный момент в контекст, удалять нельзя. Одновременно в конкретный момент времени в контексте может находиться лишь один экземпляр каждого объекта. Например, при помещении в контекст нового пера, старое перо из него изымается (но не удаляется!).
- находиться в свободном состоянии. Такие объекты (которые не планируется использовать в будущем) необходимо удалять (с целью эффективного использования системной памяти). Удаление свободных и более неиспользуемых объектов GDI - забота программиста.
Помещение объекта в контекст
Производится с помощью функции SelectObject:HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj);
Параметры функции SelectObject:
ПАРАМЕТР | ОПИСАНИЕ |
---|---|
HDC hdc | Дескриптор контекста устройства. |
HGDIOBJ hgdiob | Дескриптор объекта, помещаемого в контекст. |
Возвращаемый результат функции - дескриптор ОСВОБОЖДАЕМОГО объекта GDI, который содержался ранее в контексте (он тоже имеет тип HGDIOBJ). Тип HGDIOBJ представляет собой обобщённый дескриптор объекта GDI. Производить приведение типов:
- Желательно при использовании функции SelectObject.
- Обязательно при определении в начале листинга макроса STRICT.
SelectObject(hdc, (HGDIOBJ)hUsedFont) ;
Здесь объект hUsedFont приводится к типу HGDIOBJ.
То же самое можно проделать, используя GDI-макрос из файла windowsx.h:
SelectFont(hdc, hUsedFont);
Здесь приведение типа уже не требуется, да и название функции говорит само за себя. Такие GDI-макросы существуют для большинства объектов GDI и применимы к действиям выбор в контекст, удаления объекта и получения предопределённого (stock) объекта. Все они также определены в windowsx.h . В файле windowsx.h можно найти соответствующий макрос:
#define SelectFont(hdc, hfont) ((HFONT)SelectObject((hdc), (HGDIOBJ)(HFONT)(hfont)))
Удаление объекта из контекста
Производится с помощью функции DeleteObject:HGDIOBJ DeleteObject(HGDIOBJ hgdiobj);
Её единственный параметр - дескриптор удаляемого объекта GDI. В случае успеха функция возвращает значение отличное от нуля.
В данном примере из контекста изымается объект hUsedFont:
DeleteObject((HGDIOBJ) hUsedFont);
Здесь объект hUsedFont приводится к типу HGDIOBJ.
То же самое можно проделать, используя GDI-макрос из файла windowsx.h:
DeleteFont(hUsedFont);
Здесь приведение типа уже не требуется, да и название функции говорит само за себя. В файле windowsx.h можно найти соответствующий макрос:
#define DeleteFont(hfont) DeleteObject((HGDIOBJ)(HFONT)(hfont))
Создание объектов GDI
Операционная система поддерживает набор предопределённых (stock) объектов GDI (перо, кисть, шрифт и др.) с характеристиками по умолчанию. Для получения дескриптора предопределённого объекта GDI применяется функция GetStockObject:HGDIOBJ GetStockObject(int fnObject);
В качестве параметра указывается один из следующих типов предопределённых объектов:
Таблица 3. Некоторые типы предопределённых объектов GDI
ТИП | ОПИСАНИЕ |
---|---|
BLACK_BRUSH | Сплошная кисть чёрного цвета. |
WHITE_BRUSH | Сплошная кисть белого цвета. |
GRAY_BRUSH | Сплошная кисть серого цвета. |
DKGRAY_BRUSH | Сплошная кисть тёмно-серого цвета. |
LTGRAY_BRUSH | Сплошная кисть светло-серого цвета. |
DC_BRUSH | Windows 2000/XP: Кисть сплошного тона. Цвет по умолчанию является белым. Цвет может измениться при помощи использования функция SetDCBrushColor. |
HOLLOW_BRUSH | Пустая кисть (эквивалент NULL_BRUSH). |
NULL_BRUSH | Пустая кисть (эквивалент HOLLOW_BRUSH). Применяется для рисования фигур, у которых отсутствует заливка. |
BLACK_PEN | Чёрное перо. |
WHITE_PEN | Белое перо. |
DC_PEN | Windows 2000/XP: Сплошной цвет пера. Цвет по умолчанию является белым. Цвет может измениться при помощи использования функции SetDCPenColor. |
DEFAULT_GUI_FONT | Шрифт, используемый по умолчанию в графическом интерфейсе пользователя. Заданный по умолчанию (типичный) шрифт для объектов пользовательского интерфейса таких как меню и диалоговые окна. Им является MS Sans Serif. |
SYSTEM_FONT | Системный шрифт, используемый для отображения меню и текста в диалоговых окнах. Windows 95/98 and Windows NT: Системный шрифт - MS Sans Serif. Windows 2000/XP: Системный шрифт - Tahoma. |
SYSTEM_FIXED_FONT | Моноширинный (фиксированной ширины) системный шрифт . Этот предопределенный (стандартный) объект предусматривается только для совместимости с 16-битовыми версиями Windows ранее чем 3.0. |
ANSI_FIXED_FONT | Моноширинный системный шрифт (фиксированной ширины) Windows. |
ANSI_VAR_FONT | Системный шрифт Windows с переменным шагом (разноширинный шрифт). |
DEVICE_DEFAULT_FONT | Windows NT/2000/XP: Аппаратно-зависимый шрифт. |
OEM_FIXED_FONT | Предусматриваемый поставщиком основного оборудования (OEM) моноширинный шрифт (фиксированной ширины). |
DEFAULT_PALETTE | Заданная по умолчанию (Типичная) палитра. Состоит из статических цветов системной палитры. |
В случае успешного завершения возвращается дескриптор затребованного логического объекта. Иначе - 0 (ноль). У предопределённых объектов есть одна особенность - их не требуется самостоятельно удалять с помощью функции DeleteObject. Впрочем, это не запрещено.
Используйте флажки DKGRAY_BRUSH, GRAY_BRUSH и объекты предопределения LTGRAY_BRUSH только в окнах со стилями CS_HREDRAW и CS_VREDRAW . Использование серой предопределенной кисти в любом другом стиле окна может привести к нарушению границ шаблонов кисти после того, как окно перемещается или изменяется по размеру. Начало координат предопределенных кистей не могут быть откорректированы. Стандартные объекты HOLLOW_BRUSH и NULL_BRUSH эквивалентны.
Windows 2000/XP: И DC_BRUSH и DC_PEN могут быть использованы взаимозаменяемо с другими предопределенными объектами подобно BLACK_BRUSH и BLACK_PEN.
Шрифт, используемый объектом предопределения DEFAULT_GUI_FONT может измениться. Используйте этот стандартный объект, когда Вы хотите использовать шрифт, который используют меню, диалоговые окна, и другие объекты пользовательского интерфейса.
В GDI предусмотрена возможность создания собственных объектов, удовлетворяющих любым требуемым характеристикам. Для создания нового объекта GDI конкретного типа предусмотрена своя, уникальная для этого типа функция (часто их даже несколько).
Рассмотрим создание объекта GDI на примере шрифта. Для этого используются функции CreateFont и CreateFontIndirect.
Прототип функции CreateFont выглядит так:
HFONT CreateFont( int nHeight, // Высота шрифта в логических единицах int nWidth, // Средняя ширина шрифта в логических единицах int nEscapement, // Угол наклона шрифта int Orientation, // Угол наклона базовой линии шрифта int fnWeight, // Плотность DWORD fdwItalic, // Является ли шрифт наклонным DWORD fdwUnderline, // Является ли шрифт подчёркнутым DWORD fdwStrikeOut, // Является ли шрифт зачёркнутым DWORD fdwCharSet, // Идентификатор набора символов DWORD fdwOutputPrecision, // Точность вывода DWORD fdwOutputClipPrecision, // Точность отсечения DWORD fdwQuality, // Качество вывода DWORD fdwPitchAndFamily, // Шаг и семейство шрифта LPCTSTR lpszFace // Название шрифта );
Несмотря на обилие параметров, эта функция проста для понимания. Большинство параметров можно устанавливать в значения по умолчанию.
- Если параметр nWidth равен нулю, то при создании шрифта Windows руководствуется лишь параметром nHeight. Возможные значения параметра fnWeight - FW_NORMAL, FW_BOLD, FW_EXTRABOLD и др.
hUsedFont=CreateFont(38, 0, 0, 0, FW_EXTRABOLD, FALSE, TRUE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, BARIABLE_PITCH, "Times New Roman");
Здесь создаётся шрифт размером 38 единиц, без наклона. жирный, подчёркнутый, использующий качество по умолчанию, с названием "Times New Roman".
Прототип функции CreateFontIndirect выглядит так:
HFONT CreateFontIndirect(CONST LOGFONT *lplf);
Здесь параметром является адрес структуры типа LOGFONT. Поля этой структуры полностью эквиваленты полям функции CreateFont:
typedef struct tagLOGFONT( LONG lfHeight; // Высота шрифта в логических единицах LONG lfWidth; // Средняя ширина шрифта в логических единицах LONG lfEscapement; // Угол наклона шрифта LONG lfOrientation; // Угол наклона базовой линии шрифта LONG lfWeight; // Плотность BYTE lfItalic; // Является ли шрифт наклонным BYTE lfUnderline; // Является ли шрифт подчёркнутым BYTE lfStrikeOut; // Является ли шрифт зачёркнутым BYTE lfCharSet; // Идентификатор набора символов BYTE lfOutPrecision; // Точность вывода BYTE lfClipPrecision; // Точность отсечения BYTE lfQuality; // Качество вывода BYTE lfPitchAndFamily, // Шаг и семейство шрифта TCHAR lfFaceName[LF_FACESIZE]; // Название шрифта ) LONGFONT;
Функцию CreateFontIndirect обычно применяют при необходимости создать несколько объектов-шрифтов, имеющих схожие характеристики и небольшие различия.
Объект-шрифт также удаляется с помощью функции DeleteObject.
Порядок действий при работе с объектами GDI
На основе материала из предыдущих подпунктов, сформулируем алгоритм работы с объектом GDI:- Получить дескриптор объекта GDI (а. Воспользоваться одним из предопределённых объектов. б. Создать новый объект при помощи соответствующей функции.)
- Выбрать объект GDI в контекст устройства при помощи функции SelectObject (либо воспользовавшись макросом из файла windowsx.h). Не забываем сохранить при этом дескриптор объекта GDI, находившегося в контексте до этого.
- Выполнить необходимые действия с контекстом устройства (обычно это рисование или перерисовка).
- Выбрать в контекст устройства дескриптор объекта GDI, сохранённый в шаге 2. При этом дескриптор объекта, созданного нами, освободится.
- Удалить созданный объект GDI. Если этот объект является предопределённым, то удалять его необязательно.
Получение контекста устройства
Для рисования в окне необходимо сначала получить дескриптор его контекста устройства. Это можно сделать с помощью функций GetDC или GetWindowDC. Вот их прототипы:HDC GetDC(HWND hWnd);
HDC GetWindowDC(HWND hWnd);
Обе этих функции получают в качестве параметра дескриптор окна и возвращают его контекст. Отличие состоит в том, что GetDC возвращает дескриптор клиентской области окна, а GetWindowDC - дескриптор всего окна целиком, включая заголовк и полосы прокрутки.
По завершении работы с контекстом устройства, его необходимо освободить при помощи функции ReleaseDC:
int ReleaseDC(HWND hWnd, HDC hDC);
Первый параметр - дескриптор окна, для которого получили контекст. Второй - дескриптор освобождаемого контекста. При успешном завершении функция возвращает 1, иначе - 0.
Пример применения:
DC hdc; char* szString="Тестовое сообщение"; hdc=GetDC(hwnd); TextOut(hdc, 10, 10, szString, strlen(szString)); ReleaseDC(hWnd, hdc);
Данный фрагмент, при включении его в исходный код оконного приложения, выводит в клиентской области окна надпись "Тестовое сообщение". Используем функцию GetDC, т.к. вывод осуществляется именно в клиентскую область окна. Освобождаем контекст функцией ReleaseDC.
Функция TextOut имеет прототип:
BOOL TextOut(HDC hdc, int x, int y, LPCSTR lpStr, int cbLen);
Параметры функции TextOut:
ПАРАМЕТР | ОПИСАНИЕ |
---|---|
HDC hdc | Дескриптор контекста устройства, в который будет осуществляться вывод. |
int x | Координата точки по оси x (отсчитывается от левого верхнего угла), где будет размещена надпись. |
int y | Координата точки по оси y (отсчитывается от левого верхнего угла), где будет размещена надпись. |
LPCSTR lpStr | Указатель на строку с текстом. Строка обязательно заканчивается комбинацией символов \0. |
int cbLen | Число символов в строке с текстом. |
При успешном завершении функция возвращает 1, иначе - 0.
Особый случай: сообщение WM_PAINT
- Указывает на необходимость перерисовки окна приложения.
- Занимает особое положение среди сообщений приложения, использующего графический интерфейс пользователя (Graphic User Interface - GUI).
- Практически не используется при создании приложений DirectX.
Как только ранее скрытый участок окна становится видимым, ОС помечает его как "недействительный участок" (invalid region) и помещает сообщение WM_PAINT в очередь сообщений приложения.
В ответ на это сообщение приложение дожно не только восстановить содержимое недействительного участка, но и объявить его действительным. Именно поэтому в ответ на сообщение WM_PAINT приложение получает контекст своим особым способом, совершенно отличным от использования функций GetDC и GetWindowDC (т.к. эти функции неспособны делать участок окна действительным или недействительным).
При обработке сообщения WM_PAINT дескриптор контекста устройства получают при помощи функции BeginPaint:
HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
Первый параметр - дескриптор окна, нуждающегося в обновлении. Второй - адрес структуры PAINTSTRUCT, из которой можно извлечь информацию, например, о размерах и положении недействительного участка. При этом перерисовывается только этот недействительный участок (в целях экономии памяти) - GDI автоматически выполнит отсечение в случае необходимости. При обработке сообщения WM_PAINT контекст устройства освобождают при помощи функции EndPaint:
HDC EndPaint(HWND hWnd, CONST PAINTSTRUCT *lpPaint);
Функция EndPaint имеет такие же параметры, что и функция BeginPaint. Пример применения:
HDC hdc; PAINTSTRUCT ps; char* szString="Тестовое сообщение"; hdc=BeginPaint(hwnd, &ps); TextOut(hdc, 10, 10, szString, strlen(szString)); EndPaint(hwnd, &ps);
В отличие от предыдущего примера, здесь присутствуют две специализированные функции BeginPaint и EndPaint + инициализирована структура PAINTSTRUCT.
Режимы отображения
Для каждого устройства его контекст в GDI поддерживает определённый режим отображения (mapping mode). Он определяет единицы измерения, используемые для преобразования логических координат в физические координаты устройства, а также направление осей координат.- Логические координаты - это те координаты, которые использует программист при вызове графических функций.
- Физические координаты используются GDIдля реального вывода изображения на устройстве.
int SetMapMode(HDC hdc, int fnMapMode);
Здесь первый параметр - дескриптор контекста устройства. Второй параметр - устанавливаемый режим отображения, где указывается одно из следующих значений:
Таблица 4. Возможные значения параметра fnMapMode функции SetMapMode
ЗНАЧЕНИЕ | ОПИСАНИЕ |
---|---|
MM_TEXT | Логические координаты совпадают с физическими координатами устройства. В качестве единиц измерения используются пиксели. Левый верхний угол экрана имеет координаты (0, 0); ось Х направлена вправо, а ось Y - вниз. Этот режим отображения принят по умолчанию. |
MM_HIMETRIC | Каждая единица логических координат соответствует 0,01 мм. Левый нижний угол имеет координаты (0, 0). Ось Х направлена вправо. Ось Y направлена вверх. |
MM_HIENGLISH | Каждая единица логических координат соответствует 0,001 дюйма. Левый нижний угол имеет координаты (0, 0). Ось Х направлена вправо. Ось Y направлена вверх. |
MM_LOENGLISH | Каждая единица логических координат соответствует 0,01 дюйма. Левый нижний угол имеет координаты (0, 0). Ось Х направлена вправо. Ось Y направлена вверх. |
MM_TWIPS | Каждая единица логических координат соответствует 1/1440 дюйма. Левый нижний угол имеет координаты (0, 0). Ось Х направлена вправо. Ось Y направлена вверх. |
MM_ANISOTROPIC | Каждая единица логических координат может соответствовать произвольному количеству точек устройства, которое независимо масштабируется по каждой из осей. Задавать поведение этого режима можно при помощи функций SetWindowExtEx и SetViewportExtEx. |
MM_ISOTROPIC | Каждая единица логических координат может соответствовать произвольному количеству точек устройства, которое одинаково масштабируется по осям. Задавать поведение этого режима можно при помощи функций SetWindowExtEx и SetViewportExtEx. |
Большинство функций GDI, в том числе и CreateFont, получают свои параметры именно в логических единицах контекста устройства.
Узнать текущий режим отображения можно с помощью функции GetMapMode:
int GetMapMode(HDC hdc);
Единственный параметр функции - дескриптор контекста устройства, а возвращаемый результат - одно из значений, приведённых в Таблице 4. В случае неудачи функция возвращает 0 (ноль).
Пример приложения, выводящее в окне текст и точки
Создаём оконное приложение.
- Следуя инструкциям статьи Создание приложений Сpp Win32, в MSVC++2010 создай пустой Проект WinDots и добавь в него файл исходного кода WinMain.cpp .
- Скопируй из этой же статьи весь исходный код базового оконного приложения Windows и вставь в WinMain.cpp:
// Файл: WinMain.cpp // Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows, // выводящее на экран окно. // www.igrocoder.ru 2015 // При использовании просьба указывать ссылку на источник. #define STRICT #define WIN32_LEAN_AND_MEAN // Уменьшаем кол-во используемых компонентов в программе. #include <windows.h> // Прототип функции WinMain int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow); // Объявление оконной процедуры LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // Реализация главной функции программы int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hWnd; // Дескриптор окна MSG msg; // Структура сообщения WNDCLASSEX wndClass; // Объявляем экземпляр класса программы на базе WNDCLASSEX // Заполняем структуру оконного класса wndClass.cbSize=sizeof(wndClass); wndClass.style=CS_CLASSDC; // Поочерёдный доступ к контексту устройства (Device Context) wndClass.lpfnWndProc=WindowProc; // Оконная процедура wndClass.cbClsExtra=0; wndClass.cbWndExtra=0; wndClass.hInstance=hInstance; // Экземпляр окна wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION); // Пиктограмма приложения wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION); // Малая пиктограмма приложения wndClass.hCursor=NULL; // Курсор при наведении на окно wndClass.hbrBackground=NULL; // Закрашиваем окно белым цветом wndClass.lpszMenuName=NULL; // Дескриптор Главного меню окна. Сейчас оно не нужно wndClass.lpszClassName="GameClass"; // Обзываем оконный класс. // Если класс не зарегистрируется, досрочно прерываем выполнение программы if(!RegisterClassEx(&wndClass)) return FALSE; // Создание окна на основе зарегистрированного класса hWnd=CreateWindow( "GameClass", // Класс окна. "My game title", // Текст заголовка (на верхнем тулбаре). WS_OVERLAPPEDWINDOW, // Обычное окно с кнопками в правом верхнем углу. 0, 0, // Координаты X и Y GetSystemMetrics(SM_CXSCREEN), // Ширина окна GetSystemMetrics(SM_CYSCREEN), // Высота окна NULL, // Дескриптор родительского окна NULL, // Дескриптор меню hInstance, // Дескриптор экземпляра приложения NULL); // Дополнительные данные if(hWnd==NULL) return(FALSE); // Собственно, показываем окно ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Очищаем структуру сообщения ZeroMemory(&msg, sizeof(MSG)); // Процедура выборки сообщений // Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение). while(msg.message != WM_QUIT) { // Просмотрим очередь: вдруг там ожидает сообщение... if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // Есть сообщение! Обработаем его как обычно... TranslateMessage(&msg); DispatchMessage(&msg); } else { // Нет сообщений, ожидающих обработки. // Значит приступим к выполнению критичных ко времени операций. // Например, займёмся рендерингом. } } //Удаляем регистрацию класса UnregisterClass("GameClass", hInstance); // Выходим из приложения return (msg.wParam); } // Оконная процедура LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_DESTROY: // В случае этого сообщения... PostQuitMessage(0); // Говорим Windows закрыть приложение break; // В случае любых других сообщений... default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; }
- Сохрани Решение (Файл->Сохранить все).
Готовим Проект WinDots к компиляции
Выбираем многобайтовую кодировку
Примечание
В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.
Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...
- Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект WinDots.
- В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта WinDots.
- Во всплывающем контекстном меню выбираем "Свойства".
- В появившемся окне установки свойств Проекта жмём Свойства конфигурации->Общие, в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
- Жмём ОК.
- Сохрани Решение (Файл->Сохранить все).
Отключаем инкрементную компоновку (incremental linking)
Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден
Ошибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt
В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.
ПРИЧИНА: MS Visual С++ 2010 "не нравится" версия .NET Framework, установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой "родной" .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от "родной" припиской вроде "Beta" или "Release Candidate".
ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe . Официальное название данного приложения - Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE "спотыкается" именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню "Программы и компоненты" (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.
Третий пункт - самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq. После этого линковка (=компоновка) проходит идеально. Подобные "костыли" в IDE от Майков - не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).
Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:
- Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.
- В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
- Во всплывающем контекстном меню выбираем "Свойства".
- В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение Нет (/INCREMENTAL:NO).
- Жмём ОК.
Компилируем Проект WinDots
Наконец, наш тестовый Проект готов к компиляции.- Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 на клавиатуре.
Скомпилированное .exe-приложение в нашем случае (Win7 x64) расположено по пути C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\WinDots\Debug .
После компиляции приложение Font01 автоматически запустится и покажет окно с белым фоном.
Обрати внимание
Если попытаться запустить скомпилированный .exe-файл на компьютере без установленной MSVC++2010, то экзешник (скорее всего) "ругнётся" на отсутствующую библиотеку MSVCR100D.dll. Буква D в её имени означает Debug, т.е. данное приложение скомпилировано с отладочной конфигурацией (профилем), которая выставляется по умолчанию у каждого вновь создаваемого Проекта/Решения. При релизе игры приложение напротив, компилируют с конфигурацией Release (релиз). При этом при создании инсталлятора в дистрибутив с игрой добавляют т.н. набор библиотек времени выполнения (в нашем слчае MS Visual C++ 2010 Runtime Redistributable). Он устанавливается вместе с игрой, т.к. без него игра тупо не стартанёт. Если для Release-профиля такой набор нетрудно нагуглить (например здесь: https://www.mydigitallife.net/visual-c-2010-runtime-redistributable-package-x86-x64-ia64-free-download, 4,8 Мб для 64-разрядной версии ОС), то для запуска Debug-версии на отдельном компе на него потребуется установить целую MSVC++2010. Всё так сложно в том числе с целью не допустить утечек предрелизных разработок с компов игрокодерских компаний. Релиз есть релиз. А с дебаг-версиями, как правило, работают только сами игрокодеры, на своих компах отлавливая ошибки.
Конфигурации Debug и Release легко переключаются на странице свойств открытого Проекта (в MSVC++2010 в главном меню выбираем Проект->Свойства, в верхней части диалога видим всплывающий список "Конфигурация").
- Закрой окно базового приложения, нажав на кнопку с крестиком в правом верхнем углу или нажав Alt+F4 на клавиатуре.
Сменяем фон на белый
Для наглядности. Наши текст и точки в окне будут чёрными. Впрочем, цвет можно выбрать любой. Но об этом чуть позже.Подключаем заголовочный файл windowsx.h
- В исходном коде WinMain.cpp найди следующую строку:
#include <windows.h>
- Строкой ниже добавь:
#include <windowsx.h>
Теперь нам доступны специальные макросы Windows GDI.
Имзменяем значение члена оконного класса hbrBackground
- В исходном коде WinMain.cpp найди следующую строку:
... wndClass.hbrBackground=NULL; // Закрашиваем окно белым цветом ...
Она стоит на этапе заполнения оконного класса.
- Замени её на строку
wndClass.hbrBackground=GetStockBrush(GRAY_BRUSH); // Закрашиваем окно серым цветом
- Сохрани Решение (Файл->Сохранить все).
- Перекомпилируй Проект/Решение (в Обозревателе решений жмём правой кнопкой мыши по названию Проекта WinDots => Перестроить).
- Закрой окно, нажав на кнопку с крестиком в правом верхнем углу или нажав Alt+F4 на клавиатуре.
Добавляем в оконную процедуру сообщение WM_PAINT и сопутствующий код
Добавляем текст в окне
- В WinMain.cpp найди следующий фрагмент кода:
... // Оконная процедура LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_DESTROY: // В случае этого сообщения... PostQuitMessage(0); // Говорим Windows закрыть приложение break; // В случае любых других сообщений... default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; } ...
Сейчас здесь указано, что наше приложение реагирует только на закрытие окна, освобождая память.
- Замени код оконной процедуры на следующий:
// Оконная процедура LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; // Дескриптор контекста устройства char *szHello = "Hello World!"; RECT rt; switch(msg) { case WM_PAINT: // Получаем размеры окна. GetClientRect(hWnd, &rt); // Начинаем рисовать в контексте устройства (device context). hdc = BeginPaint(hWnd, &ps); // Пишем текст в окне. DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER); // Завершаем рисование в контексте устройства (device context). EndPaint(hWnd, &ps); break; case WM_DESTROY: // В случае этого сообщения... PostQuitMessage(0); // Говорим Windows закрыть приложение break; // В случае любых других сообщений... default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; }
Видим, что добавился новый оператор case, обрабатывающий событие WM_PAINT.
- Сохрани Решение (Файл->Сохранить все).
- Перекомпилируй Проект/Решение (в Обозревателе решений жмём правой кнопкой мыши по названию Проекта WinDots => Перестроить).
Исследуем заменённый код.
Перед началом рисования средствами GDI прямо внутри оконной процедуры добавлено несколько объявлений, необходимых для функций рисования:... LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; // Дескриптор контекста устройства char *szHello = "Hello World!"; RECT rt; ...
Сам текст надписи вынесен в отдельную переменную szHello.1 Это обычная практика, заметно упрощающая работу с изменяемыми данными.
Сама функция вывода текста DrawText обрамлена двумя служебными функциями BeginPaint и EndPaint:
... hdc = BeginPaint(hWnd, &ps); // Пишем текст в окне. DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER); // Завершаем рисование в контексте устройства (device context). EndPaint(hWnd, &ps); ...
Это обязательно при рисовании средствами GDI. Функция BeginPaint "захватывает" ( = монополизирует) выбранный контекст устройства перед размещением в нём графических объектов.
Служебное слово break не даёт перейти к обработке следующего сообщения (WM_DESTROY). Если его убрать, то программа выведет текст в окне и моментально завершит свою работу.
Рисуем 1000 рандомных (=произвольных) пикселей
Работаем со всё той же оконной процедурой. В начале WinMain.cpp потребуется подключить заголовок stdlib.h. Но обо всём по порядку.- В самом начале реализации оконной процедуры, в объявлении различных переменных, добавь следующие строки, чуть пониже остальных (после строки RECT rt;):
int x, y, n; COLORREF c;
- Между функциями DrawText и EndPaint добавь следующий код:
for (n=0; n<1000; n++) { x = rand() % (rt.right - rt.left); y = rand() % (rt.bottom - rt.top); c = RGB(rand() %256, rand()%256, rand()%256); SetPixel(hdc, x, y, c); }
После всех изменений оконная процедура будет выглядеть так:
... // Оконная процедура LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; // Дескриптор контекста устройства char *szHello = "Hello World!"; RECT rt; int x, y, n; COLORREF c; switch(msg) { case WM_PAINT: // Получаем размеры окна. GetClientRect(hWnd, &rt); // Начинаем рисовать в контексте устройства (device context). hdc = BeginPaint(hWnd, &ps); // Пишем текст в окне. DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER); // Рисуем 1000 произвольных пикселей. for (n=0; n<1000; n++) { x = rand() % (rt.right - rt.left); y = rand() % (rt.bottom - rt.top); c = RGB(rand() %256, rand()%256, rand()%256); SetPixel(hdc, x, y, c); } // Завершаем рисование в контексте устройства (device context). EndPaint(hWnd, &ps); break; case WM_DESTROY: // В случае этого сообщения... PostQuitMessage(0); // Говорим Windows закрыть приложение break; // В случае любых других сообщений... default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; } ...
- В самом начале WinMain.cpp, где директивой #include подключаются необходимые заголовки, пониже всех (после строки #include <windowsx.h>) добавь строку:
#include <stdlib.h>
Он нужен для работы функции rand, случайным образом генерирующую точки в клиентской области окна приложения.
- Сохрани Решение (Файл->Сохранить все).
- Перекомпилируй Проект/Решение (в Обозревателе решений жмём правой кнопкой мыши по названию Проекта WinDots => Перестроить).
- "Поиграй" с исходным кодом, выставив в условии цикла for, работающего с точками, не 1000, а 3000 или любое другое число.
- После каждого изменения отправляй Проект WinDots на перекомпиляцию и затем запускай итоговое приложение, чтобы увидеть резуьтат.
- Мышью измени размеры окна запущенного приложения. Наблюдай, как при этом перерисовываются все точки.
Источники
1. Harbour J.S. Beginning Game Programming. - Thomson Course Technology, 2005
Последние изменения страницы Суббота 18 / Июнь, 2022 19:23:24 MSK
Последние комментарии wiki