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

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

Приклад знаходиться у папці DISK\GDI\GDI10.

Створимо SDI програму для роботи з двовимірною графікою. У даній програмі необхідно вивести статистичну інформацію. Відповідна інформація задається трьома показниками: X – координата по вісі OX; Y – координата по вісі OY; Z – відмітка показника (значення досліджуємого показника).

Дана програма може бути призначена для виведення та аналізу довільної статистичної інформації.

При роботі з двовимірною графікою – треба мати можливість відібрати потрібні показники – за допомогою їх виділення у прямокутну область; переміщувати показники у площині; збільшувати та зменшувати масштаб виділеної області.

Для формування прямокутної області для виділення показників будемо використовувати ліву кнопку миші у сукупності з її переміщенням. Для переміщення показників – праву кнопку миші у сукупності з її переміщенням. При роботі з масштабуванням доцільно використовувати колесо миші.

На першому етапі для створення даної програми додамо у клас документу CGrafDoc вказівки на три вхідні масиви та змінну кількості елементів масивів:

int KOL;

double *X;

double *Y;

double *Z;

Потім до класу виду CGrafView додамо наступні змінні:

double Xmin_d, Xmax_d, Ymin_d, Ymax_d;

double Xmin, Xmax, Ymin, Ymax;

int KDX, KDY;

int ots_lev1, ots_lev2, ots_pra1, ots_pra2;

int ots_ver1, ots_ver2, ots_niz1, ots_niz2;

double K_e_px, K_e_py;

double CD;

double N;

CPoint startPt, endPt;

bool flag;

CFont font_os;

CFont font_nad;

CFont font_pok;

Змінні Xmin_d, Xmax_d, Ymin_d, Ymax_d – відповідають початковим граничним координатам по вісях OX та OY;

Змінні Xmin, Xmax, Ymin, Ymax в свою чергу характеризують граничні координати для виведення графіки;

KDX, KDY – кількість поділів відповідно по вісі OX та вісі OY;

ots_lev1, ots_lev2 – відступи ліворуч;

ots_pra1, ots_pra2 – відступи праворуч;

ots_ver1, ots_ver2 – відступи зверху;

ots_niz1, ots_niz2 – відступи знизу;

K_e_px – кількість одиниць на піксель по вісі OX, K_e_py – кількість одиниць на піксель по вісі OY;

CD – ціна ділення;

N – кратність кроку ціни ділення;

startPt, endPt – початкова та кінцева точки для формування резинового контуру;

flag – прапор для установки режимо роботи з двовимірною графікою (false – якщо включений режим переміщення та виділення у прямокутну область, true – режим колеса миші);

font_os, font_nad, font_pok – відповідно шрифт для виведення написів осей координат, напису заголовку та написів виведення показників.

Спочатку спрацьовує конструктор документу, в якому виділимо динамічну пам’ять трьом масивам – X, Y, Z. Приведемо текст даної функції.

CGrafDoc::CGrafDoc()

{

srand(time(0));

KOL = 100;

X = new double[KOL];

Y = new double[KOL];

Z = new double[KOL];

}

Потім спрацьовує функція OnNewDocument, в якій викликаємо функцію формування масиву Form_mas. Наведемо текст даних функцій.

BOOL CGrafDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

Form_mas();

return TRUE;

}

// Функція формування масива показників

void CGrafDoc::Form_mas()

{

int XY[101][101];

int xx, yy;

int kol = 0;

for (int i = 0; i < KOL + 1; i++)

for (int j = 0; j < KOL + 1; j++)

XY[i][j] = 0;

//формування точок з неоднаковими координатами

while (true)

{

xx = rand()%KOL + 1;

yy = rand()%KOL + 1;

if (XY[xx][yy] == 1) continue;

XY[xx][yy] = 1;

X[kol] = xx;

Y[kol] = yy;

Z[kol] = rand()%KOL + 1;

if (kol == KOL - 1) break;

kol++;

}

}

У даній функції відбувається заповнення масивів X, Y, Z випадковим чином числами від 1 до 100, при чому відповідні числа не повторюються.

Потім спрацьовує функція OnInitialUpdate класу CGrafView.

void CGrafView::OnInitialUpdate()

{

CView::OnInitialUpdate();

Nastroyka(); // Налаштування

Max_Min(); // Max-Min

}

У функції Nastroyka відбувається налаштування первинних параметрів. Приведемо текст даної функції.

// Функція налаштування

void CGrafView::Nastroyka()

{

CDC* dc = GetDC();

CFont* font_st;

font_st = dc->SelectObject(&font_os);

CSize pr_po_os = dc->GetTextExtent("9",1);

dc->SelectObject(&font_nad);

CSize pr_po_nad = dc->GetTextExtent("9",1);

dc->SelectObject(&font_pok);

CSize pr_po_pok = dc->GetTextExtent("9",1);

ots_lev1 = 4 * pr_po_os.cx;

ots_lev2 = 2 * pr_po_pok.cx;

ots_pra1 = 2 * pr_po_pok.cx;

ots_pra2 = 2 * pr_po_pok.cx;

ots_ver1 = 2 * pr_po_nad.cy;

ots_ver2 = 2 * pr_po_pok.cy;

ots_niz1 = 2 * pr_po_os.cy;

ots_niz2 = (int)(0.5 * pr_po_os.cy);

dc->SelectObject(font_st);

}

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

У функції Max_Min() відбувається пошук мінімальних та максимальних координат для розташування вхідних даних у площині. Це координати X та Y. Приведемо текст даної функції.

void CGrafView::Max_Min()

{

CGrafDoc* pDoc = GetDocument();

Xmax = Xmin = pDoc->X[0];

Ymax = Ymin = pDoc->Y[0];

for (int i = 1; i < pDoc->KOL ; i++)

{

if ( pDoc->X[i] > Xmax)Xmax = pDoc->X[i];

if ( pDoc->X[i] < Xmin)Xmin = pDoc->X[i];

if ( pDoc->Y[i] > Ymax)Ymax = pDoc->Y[i];

if ( pDoc->Y[i] < Ymin)Ymin = pDoc->Y[i];

}

Xmax = Xmax_d = ceil(Xmax);

Ymax = Ymax_d = ceil(Ymax);

Xmin = Xmin_d = floor(Xmin);

Ymin = Ymin_d = floor(Ymin);

}

Після початкової ініціалізації спрацьовує функція OnDraw.

void CGrafView::OnDraw(CDC* dc)

{

CGrafDoc* pDoc = GetDocument();

GetParentFrame()->SetWindowText("Работа с графикой");

int old_mode;

int i,koor_x, koor_y;

double pr;

CString buf;

CRect rect;

GetClientRect(&rect);

CFont* font_st;

font_st = dc->SelectObject(&font_nad);

old_mode = dc->SetBkMode(TRANSPARENT);

if(flag==false)buf = "Работа с двумерной графикой";

else buf = "Режим колесика";

//Прапор центрування

dc->SetTextAlign(TA_CENTER);

dc->TextOut(rect.Width()/2,

(ots_ver1 - dc->GetTextExtent("9").cy)/2,buf);

if (flag == false)

{

Xmin = floor(Xmin/N)*N;

Ymin = floor(Ymin/N)*N;

}

if (CD>=0)

{

pr = CD = 0;

while ((Xmin+CD*KDX)< Xmax || (Ymin+CD*KDY) < Ymax)

{

pr++;

CD=N*pr;

}

if(CD == 0)CD = N;

}

else

CD*=-1.0;

Xmax = Xmin+KDX*CD;

Ymax = Ymin+KDY*CD;

// -------------------------------------------------

K_e_px = (Xmax-Xmin)/(rect.right - rect.left

- ots_lev1 - ots_lev2 – ots_pra1 – ots_pra2);

K_e_py = (Ymax-Ymin)/(rect.bottom - rect.top

- ots_niz1 - ots_niz2 – ots_ver1 – ots_ver2);

CPen pen;

CPen* pen_st;

pen.CreatePen(PS_DOT,1,RGB(0,0,0));

pen_st = dc->SelectObject(&pen);

// горизонтальні вісі сітки та написи

dc->SelectObject(&font_os);

dc->SetTextAlign(TA_CENTER);

for (i = 0; i < KDY + 1; i++)

{

koor_x = ots_lev1;

koor_y = (int)((Ymax - Ymin - CD*i)/K_e_py +

ots_ver1 + ots_ver2);

//Написи вісі

if (flag == false)

{

buf.Format("%.0f",Ymin + CD*i);

dc->TextOut(ots_lev1/2,

koor_y-dc->GetTextExtent("9").cy/2,buf);

}

//лінії

dc->MoveTo(koor_x,koor_y);

koor_x+=(int)((Xmax-Xmin)/K_e_px+ots_lev2);

dc->LineTo(koor_x,koor_y);

}

// кінець горизонтальних вісей сітки та написів

//-------------------------------------------------

dc->SetTextAlign(TA_CENTER);

// вертикальні вісі сітки та написи

for (i = 0; i < KDX + 1; i++)

{

koor_y = ots_ver1 + ots_ver2;

koor_x = (int)((Xmax - Xmin - CD*i)/K_e_px +

ots_lev1 + ots_lev2);

dc->MoveTo(koor_x,koor_y);

koor_y+=(int)((Ymax-Ymin)/K_e_py + ots_niz2);

dc->LineTo(koor_x,koor_y);

if (flag == false)

if(!(i&1))//парні

{

buf.Format("%.0f",Xmax - CD*i);

dc->TextOut(koor_x, koor_y,buf);

}

}

// кінець вісей X ,Y

CBrush brush;

CBrush* brush_st;

brush.CreateSolidBrush(RGB(255,0,0));

brush_st = dc->SelectObject(&brush);

dc->SelectObject(&font_pok);

dc->SetTextAlign(TA_CENTER|TA_BOTTOM);

pen.DeleteObject();

pen.CreatePen(PS_NULL,1,RGB(0,0,0));

dc->SelectObject(&pen);

//---==============================================

for (i = 0; i < pDoc->KOL ; i++)

{

if (pDoc->X[i] > Xmax || pDoc->X[i] < Xmin ||

pDoc->Y[i] > Ymax || pDoc->Y[i] < Ymin) continue;

koor_x = (int)((pDoc->X[i]-Xmin)/K_e_px+ots_lev1+ots_lev2);

koor_y = (int)((Ymax-pDoc->Y[i])/K_e_py+ots_ver1+ots_ver2);

//Виведення номеру

buf.Format("%.0f",pDoc->Z[i]);

dc->TextOut(koor_x, koor_y,buf);

dc->Ellipse(koor_x-3,koor_y-3,koor_x+3,koor_y+3);

}

//----------------------------------------------------

dc->SetBkMode(old_mode);

dc->SelectObject(font_st);

dc->SelectObject(pen_st);

dc->SelectObject(brush_st);

}

Для виведення графіки важливо визначитися з ціною ділення CD. Ціна ділення визначається в такий спосіб.

// ----------------------------------------------------

if (CD>=0)

{

pr = CD = 0;

while((Xmin+CD*KDX)< Xmax || (Ymin+CD*KDY) < Ymax)

{

pr++;

CD=N*pr;

}

if(CD == 0)CD = N;

}

else

CD*=-1.0;

Xmax = Xmin+KDX*CD;

Ymax = Ymin+KDY*CD;

// -------------------------------------------------

Цикл while буде працювати до тих пір, поки не буде здійснено вихід за межі максимуму по вісі OX або по вісі OY.

Після визначення ціни ділення CD перераховуються координати максимуму Xmax та Ymax.

На наступному етапі розраховується кількість одиниць на піксель відповідно по вісі OX та OY.

K_e_px = (Xmax-Xmin)/(rect.right - rect.left

- ots_lev1 – ots_lev2 - ots_pra1 – ots_pra2);

K_e_py = (Ymax-Ymin)/(rect.bottom - rect.top

- ots_niz1 - ots_niz2 – ots_ver1 – ots_ver2);

Далі виводиться сітка координат, а також статистичні показники з координатами розташування X та Y та відміткою Z, яка виводиться над кожною точкою відповідного показника.

У функціях відгуку на натискання лівої або правої кнопки миші (OnLButtonDown, OnRButtonDown) відбувається фіксація введення даних від миші у дане вікно за допомогою функції SetCapture. Крім того, в якості курсору встановлюється хрестик (ідентифікатор IDC_CROSS) та ініціалізуються початкова та кінцева точки – startPt, endPt. Наведемо дані функції відгуку.

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

{

if (flag == true) return;

SetCursor(LoadCursor(NULL, IDC_CROSS));

SetCapture();

startPt = endPt = point;

CView::OnLButtonDown(nFlags, point);

}

void CGrafView::OnRButtonDown(UINT nFlags, CPoint point)

{

if (flag == true) return;

SetCursor(LoadCursor(NULL, IDC_CROSS));

SetCapture();

startPt = endPt = point;

CView::OnRButtonDown(nFlags, point);

}

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

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

{

if (flag == true)

{

ReleaseCapture();

return;

}

if(nFlags!=MK_LBUTTON&&nFlags!=MK_RBUTTON)

{

CView::OnMouseMove(nFlags, point);

return;

}

if(endPt != point)

{

CDC *dc = GetDC();

int oldMode = dc->SetROP2(R2_NOTXORPEN);

CPen pen;

pen.CreatePen(PS_DOT,1,RGB(0,0,255));

dc->SelectObject(&pen);

if (nFlags==MK_LBUTTON)

{

dc->Rectangle(&CRect(startPt,endPt));

endPt = point;

dc->Rectangle(&CRect(startPt,endPt));

}

if (nFlags==MK_RBUTTON)

{

dc->MoveTo (startPt);

dc->LineTo (endPt);

endPt = point;

dc->MoveTo (startPt);

dc->LineTo (endPt);

}

dc->SetROP2(oldMode);

}

CView::OnMouseMove(nFlags, point);

}

У даній функції перевіряємо змінну nFlags. Якщо вона дорівнює MK_LBUTTON – натиснута ліва кнопка миші, якщо MK_RBUTTON – права.

В якості режиму роботи з пікселями за допомогою функції SetROP2 вибрано режим R2_NOTXORPEN, тобто зображення повинно малюватися декілька разів – перший раз відбувається затирання попереднього зображення, другий раз малюється нове зображення.

Для розтягування прямокутника лівою кнопкою миші його необхідно намалювати два рази:

dc->Rectangle(&CRect(startPt,endPt));

endPt = point;

dc->Rectangle(&CRect(startPt,endPt));

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

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

dc->MoveTo (startPt);

dc->LineTo (endPt);

endPt = point;

dc->MoveTo (startPt);

dc->LineTo (endPt);

При відпусканні лівої кнопки миші спрацьовує відгук OnLButtonUp, у якому треба перерахувати мінімальні та максимальні координати виведення графіки по осям OX та OY.

Приведемо текст даної функції відгуку.

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

{

if (flag == true) return;

if (abs(startPt.x - endPt.x) < 4

|| abs(startPt.y - endPt.y) < 4)

{

if (startPt!=endPt)

{

MessageBox("Размер окна ограничен\n

необходимо задавать большее окно");

Invalidate();

}

ReleaseCapture();

return;

}

CRect outline(startPt,endPt);

outline.NormalizeRect();

Xmax = (outline.right - ots_lev1 – ots_lev2)* K_e_px + Xmin;

Xmin = (outline.left - ots_lev1 – ots_lev2)* K_e_px + Xmin;

Ymin = Ymax - (outline.bottom - ots_ver1 – ots_ver2)*K_e_py;

Ymax = Ymax - (outline.top - ots_ver1 – ots_ver2)*K_e_py;

Invalidate();

ReleaseCapture();

CView::OnLButtonUp(nFlags, point);

}

На початку треба нормалізувати виділену прямокутну область за допомогою функції NormalizeRect. Це робиться для того щоб перша точка головної діагоналі прямокутника була у лівому верхньому куті, друга точка – у правому нижньому куті (для правильного переведення мінімальних та максимальних координат виділеної прямокутної області).

Для переведення координат з пікселів до реальних треба скористатися величинами K_e_px та K_e_py – кількість одиниць на піксель відповідно по вісі OX та OY. Відносно нормалізованого прямокутнику outline дані рядки будуть мати наступний вигляд.

Xmax = (outline.right - ots_lev1 – ots_lev2)* K_e_px + Xmin;

Xmin = (outline.left - ots_lev1 – ots_lev2)* K_e_px + Xmin;

Ymin = Ymax - (outline.bottom - ots_ver1 – ots_ver2)*K_e_py;

Ymax = Ymax - (outline.top - ots_ver1 – ots_ver2)*K_e_py;

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

Наприкінці потрібно викликати функцію ReleaseCapture для того, щоб інші вікна могли отримувати повідомлення від миші.

При відпусканні правої кнопки миші спрацьовує відгук OnRButtonUp, у якому треба перерахувати мінімальні та максимальні координати виведення графіки по осям OX та OY в залежності від переміщення.

Приведемо текст даної функції відгуку.

void CGrafView::OnRButtonUp(UINT nFlags, CPoint point)

{

if (flag == true) return;

ReleaseCapture();

if(startPt != endPt)

{

Xmin = Xmin-(endPt.x - startPt.x)*K_e_px;

Ymin = Ymin-(startPt.y - endPt.y)*K_e_py;

CD = -1*CD;

Invalidate();

}

CView::OnRButtonUp(nFlags, point);

}

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

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

BOOL CGrafView::OnMouseWheel(UINT nFlags, short zDelta,

CPoint pt)

{

if(flag==false)return 0;

double x,y,k_uv,vr_cd;

ScreenToClient(&pt);

x = (pt.x - ots_lev1-ots_lev2)* K_e_px + Xmin;

y = Ymax – (pt.y – ots_ver1 – ots_ver2)*K_e_py;

k_uv = (zDelta > 0) ? 1.1 : 0.9;

vr_cd = floor(CD * k_uv / N) * N;

if (fabs(CD - vr_cd) < 0.0001)

vr_cd = (k_uv > 1) ? vr_cd += N : vr_cd -= N;

if (vr_cd <= N) vr_cd = N;

if (vr_cd > (Xmax_d - Xmin_d) &&

vr_cd > (Ymax_d - Ymin_d))

return 0;

Xmin = x -(x - Xmin)/CD*vr_cd;

Ymin = y -(y - Ymin)/CD*vr_cd;

CD = - vr_cd;

SetCursor(LoadCursor(NULL, IDC_CROSS));

SetCapture();

Invalidate();

return CView::OnMouseWheel(nFlags, zDelta, pt);

}

Зміна масштабу відбувається відносно поточної позиції курсору pt. Координати точки pt містять екранні координати, отже їх треба привести в координати клієнтської області. Робиться це за допомогою функції ScreenToClient. Координати цієї точки перераховуються до реальних координат x та y відповідно.

Потім за допомогою параметру zDelta формуємо коефіцієнт збільшення масштабу – змінну k_uv. Якщо колесо крутиться вперед – zDelta>0, k_uv = 1,1, отже ціна ділення vr_cd збільшується, а масштаб зображення зменшується. І навпаки, якщо колесо крутиться назад – zDelta<0, k_uv = 0,9, отже ціна ділення vr_cd зменшується, а масштаб зображення збільшується.

У наступних рядках коду продемонстровано перерахунок граничних координат відносно координат x та y, а також привласнення ціні ділення CD змінної vr_cd з протилежним знаком. Це робиться для того, щоб в функції OnDraw не відбувалось перерахування ціни ділення CD.

Xmin = x -(x - Xmin)/CD*vr_cd;

Ymin = y -(y - Ymin)/CD*vr_cd;

CD = - vr_cd;

Для того щоб відновити поточні налаштування необхідно викликати функцію OnVosst, у якій необхідно викликати функції налаштування – Nastroyka та пошук граничних показників – Max_Min. Приведемо текст даної функції.

void CGrafView::OnVosst()

{

Nastroyka(); // Налаштування

Max_Min(); //Max-Min

Invalidate();

}

Отже, в даній програмі в повній мірі розглянуто роботу з двовимірною графікою з використанням резинового контуру. Задіяні всі відгуки миші: натискання лівої кнопки миші – OnLButtonDown; відпускання лівої кнопки миші – OnLButtonUp; натискання правої кнопки миші – OnRButtonDown; відпускання правої кнопки миші – OnRButtonUp; переміщення миші – OnMouseMove; робота з колесом миші – OnMouseWheel.

Результат виконання програми з двовимірною графікою приведено на рис. 5.28.

Рис. 5.28. Результат роботи з двовимірною графікою

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