Windows окно для рисования

Рисование в Windows без мерцания (Flicker-Free Drawing in Windows) v1.2

Данный документ представляет собой перевод статьи Джеймса Брауна «Flicker-Free Drawing in Windows»

Введение

Мелькание при рисовании в окне — следствие неряшливого программирования и невнимания к деталям. Не существует весомой причины, из-за которой хоть одна Windows программа должна выводить изображение с мерцанием. Цель этой статьи — ознакомить читателя с методами, позволяющими избежать мерцания при выводе графики в своих приложениях.

Что такое мерцание?

Мерцание это просто-напросто, когда поверх одного видимого на экране изображения сразу же рисуется другое. Т.е. вы видите на экране какое-то изображение очень короткое время, после чего оно заменяется другим. Когда я сталкиваюсь с приложением, которое выводит графику с мерцанием, причиной которого всегда является лишь плохое кодирование пользовательского интерфейса, я задаю себе вопрос: «Если в приложении так плохо реализована работа с графикой, то, как хорошо оно тогда решает задачу, для которой оно предназначено?»

Мерцания возникают при разных обстоятельствах. В основном оно происходит при изменении размеров окна, т.к. при этом окно перерисовывает свое содержимое.

Рисуйте объект только один раз

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

Ваша обязанность обеспечить отсутствие перерисовки одних и тех же областей экрана более одного раза. Windows и ваш компьютер в основе своей туп, они не будут ничего делать, пока вы не дадите им точные инструкции к действию. Таким образом, если вы заметили мерцание, то это потому что ваша программа перерисовывает некоторую область несколько раз.

WM_ERASEBKGND

Первым под подозрение обычно попадает сообщение WM_ERASEBKGND. Это сообщение посылается окну, когда оно нуждается в очистке своего фона (background). Мерцание происходит потому, что окна, обычно, рисуются в две стадии:

  • WM_ERASEBKGND: Очистка фона окна
  • WM_PAINT: Рисование содержимого поверх фона

Это облегчает рисование содержимого окна: так как каждый раз когда вы получаете сообщение WM_PAINT вы знаете что вам предоставлено абсолютно чистое полотно (канва), и вам остается только нарисовать содержимое (текст, картинка и т.п.). Однако, из-за рисования в окне дважды (сначала в WM_ERASEBKGND, а потом еще раз в WM_PAINT) возникает очень сильное мерцание. Для примера, просто посмотрите на стандартный элемент Edit в Windows — запустите notepad.exe, наберите какой-нибудь текст и измените размер окна. Вы увидите, как при этом будет мерцать содержимое окна.

Так как же избежать очистки канвы окна? Есть два способа:

  1. Выбрать в качестве кисти фона окна — пустую кисть (NULL / 0). (присвоить полю hbrBackground структуры WNDCLASS ноль при регистрации класса окна.
  2. Возвращать ненулевое значение в обработчике сообщения WM_ERASEBKGND.

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

Также можно запретить посылку сообщения WM_ERASEBKGND, когда вы обновляете окно или объявляете его недействительным. Функция InvalidateRect последним параметром запрашивает должно ли окно очистить канву при следующей перерисовке. Указав в качестве этого параметра FALSE вы запретите посылку сообщения WM_ERASEBKGND перед перерисовкой окна.

Рисуйте что-либо только при необходимости

Перерисовка всего содержимого окна, даже если в нем изменилась лишь малая часть (например, при изменении размеров окна) — обычное дело для многих Windows приложений. Однако обычно в такой перерисовке нет необходимости. Когда размеры окна изменяются, предыдущее изображение сохраняется и лишь небольшая часть окна требует перерисовки (при увеличении размеров). Таким образом, алгоритм рисования можно реализовать так, чтобы он рисовал лишь необходимый минимум.

С каждым окном в системе связана область обновления (update region). Она описывает область окна, которая стала невалидной и требует перерисовки. Если окно будет перерисовывать только эту область и ничего более, то значительно увеличится скорость прорисовки окна.

Существует несколько способов получения информации об области обновления окна. Функция GetUpdateRgn возвращает точную информацию об области обновления, будь то прямоугольник, или более сложная фигура. В отличие от нее, функция GetUpdateRect возвращает информацию не о самой области обновления, а о наименьшем прямоугольнике, который целиком содержит в себе область обновления. Естественно, что работать с прямоугольной областью гораздо легче. Третий же способ заключается в использовании структуры PAINTSTRUCT совместно с функциями BeginPaint/EndPaint.

Обычно, процедура рисования выглядит так:

Функция BeginPaint заполняет структуру ps (PAINTSTRUCT). Одно из полей структуры — rcPaint является структурой типа RECT, которое описывает наименьший прямоугольник, содержащий в себе область обновления (т.е. тот же самый, что вернула бы нам функция GetUpdateRect). Одним лишь ограничением рисования только в этом прямоугольнике, мы значительно увеличим скорость прорисовки окна.

Следует помнить, что Windows автоматически обрезает любое рисование вне области обновления, когда вы используете функции BeginPaint/EndPaint. Это значит, что вы не сможете что-нибудь нарисовать вне области обновления. Вы можете подумать, в чем же тогда смысл ограничения рисования лишь в области обновления, если все равно ничего лишнего нарисовано не будет. Однако не забывайте, что вы вызываете функции и вычисления — в холостую, которые занимают какое-то время, а результата от них — ноль.

Еще одна причина мерцания

Возможна ситуация, когда вы потратите кучу времени на оптимизацию процесса рисования окна, однако выяснится, что окно все равно перерисовывается полностью. Обычно это происходит из-за двух стилей класса окна: CS_VREDRAW и СS_HREDRAW. Когда класс окна имеет установленным, хотя бы из этих стилей, содержимое окна будет полностью перерисовываться при изменении его размеров по вертикали или по горизонтали соответственно.

Одним из решений проблемы является отключение этих двух стилей.

Замечание: если главное окно приложения имеет эти стили, то независимо от того имеют ли их дочерние окна, они будут полностью перерисовываться во время изменения размеров главного окна.

Далее рассматривается другое решение этой проблемы (более правильное, на мой взгляд — зам. переводчика).

Clipping child windows

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

Когда окну установлен этот стиль, все области, занимаемые дочерними окнами, автоматически вырезаются из области обновления. А как говорилось ранее, при использовании , рисование вне области обновления не дает никакого результата, поэтому дочерние окна не затираются, и мерцания не происходит.

Читайте также:  Файл подкачки windows 10 4гб озу

Двойная буферизация (double-buffing) и memory-DC’s

Основной метод борьбы с мерцанием — так называемая двойная буферизация. Идея метода заключается в том, что сначала вы рисуете окно в не экрана, в памяти, а затем, выводите полностью готовое изображение окна на экран, используя, например, . Это очень эффективное оружие против мерцания, но часто, им злоупотребляют, особенно программисты, которые не знают, как эффективно пользоваться графическими средствами Windows.

Простейший способ двойной буферизации выглядит примерно так:

Этот пример довольно медленный, потому что внеэкранный буфер создается / уничтожается каждый раз при обработке сообщения . Для увеличения производительности, память под буфер можно выделять при обработке сообщения , а удалять его при уничтожении окна. Потенциально, оба этих метода могут быть очень требовательны к памяти. Например, если окно будет развернуто на весь экран, то, при разрешении экрана 1024x768x32, для буфера потребуется 2,5 Мб памяти.

Кроме того, метод двойной буферизации медленней, чем обычное рисование сразу на экран. Т.к. сначала мы рисует в памяти, а затем еще раз рисуем созданный образ, но, уже на экране. Конечно, современные видио-карты сделают это быстро, однако факт остается фактом — прямое рисование на экране было бы быстрей.

Если вашему приложению требуется отображать очень сложную информацию / графику (например, веб-страничку), тогда вы должны использовать двойную буферизацию. Так, например, поступает Internet Explorer. Не существует другого способа вывести содержимое веб-странички или другой сложной графики на экран без мерцания.

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

Часто, после небольшого и аккуратного продумывания процесса рисования, можно вообще не использовать двойную буферизацию и рисовать прямо на экран, при этом, не нарушая золотого правила «никогда не рисуй в одном пикселе дважды».

Устранение мерцания при преднамеренной перерисовке окна

Что я под этим подразумеваю? Допустим, вы хотите изменить вид заголовка вашего окна, добавив в него дополнительную графику: картинку, кнопку и т.п. Для этого, вы будете рисовать свое изображение поверх уже нарисованного заголовка — возникает мерцание. Это происходит из-за того, что нарушено золотое правило.

Существует две техники устранения такого типа мерцания. Первая заключается в использовании clipping, а вторая — в использовании смекалки.

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

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

А теперь, немного подумаем. Нарисовать сетку можно и другим способом. Вместо рисования одноцветного фона, можно рисовать серию квадратиков разделенных между собой линией.

А если подумать еще, то можно вспомнить, что мы может создать и использовать свою кисть для закраски фона окна (WNDCLASSEX.hbrBackground). Кисть должна выглядеть следующим образом:

Т.е. кисть должна быть прямоугольником, левая и верхняя (можно и другие) стороны которого — цвет сетки, а остальная часть — цвет фона окна. При этом нам достаточно будет выбрать такую кисть в качестве кисти для фона, и сетка будет рисоваться сама.

Заключение

Надеюсь, теперь вы никогда не зададите вопрос: «Почему мое окно мерцает?». Я описал основные причины мерцания, а также методы избавления от него. И если вы в будущем столкнетесь с ним, вы сможете установить причину и окончательно избавиться от него.

КАК рисовать в Win32 API?

Обществом преподавателей информатики замечено, что очень многие, при изучении нового языка программирования, прежде всего интересуются его графическими возможностями. Видимо, ещё с детства в нас не остыл интерес к красивым разноцветным кружочкам и овалам. Как вы и думали, API даёт в этом плане огромнейшую свободу, ибо всё, что знает Windows о рисовании, она знает от API.
Разноцветные геометрические фигуры, которые можно заливать любым цветом, эллипсы, окружности, прямоугольники, линии. Во введении я уже говорил про кисти и перья. Эта важнейшие особенности API дают нам возможность заливать фигуры не только сплошным покровом, а линии делать пунктирными и штрих-пунктирными.
С текстом мы вроде как разобрались, теперь переходим к нашим любимым графическим примитивам. Так как в этой главе мы будем изучать почти все графические функции, предлагаю сделать заготовочку, в которую можно примерять новые изученные функции.

КАК и где вставлять графические функции?

Сам текст программы ничем не отличается от предыдущих. Рассмотрим лишь сообщение WM_PAINT. Я покажу куда рисовать.

case WM_PAINT :
hdc=BeginPaint(hWnd, &ps);
//здесь можно вставить какие-нибудь функции рисования
.

//обновляем окно
ValidateRect(hWnd, NULL);
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break;

Графические функции GDI:
1. Вывод точки. SetPixel устанавливает заданный цвет в точке с указанными координатами:
COLORREF SetPixel(HDC hDC, int x, int y, COLORREF crColor);

Пример:
SetPixel(hDC, 10,10, RGB(0,0,0));
Функция GetPixel соответственно возвращает цвет в заданных координатах.
COLORREF Getpixel(hDC, int x, int y);

2. Рисование линий.
BOOL LineTo(hDC, int x, int y);
Функция рисует линию от текущей позиции до места, указанного в аргументах. Чтобы изменить тип линии (толщину, стиль)- меняется тип пера. Но об этом позже.

Так как в отличие от многих других подходов, в GDI нет функции рисования линии от одного указанного места до другого, её можно создать самому. Она будет соединять линией точки с координатами: x1,y1 и x2,y2.

BOOL Line(HDC hdc, int x1, int y1, int x2, int y2)
<
MoveToEx(hdc, x1, y1, NULL); //сделать текущими координаты x1, y1
return LineTo(hdc, x2, y2);
>

3. Дуга
BOOL Arc(hDC, int left, int top, int right, int bottom, int x1, int y1, int x2, int y2);
Первые четыре аргумента — левый верхний и правый нижний углы прямоугольника, в который вписан эллипс. Остальные значения — координаты точек, от которых будут проведены прямые к центру эллипса. В местах пересечения первой и второй прямой с радиусом эллипса, начинается и кончается дуга.

4. Прямоугольник. По умолчанию прозрачный, а вообще, тип его заливки определяется текущей кистью. По умолчанию она тоже прозрачная.
BOOL Rectangle(hDC, int left, int top, int right, int bottom); //аргументы — это коордианты левого верхнего и правого нижнего углов

5. Закруглённый прямоугольник. Его можно использовать, как импровизированную кнопку, если не лень возиться.
BOOL RoundRect(hDC, int left, int top, int right, int bottom, int width, int height);
Первые пять параметров совпадают с параметрами предыдущей фукнции. Далее width и height задают ширину и высоту эллипса, дуги которого ограничивают прямоугольник.

Читайте также:  The following files are missing or corrupt windows system32 config system

6. Кисти. Самое время познакомиться с кистями, так как фигуры, которые пойдут дальше выглядят лучше закрашенными. Мы уже немного затронули эту тему во вводной части. Теперь рассмотрим как задать свой стиль кисти. Как и setfillstyle() в DOS, кисть закрашивает какую-то область в какой-то цвет. В зависимости от кисти, она может делать это в полосочку, в клеточку, по диагонали.
Есть два способа объявить кисть. Первый — задать сплошную заливку, второй — указать стиль. Для этого существуют соответственно функции: CreateSoldBrush() и CreateHatchBrush().

Пример:
HBRUSH hBrush; //создаём объект-кисть
CreateSolidBrush(RGB(255,0,67)); //задаём сплошную кисть, закрашенную цветом RGB
SelectObject(hdc, hBrush); //делаем кисть активной

А вот как объявить не сплошную кисть:
CreateHatchBrush(int fnStyle, RGB(r,g,b));

Аргумент fnStyle принимает ряд константных значений:
HS_DIAGONAL — штрихует по диагонали
HS_CROSS — клеточка
HS_DIAGCROSS — диагональная сетка
HS_FDIAGONAL — по диагонали в другую сторону
HS_HORIZONTAL — горизонтальная «тельняшка»
HS_VERTICAL — вертикальный «забор»

HBRUSH hBrush1;
CreateHatchBrush(int fnStyle, RGB(r,g,b));
SelectObject(hdc, hBrush1); //делаем кисть активной

Вот махонький пример сообщения WM_PAINT с использованием кистей.

//сообщение рисования
case WM_PAINT :
//начинаем рисовать
hdc=BeginPaint(hWnd, &ps);
HBRUSH hBrush;
hBrush=CreateHatchBrush(HS_FDIAGONAL, RGB(255,0,0));
SelectObject(hdc,hBrush);

Ellipse(hdc, 100,100,200,300); //эллипс будет заштрихован

//заканчиваем рисовать
EndPaint(hWnd, &ps);
break;

7. Перья.
Они задают стиль линий, как и setlinestyle в DOS. Линия может быть жирной и тонкой, прерывистой и штрих-пунктирной. Всё предусмотрено. Это очень удобно для создания графиков функций, когда на график накладывается сетка, рисуются оси и выводится сама функция. Вы можете сказать, что это касается только математиков! Но почти любая фирма, что бы она не производила, иногда проводит презентации. На графике можно показать рост внешнего капитала, объём продаж и многое другое.

HPEN hPen; //Объявляется кисть
CreatePen(fnPenStyle, int width, RGB(r,g,b)); //Создаётся объект
SelectObject(hdc, hPen); //Объект делается текущим

fnStyle может принимать следующие значения:
PS_SOLD — сплошная
PS_DASH — состоящая из точек
PS_DOT — состоящая из тире
PS_DASHDOT — «точка-тире»
PS_DASHDOTDOT — «тире-точка-точка-тире»
PS_NULL — невидимая
PS_INSIDEFRAME — обводка замкнутых фигур

И пример будет такой:
//сообщение рисования
case WM_PAINT :
//начинаем рисовать
hdc=BeginPaint(hWnd, &ps);

HPEN hPen1, hPen2, hPen3; //объявляем сразу три объекта-пера
hPen1=CreatePen(PS_DASHDOT, 1, RGB(0,0,255)); //создаём всё три
hPen2=CreatePen(PS_DASH, 1, RGB(255,0,255));
hPen3=CreatePen(PS_DOT, 1, RGB(0,128,256));

SelectObject(hdc, hPen1); //но в одним момент времени может быть только 1
Rectangle(hdc, 10,10,100,100); //рисуем фигуру соответствующим пером

SelectObject(hdc, hPen2); //меняем перо
Ellipse(hdc, 100,100,200,300); //рисуем другим пером

SelectObject(hdc, hPen3);
LineTo(hdc, 200,100);

ValidateRect(hWnd, NULL);
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break;

Важно понять, что можно создавать хоть 10 перьев с помощью CreatePen, но применить в данный момент времени можно только 1 из них. Для этого и нужен SelectObject, чтобы окно поняло какую кисть в настоящий момент мы достаём из этюдника GDI.

6. Закрашенный прямоугольник
int FillRect(HDC hDC, CONST RECT *lprc, HBRUSH hbr);
lprc — закрашиваемый прямоугольник типа RECT.
hbr — кисть

Вот пример-фрагмент WM_PAINT:

RECT r; //объявляем экзмепляр структуры RECT — координаты прямоугольника.
r.left=100; //левый верхний угол
r.top=100;
r.right=200; //правый нижний
r.right=300;

А вот и первый пример программы подоспел! Нарисуем что-то очень красивое — то, что мы уже умеем.

В этом примере мы освежим методы работы со шрифтами, поменяв стандартный системный шрифт на свой, затем нарисуем прямоугольник, заполненный красным цветом, зелёный эллипс и прямоугольник с закруглёнными краями жёлтого цвета. Естественно, что для того, чтобы их заполнить нам понадобятся кисти, а для синей рамки на жёлтом прямоугольнике понадобятся перья. Всё, что есть в этой программе вы уже умеете. Мы просто закрепляем теорию на практике.

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
char szProgName[]=»Имя программы»;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
<
HWND hWnd;
MSG lpMsg;
WNDCLASS w;

w.lpszClassName=szProgName; //имя программы — объявлено выше
w.hInstance=hInstance; //идентификатор текущего приложения
w.lpfnWndProc=WndProc; //указатель на функцию окна
w.hCursor=LoadCursor(NULL, IDC_ARROW); //загружаем курсор
w.hIcon=0;
w.lpszMenuName=0;
w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //цвет фона окна
w.style=CS_HREDRAW|CS_VREDRAW;
w.cbClsExtra=0;
w.cbWndExtra=0;

//Если не удалось зарегистрировать класс окна — выходим
if(!RegisterClass(&w))
return 0;

//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd=CreateWindow(szProgName, //Имя программы
«Грфические возможности Win32 API», //Заголовок окна
WS_OVERLAPPEDWINDOW, //Стиль окна — перекрывающееся
100, //положение окна на экране по х
100, //положение по у
500, //ширина
400, //высота
(HWND)NULL, //идентификатор родительского окна
(HMENU)NULL, //идентификатор меню
(HINSTANCE)hInstance, //идентификатор экземпляра программы
(HINSTANCE)NULL); //отсутствие дополнительных параметров

//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);

//Цикл обработки сообщений

//Функция окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём контекст устройства
PAINTSTRUCT ps; //создаём экземпляр структуры графического вывода
LOGFONT lf;
HFONT hFont;
RECT r;
HBRUSH hBrush;
HPEN hPen;

//Цикл обработки сообщений
switch(messg)
<
//сообщение рисования
case WM_PAINT :
hdc=BeginPaint(hWnd, &ps);

//Создаём свой шрифт
strcpy(lf.lfFaceName,»Times New Roman»); //копируем в строку название шрифта
lf.lfHeight=20;
lf.lfItalic=1;
lf.lfStrikeOut=0;
lf.lfUnderline=0;
lf.lfWidth=10;
lf.lfWeight=40;
lf.lfCharSet=DEFAULT_CHARSET; //значение по умолчанию
lf.lfPitchAndFamily=DEFAULT_PITCH; //значения по умолчанию
lf.lfEscapement=0;

hFont = CreateFontIndirect(&lf);
SelectObject(hdc, hFont);
SetTextColor(hdc, RGB(0,0,255));
TextOut(hdc, 80,40, «Красота спасёт мир!!», 20);

//рисуем зелёный эллипс
hBrush=CreateSolidBrush(RGB(10,200,100));
SelectObject(hdc, hBrush);
Ellipse(hdc, 20,100,200,200);

//рисуем закруглённый прямоугольник
hBrush=CreateSolidBrush(RGB(250,200,100));
SelectObject(hdc, hBrush);
hPen=CreatePen(2,2,RGB(0,0,255));
SelectObject(hdc, hPen);
RoundRect(hdc, 20, 250, 250, 350, 15, 15);


ValidateRect(hWnd, NULL);
EndPaint(hWnd, &ps);
break;

//сообщение выхода — разрушение окна
case WM_DESTROY:
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
DeleteObject(hPen);
DeleteObject(hBrush);
break;

default:
return(DefWindowProc(hWnd, messg, wParam, lParam)); //освобождаем очередь приложения от нераспознаных
>
return 0;
>

Правда, красиво? Наконец-то из этой API нам удалось выжать что-то стоящее. Продолжаем обзор.

7. Прямоугольная рамка — как видите, существует немало функций для работы с прямоугольниками:

int FrameRect(HDC hDC, CONST RECT *lprc, HBRUSH hbr);
Применение аналогично предыдущей.

8. Инверсия значения цветов точек в заданной области
BOOL InvertRect(HDC hDC, CONST RECT *lprc);

9. Эллипс
BOOL Ellipse(HDC hdc, int x1, int y1, int x2, int y2);
координаты — это прямоугольник, в который вписывается эллипс

10. Хорда (сегмент эллипса) — параметры аналогичны Arc
BOOL Chord(HDC hDC, int left, int top, int right, int bottom, int x1, int y1, int x2, int y2); Функция соединяет хордой точки начала и конца дуги эллипса и закрашивает выделенный сегмент текущей кистью.

11. Сектор эллипса
— аналог pieslice в DOS.
BOOL Pie(HDC hDC, int left, int top, int right, int bottom, int x1, int y1, int x2, int y2);

Читайте также:  Zip linux добавить файл

12. Многоугольник . Есть много функций рисования мноугольников. Мы рассмотрим две. Рисования от вершины к вершине и рисования отрезками:
PolyDraw оперирует вершинами:

POINT poly[8];
BYTE polytype[8];

poly[0].x=375; //координаты первой вершины
poly[0].y=375;

. //и так заполняем координаты всех восьми вершин

poly[7].x=400; //координаты восьмой вершины
poly[7].y =400;

. //другой массив содержит режим рисования

PolyDraw(hdc, poly, polytype, 8); //рисование многоугольника

Функция Polyline рисует набором отрезков:

Polyline(hdc, poly , 4);

КАК вывести график функции?

Всё это были фрагменты графических функций. Теперь рассмотрим настоящий полноценный пример. Это будет простейший график функции.

Вообще графики очень часто используются в промышленных приложениях. А ведь промышленная автоматизация один из главныз заказчиков программиста. На деле никто не занимается программированием ради программирования. Обязательно придётся осваивать какую-то ещё предметную область. Будь то склад для программиста баз данных или производство упаковок для системного программиста. Так или иначе, программы, контроллирующие какой-нибудь физический парметр (температуру больного, давление в шахте, скорость двигателя, частоту оборотов, напряжённость магнитного поля) выводят оперативную информацию на грфик. Специальный человек — оператор следит за тем, чтобы значения графика не достигали предельных значений.
Как вы сами понимаете, мы не можем обойти столь важную тему. Именно для неё наша следующая программа.
Создайте пустой проект Win API и включите в него следующий текст:

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//Процедура рисования линии
BOOL Line(HDC hdc, int x1, int y1, int x2, int y2);

char szProgName[]=»Имя программы»;

int i, xView, yView;
double y;
char Buf[2];

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
<
HWND hWnd;
MSG lpMsg;
WNDCLASS w;

w.lpszClassName=szProgName;
w.hInstance=hInstance;
w.lpfnWndProc=WndProc;
w.hCursor=LoadCursor(NULL, IDC_ARROW);
w.hIcon=0;
w.lpszMenuName=0;
w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
w.style=CS_HREDRAW|CS_VREDRAW;
w.cbClsExtra=0;
w.cbWndExtra=0;

//Если не удалось зарегистрировать класс окна — выходим
if(!RegisterClass(&w))
return 0;

//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd=CreateWindow(szProgName,
«График функции»,
WS_OVERLAPPEDWINDOW,
100,
100,
500,
400,
(HWND)NULL,
(HMENU)NULL,
(HINSTANCE)hInstance,
(HINSTANCE)NULL);

//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);

//Цикл обработки сообщений

while(GetMessage(&lpMsg, NULL, 0, 0)) < //Получаем сообщение из очереди
TranslateMessage(&lpMsg); //Преобразует сообщения клавиш в символы
DispatchMessage(&lpMsg); //Передаёт сообщение соответствующей функции окна
>
return(lpMsg.wParam);
>

//Функция окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём контекст устройства
PAINTSTRUCT ps; //создаём экземпляр структуры графического вывода
HPEN hPen; //создаём перо
//Цикл обработки сообщений
switch(messg)
<

case WM_SIZE:
xView=LOWORD(lParam);
yView=HIWORD(lParam);

//сообщение рисования
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
SetMapMode(hdc, MM_ISOTROPIC); //логические единицы отображаем, как физические
SetWindowExtEx(hdc, 500,500, NULL); //Длина осей
SetViewportExtEx(hdc, xView, -yView, NULL); //Определяем облась вывода
SetViewportOrgEx(hdc, xView/6, yView/2, NULL); //Начало координат

//Рисуем оси координат
Line(hdc,0, 220,0,-220);//ось У
Line(hdc, -100,0,500,0);//ось Х
MoveToEx(hdc, 0,0,NULL); //перемещаемся в начало координат

//Создание красного пера
hPen=CreatePen(1,4,RGB(255,25,0));
SelectObject(hdc, hPen);

//Делаем перо снова чёрным
hPen=CreatePen(1,1,RGB(0,0,0));
SelectObject(hdc, hPen);

//Наносим деления
for(i=-100; i

//сообщение выхода — разрушение окна
case WM_DESTROY:
DeleteObject(hPen); //не забываем уничтожать перья
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
break;

default:
return(DefWindowProc(hWnd, messg, wParam, lParam)); //освобождаем очередь приложения от нераспознаных
>
return 0;
>

//Функция рисования линии
BOOL Line(HDC hdc, int x1, int y1, int x2, int y2)
<
MoveToEx(hdc, x1, y1, NULL); //сделать текущими координаты x1, y1
return LineTo(hdc, x2, y2); //нарисовать линию
>

Поскольку в данной программе большое внимание уделяется всяким украшательствам: делениям и надписям, обращу ваше внимание на главное — создавать графики совсем не сложно. И вот как это делается:

Мы знаем, что отсчёт координат задаётся от левого верхнего угла вниз и вправо. Как известно, значения на графике изменяются вверх и вправо. Вряд ли мы сможем объяснить пользователю, почему график «растёт вниз». На счастье, в Windows предусмотрена функция, преобразует координаты в нужном нам направлении.
Первым делом, мы узнаём размер окна. Для этого используется сообщение WM_SIZE. Параметр lParam содержит по этому сообщению размеры экрана. Переменные xView и yView будут содержать эти значения:

case WM_SIZE:
xView=LOWORD(lParam);
yView=HIWORD(lParam);

break;

Затем определим область вывода. Мы хотим, чтобы при увеличении координаты по у график рос вверх, а не вниз.

SetViewportExtEx(hdc, xView, -yView, NULL);

Обратите внимание: yView указан со знаком . Значит все координаты по у будут расти в обратную сторону — вверх.
Центр графика обычно где-нибудь посередине экрана. Координаты же увеличиваются из левого верхнего угла. Перенесём центр графика:

SetViewportOrgEx(hdc, xView/6, yView/2, NULL);

В точке, равной 1/6 максимального значения по х и 1/2 значения по этого значения по у будет центр.
Можно также задать длину осей — 500 и 500. Для этого применяется следующая функция:

SetWindowExtEx(hdc, 500,500, NULL);

Как вы уже знаете, окно имеет логические координаты и физические. Для того, чтобы логические координаты совпадали с физическими, а также, чтобы единица отложенная по х была равно единице отложенной по у, задаётся режим MM_ISOTROPIC. Его задаёт функция:

Вот, как будет выглядеть эта конструкция вцелом:

SetMapMode(hdc, MM_ISOTROPIC); //логические единицы отображаем, как физические
SetWindowExtEx(hdc, 500,500, NULL); //Длина осей
SetViewportExtEx(hdc, xView, -yView, NULL); //Определяем облась вывода
SetViewportOrgEx(hdc, xView/6, yView/2, NULL); //Начало координат

Дальше надо нарисовать оси. К нашей радости, точка 0, 0 сместилась на середину экрана, в левую его часть. Исходя из этого, рисуем оси, применяя самописную функцию Line:

Line(hdc,0, 220,0,-220);//ось У
Line(hdc, -100,0,500,0);//ось Х
MoveToEx(hdc, 0,0,NULL);

В выводе графика нет ничего примечательного. Переменная i меняется от 0 до 450. Подставляя i в формулу, мы получаем зависимость y от i. Рисуем линию до этой точки. Небольшие отрезки сольются в непрерывную линию.

Иногда, требуется хранить значения физических величин несколько дней. Тогда график выполняется с длиннющей горизонтальной полосой прокрутки. При желании, можно вернуться и посмотреть были ли изменения, и если да, то какие.
Другой выход из положения — вести историю, записывая её в файл, а на экран выводя только показания последних пяти минут.

Задания:

1. Добавьте в приложения с графиком стрелочки и подписи к осям.
2. Нарисуйте снеговика известными средствами GDI. Напомню, что снеговик состоит из трёх непрозрачных эллипсов грязно-белого цвета, на груди у него должны быть пуговки, в руке — метла, а на голове — ведро.
3. Нарисуйте красивый паровозик, клубы из трубы которого будут выводиться в цикле в виде синих эллипсов
4. Нарисуйте кораблик с жёлтой палубой и красными бортами, используя Polyline или Polydraw. Кораблик покоится на синих волнах, которые нарисованы дугами разной толщины. Попробуйте вывести дуги в цикле.
5. Нарисуйте с помощью одних только линий домик с забором

Оцените статью