Создавая приложения Presentation Manager, вы не сможете использовать для рисования в его окнах приемы, знакомые вам из опыта программирования для операционной системы MS-DOS . Приложения Presentation Manager не могут вызывать прерывания BIOS или такие стандратные функции вывода, как printf, putc или puts. Однако, как вы увидите дальше, в вашем распоряжении имеются куда более мощные средства.
Для рисования в среде Presentation Manager имеется набор функций графического интерфейса GPI (Graphics Programming Interface ), в котором есть все необходимые средства для отображения текста, графических изображений, составленных из графических примитивов, таких как линия или окружность, а также растровых графических изображений. Функции этого интерфейса мы подробно рассмотрим позже в одном из следующих томов "Библиотеки системного программиста". В этой главе мы ограничимся только кратким знакомством с принципами рисования в окнах приложений Presentation Manager.
Следует заметить, что интерфейс GPI позволяет программистам создавать аппаратно-независимые приложения, которые будут работать, например, с любым видеоадаптером и любым принтером, если в составе операционной системы IBM OS/2 для них есть драйверы. Аппаратная независимость сильно упрощает программирование, исключая необходимость учитывать особенности различных моделей "почти стандартных" устройств компьютера.
Одной из особенностей приложений Presentation Manager является централизованный способ рисования содержимого окна. Как правило, приложение рисует в своем главном окне только в процессе обработки сообщения с кодом WM_PAINT (хотя можно рисовать и в любое другое время).
Сообщение WM_PAINT передается системой Presentation Manager функции окна в тот момент, когда необходимо обновить содержимое окна. Таким образом, приложение Presentation Manager должно быть готово выполнить операцию обновления окна в любой момент, как только это потребуется. Для этого приложение должно хранить в памяти текущее состояние окна, пользуясь которым обработчик сообщения WM_PAINT сможет выполнить операцию полной или частичной перерисовки содержимого окна Client Window .
Прежде чем приступать к рисованию, приложение должно получить в свое распоряжение пространство отображения , которое называется Presentation Space (PS ).
Пространство отображения можно представить себе как холст, на котором будет выполняться рисование. Однако в отличие от обычного холста, которым пользуются художники, в комплекте с пространством отображения приложение получает набор инструментов для рисования, таких как шрифт для рисования текста, набор цветов, "карандаш" для рисования линий определенной толщины, систему координат и т. д.
При необходимости приложение может изменять свойства пространства отображения, выбирая, например, другой шрифт или другой цвет для рисования.
Одним из важных атрибутов пространства отображения является размер области отображения. Например, если приложение получило пространство отображения для окна Client Window , оно может рисовать только во внутренней области этого онка. Если же получено пространство отображения для окна Frame Window , область рисования будет шире.
Подробным изучением атрибутов пространства отображения мы займемся позже, а пока расскажем о двух способах получения идентификатора пространства отображения.
Первый способ используется только при обработке сообщения WM_PAINT . Он заключается в вызыве функции WinBeginPaint :
HPS hps; RECTL rec; . . . case WM_PAINT : { hps = WinBeginPaint (hWnd, NULLHANDLE, &rec); . . . // Вызов функций рисования . . . WinEndPaint (hps); return 0; }
Функция WinBeginPaint возвращает идентификатор пространства отображения, который в дальнейшем будет использоваться всеми функциями рисования.
Заметим, что перед тем как вернуть управление, обработчик сообщения WM_PAINT должен освободить полученное пространство отображения при помощи фукнции WinEndPaint .
Прототип функции WinBeginPaint приведен ниже:
HPS WinBeginPaint ( HWND hwnd, // идентификатор окна HPS hps, // идентификатор пространства отображения PRECTL prclPaint); // область ограничения
Через параметр hwnd функции WinBeginPaint передается идентификатор окна, для которого необходимо получить пространство отображения. Вы можете указать идентификатор любого окна, созданного вашим приложением, или значение HWND_DESKTOP . В последнем случае будет получено пространство отображения, пригодное для рисования во всей области рабочего стола Workplace Shell.
Для получения так называемого кэшированного пространства отображения (которое мы будем использовать в приложениях, исходные тексты которых приведены в этой книге) для параметра hps необходимо указать значение NULLHANDLE. Подробнее о типах пространств отображения вы узнаете позже.
Что же касается параметра prclPaint, то вы можете указать его как NULL либо передать через этот параметр указатель на структуру типа RECTL, описывающую координаты прямоугольной области.
Если этот параметр указан как NULL, от функции WinBeginPaint будет получено пространство отображения, пригодное для рисования в любом месте окна, заданного параметром hwnd. В противном случае в структуру типа RECTL записываются размеры области, которая будет перерисована.
Структура RECTL определена следующим образом:
typedef struct _RECTL { LONG xLeft; // координата X левой границы // прямоугольной области LONG yBottom; // координата Y нижней границы области LONG xRight; // координата X правой границы области LONG yTop; // координата Y верхней границы области } RECTL; typedef RECTL *PRECTL;
В случае успеха функция WinBeginPaint возвращает идентификатор полученного пространства отображения. При ошибке возвращается значение NULLHANDLE.
Рассмотрим теперь второй способ получения пространства отображения, который должен использоваться во всех случаях, кроме обработки сообщения WM_PAINT . Этот способ основан на использовании функции WinGetPS :
HPS hps; . . . case WM_BUTTON1DOWN : { hps = WinGetPS (hWnd); . . . // Вызов функций рисования . . . WinReleasePS (hps); return 0; }
Функция WinGetPS возвращает идентификатор пространства отображения для окна, идентификатор которого передается ей в качестве единственного параметра.
Отметим, что после завершения процедур рисования вы должны освободить пространство отображения, полученное от функции WinGetPS , при помощи функции WinReleasePS (но не в коем случае не при помощи функции WinEndPaint , которая используется только в паре с функцией WinBeginPaint ).
В нашем первом приложении, рисующем текст, мы будем использовать функцию GpiCharString At . Префикс имени Gpi означает, что эта функция относится к функциям графического интерфейса GPI.
Прототип функции GpiCharString At , выполняющей рисование текстовой строки, начиная с заданной позиции, приведен ниже:
LONG GpiCharString At ( HPS hps, // иднетификатор пространства отображения PPOINTL pptlPoint, // начальная позиция LONG lCount, // количество символов в строке PCH pchString); // адрес текстовой строки
Параметр hps определяет пространство отображения, в котором будет нарисована текстовая строка. Размер этой строки в байтах задается параметром lCount, а адрес - параметром pchString.
Начальная позиция, в которой будет отображена строка, должна быть записана перед вызовом функции в структуру типа POINTL , содержащей поля x и y типа LONG. Адрес этой структуры передается через параметр pptlPoint.
Ниже приведен фрагмент кода, отображающий текстовую строку Hello, PM!. Нижний левый угол воображаемого прямоугольника, в пределах которого будет нарисована строка, расположен в точке с координатами (10, 10):
POINTL ptl; ptl.x = 10L; ptl.y = 10L; GpiCharString At (hps, &ptl, 10, "Hello, PM!");
В программах MS-DOS для вывода текста вы использовали окно, состоящее из 25 строк по 80 символов в каждой. Начало соответствующей системы координат располагалось в верхнем левом углу экрана. Ось X была направлена слева направо, а ось Y - сверху вниз.
Для рисования графических изображений программа MS-DOS переключала видеоадаптер в один из графических режимов, например, в режим VGA с разрешением 640 х 480 пикселов. Если нужно было выводить текст в графическом режиме, для каждого видеорежима приходилось использовать отдельный набор шрифтов.
Presentation Manager предоставляет в ваше распряжение так называемую логическую систему координат, параметры которой при необходимости можно изменять.
По умолчанию в пространстве отображения используется прямоугольная система координат с началом, расположенным в левом нижнем углу окна. Ось X направлена слева направо, ось Y - снизу вверх.
Что же касается размеров окна, то они могут изменяться пользователем. При этом функция окна получает сообщение WM_SIZE , параметры которого отражают старые и новые размеры окна. Это сообщение передается и при создании окна.
Новые размеры окна Client Window , которые мы будем обозначать cxClient и cyClient, передаются через малдшее и старшее слово парметра mp2, соответственно. Вы можете определить размеры окна следующим образом:
case WM_SIZE : { cxClient = SHORT1FROMMP (mp2); cyClient = SHORT2FROMMP (mp2); return 0; }
Другой способ определения размеров окна основан на вызове функции WinQueryWindow Rect, прототип которой приведен ниже:
BOOL WinQueryWindow Rect( HWND hwnd, // идентификатор окна PRECTL prclDest); // указатель на структуру RECTL
Эта функция запишет размеры окна, идентификатор которого передается через параметр hwnd, в структуру типа RECTL с адресом prclDest. При успешном завершении функция возвращает значение TRUE, при ошибке (например, если был задан неправильный идентификатор окна) - значение FALSE.
Ваше приложение не может ориентироваться на какие-либо конкретные размеры окна, так как пользователю удобно изменять эти размеры по своему усмотрению.
В отдельной главе одной из следующих книг "Библиотеки системного программиста", посвященной графическому интерфейсу, мы расскажем о том, как выбрать в пространство отображения другую систему координат, изменив, например, расположение начала координат, направление осей или масштаб по координатным осям.
Для иллюстрации всего сказанного выше в этой главе мы приведем исходные тексты приложения TEXTOUT. В своем главном окне это приложение отображает строку Hello, PM!. Кроме того, если делать щелчки левой клавишей мыши в окне приложения, то в месте расположения курсора мыши будут отображаться его координаты (рис. 4.1).
Рис. 4.1. Главное окно приложения TEXTOUT
Исходные тексты приложения представлены в листинге 4.1.
Листинг 4.1. Файл textout\textout.c
// ================================================= // Определения // ================================================= #define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include <string.h> #include "textout.h" // Прототип функции окна приложения MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM); // ================================================= // Глобальные переменные // ================================================= HAB hab; HWND hWndFrame; HWND hWndClient; CHAR szAppTitle[] = "TextOut Application"; // ================================================= // Главная функция приложения main // ================================================= int main () { HMQ hmq; QMSG qmsg; BOOL fRc; // Флаги для создания окна Frame Window ULONG flFrameFlags = FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON; // Имя класса главного окна CHAR szWndClass[] = "TEXTOUT"; hab = WinInitialize (0); if(hab == NULLHANDLE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка инициализации", "Ошибка", 0, MB_ICONHAND | MB_OK); return(-1); } // Создаем очередь сообщений hmq = WinCreateMsgQueue (hab, 0); if(hmq == NULLHANDLE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при создании очереди сообщений", "Ошибка", 0, MB_ICONHAND | MB_OK); WinTerminate (hab); return(-1); } // Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(-1); } // Создаем главное окно приложения hWndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE , &flFrameFlags, szWndClass, szAppTitle, 0, 0, ID_APP_FRAMEWND, &hWndClient); if(hWndFrame == NULLHANDLE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при создании главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(-1); } while(WinGetMsg (hab, &qmsg, 0, 0, 0)) WinDispatchMsg (hab, &qmsg); WinDestroyWindow(hWndFrame); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); } // ================================================= // Функция главного окна приложения // ================================================= MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { CHAR szMsg[100]; int nMsgSize; HPS hps; POINTL ptl; RECTL rec; switch (msg) { // В ответ на это сообщение рисуем в // окне Client Window case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec); // Закрашиваем область, требующую обновления WinFillRect (hps, &rec, CLR_WHITE); // Рисуем текстовую строку, начиная с точки, // имеющей координаты (10, 10) ptl.x = 10L; ptl.y = 10L; GpiCharString At (hps, &ptl, 10, "Hello, PM!"); // Освобождаем пространство отображения WinEndPaint (hps); return 0; } // Позволяем окну Frame Window стереть содержимое // окна Client Window case WM_ERASEBACKGROUND : return(MRFROMLONG(1L)); // При изменении размеров окна приложения // перерисовываем его содержимое case WM_SIZE : { WinInvalidateRect (hWnd, NULL, TRUE); return 0; } // Если пользователь сделал щелчок левой клавишей // мыши в окне приложения, в месте расположения // курсора мыши отображаем координаты этого курсора case WM_BUTTON1DOWN : { // Получаем пространство отображения hps = WinGetPS (hWnd); // Определяем координаты курсора мыши и // записываем их в виде текстовой строки // во временный буфер sprintf (szMsg, "(x, y) = (%ld, %ld)", SHORT1FROMMP (mp1), SHORT2FROMMP (mp1)); // Определяем координаты точки для вывода текста ptl.x = SHORT1FROMMP (mp1); ptl.y = SHORT2FROMMP (mp1); // Определяем размер текстовой строки nMsgSize = strlen(szMsg); // Рисуем текстовую строку GpiCharString At (hps, &ptl, nMsgSize, szMsg); // Освобождаем пространство отображения WinReleasePS (hps); return 0; } default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }
Наше приложение рисует текстовую строку во время обработки сообщения WM_PAINT , а также во время обработки сообщения WM_BUTTON1DOWN . Кроме того, функция окна обрабатывает сообщение WM_SIZE , поступающее в функцию окна при изменении его размеров.
Обработчик сообщения WM_PAINT получает пространство отображения с помощью функции WinBeginPaint , освобождая его после рисования при помощи функции WinEndPaint .
В качестве последнего параметра функции WinBeginPaint передается адрес структуры rec. Функция запишет в нее координаты прямоугольной области, в пределах которой будет выполняться рисование.
Сразу после получения пространства отображения обработчик сообщения WM_PAINT закрашивает область белым цветом, вызывая для этого функцию WinFillRect :
WinFillRect (hps, &rec, CLR_WHITE );
После этого в окне Client Window рисуется строка текста, для чего вызывается функция GpiCharString At .
Сообщения WM_SIZE поступает в функцию окна, когда пользователь изменяет его размеры, например, при помощи мыши. Параметры этого сообщения содержат старые и новые размеры окна, однако в нашем приложении они не используются. В ответ на сообщение WM_SIZE прилоежние TEXTOUT вызывает функцию WinInvalidateRect , как это показано ниже:
WinInvalidateRect (hWnd, NULL, TRUE);
Функция WinInvalidateRect объявляет, что область внутри окна hWnd требует перерисовки, для чего в очередь приложения помещается сообщение WM_PAINT . При получении пространства отображения функцией WinBeginPaint в структуру rec будет записаны границы этой области.
Прототип функции WinInvalidateRect приведен ниже:
BOOL WinInvalidateRect ( HWND hwnd, // идентификатор обновляемого окна PRECTL pwrc, // обновляемая область BOOL fIncludeChildren); // дополнительный признак
Через параметр hwnd передается идентификатор окна, область внутри которого необходимо обновить.
Границы обновляемой области задаются параметром pwrc. Если вместо указателя на структуру типа RECTL указано значение NULL, обновляется все окно.
Если значение параметра fIncludeChildren равно TRUE, дополнительно обновляются также все дочерние окна, функциям которых также будет передано сообщение WM_PAINT . Если же значение этого параметра равно FALSE, дочерние окна не будут перерисованы, если родительское окно имеет стиль WS_CLIPCHILDREN .
В случае успеха функция WinInvalidateRect возвращает значение TRUE, при ошибке - FALSE.
В нашем приложении обработчик сообщения WM_SIZE инициирует перерисовку всего окна Client Window , поэтому при любом изменении размеров главного окна приложения функция окна получит сообщение WM_PAINT . Обработчик этого сообщения вначале сотрет текущее содержимое окна, а затем нарисует текстовую строку Hello, PM! в точке с заданными координатами.
Сообщение WM_ERASEBACKGROUND посылается окном Frame Window перед тем, как оно будет рисовать окно Client Window . Если обработчик этого сообщения вернет значение TRUE, содержимое окна Client Window будет стерто (точнее говоря, закрашено системным цветом, выбранным для закрашивания внутренней области окна). Если же обработчик сообщения WM_ERASEBACKGROUND вернет значение FALSE, предполагается, что этот обработчик выполнит закрашивание внутренней области окна Frame Window самостоятельно. Именно такая обработка сообщения WM_ERASEBACKGROUND выполняется по умолчанию функцией WinDefWindowProc .
Заметим, что вместе с этим сообщением в первом параметре mp1 передается идентификатор пространства отображения для окна Frame Window . Этот идентификатор не следует использовать для закраски окна Client Window , так как сообщение WM_ERASEBACKGROUND передается только в том случае, если необходимо перерисовать окно Frame Window.
Может возникнуть такая ситуация, когда содержимое окна Client Window оказалось испорченным, например, в результате размещения там другого окна, а окно Frame Window не изменилось. В этом случае сообщение WM_ERASEBACKGROUND не будет записано в очередь приложения. Поэтому для закрашивания окна Client Window мы использовали обработчик сообщения WM_PAINT .
Когда пользователь делает щелчок левой клавишей мыши в области окна Client Window , функции окна передается сообщение WM_BUTTON1DOWN . В ответ на это сообщение она получает пространство отображения и рисует в месте расположения курсора мыши текстовую строку с координатами курсора.
Так как пространство отображения получается не во время обработки сообщения WM_PAINT , вместо функции WinBeginPaint используется функция WinGetPS . После выполнения рисования пространство отображения освобождается функцией WinReleasePS .
В файле textout.h (листинг 4.2) определена константа ID_APP_FRAMEWND, которая используется при создании главного окна приложения.
Листинг 4.2. Файл textout\textout.h
#define ID_APP_FRAMEWND 1
В файле описания ресурсов textout.rc (листинг 4.3) определена только пиктограмма приложения.
Листинг 4.3. Файл textout\textout.rc
#include <os2.h> #include "textout.h" ICON ID_APP_FRAMEWND TEXTOUT.ICO
Файл определения модуля textout.def приведен в листинге 4.4.
Листинг 4.4. Файл textout\textout.def
NAME TEXTOUT WINDOWAPI DESCRIPTION 'TextOut Application (C) Frolov A., 1996' HEAPSIZE 4096 STACKSIZE 32768 EXPORTS WndProc
Кроме изученной нами функции GpiCharString At приложения Presentation Manager могут использовать для рисования текста еще несколько функций.
Функция GpiCharString отображает строку текста с текущей позиции и имеет следующий прототип:
LONG GpiCharString ( HPS hps, // идентификатор пространства отображения LONG lCount, // размер текстовой строки PCH pchString); // адрес текстовой строки
Текущая позиция для вывода текста устанавливается функцией GpiMove . После рисования строки текста текущая позиция устанавливается в конце этой строки, так что с помощью последующих вызовов функции GpiCharString вы можете дописать строку.
Функция GpiMove имеет два параметра:
BOOL GpiMove ( HPS hps, // идентификатор пространства отображения PPOINTL pptlPoint); // новая позиция
Ниже мы привели пример совместного использования фукнций GpiMove и GpiCharString :
HPS hps; POINTL ptl; ptl.x = 10L; ptl.y = 10L; GpiMove (hps, &ptlStart); GpiCharString (hps, 5L, "Hello"); GpiCharString (hps, 8L, ", world!");
Нетрудно заметить, что рассмотренная нами ранее функция GpiCharString At является комбинацией функций GpiMove и GpiCharString.
Для рисования текста вы также можете использовать функции GpiCharString Pos, GpiCharStringPosAt и WinDrawText . Эти функции предназначены для форматного вывода текста и допускают выбор индивидуальной позиции для каждого отображаемого символа и использование таких элементов оформления текста, как подчеркивание и перечеркивание.
Расскажем подробнее о функции WinDrawText , которая предназначена для вывод текста в прямоугольную область окна и имеет некоторые возможности форматирования. Прототип этой функции приведен ниже:
LONG WinDrawText ( HPS hps, // идентификатор пространства отображения LONG cchText, // длина текстовой строки в байтах PCH lpchText, // адрес отображаемой текстовой строки PRECTL prcl, // прямоугольная область LONG clrFore, // цвет текста LONG clrBack, // цвет фона ULONG flCmd); // флаги режима отображения текста
Параметр hps задает идентификатор пространства отображения, который необходимо получить перед использованием этой фукнции.
При помощи параметра cchText необходимо задать длину отображаемой текстовой строки, адрес которой передается через параметр lpchText. Если эта строка закрыта двоичным нулем, вместо реальной длины строки можно указать значение -1, что очень удобно. Кроме двоичного нуля, строка может быть закрыта символом возврата каретки или перевода строки.
Перед вызовом функции WinDrawText необходимо подготовить структуру типа RECTL, записав в нее координаты прямоугольной области, внутри которой будет отображаться текст. Адрес этой структуры следует передать через параметр prcl.
Если текст не помещается по длине окна, он будет обрезан.
После возвращения из функции WinDrawText содержимое указанной выше структуры изменится. В ней будет записана высота прямоугольной области, занятая текстовой строкой (если в параметре flCmd указан флаг DT_QUERYEXTENT ).
Параметры clrFore и clrBack используются только в том случае, если в параметре flCmd указан флаг DT_TEXTATTRS . С помощью этих параметров вы можете задать, соответственно, цвет текста и цвет фона, которые будут использованы при выводе.
И, наконец, с помощью параметра flCmd вы можете указывать различные флаги, объединяя их при помощи логической операции ИЛИ. Эти флаги, определяющие режим работы функции WinDrawText , перечислены ниже.
Флаг |
Описание |
DT_LEFT |
Выравнивание строки по левой границе
заданной прямоугольной области |
DT_RIGHT |
Аналогично, но по правой границе |
DT_TOP |
Аналогично, но по верхней границе |
DT_BOTTOM |
Аналогично, но по нижней границе |
DT_CENTER |
Центрирование строки по горизонтали |
DT_VCENTER |
Центрирование строки по вертикали |
DT_HALFTONE |
Отображение текста с исользованием
полутонов |
DT_MNEMONIC |
Отображение мнемонических символов |
DT_QUERYEXTENT |
После возвращения из функции WinDrawText
содержимое структуры, адрес которой передается
через параметр prcl, изменяется таким образом,
чтобы отражать реальную высоту нарисованной
строки символов |
DT_WORDBREAK |
Если строка символов, состоящая из
отдельных слов, не помещается в окне, она будет
обрезана таким образом, что в окне будут
отображаться только полные слова текста |
DT_EXTERNALLEADING |
Используется вместе с флагами DT_TOP и
DT_QUERYEXTENT. Если указан этот флаг, возвращаемая в
структуре prcl высота символов будет увеличена на
размер выступающей части символов |
DT_TEXTATTRS |
При отображении текста необходимо
использовать цвета символов и фона, указанные в
параметрах функции clrFore и clrBack |
DT_ERASERECT |
Перед отображением текста содержимое
прямоугольной области prcl будет стерто |
DT_UNDERSCORE |
Символы текста выделяются
подчеркиванием |
DT_STRIKEOUT |
Символы текста выделяются
перечеркиванием |
После ввыполнения рисования функция возвращает количество нарисованных символов. При ошибке возвращается нулевое значение.
Флаги DT_LEFT, DT_CENTER и DT_RIGHT нельзя использовать вместе, точно также, как и флаги DT_TOP, DT_VCENTER и DT_BOTTOM.
Ниже мы привели пример использования функции WinDrawText для отображения текста в центре окна, размеры которого определяются при помощи функции WinQueryWindow Rect:
WinQueryWindow Rect(hWnd, &rec); WinDrawText (hps, -1, pszText, &rec, 0L, 0L, DT_WORDBREAK | DT_CENTER | DT_VCENTER | DT_TEXTATTRS | DT_ERASERECT);