Шаг 12.
Библиотека OWL.
Оконная графика. GDI-графика

    На этом шаге мы рассмотрим различные способы рисования.

    Класс TDC библиотеки OWL включает множество Windows-функций, реализующих GDI. Помимо вывода текста, с помощью этих функций можно рисовать различные фигуры, создавать и выбирать GDI-объекты (такие, как перья, кисти и шрифты) и многое другое.

    Контекст устройства - это структура данных, в которой содержатся различные графические параметры. По умолчанию эти параметры принимают стандартные значения, чтобы можно было сразу же после создания использовать контекст устройства. Например, после создания DC можно нарисовать прямоугольник в области пользователя стандартным цветом, кистью, используя стандартный тип линии.

    Однако придерживаться стандартных параметров вовсе не обязательно. С помощью функций-членов класса TDC вы можете четко определить тот контекст устройства, который вам нужен для конкретных целей. В следующем примере показано, как использовать класс TDC.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dc.h>

// Максимальное количество выводимых точек.
#define MAXPOINTS 100

//Класс приложения
class TApp: public TApplication
{
  public:
	 TApp():TApplication(){}
	 void InitMainWindow();
};

//Класс основного окна
class TWndw: public TFrameWindow
{
  protected:
	 TPoint *points[MAXPOINTS];
	 int count, penWidth, penColor;
  public:
	 TWndw(TWindow *parent, const char far *title);
	 ~TWndw();
  protected:
	 void EvLButtonDown(UINT,TPoint &point);
	 BOOL CanClose();
	 void Paint(TDC&, BOOL, TRect&);
	 DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
	EV_WM_LBUTTONDOWN,
END_RESPONSE_TABLE;

TWndw::TWndw(TWindow *parent, const char far *title):
		  TFrameWindow(parent,title)
{
	// Определить расположение и размеры окна.
	Attr.X=100;
	Attr.Y=100;
	Attr.W=400;
	Attr.H=300;
	// Инициализировать переменные.
	count = penWidth = penColor = 0;
}

// TWndw::~TWndw()
//
// Это деструктор главного окна.
TWndw::~TWndw()
{
  // Удалить все объекты TPoint.
  for (int x=0; x<count; ++x)
	 delete points[x];
}

// TWndw::Paint()
//
// Эта функция, переопределяющая функцию Paint()
// класса TWindow, отвечает на сообщения WM_PAINT,
// посылаемые Windows окну при необходимости
// его перерисовки.
void TWndw::Paint(TDC &paintDC, BOOL, TRect&)
{
  // Задать начальную ширину и цвет пера.
  int pw = 0;
  int pc = 0;

  // Перерисовать прямоугольник в каждой сохраненной координате.
  for (int x=0; x<count; ++x)
  {
	 // Вычислить ширину и цвет следующего пера.
	 if (++pw > 16) pw = 1;
	 if (++pc > 16) pc = 1;
	 // Создать новые перо и кисть.
	 TPen *pen = new TPen(pc, pw, PS_SOLID);
	 TBrush *brush = new TBrush(TColor::Black);
	 // Поместить новые перо и кисть в контекст устройства.
	 paintDC.SelectObject(*pen);
	 paintDC.SelectObject(*brush);
	 // Вычислить остальные точки прямоугольника.
	 TPoint point2 (points[x]->x + 20, points[x]->y + 20);
	 // Нарисовать прямоугольник в точке, указанной пользователем.
	 paintDC.Rectangle(*points[x], point2);
	 // Удалить новые перо и кисть.
	 delete pen;
	 delete brush;
  }
}

void TWndw::EvLButtonDown(UINT,TPoint &point)
{
  // Разрешить не более чем MAXPOINTS нажатий.
  if (count < MAXPOINTS)
  {
	 // Получить контекст устройства для
	 // области пользователя окна.
	 TClientDC DC(HWindow);
	 // Вычислить ширину и цвет нового пера.
	 if (++penWidth > 16) penWidth = 1;
	 if (++penColor > 16) penColor = 1;
	 // Создать новые перо и кисть.
	 TPen pen(penColor, penWidth, PS_SOLID);
	 TBrush brush(TColor::Black);
	 // Поместить новые перо и кисть в контекст устройства.
	 DC.SelectObject(pen);
	 DC.SelectObject(brush);
	 // Вычислить остальные точки прямоугольника.
	 TPoint point2(point.x + 20, point.y + 20);
	 // Нарисовать прямоугольник в точке, указанной пользователем.
	 DC.Rectangle(point,point2);
	 // Запомнить новую точку и увеличить значение счетчика.
	 points[count++] = new TPoint(point);
  }
}

BOOL TWndw::CanClose()
{
  int result=MessageBox("Вы действительно хотите закрыть окно?","Закрытие",
					 MB_YESNO | MB_ICONQUESTION);
  if (result==IDYES) return TRUE;
  else return FALSE;
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw=new TWndw(0,"Пример GDI-окна с использованием графики");
  SetMainWindow(wndw);
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}
Текст этой программы можно взять здесь.

    Запустите эту программу и нажмите в окне несколько раз левую кнопку мыши. При этом каждый раз будет появляться цветной квадратик, как показано на рисунке 1.


Рис.1. Результат работы приложения

    В соответствии с содержимым объекта TClientDC используются различная толщина и цвета линий. Поэтому каждое нажатие порождает отличный от других квадратик.

    Эта программа очень похожа на предыдущую, которая выводит на экран слово "Щелчок!", однако вместо слов здесь выводятся квадратики, демонстрируя работу с контекстом устройства с помощью OWL. По-прежнему класс главного окна обрабатывает сообщения WM_LBUTTONDOWN и отслеживает массив точек. Однако теперь этот класс также включает члены-данные penWidth и penColor, в которых содержатся соответственно ширина и цвет пера.

    Функция EvLButtoriDown() также имеет существенные отличия. Как и в предыдущей программе, сначала функция проверяет значение счетчика count, чтобы избежать хранения чрезмерно большого числа точек. Если в массиве points[] есть свободное место, функция создает для окна контекст устройства, но теперь это делается статически, а не динамически:

    TClientDC  DC(HWindow);

    Создавая объект DC статически, вы избавляетесь от необходимости удалять его самому. DC уничтожается при выходе из области видимости при выходе из функции по окончании ее работы. Получив DC, функция вычисляет ширину и цвет нового пера, увеличивая значения penWidth и penColor:

    if   (++penWidth >  16)   penWidth =  1; 
    if   (++penColor  >  16)   penColor  =  1;

    Эти переменные могут принимать значения лишь в пределах от 1 до 16. Затем функция использует новые значения ширины и цвета для создания нового OWL-объекта пера для DC.

    TPen pen(penColor, penWidth, PS_SOLID);

    OWL-класс TPen поддерживает три варианта конструктора для 16-битовых приложений:

    TPen(HPEN handle, TAutoDelete autoDelete = NoAutoDelete); 
    TPen(TColor color, int width=l,   int   style=PS_SOLID); 
    TPen(const LOGPEN  far *logPen);

    Эти конструкторы позволяют создавать TPen на основе существующего пера; по задаваемым цвету, ширине и стилю; на основе структуры LOGPEN. Обратите внимание, что второй конструктор, который, кстати, используется в приведенном выше примере, устанавливает значения по умолчанию для ширины и стиля пера, позволяя тем самым указывать для нового пера лишь параметр цвета:

    ТРеn pen(penColor);


    Замечания.
  1. Хотя этот конструктор требует в качестве первого параметра объект типа TColor, можно задавать в качестве аргумента целое и затем его преобразовывать. Именно это и сделано в приведенном примере с целочисленным полем penColor.

  2. В Windows объекты такого рода, как перо, кисть, шрифт и др., представляют собой сложные структуры данных, состоящие из множества атрибутов. Кисть, например, имеет стиль, цвет и штриховой узор, тогда как перо имеет стиль, ширину и цвет. Шрифты настолько сложны, что для полного задания требуют 14 различных атрибутов. Для простоты организации атрибутов объекта Windows определяет различного рода структуры для хранения значений этих атрибутов, включая LOGBRUSH, LOGOPEN и LOGFONT. Поскольку в процессе программирования эти структуры встречаются довольно часто, GDI-объекты OWL имеют конструкторы, получающие соответствующие структуры в качестве аргумента лри создании нового GDI-объекта. Эти объекты также часто содержат функции-члены для доступа к таким структурам.

    Класс TPen также содержит функцию-член GetObject(), которая возвращает в структуре LOGPEN информацию о пере, а также функцию-член GetStock(), осуществляющую доступ к наборам перьев Windows.

    Вернемся к функции EvLButtonDown(). Эта функция создает также новый OWL-объект кисть:

    TBrush brush(TColor::Black);

    Приведенная выше строка создает черную кисть, используя в качестве аргумента, задающего цвет, один из статистических цветовых членов- данных (представляющих 10 цветов - от белого до черного) класса TColor. Существуют шесть перегружаемых конструкторов OWL-класса TBrush:

    TBrush (HBRUSH handle, TAutoDelete autoDelete=NoAutoDelete); 
    TBrush(TColor  color); 
    TBrush(TColor color, int style); 
    TBrush(const TBitmap  &pattern); 
    TBrush(const Tdib  &pattern); 
    TBrush(const  LOGBRUSH  far   *logBrush);

    Эти конструкторы позволяют создать объект TBrush из существующей кисти; из цвета и стиля; из растрового изображения; из аппаратно-независимого растрового изображения; из структуры LOGBRUSH. OWL-класс TBrush содержит такие функции-члены, как GetObject(), которая возвращает через структуру LOGBRUSH информацию о кисти, и GetStock(), которая поддерживает доступ к наборам кистей Windows.

    После того, как перо и кисть созданы, для работы с ними необходимо занести их в DC:

    DC.SelectObject(pen); 
    DC.SelectObject(brush);

    Одно из удобств при использовании OWL-версии функции GDI заключается в автоматическом удалении новых объектов, занесенных в DC. Кроме того, старые автоматически восстанавливаются.

    После занесения пера и кисти в DC остается только вычислить координаты нижнего правого угла квадрата, нарисовать его и запомнить новую точку. Координаты нижнего правого угла получаются прибавлением 20 к координатам исходной точки. Затем функция использует новые координаты для создания нового объекта TPoint:

    TPoint  point2(point.x  +  20,   point.у  +   20);

    Вызов функции Rectangle() объекта DC рисует в окне квадрат с координатами, взятыми из объектов типа TPoint:

    DC.Rectangle(point, point2);

    Windows использует новое перо для прорисовки границы прямоугольника и новую кисть для заливки его внутренней области. И наконец, как и в предыдущей версии программы, функция запоминает новую точку в массиве points[].

    Как только программа получает сообщение WM_PAINT, начинает работать функция Paint() главного окна:

void TWndw::Paint(TDC &paintDC, BOOL, TRect&)
{
  // Задать начальную ширину и цвет пера.
  int pw = 0;
  int pc = 0;

  // Перерисовать прямоугольник в каждой сохраненной координате.
  for (int x=0; x<count; ++x)
  {
	 // Вычислить ширину и цвет следующего пера.
	 if (++pw > 16) pw = 1;
	 if (++pc > 16) pc = 1;
	 // Создать новые перо и кисть.
	 TPen *pen = new TPen(pc, pw, PS_SOLID);
	 TBrush *brush = new TBrush(TColor::Black);
	 // Поместить новые перо и кисть в контекст устройства.
	 paintDC.SelectObject(*pen);
	 paintDC.SelectObject(*brush);
	 // Вычислить остальные точки прямоугольника.
	 TPoint point2 (points[x]->x + 20, points[x]->y + 20);
	 // Нарисовать прямоугольник в точке, указанной пользователем.
	 paintDC.Rectangle(*points[x], point2);
	 // Удалить новые перо и кисть.
	 delete pen;
	 delete brush;
  }
}

    Эта функция работает почти так же, как EvLButtonDown(), только вместо занесения новых точек в массив points[] в ней организован цикл до элементам существующего массива и восстанавливаются нарисованные ранее прямоугольники. Как и EvLButtonDown(), при каждой итерации цикла функция Paint() должна создать новые перо и кисть, используя новые значения переменных рс и pw. Однако, в отличие от EvLButtonDown(), функция Paint() создает эти объекты динамически и удаляет их в конце каждой итерации.


    Замечание. Windows предоставляет набор готовых объектов. Эти готовые объекты, которые представлены константами BLACK_BRUSH, DKGRAY_BRUSH, GRAY_BRUSH, LTGRAY_BRUSH, LTGRAY_BRUSH, NULL_BRUSH, WHITE_BRUSH, NULL_PEN, WHITE_PEN, полностью готовы для использования в вашем приложении. Вам нет необходимости создавать эти объекты, вам также не надо пытаться их удалять.

    В таблице 1 приведены некоторые функции класса TDC.

Таблица 1. Некоторые функции класса TDC
Функция Описание
bool Ellipse (const TPoint& point1, const TPoint& point2); Чертит окружность (эллипс). Point1 - координаты левого верхнего угла, point2 - координаты правого нижнего угла прямоугольника, в который вписывается эллипс.
bool Rectangle (const TPoint& point1, const TPoint& point2); Чертит прямоугольник (квадрат). Point1 - координаты левого верхнего угла, point2 - координаты правого нижнего угла прямоугольника.
bool Arc(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); Чертит дугу эллипса в охватывающем прямоугольнике (x1,y1)-(x2,y2). Начало дуги лежит на пересечении эллипса и луча, проведенного из его центра в точку (x3,y3), а конец - на пересечении с лучом из центра в точку (x4,y4). Дуга чертится против часовой стрелки (см. рисунок 2, а).
bool Chord(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); Чертит сегмент эллипса в охватывающем прямоугольнике (x1,y1)-(x2,y2). Начало дуги сегмента лежит на пересечении эллипса и луча, проведенного из его центра в точку (x3,y3), а конец - на пересечении с лучом из центра в точку (x4,y4). Дуга сегмента чертится против часовой стрелки, а начальная и конечная точки дуги соединяются прямой (см. рисунок 2, б).
bool ExtFloodFill(const TPoint& point, TColor color, uint16 fillType); Эта функция используется для заливки области заданным цветом. point - точка, в которой должна начаться заливка, color - цвет границы или заливаемой области, fillType - флаг, указывающий, как должен интерпретироваться второй аргумент. Возможные значения fillType: FLOODFILLSURFACE - производится заливка всех окружающих пикселов с заданным цветом, FLOODFILLBORDER - производится заливка всех пикселов любого цвета до тех пор, пока не будет достигнута граница, имеющая заданный цвет.
bool FloodFill(const TPoint& point, TColor color); Производится заливка всех пикселей любого цвета, начиная с точки point до тех пор, пока не будет достигнута граница, имеющая заданный цвет color.
TColor GetPixel(int x, int y) const; Возвращает объект TColor, содержащий цвет точки (x,y).
bool LineTo(int x, int y); Чертит линию от текущего положения пера до точки (x,y).
bool MoveTo(int x, int y); Перемещает перо в положение (x,y) без вычерчивания линий.
bool Pie(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); Рисует сектор эллипса в охватывающем прямоугольнике (x1,y1) - (x2,y2). Начало дуги лежит на пересечении эллипса и луча, проведенного из его центра в точку (x3,y3), а конец - на пересечении с лучом из центра в точку (x4,y4). Дуга чертится против часовой стрелки. Начало и конец дуги соединяются прямыми с ее центром (см. рисунок 2, в).
bool RoundRect(int x1, int y1, int x2, int y2, int x3, int y3); Вычерчивает прямоугольник (x1,y1) - (x2,y2) со скругленными углами. Прямоугольник (x1,y1) - (x3,y3) определяет дугу эллипса для скругления углов (см. рисунок 2, г).
TColor SetPixel(int x, int y, TColor color); Устанавливает пиксель в точке (x,y) цветом color.


Рис.2. Параметры обращения: а) к функции Arc; б) к функции Chort; в) к функции Pie; г) к функции RoundRect

    Со следующего шага мы начнем знакомиться с организацией меню.




Предыдущий шаг Содержание Следующий шаг