Программирование на Visual C++. Архив рассылки - Алекс Jenter
Шрифт:
Интервал:
Закладка:
Дальнейшее наследование (например, класс TextureBrush порожден от Brush) скорее отражает цели разработчиков (скрытие деталей реализации и повторное использование оберточного кода), чем инфраструктуру библиотеки, так как в inline-методах "родственных" классов просто содержатся вызовы различных функций GdiPlus.dll. Можно сказать, что Microsoft в очередной раз спроецировала обычный "плоский" API языка C на объектно-ориентированную библиотеку C++.
Оставшаяся часть классов не имеет общего родителя и предназначена для упрощения работы со структурами данных GDI+.
Инициализация и завершениеПеред тем как начать использовать классы и функции GDI+, необходимо инициализировать эту библиотеку. Для этого где-нибудь в начале своей программы нужно поместить вызов функции GdiplusStartup:
Status GdiplusStartup(ULONG_PTR* token, const GdiplusStartupInput* input, GdiplusStartupOutput* output);
Поля структуры GdiplusStartupInput управляют различными аспектами инициализации: в частности, можно задать функцию, которая будет вызываться при возникновении ошибок, или перехватывать все обращения к функциям GDI+. Эти детали мы рассматривать не будем. К счастью, конструктор по умолчанию структуры GdiplusStartupInput выполняет инициализацию, достаточную в большинстве случаев. При этом в качестве выходного параметра output можно задать NULL.
"Магическое значение", на которое указывает выходной параметр token, необходимо сохранить.
Для завершения работы с библиотекой вызовите функцию GdiplusShutdown:
VOID GdiplusShutdown(ULONG_PTR token);
Здесь в качестве параметра и необходимо передать то самое число, которое возвратила GdiplusStartup в параметре token.
ПРИМЕЧАНИЕ
Вы можете вызвать GdiplusStartup и GdiplusShutdown из разных потоков, но необходимо убедиться, что вне этой пары функций никакого обращения к объектам GDI+ не происходит. В частности, будьте осторожны, объявляя глобальными экземпляры классов – ведь их деструкторы выполнятся уже после WinMain. Кроме того, как обычно, нельзя вызывать функции инициализации и очистки из DllMain, поскольку это может привести ко входу в бесконечную рекурсию или другим неприятностям.
Создаем первое приложениеНастало время применить все эти сведения на практике. Для этого создадим в MS Visual C++ базовое WINAPI-приложение, которое послужит полигоном для дальнейших экспериментов. Ниже для этого приведена пошаговая процедура.
Итак, создаем новый проект Win32 Application. Выбираем опцию A typical "Hello, World!" application и нажимаем "finish". Получившееся приложение необходимо подготовить для использования GDI+. Для этого в файле stdafx.h после строки с комментарием:
// TODO: reference additional headers your program requires here
добавляем следующие строчки:
#include <GdiPlus.h>
using namespace Gdiplus;
и в конце файла stdafx.cpp добавляем строку
#pragma comment(lib, "GdiPlus.lib")
Кроме того, в файле stdafx.h необходимо удалить или закомментировать строку
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
Иначе компилятор выдаст кучу ошибок об отсутствии символов MIDL_INTERFACE, PROPID, IStream и т.д.
Если полученное в результате приложение успешно собралось, значит, мы все сделали правильно. Пойдем дальше.
Найдем в сгенерированном основном .cpp файле нашего проекта функцию WinMain и добавим в начале ее код инициализации:
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
а в конце, перед оператором return, добавим код очистки:
GdiplusShutdown(gdiplusToken);
Готово. Наконец-то мы можем что-нибудь нарисовать. Найдите в теле функции WndProc обработчик сообщения WM_PAINT и замените следующим кодом:
hdc = BeginPaint(hWnd, &ps);
OnPaint(hdc, ps.rcPaint);
EndPaint(hWnd, &ps);
return 0;
Теперь где-нибудь перед функцией WndProc создадим функцию OnPaint с кодом рисования:
void OnPaint(HDC hdc, const RECT& rc) {
// Все строки – в кодировке Unicode
WCHAR welcome[]=L"Welcome, GDI+ !";
// Создаем контекст рисования и устанавливаем
// пиксельную систему координат
Graphics g(hdc);
g.SetPageUnit(UnitPixel);
RectF bounds(0, 0, float(rc.right), float(rc.bottom));
// Загружаем фоновое изображение и растягиваем его на все окно Image
bg(L"BACKGRND.gif");
g.DrawImage(&bg, bounds);
// Создаем кисть с градиентом на все окно и полупрозрачностью
LinearGradientBrush brush(bounds, Color(130, 255, 0, 0), Color(255, 0, 0, 255), LinearGradientModeBackwardDiagonal);
// Готовим формат и параметры шрифта
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
format.SetLineAlignment(StringAlignmentCenter);
Font font(L"Arial", 48, FontStyleBold);
// Выводим текст приветствия, длина –1 означает,
// что строка заканчивается нулем
g.DrawString(welcome, –1, &font, bounds, &format, &brush);
}
В результате у нас получится примерно вот что:
ПРИМЕЧАНИЕ
Приведенный пример носит только ознакомительный характер. В реальном приложении, для того чтобы нарисовать растр, его, как правило, не нужно каждый раз загружать с дискового файла :). Далее я буду пользоваться созданным макетом программы для создания других демонстрационных приложений. В качестве примера рисования будет приводиться только код функции OnPaint.
Пример WinForms – приложения с использованием GDI+Для того чтобы можно было сравнить рассматриваемую реализацию GDI+ с той, что используется в .NET, приведу полный текст соответствующего приложения на новом языке C#:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class GraphicsForm: Form {
public static int Main() {
Form fm = new GraphicsForm();
fm.ShowDialog();
return 0;
}
protected override void OnPaint(PaintEventArgs a) {
DoPaint(a.Graphics, a.ClipRectangle);
}
protected void DoPaint(Graphics g, Rectangle clipBox) {
RectangleF bounds = clipBox;
string welcome = "Welcome, GDI+ !";
Bitmap bg = new Bitmap("BACKGRND.gif");
g.DrawImage(bg, bounds);
LinearGradientBrush brush =
new LinearGradientBrush(bounds, Color.FromArgb(130, 255, 0, 0), Color.FromArgb(255, 0, 0, 255), LinearGradientMode.BackwardDiagonal);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
Font font = new Font("Arial", 48, FontStyle.Bold);
g.DrawString(welcome, font, brush, bounds, format);
}
}
Как видим, помимо чисто синтаксических отличий имеются и принципиальные, например, использование в CLR-модели свойств против использования Set-методов в C++. Кроме того, в .NET активно используются пространства имен.
ПРИМЕЧАНИЕ
Замечу, что здесь приведен полный текст программы, аналогичной по возможностям той, что мы создали в предыдущем разделе. Сравните объем исходных текстов этих двух примеров. NO COMMENTS.
Если вы запустите приведенный пример, то увидите, что текст отрисовывается без сглаживания, характерного для предыдущего примера. Это связано с тем, что WinForms по умолчанию отключает улучшенный режим отрисовки шрифтов – и без этого причин для торможения достаточно :)
Несколько замечаний о компиляции и сборке проектовХочется указать на несколько "подводных камней", которые могут сбить с толку при первой попытке откомпилировать и собрать проект, использующий GDI+. В основном здесь упомянуты те проблемы, с которыми сталкиваются (и постоянно спрашивают о них в различных форумах) начинающие.
Где взять GdiPlus.h?Как я уже сказал, все заголовочные файлы, библиотека импорта и документация к библиотеке входят в состав последнего Platform SDK. Они не идут в составе Visual C++ 6.0 и его сервис паков.
Почему выдается ошибка о типе ULONG_PTR?Похоже, что компилятор находит старый заголовочный файл basetsd.h – например, из комплекта VC++. Измените пути поиска заголовочных файлов так, чтобы вначале были найдены файлы Platform SDK.
Почему компилятор не дает создать объект GDI+ при помощи new?Такое поведение возможно при попытке откомпилировать MFC-приложение с использованием GDI+ в Debug-конфигурации.
В начале файла программы, видимо, имеется следующий фрагмент:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
Либо откажитесь от создания объектов GDI+ с помощью new, либо откажитесь от проверок динамической памяти в этом файле (удалив вышеприведенную директиву #define).
Не забудьте про пространство имен Gdiplus и библиотеку импортаВ приводимых примерах кода используются простые имена классов, такие как Brush и Rect. Это стало возможным благодаря тому, что в начале заголовочного файла программы есть директива