Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Visual1.doc
Скачиваний:
8
Добавлен:
07.03.2016
Размер:
4.35 Mб
Скачать

5.10. Малювання графічних об'єктів з використанням резинових контурів та метафайлів

Приклад знаходиться у папках DISK\GDI\GDI8_1 та DISK\GDI\GDI8_2. У папці DISK\GDI\GDI8_1 знаходиться частина прикладу тільки з використанням резинових контурів, у папці DISK\GDI\GDI8_2 повний приклад з використанням резинових контурів і метафайлів.

Проектування програми painter

Програма painter складається з двох частин: перша відповідає за взаємодію з користувачем, а друга – за графічні операції. Великі програми бажано ділити на менші компоненти (це допомагає ізолювати помилки і полегшує подальшу роботу над програмою). Для простоти ми розділимо додаток на дві частини: перша буде обробляти команди меню, кнопки на панелі інструментів та повідомлення від миші, а друга – малювати у вікні.

Якщо користувач вибирає команду меню, натискає кнопку на панелі або працює з мишею, ми просто встановлюємо відповідний прапор. Другій частині програми (тієї, що відповідає за малювання) залишається лише перевірити значення прапорців і визначити, що слід робити.

Наприклад, якщо користувач вибирає з меню команду малювання ліній або натискає відповідну кнопку на панелі інструментів, прапору bLineFlag присвоюється значення true. Якщо після цього користувач переміщує мишку і потім відпускає кнопку, графічна частина програми визначає, яку фігуру їй слід малювати. З’ясовується, що прапор bLineFlag дорівнює true, і тому слід намалювати лінію. Таким чином ми відокремлюємо користувацький інтерфейс програми від графіки. Почнемо з опрацювання користувацького інтерфейсу.

Розробка зручного інтерфейсу для програми painter

Для користувача інтерфейс програми painter складається з команд меню, кнопок панелі інструментів та повідомлень миші, які будуть оброблятися в нашій програмі. Припустимо, користувач захотів намалювати лінію – він може вибрати команду Tools → Line або натиснути кнопку Line на панелі інструментів. Обрана команда позначається «галочкою», а кнопка Line на панелі інструментів переходить в натиснений (обраний) стан, як показано на наступному малюнку.

Потім користувач може клацнути мишею там, де повинна починатися нова лінія; назвемо цю точку початковою.

Тепер користувач перетягує курсор миші в кінцеву точку і відпускає кнопку миші. Після цього необхідно намалювати лінію від початкової до кінцевої точки, як показано на малюнку.

Поки користувач утримує кнопку миші натиснутою, ми продовжуємо малювати лінії, начебто відбувається розтягування ліній мишею. Коли кнопка миші відпускається, лінія фіксується на екрані. Аналогічно, із застосуванням початкової і кінцевої точок, малюються прямокутники та інші фігури.

Отже, завдання призначеного для користувача інтерфейсу полягає в установці прапорів для взаємодії з графічною частиною програми. Зараз ми докладніше розглянемо призначення цих прапорів.

Установка прапорців

Загальна послідовність дій у програмі виглядає так: коли користувач вибирає режим малювання, ми встановлюємо відповідний прапорець і тим самим визначаємо тип малюємої фігури: bDrawFlag , (для фігур довільної форми), bLineFlag (для ліній), bRectangleFlag (для прямокутників), bEllipseFlag (для еліпсів) або bFillFlag (для заповнення фігур).

Потім користувач натискає кнопку миші, щоб задати положення початкової точки. Ми запам'ятовуємо координати курсору при цій події.

Далі користувач переміщує курсор у кінцеву точку і відпускає кнопку миші. Оскільки координати початкової точки вже відомі, а прапор був встановлений раніше, при відпусканні кнопки миші ми можемо просто намалювати потрібну фігуру.

Створіть однодокументну (SDI) програму painter за допомогою AppWizard. Почнемо з оголошення необхідних прапорів, а також об'єктів для зберігання координат початкової та кінцевої точок:

class CPainterView : public CView

{

.............................................................

protected:

CClientDC* pDC;

CPoint Anchor;

CPoint OldPoint;

boolean bDrawFlag;

boolean bLineFlag;

boolean bRectangleFlag;

boolean bEllipseFlag;

boolean bFillFlag;

.............................................................

};

У конструкторі цим прапорцям присвоюється значення false. Їх необхідно скидати кожен раз, коли користувач вибирає новий режим малювання (тобто в будь-який момент часу може бути активний лише один режим), тому ми створимо новий метод MakeAllFlagsFalse() і оголосимо його в заголовному файлі класу виду painterView.h

Метод MakeAllFlagsFalse() виглядає дуже просто – усім флагам у ньому присвоюється значення false:

void CPainterView::MakeAllFlagsFalse()

{

bDrawFlag = false;

bLineFlag = false;

bRectangleFlag = false;

bEllipseFlag = false;

bFillFlag = false;

}

Новий метод слід визвати у конструкторі класу виду, щоб відключити всі режими малювання при запуску програми:

CPainterView::CPainterView()

{

// ЗРОБИТИ: додайте код конструктора

MakeAllFlagsFalse();

}

Флаги готові до роботи, тепер необхідно зв’язати їх з командами меню.

Створення меню Tools і кнопок панелі інструментів

Відкрийте редактор меню (рис. 5.18). Додамо меню Tools, зображене на рис. 5.18, і включимо в нього п'ять команд: Draw freehand, Line, Rectangle, Ellipse та Fill Figure. Редактор меню присвоює їм ідентифікатори – наприклад, Line отримує ідентифікатор ID_TOOLS_LINE.

Рис. 5.18. Побудова меню для програми painter

Потім відкрийте панель інструментів програми (рис. 5.19). Додайте в кінець панелі п'ять нових кнопок (кожен раз, коли ви малюєте що-небудь на кнопці, редактор додає в кінець панелі нову порожню кнопку) та відтягніть їх трохи в бік, щоб вони утворили групу (як показано на рис. 5.19). Тепер необхідно нанести на кнопки відповідні зображення. Наприклад, на кнопці для малювання ліній буде намальований короткий відрізок, на кнопці для малювання прямокутників – рамка і т. д.; при цьому слід зберегти той же порядок кнопок, що і на ілюстрації, – фігури довільної форми, лінії, прямокутники, еліпси і заповнення. Зовнішній вигляд нових кнопок зображений на рис. 5.19.

Рис. 5.19. Побудова панелі інструментів для програми painter

Нарешті, зв’яжіть кнопки з командами меню, які вони представляють, – для цього двічі клацніть на кнопці і виберіть ідентифікатор команди меню зі списку ID діалогового вікна Properties. Наприклад, кнопці для малювання прямокутників слід присвоїти ідентифікатор ID_TOOLS_RECTANGLE, як показано на рис. 5.20.

Ми підготували основні засоби користувацького інтерфейсу – команди меню і кнопки панелі інструментів. Тепер необхідно пов'язати їх з кодом програми.

Рис. 5.20. Присвоєння ідентифікаторів кнопкам панелі інструментів

Зв'язування прапорів із засобами для інтерфейсу користувача

Коли користувач вибере режим малювання, ми повинні задати відповідні значення прапорів. За допомогою ClassWizard необхідно зв’язати метод з кожною командою меню, наприклад, метод OnToolsEllipse() – для команди Ellipse, OnToolsDrawfreehand() – для команди Draw freehand і т. д. У обробнику кожної команди слід встановити значення всіх прапорів у false, після чого привласнити true прапору режиму, представленого поточною командою меню. Наприклад, якщо користувач хоче малювати лінії, ми призначимо значення true прапорцю bLineFlag і false – всім іншим:

void CPainterView::OnToolsDrawfreehand()

{

MakeAllFlagsFalse();

bDrawFlag = true;

}

void CPainterView::OnToolsEllipse()

{

MakeAllFlagsFalse();

bEllipseFlag = true;

}

void CPainterView::OnToolsFillfigure()

{

MakeAllFlagsFalse();

bFillFlag = true;

}

void CPainterView::OnToolsLine()

{

MakeAllFlagsFalse();

bLineFlag = true;

}

void CPainterView::OnToolsRectangle()

{

MakeAllFlagsFalse();

bRectangleFlag = true;

}

Тепер для різних режимів малювання програма встановлює прапори типу bLineFlag (за допомогою яких інтерфейсна частина спілкується з графічною). Однак для завершення користувацького інтерфейсу залишилося зробити ще дещо – необхідно повідомити користувачеві про поточний вибраний режим. Для цієї мети ми скористаємося методом SetCheck().

Позначка команд меню

Відомо, що перед відображенням меню програма викликає для кожної команди спеціальний метод оновлення користувацького інтерфейсу, у якому можна встановити позначку для команди, відповідної активному режиму малювання. За допомогою ClassWizard необхідно зв'язати з кожною командою меню метод оновлення (для цього слід зв'язати обробник команди з повідомленням UPDATE_COMAND_UI). У ньому можна помітити команду меню (або зняти позначку) залежно від значення прапора команди:

void CPainterView::OnUpdateToolsDrawfreehand(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bDrawFlag);

}

void CPainterView::OnUpdateToolsEllipse(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bEllipseFlag);

}

void CPainterView::OnUpdateToolsFillfigure(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bFillFlag);

}

void CPainterView::OnUpdateToolsLine(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bLineFlag);

}

void CPainterView::OnUpdateToolsRectangle(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bRectangleFlag);

}

Крім позначки команд активного режиму в меню Tools, ці методи також змушують, щоб відповідна кнопка на панелі інструментів виглядала «натиснутою», для того щоб користувач міг у будь-який момент визначити активний режим малювання. Фактично, вся робота користувацького інтерфейсу зводиться до правильного розміщення прапорів. Коли справа доходить до малювання, встановлений прапор визначає фігуру, яку слід намалювати. Від призначеного для користувача інтерфейсу ми переходимо до роботи з мишею.

Обробка повідомлень при натисканні кнопки миші

Якщо користувач натискає кнопку миші в клієнтській області програми, значить, він збирається малювати. Натискання кнопки миші задає початкову точку фігури. За допомогою ClassWizard необхідно додати в програму метод OnLButtonDown() і включити в нього код для збереження початкової точки:

void CPainterView::OnLButtonDown(UINT nFlags, CPoint point)

{

Anchor = OldPoint = point;

CView::OnLButtonDown(nFlags, point);

}

Нам відомі положення початкової точки і тип фігури. Тепер потрібно намалювати на екрані зображення.

Малювання ліній

Якщо користувач натискає кнопку миші і задає положення початкової точки, а потім переміщує мишу і відпускає кнопку, задаючи положення кінцевої точки, то програма повинна намалювати потрібну фігуру і розтягнути її від початкової до кінцевої точки.

Почнемо з малювання ліній – у цьому випадку повинен бути встановлений прапор bLineFlag. Остаточна лінія малюється при відпусканні кнопки миші, тому необхідно додати в програму метод OnLButtonUp():

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

CView::OnLButtonUp(nFlags, point);

}

Приступимо до малювання ліній.

У звичайній програмі ми б занесли потрібну інформацію в спеціальні змінні і потім викликали Invalidate(), щоб все малювання відбувалося в методі OnDraw(). Але в нашому випадку це небажано. До поточної лінії користувач може намалювати безліч фігур, так що програмі довелося б запам'ятовувати все, що відбувалося раніше, і відтворювати зображення в методі OnDraw() при додаванні нових фігур.

Ми будемо оновлювати вміст вікна в OnDraw() за допомогою метафайлів, так що набагато логічніше буде просто малювати нові фігури при відпусканні кнопки миші, а не викликати метод OnDraw(). Крім того, цей приклад показує, як отримати контекст пристрою для виду в довільний момент, а не тільки в методі OnDraw(). Для цієї мети використовується клас CClientDC. Він є похідним від класу CDC (методи останнього перераховані в табл. 2.4). На відміну від CDC, клас CClientDC має простий і зручний конструктор – достатньо передати йому вказівку на поточний об'єкт виду, щоб створити відповідний контекст пристрою:

int CPainterView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

pDC = new CClientDC(this);

return 0;

}

Конструктору класу CClientDC необхідно передати вказівку на поточний об'єкт виду, тобто вказівку this. Передаючи її, ми показуємо, що нам потрібен контекст пристрою для поточного виду.

Ми отримали контекст пристрою pDC для малювання в клієнтській області вікна.

Для виконання умови малювання ліній, слід перевірити прапорець bLineFlag. Якщо він дорівнює true, можна малювати лінії. Для цього необхідно спочатку переміститися до початкової точки методом MoveTo() класу CClientDC, а потім намалювати лінію від неї до кінцевої точки методом LineTo():

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

.............................................................

}

Таким чином намальована лінія зображується на екрані поточним кольором, вибраним в контексті пристрою (за замовчуванням – чорним), як показано на рис. 5.21.

Якщо необхідно намалювати лінію певного кольору треба створити для контексту пристрою новий об'єкт пера (що відноситься до класу CPen) і вибрати його в поточному контексті методом SelectObject() об'єкту контексту.

На наступному етапі перейдемо до малювання прямокутників.

Рис. 5.21. Малювання ліній у програмі painter

Малювання прямокутників

Намалювати прямокутник не складніше, ніж лінію. Для цього слід перевірити прапорець bRectangleFlag.

Для малювання прямокутників використовується метод Rectangle() класу CClientDC. Йому передаються координати двох точок (початкової і кінцевої), що визначають положення фігури:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

if(bRectangleFlag)

pDC->Rectangle(Anchor.x, Anchor.y,

point.x, point.y);

}

Але на цьому робота не закінчується – на екрані прямокутник буде заповнений поточним кольором фону. Поки він знаходиться в стороні від інших фігур все буде добре, але при накладенні на них він зітре все, що опиниться всередині нього.

Щоб уникнути псування зображення на екрані (наприклад, якщо користувач захоче намалювати прямокутник як рамку для інших фігур), ми вибираємо в поточному контексті пристрою порожній пензлик методом SelectStockObject() класу CClientDC. Внутрішня область прямокутника зафарбовується пензлем, і якщо він буде порожній, то внутрішня область залишається без змін:

void CPainterView: :OnLButtonUp(UINT nFlags, CPoint point)

{

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

pDC->SelectStockObject(NULL_BRUSH);

if(bRectangleFlag)

pDC->Rectangle(Anchor.x, Anchor.y,

point.x, point.y);

}

Крім NULL_BRUSH, в Windows існує ряд інших стандартних пензлів, наприклад, BLACK_BRUSH і WHITE_BRUSH. Ви також можете визначити власний пензлик, користуючись класом CBrush.

Саме так відбувається будь-яке малювання в контексті пристрою – для малювання об'єктів використовується поточне перо (можна вибрати його методом SelectStockObject() для стандартних олівців, наприклад, BLACK_PEN або WHITE_PEN, або ж створити своє власне перо за допомогою класу CPen і вибрати його в контексті методом SelectObject). Для зафарбовування об'єктів використовується поточний пензлик (при цьому також можна скористатися стандартними пензликами, наприклад BLACK_BRUSH, або створити власний пензлик за допомогою класу CBrush).

На рис. 5.22. представлено малювання прямокутників.

Рис. 5.22. Малювання прямокутників у програмі painter

Перейдемо до малювання наступної фігури – еліпса.

Малювання еліпсів і кругів

У Visual C++ колом вважається еліпс з однаковими розмірами осей, тому і кола, і еліпси малюються одним і тим же методом – Ellipse(). Еліпси малюються при встановленому прапорі bEllipseFlag; якщо його значення дорівнює true, ми вибираємо в контексті пристрою порожній пензлик й малюємо еліпс:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

pDC->SelectStockObject(NULL_BRUSH);

if(bRectangleFlag)

pDC->Rectangle(Anchor.x, Anchor.y,

point.x, point.y);

if(bEllipseFlag)

pDC->Ellipse(Anchor.x, Anchor.y,

point.x, point.y);

}

На рис. 5.23. представлено малювання різноманітних еліпсів.

Рис. 5.23. Малювання еліпсів у програмі painter

Перейдемо до заповнення кольором намальованих фігур.

Зафарбування фігур

Поки що всі намальовані фігури були «порожніми» і складалися з одних контурів. Ми можемо змінити ситуацію і скористатися методом FloodFill(). Цей зручний метод дозволяє вибрати точку в контексті пристрою і задати колір границі. Потім метод FloodFill() зафарбовує фігуру (оточену кольором границі, який в нашому випадку буде чорним) поточном пензлем.

Скористаємося пензлем BLACK_BRUSH для заповнення методом FloodFill() області, в якій знаходиться початкова точка (тобто в якій була натиснута кнопка миші). Це робиться таким чином:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

pDC->SelectStockObject(NULL_BRUSH);

if(bRectangleFlag)

pDC->Rectangle(Anchor.x, Anchor.y,

point.x, point.y);

if(bEllipseFlag)

pDC->Ellipse(Anchor.x, Anchor.y,

point.x, point.y);

if(bFillFlag)

{

pDC->SelectStockObject(BLACK_BRUSH);

pDC->FloodFill(Anchor.x, Anchor.y, RGB(0, 0, 0));

}

CView::OnLButtonUp(nFlags, point);

}

Фігура, всередині якої користувач клацає мишкою, заповнюється чорним кольором. Зверніть увагу: для завдання кольору границі в методі FloodFill() ми скористалися макросом RGB. Він повертає значення типу COLORREF, що використовується для кодування кольорів у Windows. Параметрами макросу RGB є три числа, що визначають інтенсивність червоної, зеленої та синьої складових кольору. Значення інтенсивності кожної з них може змінюватися в межах від 0 до 255, тому чорний колір задається у вигляді RGB(0, 0, 0). Яскраво-червоному кольору відповідає значення RGB(255, 0, 0), яскраво-зеленому – RGB(0, 255, 0), яскраво-синьому – RGB(0, 0, 255), сірому – RGB(128, 128, 128) і т. д.

За аналогією з олівцем, якщо необхідно заповнювати фігури певним кольором, треба створити новий пензлик як об'єкт класу CBrush та вибрати його в контексті пристрою методом SelectObject().

Після запуску програми painter виберіть режим заповнення та перевірте його на декількох фігурах, як показано на рис. 5.24.

Рис. 5.24. Заповнення фігур у програмі painter

Отже, програма painter малює лінії, прямокутники, еліпси і заповнені фігури. Наступним кроком має стати малювання фігур довільної форми.

Малювання фігур довільної форми

Малювати фігури довільної форми нескладно – ми відстежуємо переміщення миші, отримуємо послідовність точок і з'єднуємо їх лініями.

Кількість повідомлень про переміщення миші в секунду обмежена, тому ми не будемо отримувати повідомлення при кожному зсуві миші на один піксель. Якщо ми будемо просто зафарбовувати кожний поточний піксель, то отримаємо на екрані ланцюжок розрізнених точок, а не фігуру.

Малювання фігур довільної форми виглядає так: при натисканні кнопки миші фіксується положення початкової точки. Коли миша переміщується в нову точку, ми малюємо лінію від початкової точки до поточної і потім робимо поточну точку початковою для наступного переміщення миші.

Для початку скористаємося ClassWizard, щоб додати оброблювач переміщень миші OnMouseMove().

Потім необхідно переконатися, що в поточному режимі ми дійсно повинні малювати фігури довільної форми, – для цього слід перевірити прапорець bDrawFlag. Крім того, малювання повинно відбуватися тільки при натиснутій лівій кнопці миші (тобто під час перетягування вказівника миші з натиснутою кнопкою), тому ми перевіряємо, чи натиснута ліва кнопка, порівнюючи параметр nFlags методу OnMouseMove() з константою Visual C++ MK_LBUTTON:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point)

{

if((nFlags == MK_LBUTTON) && bDrawFlag)

{

.......................................................

}

CView::OnMouseMove(nFlags, point);

}

Коли змінна nFlags дорівнює 0 – відразу відбувається вихід з функції.

Можна перевірити й інші константи – MK_RBUTTON для правої кнопки миші, MK_MBUTTON для середньої кнопки, MK_CONTROL для клавіші Ctrl та MK_SHIFT для клавіші Shift.

Якщо в результаті перевірки з'ясовується, що ми дійсно повинні малювати фігури довільної форми, необхідно намалювати лінію від початкової точки до поточної, зробити поточну точку початковою і приготуватися до отримання наступного повідомлення від миші:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point)

{

if((nFlags == MK_LBUTTON) && bDrawFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

Anchor = point;

return;

}

CView::OnMouseMove(nFlags, point);

}

Замінимо стандартний курсор миші у вигляді стрілки на хрестик, як це робиться в більшості графічних редакторів.

Зміна курсору миші

Новий курсор миші для нашого виду можна задати при у методі PreCreateWindow(), присутнім у класі виду:

BOOL CPainterView::PreCreateWindow(CREATESTRUCT& cs)

{

// ЗРОБИТИ: Змініть клас вікна або його стилі,

// редагуючи вміст CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

Змінимо вміст інформаційної структури вікна, переданої у вигляді параметра cs, і задамо в ній новий курсор у вигляді хрестика, IDC_CROSS. Зміна курсору робиться на рівні класу вікна Windows:

BOOL CPainterView::PreCreateWindow(CREATESTRUCT& cs)

{

// ЗРОБИТИ: Змініть клас вікна або його стилі,

// редагуючи вміст CREATESTRUCT cs

cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS,

AfxGetApp()->LoadStandardCursor(IDC_CROSS),

(HBRUSH)(COLOR_WINDOW+1),

AfxGetApp()->LoadIcon(IDR_MAINFRAME));

return CView::PreCreateWindow(cs);

}

Новий курсор, зображений на рис. 5.25, полегшує малювання фігур довільної форми, і при переміщенні миші на екрані з'являється черговий фрагмент фігури.

У даній програмі продемонстровано малювання різних фігур при переміщенні курсору миші. Проте бажано, щоб користувач міг бачити контури створюваних ним фігур у процесі малювання, як це робиться у графічних редакторах. Допрацюємо нашу програму.

Рис. 5.25. Малювання фігур довільної форми у програмі painter

Розтягування графічних фігур

У професійних графічних редакторах користувач може «розтягувати» мишею створювану фігуру (тобто стежити за контуром фігури під час її створення); така можливість буде присутньою і в нашій програмі.

Код для імітації «розтягування» фігур включено в обробник OnMouseMove(). Почнемо з найпростішого випадку – розтягування ліній.

Перш за все необхідно з'ясувати, чи дійсно ми повинні розтягувати лінію, а не якусь іншу фігуру. Іншими словами, наступний фрагмент коду виконується лише в тому випадку, якщо обраний режим малювання ліній і якщо миша переміщається з натиснутою лівою кнопкою. Щоб провести лінію від початкової точки до поточної, необхідно зробити наступні дії: стерти старе зображення лінії від початкової точки до попередньої і намалювати нову лінію від початкової точки до поточної позиції курсору миші.

Щоб стерти стару лінію від початкової точки до попередньої, необхідно запам'ятати її положення – для цього ми додамо до програми об'єкт класу CPoint з ім'ям OldPoint. У ньому буде зберігатися положення курсору миші при викликах OnMouseMove(). При натисканні кнопки миші необхідно занести в OldPoint поточні координати курсору, щоб у цьому об'єкті завжди зберігалося попереднє значення:

void CPainterView::OnLButtonDown(UINT nFlags, CPoint point)

{

Anchor = OldPoint = point;

CView::OnLButtonDown(nFlags, point);

}

Майбутню роботу можна розділити на три етапи: стирання лінії від початкової точки Anchor до попередньої точки OldPoint, малювання нової лінії від точки Anchor до поточної позиції курсору та копіювання координат поточної точки в OldPoint для наступного переміщення миші.

Бінарні растрові операції

Щоб стерти лінію від Anchor до OldPoint, необхідно вибрати в контексті пристрою бінарний растровий режим R2_NOT і знову намалювати ту ж лінію. Що це означає? Бінарний растровий режим показує, як будуть виглядати на екрані результати малювання (можливі значення перераховані в табл. 5.4). Режим R2_NOT означає, що при малюванні кожного нового пікселя екранний піксель інвертується. Оскільки в нашій програмі лінії мають чорний колір, а фон – білий, після повторного промальовування в цьому режимі лінія стане білою, тобто фактично опиниться стертою. Крім того, якщо розтягувана лінія пройде над чорною ділянкою екрану, її відповідний відрізок стане білим, щоб користувач побачив його на чорному фоні.

Таблиця 5.4

Бінарні растрові режими Windows

Бінарний растровий режим (R2)

Призначення

R2_BLACK

Піксель завжди має чорний колір

R2_COPYPEN

Колір пікселя збігається з кольором пера

R2_MASKNOTPEN

Колір пікселя = (NOT колір пера) AND колір екрану

R2_MASKPEN

Колір пікселя = колір пера AND колір екрану

R2_MASKPENNOT

Колір пікселя = (NOT колір екрану) AND колір пера

R2_MERGENOTPEN

Колір пікселя = (NOT колір пера) OR колір екрану

R2_MERGEPEN

Колір пікселя = колір екрану OR колір пера

R2_MERGEPENNOT

Колір пікселя = (NOT колір екрану) OR колір пера

R2_NOP

Піксель залишається без змін

R2_NOT

Колір пікселя є інвертованим кольором

R2_NOTCOPYPEN

Колір пікселя є інвертованим кольором

Продовження таблиці 5.4

Бінарний растровий режим (R2)

Призначення

R2_NOTMASKPEN

Колір пікселя = NOT (колір пера AND колір екрану)

R2_NOTMERGEPEN

Колір пікселя = NOT (колір пера OR колір екрану)

R2_NOTXORPEN

Колір пікселя = NOT (колір пера XOR колір екрану)

R2_WHITE

Піксель завжди має білий колір

R2_XORPEN

Колір пікселя = колір пера XOR колір екрану

Таким чином відбувається стирання попередньої лінії. Зверніть увагу – ми зберігаємо старий бінарний растровий режим (ціла величина), щоб пізніше відновити його:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point)

{

int nOldMode = pDC->GetROP2();

pDC->SetROP2(R2_NOT);

pDC->SelectStockObject(NULL_BRUSH);

if ((nFlags == MK_LBUTTON) && bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(OldPoint);

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

...................................................

OldPoint = point;

pDC->SetROP2(nOldMode);

}

Потім ми малюємо нову лінію, оновлюємо OldPoint і відновлюємо попередній бінарний растровий режим контексту пристрою.

Тепер користувач може скільки завгодно розтягувати малюєму лінію, як показано на рис. 5.26. Зверніть увагу: над чорним об'єктом лінія виводиться білим кольором.

Рис. 5.26. Розтягування лінії в програмі painter

Аналогічно виглядає код для розтягування прямокутників і еліпсів:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point)

{

int nOldMode = pDC->GetROP2();

pDC->SetROP2(R2_NOT);

pDC->SelectStockObject(NULL_BRUSH);

.............................................................

if((nFlags == MK_LBUTTON) && bRectangleFlag)

{

pDC->Rectangle(OldPoint.x, OldPoint.y, Anchor.x, Anchor.y);

pDC->Rectangle(Anchor.x, Anchor.y, point.x, point.y);

}

if((nFlags == MK_LBUTTON) && bEllipseFlag)

{

pDC->Ellipse(OldPoint.x, OldPoint.y, Anchor.x, Anchor.y);

pDC->Ellipse(Anchor.x, Anchor.y, point.x, point.y);

}

.............................................................

OldPoint = point;

pDC->SetROP2(nOldMode);

CView::OnMouseMove(nFlags, point);

}

Коли користувач натискає кнопку миші і переміщає курсор до кінцевої точки, на екрані розтягується фігура потрібного типу – користувач бачить, як буде виглядати готове зображення. Тепер ця надзвичайно корисна можливість реалізована і в програмі painter.

Але все ж її рано вважати закінченою – ми ще не написали метод OnDraw(). Його код оновлює зображення у вікні програми. У складних додатках з написанням методу OnDraw() нерідко виникають проблеми, тому що в ньому необхідно відтворити все зображення, яким би складним воно не було.

Наприклад, у нашому випадку користувач може намалювати скільки завгодно фігур різних типів. Як вийти з положення – невже доведеться запам'ятовувати кожне переміщення миші? Як ми незабаром переконаємося, можливий вихід полягає у використанні метафайлів.

Оновлення зображення в програмі painter

Метафайлом називається зберігаємий в пам'яті об'єкт, що містить власний контекст пристрою. Будь-які операції, з контекстом пристрою, можна продублювати в метафайл. Якщо пізніше буде потрібно повторити дії, виконані користувачем (наприклад, при оновленні зображення), достатньо відтворити даний метафайл. У результаті всі графічні операції повторюються, що призводить до автоматичного створення потрібного зображення. Давайте подивимося, як це робиться. Методи класу CMetaFileDC перераховані в табл. 5.5.

Таблиця 5.5

Методи класу CMetaFileDC

Метод

Призначення

CMetaFileDC

Конструює об'єкт класу CMetaFileDC

Сreate

Створює контекст пристрою Windows для метафайлу і пов'язує його з об'єктом CMetaFileDC

CreateEnhanced

Створює контекст пристрою Windows для розширеного метафайлу

Close

Закриває контекст пристрою і створює дескриптор метафайлу

CloseEnhanced

Закриває контекст пристрою розширеного метафайлу і створює дескриптор розширеного метафайлу

Оновлення екрану відбувається в методі OnDraw(), на початку розробки програми він виглядає так:

void CPainterView::OnDraw(CDC* pDC)

{

CPainterDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// ЗРОБИТИ: додайте код для відображення даних

}

Наступний фрагмент коду призначений для відновлення зображення у вікні. Після створення контексту пристрою для метафайлу в ньому можна малювати, дублюючи основні графічні операції у вікні. Коли буде треба відновити зображення, ми просто відтворимо наш метафайл.

Спочатку ми включимо в заголовний файл документа вказівку на контекст пристрою метафайлу pMetaFileDC, який буде використовуватися в програмі:

// painterDoc.h : реалізація класа CPainterDoc

.............................................................

// Реалізація

public:

virtual ~CPainterDoc();

CMetaFileDC* pMetaFileDC;

.............................................................

}

Потім ми створюємо новий об'єкт CMetaFileDC і реалізуємо його в конструкторі документа, викликаючи метод Сreate():

CPainterDoc::CPainterDoc()

{

pMetaFileDC = new CMetaFileDC();

pMetaFileDC->Create();

}

У даному фрагменті створено контекст пристрою для метафайлу, в якому будуть дублюватися всі дії користувача.

Дублювання графічних операцій у метафайлі

Все, що користувач малює на екрані, має дублюватися в метафайл. Це означає, що при кожному виклику методу для контексту пристрою виду необхідно викликати аналогічний метод для контексту метафайлу. Наприклад, у випадку малювання прямокутників і еліпсів це робиться так:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

CPainterDoc* pDoc = GetDocument();

if(bDrawFlag)return;

pDC->SelectStockObject(NULL_BRUSH);

pDoc->pMetaFileDC->SelectStockObject(NULL_BRUSH);

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

pDoc->pMetaFileDC->MoveTo(Anchor);

pDoc->pMetaFileDC->LineTo(point);

}

if(bRectangleFlag)

{

pDC->Rectangle(Anchor.x, Anchor.y,

point.x, point.y);

pDoc->pMetaFileDC->Rectangle(Anchor.x, Anchor.y,

point.x, point.y);

}

.............................................................

}

Подібний дублюючий код повинен бути присутнім у всій програмі, щоб кожна операція з контекстом пристрою виду повторювалася в контексті пристрою метафайлу. Тим самим ми запишемо всі дії користувача в метафайл, де вони будуть готові до подальшого відтворення.

У результаті в метафайл потрапить повний запис усього, що зробив користувач, після чого ним можна буде скористатися для оновлення зображення на екрані.

Відтворення метафайлу

Метод OnDraw() повинен оновлювати зображення програми painter, що в нашому випадку зводиться до звичайного відтворення метафайлу. Спочатку необхідно закрити його і отримати дескриптор метафайлу, а потім відтворити його за дескриптором. У методі OnDraw() цей процес буде виглядати так:

void CPainterView::OnDraw(CDC* pDC)

{

CPainterDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDC->PlayMetaFile(MetaFileHandle);

.............................................................

}

Проте після закриття метафайлу ми вже не зможемо записувати в нього. Отже, треба створити новий метафайл і відтворити в ньому старий (для якого у нас є дескриптор):

void CPainterView::OnDraw(CDC* pDC)

{

CPainterDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

pDoc->pMetaFileDC->PlayMetaFile(MetaFileHandle);

pDC->PlayMetaFile(MetaFileHandle);

DeleteMetaFile(MetaFileHandle);

.............................................................

}

У результаті ми відтворили графічне зображення в клієнтській області, як і належить у методі OnDraw(), і створили новий метафайл для наступного оновлення.

Такий підхід усуває стару проблему Visual C++ – відновлення вікна програми у методі OnDraw(). У нашому випадку вдалося знайти просте рішення, засноване на роботі з метафайлом. Тепер додаток за необхідності зможе відновити зображення у своєму вікні.

Використання метафайлів пов'язане з однією проблемою - якщо ви викликаєте графічний метод, який повертає інформацію про контекст пристрою (наприклад, відомості про висоту або ширину), то відтворення метафайлу в іншому контексті може виявитися невдалим, оскільки розміри не обов'язково залишаться попередніми. Якщо ви хочете викликати методи, які повертають інформацію про контекст пристрою, скористайтеся методом SetAttribDC() класу CMetaFileDC, щоб зв'язати метафайл з контекстом пристрою того типу, в якому він повинен відтворюватися. У цьому випадку виклики інформаційних методів будуть переадресовані відповідному контексту пристрою.

Можливості метафайлів цим не обмежуються – зображення, що знаходиться в метафайлі, можна зберегти на диску.

Збереження графічних файлів

Для збереження метафайлу на диску ми скористаємося методом CopyMetaFile() класу CMetaFileDC. За допомогою ClassWizard необхідно зв'язати обробники з трьома командами меню FileNew, Save і Open.

Вони будуть створювати новий документ програми painter, зберігати зображення у файлі painter.wmf (.wmf – стандартне розширення для метафайлів Windows) і завантажувати збережений раніше метафайл.

Відкрийте метод OnFileSave():

void CPainterView::OnFileSave()

{

// ЗРОБИТИ: додайте код обробки команди

}

Ми закриваємо метафайл, отримуючи його дескриптор, і передаємо його методом CopyMetaFile() разом з ім'ям файлу на диск, в якому повинен зберігатися вміст метафайлу:

void CPainterView::OnFileSave()

{

CPainterDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

MetaFileHandle = CopyMetaFile(MetaFileHandle, "painter.wmf");

.............................................................

}

Метафайл записується на диск. Далі необхідно створити новий метафайл на зміну йому і відтворити в ньому старий. Слід зауважити, що при записі метафайлу на диску його дескриптор автоматично видаляється. З цією метою функція CopyMetaFile повертає значення дескриптору збереженого у файлі метафайлу.

void CPainterView::OnFileSave()

{

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

MetaFileHandle = CopyMetaFile(MetaFileHandle, "painter.wmf");

pDoc->pMetaFileDC->PlayMetaFile(MetaFileHandle);

DeleteMetaFile(MetaFileHandle);

.............................................................

}

Таким чином нам вдалося зберегти зображення програми painter у файлі painter.wmf і створити нову копію метафайлу, використовуваного в програмі.

Зображення знаходиться у файлі – але як завантажити його з цього файлу?

Завантаження графічного файлу

Завантаження файлу painter.wmf буде відбуватися в методі OnFileOpen():

void CPainterView::OnFileOpen()

{

// ЗРОБИТИ: додайте код обробки команди

}

Для завантаження файлу painter.wmf ми скористаємося методом GetMetaFile():

void CPainterView::OnFileOpen()

{

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = GetMetaFile("painter.wmf");

.............................................................

}

Але даний фрагмент лише створює новий дескриптор метафайлу, а ми хочемо створити метафайл, використовуваний в програмі. Для цього слід створити новий об'єкт класу CMetaFileDC, відтворити в ньому метафайл, завантажений з диска, і замінити старий метафайл нашої програми новим:

void CPainterView::OnFileOpen()

{

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = GetMetaFile("painter.wmf");

CMetaFileDC* ReplacementMetaFile = new CMetaFileDC();

pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

pDoc->pMetaFileDC->PlayMetaFile(MetaFileHandle);

DeleteMetaFile(MetaFileHandle);

Invalidate();

}

Зверніть увагу на виклик Invalidate() в кінці методу. Він забезпечує відображення нового метафайлу в об'єкті виду програми.

Таким чином наша програма вміє зберігати графічні зображення на диску та завантажувати їх. Останнє, що залишилося зробити, – забезпечити можливість створення нового документа командою New.

Створення нового документа

На даний момент метод OnFileNew() виглядає так:

void CPainterView::OnFileNew()

{

// ЗРОБИТИ: додайте код обробки команди

}

Вибираючи команду File → New, користувач створює новий документ. У цьому випадку ми створюємо порожній метафайл, встановлюємо його в своїй програмі і викликаємо Invalidate() для його відтворення. Метод Invalidate() очищає вікно виду, і користувач може приступати до створення нового зображення в порожньому вікні і в порожньому метафайлі.

Ми закриваємо та видаляємо старий метафайл, замінюємо його новим за допомогою функції Create() і викликаємо Invalidate(), щоб очистити екран:

void CPainterView::OnFileNew()

{

CPainterDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

DeleteMetaFile(MetaFileHandle);

Invalidate();

}

Програма painter готова – користувач може малювати прямокутники, лінії, кола, еліпси, заповнювати їх кольором, малювати фігури довільної форми, користуватися кнопками на панелі інструментів, розтягувати фігури, зберігати їх на диску, відновлювати зображення і створювати новий документ

Оригінальний текст програми міститься у файлах painterDoc.h / painterDoc.cpp та painterView.h / painterView.cpp.

painterDoc.h і painterDoc.cpp

// painterDoc.h : інтерфейс класа CPainterDoc

//

/////////////////////////////////////////////////////////////////////////

#if !defined(AFX_PAINTERDOC_H__A4A35E0B_94AC_11D0_8860_444553540000__INCLUDED_)

#define AFX_PAINTERDOC_H__A4A35E0B_94AC_11D0_8860_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CPainterDoc : public CDocument

{

protected: // создание только при сериализации

CPainterDoc();

DECLARE_DYNCREATE(CPainterDoc)

// Атрибути

public:

// Операції

public:

// Перевантаження

// Перевантажені віртуальні функції,

// сформовані ClassWizard-ом.

//{{AFX_VIRTUAL(CPainterDoc)

public:

virtual BOOL OnNewDocument();

virtual void Serialize(CArchive& ar);

//}}AFX_VIRTUAL

// Реалізація

public:

virtual ~CPainterDoc();

CMetaFileDC* pMetaFileDC;

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Сформовані функції карти повідомлень

protected:

//{{AFX_MSG(CPainterDoc)

// УВАГА!! Тут ClassWizard буде додавати і

// видаляти функції-члени.

// не редагуйте текст у цих блоках!

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ буде вставляти додаткові

// оголошення перед попереднім рядком.

#endif

// !defined(AFX_PAINTERDOC_H__A4A35E0B_94AC_11D0_8860_444553540000__INCLUDED_)

// painterDoc.cpp : реалізація класа CPainterDoc

//

#include "stdafx.h"

#include "painter.h"

#include "painterDoc.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////

// CPainterDoc

IMPLEMENT_DYNCREATE(CPainterDoc, CDocument)

BEGIN_MESSAGE_MAP(CPainterDoc, CDocument)

//{{AFX_MSG_MAP(CPainterDoc)

// УВАГА!! Тут ClassWizard буде додавати і

// видаляти функції-члени.

// не редагуйте текст у цих блоках!

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////

// Создание/уничтожение CPainterDoc

CPainterDoc::CPainterDoc()

{

// ЗРОБИТИ: додайте код конструктора

pMetaFileDC = new CMetaFileDC();

pMetaFileDC->Create();

}

CPainterDoc::~CPainterDoc()

{

delete pMetaFileDC;

}

BOOL CPainterDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

// ЗРОБИТИ: додайте код для повторної ініциалізації документа

// (документи SDI будуть використовувати його повторно)

return TRUE;

}

/////////////////////////////////////////////////////////////////////////

// Сериалізація в CPainterDoc

void CPainterDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

// ЗРОБИТИ: додайте код збереження даних

}

else

{

// ЗРОБИТИ: додайте код завантаження даних

}

}

/////////////////////////////////////////////////////////////////////////

// Діагностика в CPainterDoc

#ifdef _DEBUG

void CPainterDoc::AssertValid() const

{

CDocument::AssertValid();

}

void CPainterDoc::Dump(CDumpContext& dc) const

{

CDocument::Dump(dc);

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////

// Команди CPainterDoc

painterView.h і painterView.cpp

// painterView.h : інтерфейс класа CPainterView

//

/////////////////////////////////////////////////////////////////////////

#if !defined(AFX_PAINTERVIEW_H__A4A35E0D_94AC_11D0_8860_444553540000__INCLUDED_)

#define AFX_PAINTERVIEW_H__A4A35E0D_94AC_11D0_8860_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CPainterView : public CView

protected: // створення тільки при сериалізації

CPainterView();

DECLARE_DYNCREATE(CPainterView)

// Атрибути

public:

CPainterDoc* GetDocument();

// Операції

public:

// Перевантаження

// Перевантажені віртуальні функції,

// сформовані ClassWizard-ом.

//{{AFX_VIRTUAL(CPainterView)

public:

virtual void OnDraw(CDC* pDC); // Перевантажена для

// промальовки в цьому виді.

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC,

CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Реалізація

public:

virtual ~CPainterView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

CPoint Anchor;

CPoint OldPoint;

boolean bDrawFlag;

boolean bLineFlag;

boolean bRectangleFlag;

boolean bEllipseFlag;

boolean bFillFlag;

void MakeAllFlagsFalse();

// Сформовані функції карти повідомлень

protected:

//{{AFX_MSG(CPainterView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

afx_msg void OnToolsDrawfreehand();

afx_msg void OnToolsEllipse();

afx_msg void OnToolsFillfigure();

afx_msg void OnToolsLine();

afx_msg void OnToolsRectangle();

afx_msg void OnUpdateToolsDrawfreehand(CCmdUI* pCmdUI);

afx_msg void OnUpdateToolsEllipse(CCmdUI* pCmdUI);

afx_msg void OnUpdateToolsFillfigure(CCmdUI* pCmdUI);

afx_msg void OnUpdateToolsLine(CCmdUI* pCmdUI);

afx_msg void OnUpdateToolsRectangle(CCmdUI* pCmdUI);

afx_msg void OnFileSave();

afx_msg void OnFileOpen();

afx_msg void OnFileNew();

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG // отладочная версия в painterView.cpp

inline CPainterDoc* CPainterView::GetDocument()

{ return (CPainterDoc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ буде вставляти додаткові

// оголошення перед попереднім рядком.

#endif

// !defined(AFX_PAINTERVIEW_H__A4A35E0D_94AC_11D0_8860_444553540000__INCLUDED_)

// painterView.cpp : реалізація класа CPainterView

//

#include "stdafx.h"

#include "painter.h"

#include "painterDoc.h"

#include "painterView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////

// CPainterView

IMPLEMENT_DYNCREATE(CPainterView, CView)

BEGIN_MESSAGE_MAP(CPainterView, CView)

//{{AFX_MSG_MAP(CPainterView)

ON_WM_LBUTTONDOWN()

ON_WM_LBUTTONUP()

ON_WM_M0USEMOVE()

ON_COMMAND(ID_TOOLS_DRAWFREEHAND, OnToolsDrawfreehand)

ON_COMMAND(ID_TOOLS_ELLIPSE, OnToolsEllipse)

ON_COMMAND(ID_TOOLS_FILLFIGURE, OnToolsFillfigure)

ON_COMMAND(ID_TOOLS_LINE, OnToolsLine)

ON_COMMAND(ID_TOOLS_RECTANGLE, OnToolsRectangle)

ON_UPDATE_COMMAND_UI(ID_TOOLS_DRAWFREEHAND,OnUpdateToolsDrawfreehand)

ON_UPDATE_COMMAND_UI(ID_TOOLS_ELLIPSE, OnUpdateToolsEllipse)

ON_UPDATE_COMMAND_UI(ID_TOOLS_FILLFIGURE, OnUpdateToolsFillfigure)

ON_UPDATE_COMMAND_UI(ID_TOOLS_LINE, OnUpdateToolsLine)

ON_UPDATE_COMMAND_UI(ID_TOOLS_RECTANGLE, OnUpdateToolsRectangle)

ON_COMMAND(ID_FILE_SAVE, OnFileSave)

ON_COMMAND(ID_FILE_OPEN, OnFileOpen)

ON_COMMAND(ID_FILE_NEW, OnFileNew)

ON_WM_CREATE()

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////

// Создание/уничтожение CPainterView

CPainterView::CPainterView()

{

// ЗРОБИТИ: додайте код конструктора

MakeAllFlagsFalse();

}

CPainterView::~CPainterView()

{

delete pDC;

}

BOOL CPainterView::PreCreateWindow(CREATESTRUCT& cs)

{

// ЗРОБИТИ: Змініть клас вікна або його стилі,

// редагуючи вміст CREATESTRUCT cs

cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS,

AfxGetApp()->LoadStandardCursor(IDC_CROSS),

(HBRUSH)(C0L0R_WIND0W+1),

AfxGetApp()->LoadIcon(IDR_MAINFRAME));

return CView::PreCreateWindow(cs);

}

int CPainterView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

pDC = new CClientDC(this);

return 0;

}

/////////////////////////////////////////////////////////////////////////

// Рисование в CPainterView

void CPainterView::OnDraw(CDC* pDC)

{

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

pDoc->pMetaFileDC->PlayMetaFile(MetaFileHandle);

pDC->PlayMetaFile(MetaFileHandle);

DeleteMetaFile(MetaFileHandle);

// ЗРОБИТИ: додайте код для відображення даних

}

/////////////////////////////////////////////////////////////////////////

// Друк в CPainterView

BOOL CPainterView::OnPreparePrinting(CPrintInfo* pInfo)

{

// Стандартна підготовка

return DoPreparePrinting(pInfo);

}

void CPainterView::OnBeginPrinting(CDC* /*pDC*/,

CPrintInfo* /*pInfo*/)

{

// ЗРОБИТИ: додайте додаткову ініціалізацію перед друком

}

void CPainterView::OnEndPrinting(CDC* /*pDC*/,

CPrintInfo* /*pInfo*/)

{

// ЗРОБИТИ: додайте код для "зборки сміття" після друку

}

/////////////////////////////////////////////////////////////////////////

// Діагностика в CPainterView

#ifdef _DEBUG

void CPainterView::AssertValid() const

{

CView::AssertValid();

}

void CPainterView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CPainterDoc* CPainterView::GetDocument() // не отладочная

// версия является встроенной

{

ASSERT(m_pDocument->IsKindOf(

RUNTIME_CLASS(CPainterDoc)));

return (CPainterDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////

// Обробники повідомлень CPainterView

void CPainterView::OnLButtonDown(UINT nFlags, CPoint point)

{

Anchor = OldPoint = point;

CView::OnLButtonDown(nFlags, point);

}

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point)

{

CPainterDoc* pDoc = GetDocument();

if(bDrawFlag)return;

pDC->SelectStockObject(NULL_BRUSH);

pDoc->pMetaFileDC->SelectStockObject(NULL_BRUSH);

if(bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

pDoc->pMetaFileDC->MoveTo(Anchor);

pDoc->pMetaFileDC->LineTo(point);

}

if(bRectangleFlag)

{

pDC->Rectangle(Anchor.x, Anchor.y, point.x, point.y);

pDoc->pMetaFileDC->Rectangle(Anchor.x,Anchor.y,point.x,point.y);

}

if(bEllipseFlag)

{

pDC->Ellipse(Anchor.x, Anchor.y, point.x, point.y);

pDoc->pMetaFileDC->Ellipse(Anchor.x,Anchor.y,point.x,point.y);

}

if(bFillFlag)

{

pDC->SelectStockObject(BLACK_BRUSH);

pDC->FloodFill(Anchor.x,Anchor.y, RGB(0, 0, 0));

pDoc->pMetaFileDC->SelectStockObject(BLACK_BRUSH);

pDoc->pMetaFileDC->FloodFill(Anchor.x,Anchor.y,RGB(0,0,0));

}

CView::OnLButtonUp(nFlags, point);

}

void CPainterView::OnMouseMove(UINT nFlags, CPoint point)

{

if((nFlags == MK_LBUTTON) && bDrawFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(point);

pDoc->pMetaFileDC->MoveTo(Anchor);

pDoc->pMetaFileDC->LineTo(point);

Anchor = point;

return;

}

int nOldMode = pDC->GetROP2();

pDC->SetROP2(R2_NOT);

pDC->SelectStockObject(NULL_BRUSH);

if ((nFlags == MK_LBUTTON) && bLineFlag)

{

pDC->MoveTo(Anchor);

pDC->LineTo(OldPoint);

pDC->MoveTo(Anchor);

pDC->LineTo(point);

}

if((nFlags == MK_LBUTTON) && bRectangleFlag)

{

pDC->Rectangle(OldPoint.x,OldPoint.y,Anchor.x,Anchor.y);

pDC->Rectangle(Anchor.x, Anchor.y, point.x, point.y);

}

if((nFlags == MK_LBUTTON) && bEllipseFlag)

{

pDC->Ellipse(OldPoint.x, OldPoint.y,Anchor.x, Anchor.y);

pDC->Ellipse(Anchor.x, Anchor.y, point.x, point.y);

}

OldPoint = point;

pDC->SetROP2(nOldMode);

}

void CPainterView::MakeAllFlagsFalse()

{

bDrawFlag = false;

bLineFlag = false;

bRectangleFlag = false;

bEllipseFlag = false;

bFillFlag = false;

}

void CPainterView::OnToolsDrawfreehand()

{

MakeAllFlagsFalse();

bDrawFlag = true;

}

void CPainterView::OnToolsEllipse()

{

MakeAllFlagsFalse();

bEllipseFlag = true;

}

void CPainterView::OnToolsFillfigure()

{

MakeAllFlagsFalse();

bFillFlag = true;

}

void CPainterView::OnToolsLine()

{

MakeAllFlagsFalse();

bLineFlag = true;

}

void CPainterView::OnToolsRectangle()

{

MakeAllFlagsFalse();

bRectangleFlag = true;

}

void CPainterView::OnUpdateToolsDrawfreehand(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bDrawFlag);

}

void CPainterView::OnUpdateToolsEllipse(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bEllipseFlag);

}

void CPainterView::OnUpdateToolsFillfigure(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bFillFlag);

}

void CPainterView::OnUpdateToolsl_ine(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bLineFlag);

}

void CPainterView::OnUpdateToolsRectangle(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(bRectangleFlag);

}

void CPainterView::OnFileSave()

{

// ЗРОБИТИ: додайте код обробки команди

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

MetaFileHandle = CopyMetaFile(MetaFileHandle, "painter.wmf");

pDoc->pMetaFileDC->PlayMetaFile(MetaFileHandle);

DeleteMetaFile(MetaFileHandle);

}

void CPainterView::OnFileOpen()

{

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = GetMetaFile("painter.wmf");

pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

pDoc->pMetaFileDC->PlayMetaFile(MetaFileHandle);

DeleteMetaFile(MetaFileHandle);

Invalidate();

}

void CPainterView::OnFileNew()

{

// ЗРОБИТИ: додайте код обробки команди

CPainterDoc* pDoc = GetDocument();

HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close();

pDoc->pMetaFileDC->Create();

DeleteMetaFile(MetaFileHandle);

Invalidate();

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]