пятница, 6 января 2012 г.

C++ MFC: рисование и анимация

Рабочий минимум информации для рисования по windows-форме на примере MFC.

Часть I. Введение в рисование с GDI в MFC

Осуществляется рисование проще всего библиотекой GDI. Для начала хочу сказать о событии OnPaint. Оно вызывается каждый раз, когда нужно перерисовать окно (перетаскивание по экрану, изменение размеров и т.п.), поэтому в конец этой функции удобно впихнуть необходимую "заготовку" (например, оси координат, как сделано у меня). Теперь подробнее о самом рисовании. Всем известно, что "координаты" отличаются от декартовых (Центр в левом верхнем углу, ось Y направлена ВНИЗ, а X ничем не отличается), что стоит учесть (сделать пересчет) при рисовании по координатам.

Простой пример:
    CDC *pDC = GetDC();
    pDC->MoveTo(10,10);
    pDC->LineTo(20,20);
    ReleaseDC(pDC);

Первая строка: "захватываем контекст", грубо говоря, указатель на область, где можно что-то нарисовать. Вторая строка: переместить перо в точку (10,10). Третья строка: Провести линию (из исходной точки) в точку (20,20). Есть еще море функций с геометрическими фигурами, сплайнами и другой интересной ерундой....но нам это пока не обязательно. Четвертая точка освобождает ресурсы (сбрасываются кисти, указатель контекста и т.п.), рисунок остается!

Чтобы вывести текст:

pDC->TextOutA(10,10,"Some text");

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

Чтобы сделать фон текста прозрачным, перед его выводом написать:

    pDC->SetBkMode(TRANSPARENT);

Чтобы сменить ручку (да, точее всего это РУЧКА, а кистью тут принято закрашивать области (пока не нуждаемся) ):

    CPen pen(PS_SOLID,3,RGB(255,0,0));
    pDC->SelectObject(&pen);

Первая строка: выбрать тип ручки ( сейчас сплошная, задается макросами с префиксом PS), ее толщина (в пикселах) и цвет (в данном случае используется макрос RGB(красный, зеленый, синий)). Во второй строке мы применяем ручку. Мы можем их несколько заготовить и менять по надобности.

ЧАСТЬ II. О рисовании (считать продолжением прошлого поста о рисовании при помощи GDI)

Основой рисовании на форме является ее метод OnPaint(), т.е. перерисовка окна. Этот обработчик вызывается при таких явлениях как сворачивание/разворачивание окна, заслон другим окном при потере фокуса, изменении размера и т.п. Главный плюс метода - он вызывается сам при вышеназванных системных изменениях (нам не нужно отлавливать перерисовки, чтобы снова нанести свой рисунок) и то, что мы можем его инициировать в необходимом нам месте. Рисунок не становится объектом, поэтому после нанесения мы не можем на него как-то повлиять, только перерисовать окно, но тот рисунок не повторять (принцип-аналог хита 90х - розовая доска для рисования, когда чем нибудь давишь на пленку, она слипается образуя рисунок, который можно убрать проведя специальной рейкой внутри доски и разделив слои). Поэтому самый простой способ - создание глобального массива объектов с их параметрами, координатами и т.п. и отрисовывать их обходя массив в конце OnPaint().

ПРИМЕР: Создадим диалоговый проект MFC, назовем его Project_name =). Создадим в папке Headers files (панель Solution Explorer, где файлы проекта) заголовочный файл "Line.h". В него напишем такой класс:

//----------------------------------------
// Начало Line.h
using namespace std;

struct Line
{
    int X1, Y1; // Координаты начала
    int X2, Y2; // Координаты конца

    Line() { // Конструктор по умолчанию
        X1 = X2 = Y1 = Y2 = 0;
    }
    Line(int x1,int y1, int x2, int y2) { // Конструктор
        X1 = x1; X2 = x2;
        Y1 = y1; Y2 = y2;
    }
};

Line* lines; // Глобальный массив для объектов наших линий.
int count; // Здесь будем хранить кол-во линий.

// Конец Line.h
//----------------------------------------

Теперь, нужно открыть файл Project_nameDlg.cpp и в самом начале где подключаются заголовочные файлы добавить #include "Line.h" :

#include "stdafx.h"
#include "Project_name.h"
#include "Project_nameDlg.h"
#include "afxdialogex.h"
// Сюда добавить #include "Line.h" 

.....

Пусть в обработчике "ОК" мы будем добавлять 4 разных линии. В графическом редакторе диалого дважды кликнем по кнопке "ОК", автоматические будет создан обработчик void CProject_nameDlg::OnBnClickedOk(). Пусть будет 5 косых линий, оформляем обработчик так:

void CProject_nameDlg::OnBnClickedOk() {
    if (::lines == 0) { // Если массива еще нет (указатель на массив = 0)
        ::count = 5;
        Line = new Line[::count]; // Создаем линии
        for (int i=0; i<::count; ++i) {
            ::lines[i].X1 = i * 10;
            ::lines[i].Y1 = 0;
            ::lines[i].X2 = 0;
            ::lines[i].Y2 = i * 10;
        }
        RedrawWindow(); // Вызываем OnPaint()
    }
}

Итак, у нас есть массив линий с их координатами, мы вызываем OnPaint, осталось научить OnPaint рисовать наши линии. Ищем в этом же файле метод void CProject_nameDlg::OnPaint() и в самом его конце пишем цикл рисования наших линий:

if (::lines) { // Если массив линий не пустой
    CDC* dc = GetDC();
    for (int i=0; i<::count; ++i) {
        MoveTo(::lines[i].X1, ::lines[i].Y1); // Перемещаемся в начальную точку
        dc->LineTo(::lines[i].X2, ::lines[i].Y2); // Ведем карандашом в конечную точку
    }
}
x_1035bbc6

Все теперь можно компилировать и смело жать "ОК". Координаты определять эмпирически, либо по линейкам в графическом редакторе окна диалога. Предлагаю попробовать самостоятельно написать обработчик для какой-нибудь кнопки, которая будет удалять одну линию из массива (и ::count--), снова вызывать RedrawWindows().

ЧАСТЬ III Анимация.

Разница от обыкновенного рисования только в том, что this->OnPaint() будет вызываться по таймеру и соответственным образом будет изменяться массив данных.

У нас есть обычный проект, к примеру от части II.Алгоритм добавления таймера в проект:

1) Добавить в класс обработчика (в Project_nameDlg.h) объявление функции-обработчика "тика" таймера:

    afx_msg void OnTimer( UINT );

2) Добавить перехватываемое сообщение WM_TIMER в карту сообщений (в Project_nameDlg.cpp), тогда он будет примерно такого вида:

    BEGIN_MESSAGE_MAP(CProject_nameDlg, CDialogEx)
        ON_WM_PAINT()
        ON_WM_QUERYDRAGICON()
        ON_BN_CLICKED(IDOK, &CProject_nameDlg::OnBnClickedOk)
        ON_WM_TIMER() // Вот такой вот прототип
    END_MESSAGE_MAP()

3) Теперь мы можем писать функцию-обработчик тика (теперь уже конкретное определение функции из п.1):

    void CProject_nameDlg::OnTimer( UINT uTime) {
        // Обработка, к примеру, смещение объекта кубика
        //    и вызов функции OnPaint
        Box1.X += 10;
        this->RedrawWindow();
    }

4) Самое главное: запуск и остановка таймера.Запуск выполняется функцией:SetTimer(целочисленный_идентификатор_таймера,частота_тика_в_мс,NULL);

Пример:

    SetTimer(1,1000,NULL); // Таймер с id = 1, "тикающий" каждую секунду

Остановка:

    KillTimer(1);

Их вызовы можем укладывать в кнопки "старт анимация" и "стоп анимация"

Комментариев нет:

Отправить комментарий