Implementacja rysowania obiektów w oknie programu

Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 3234 razy

Wstęp

Do tej pory w programie Rysowanie omawiane były jedynie elementy związane z implementacją graficznego interfejsu użytkownika. Interfejs ten jest oczywiście mocno ograniczony do minimum, ponieważ jedynym moim celem jest pokazanie w przybliżeniu jak można stworzyć podstawy do utworzenia bardziej złożonych programów. Celem naszego programu jest oczywiście rysowanie różnych obiektów, a tymi obiektami są:

W powyższym nazewnictwie użyłem nazw angielskich, ponieważ również i w kodzie programu będę takich nazw używał tworząc deklarację klas opisujących te obiekty. I teraz powstaje pewien problem, ponieważ obiekty powinny być rysowane kolejno jeden nad drugim a ja muszę je jakoś przechowywać w jednym kontenerze. Jak to zrobić? Odpowiedź jest prosta, trzeba utworzyć interfejs, czyli klasę abstrakcyjną, po której moje klasy opisujące poszczególne obiekty będą dziedziczyły. Spójrzmy więc łaskawym okiem na poniższą ilustrację.

Diagram pokazujący powiązania klas opisujących obiekty z interfejsem
Rys. 1
Diagram pokazujący powiązania klas opisujących obiekty z interfejsem

Jak widać na powyższym rysunku, klasa i_dr_obj {i - interface (interfejs), dr - draving (rysowanego), obj - object (obiektu)} jest dziedziczona przez wszystkie klasy opisujące obiekty rysowane w programie, czyli: line; circle oraz rectangle. Teraz mogę sobie spokojnie przechowywać moje obiekty w kontenerze wskaźników do interfejsów std::vector<i_dr_obj*>. Oczywiście klasa i_dr_obj musi zawierać funkcje abstrakcyjne i wymuszać obsługę pewnych mechanizmów, które są związane z obsługą wyświetlania i dodawania nowego obiektu do programu.

Dodatkowo wewnątrz programu utworzone zostaną dwie grupy klas:

Uwzględniając wyżej wymienione klasy schemat zależności zmieni się do postaci pokazanej na poniższym rysunku.

Schemat klas w programie Rysowanie
Rys. 2
Schemat klas w programie Rysowanie.

Jak widać na powyższym schemacie klasy pen i hpen mają luźnie powiązanie z klasą abstrakcyjną i_dr_obj oraz z klasami line, circle oraz rectangle. Luźne powiązanie polega na tym, że klasy pen oraz hpen zostały wykorzystane w następujących metodach oraz konstruktorze wcześniej wymienionych klas:

Kod klas utworzonych na potrzeby programu

Zanim opiszę kod klas, najpierw trzeba do projektu dodać plik drawing_class_def.h w taki sam sposób jak plik winmain.cpp. Oto kod programu, który powinien znaleźć się w tym pliku:

#ifndef DRAWING_CLASS_DEF #define DRAWING_CLASS_DEF #include <windows.h> #include <string> // ######################### KLASA pen ############################ // Ta klasa będzie opisywała wędzel obramowania obiektów: // r - składowa czerwona; // g - składowa zielona; // b - składowa niebieska; // width - szerokość pióra; // ps_style - styl linii pióra class pen{ protected: BYTE r; // składowa czerwona koloru pióra BYTE g; // składowa zielona koloru pióra BYTE b; // składowa niebieska koloru pióra int width; // szerokość pióra public: enum ps{ // dostępne ustawienia stylu pióra solid = PS_SOLID, // linia ciągła dash = PS_DASH, // przerwywana dot = PS_DOT, // kropkowana dashdot = PS_DASHDOT, // kreska kropka dashdotdot = PS_DASHDOTDOT, // kreska kropka kropka null = PS_NULL, // brak insideframe = PS_INSIDEFRAME // a to nie wiem }; protected: enum ps ps_style; // styl pióra public: pen():r(255), g(0), b(0), width(1), ps_style(solid){}; // domyślny konstruktor pen(BYTE r, BYTE g, BYTE b, int width, enum ps ps_style) : r(r), g(g), b(b), width(width), ps_style(ps_style){}; // konstruktor ustawiający pen(const pen &p):r(p.r), g(p.g), b(p.b), width(p.width), ps_style(p.ps_style){}; // konstruktor kopiujący virtual void SetColor(BYTE r, BYTE g, BYTE b){ this->r = r; this->b = b; this->g = g; } virtual void SetStyle(enum ps style){ ps_style = style; } virtual void SetPen(BYTE r, BYTE g, BYTE b, enum ps style){ this->r = r; this->g = g; this->b = b; ps_style = style; } bool operator ==(const pen &p) const { // operator porównania if(!memcmp(this, &p, sizeof(pen))){ return true; } return false; } std::wstring toString() const { // zamiana na ciąg znaków std::wstring str; wchar_t buffor[100]; wsprintf(buffor, L"r %i; g %i; b %i; width %i; style %i", r, g, b, width, (int)ps_style); str = buffor; return str; } }; // ########################## KLASA hpen ########################### class hpen : public pen{ HPEN hPen; // uchwyt pędzla public: hpen(BYTE r, BYTE g, BYTE b, int width, pen::ps style) : pen(r, g, b, width, style), hPen(CreatePen((int)style, width, RGB(r, g, b))){}; hpen(const pen p) : pen(p), hPen(CreatePen((int)ps_style, width, RGB(r, g, b))){}; hpen(const hpen &hp) : pen(hp), hPen(CreatePen((int)ps_style, width, RGB(r, g, b))){}; hpen() : pen(), hPen(CreatePen((int)ps_style, width, RGB(r, g, b))){}; void SelectPen(HDC &hdc){ SelectObject(hdc, hPen); } virtual void SetColor(BYTE r, BYTE g, BYTE b){ this->r = r; this->b = b; this->g = g; DeleteObject(hPen); hPen = CreatePen((int)ps_style, width, RGB( r, g, b)); } virtual void SetStyle(enum ps style){ ps_style = style; DeleteObject(hPen); hPen = CreatePen((int)ps_style, width, RGB( r, g, b)); } virtual void SetPen(BYTE r, BYTE g, BYTE b, enum ps style){ this->r = r; this->g = g; this->b = b; ps_style = style; DeleteObject(hPen); hPen = CreatePen((int)ps_style, width, RGB( r, g, b)); } ~hpen(){ DeleteObject(hPen); } }; // ############################# KLASA brush ##################################### class brush{ protected: BYTE r; BYTE g; BYTE b; public: brush(BYTE r, BYTE g, BYTE b):r(r), g(g), b(b){}; brush(const brush &br) : r(br.r), g(br.g), b(br.b){}; virtual void SetColor(BYTE r, BYTE g, BYTE b){ this->r = r; this->g = g; this->b = b; } std::wstring toString() const { std::wstring str; wchar_t buffor[100]; wsprintf(buffor, L"brush: r %i, g %i, b %i", r, g, b); str = buffor; return str; } bool operator ==(const brush &br) const { if(memcmp(this, &br, sizeof(brush))){ return false; } return true; } }; // ############################# KLASA hbrush ################################## class hbrush : public brush{ protected: HBRUSH hBrush; // uchwyt wypełnienia public: hbrush(const brush &br) : brush(br), hBrush(CreateSolidBrush(RGB(r, g, b))){}; hbrush(const hbrush &hbr) : brush(hbr), hBrush(CreateSolidBrush(RGB(r, g, b))){}; virtual void SetColor(BYTE r, BYTE g, BYTE b){ this->r = r; this->g = g; this->b = b; DeleteObject(hBrush); hBrush = CreateSolidBrush(RGB(r, g, b)); } void SelectBrush(HDC &hdc){ SelectObject(hdc, hBrush); } ~hbrush(){ DeleteObject(hBrush); } }; // ###################### TYP ENUMEROWANY DLA OPISU TRYBU PRACY PROGRAMU ##################### enum state{ dr_line, // rysowanie linii dr_circle, // rysowanie okręgu dr_rect, // rysowanie prostokąta sel // tryb edycji }; // ############### DAFINICJA INTERFEJSU KLAS DZIEDZICZĄCYCH ############### class i_dr_obj{ // i - interface; dr - draving; obj - object (interfejs rysowanego obiektu) protected: bool selected; int pen_index; // index pędzla rysowania int brush_index; // index wypełnienia rysowania public: i_dr_obj(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b):selected(false), pen_index(-1), brush_index(-1){ for(int i = 0; i < tPen.size(); i++){ // dla każdego elementu kontenera tPen if((pen)tPen[i] == p){ // sprawdzam, czy nie ma już utworzonego pióra o takich samych parametrach pen_index = i; // jeżeli jest takie pióro to zapamiętuję jego index break; // i kończę zabawę z iterowaniem } } if(pen_index == -1){ // jak pen_index jest równe -1 to znaczy, że nie ma w tPen takiego pióra więc pen_index = tPen.size(); // przypisuję index ostatniego elementu hpen hp(p); // tworzę nową definicję pióra tPen.push_back(hp); // dodaję definicję pióra } for(int i = 0; i < tBrush.size(); i++){ // dla każdego elementu kontenera tBrush if((brush)tBrush[i] == b){ // sprawdzam, czy nie ma już utworzonego pędzla o takich samych parametrach brush_index = i; // jeżeli taki pędzel już istnieje to zapamiętuję jego index break; // i kończę zabawę z iterowaniem } } if(brush_index == -1){ // jak brush_index jest równy -1 to znaczy, że w tBrush nie ma takiego pędzla brush_index = tBrush.size(); // więc przypisuję index ostatniego elementu kontenera (który zaraz dodam) hbrush hbr(b); // tworzę ten element tBrush.push_back(hbr); // i dodaję go do kontenera } }; virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const = 0; // każda definicja klasy musi obsługiwać tę metodę virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tbrush) const = 0; // każda definicja klasy musi obsługiwać tę metodę virtual void WmLButtonDown(POINT &mp) = 0; virtual void WmLButtonUp(POINT &mp) = 0; bool Selected() const {return selected;}; // określa, czy obiekt został zaznaczony virtual bool Select(POINT &ps) = 0; // każda definicja klasy musi obługiwać tę metodą virtual ~i_dr_obj(){}; }; // ######################### DEFINICJA KLASY LINE (LINIA) ######################## class line : public i_dr_obj{ POINT begin; POINT end; public: line(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b):i_dr_obj(tPen, tBrush, p, b){begin.x = begin.y = end.x = end.y = 0;}; virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const { std::wstring str; wchar_t buffor[100]; wsprintf(buffor, L"Line %i %i; %i %i", begin.x, begin.y, end.x, end.y); str = buffor; str += tPen[this->pen_index].toString(); str += tBrush[this->brush_index].toString(); return str; } virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const { tPen[pen_index].SelectPen(hdc); tBrush[brush_index].SelectBrush(hdc); MoveToEx(hdc, begin.x, begin.y, NULL); LineTo(hdc, end.x, end.y); } virtual void WmLButtonDown(POINT &mp){ begin = mp; end = mp; } virtual void WmLButtonUp(POINT &mp){ end = mp; } virtual bool Select(POINT &ps){ return false; } }; // ######################### DEFINICJA KLASY CIRCLE (OKRĄG) ######################## class circle : public i_dr_obj{ POINT begin; POINT end; public: circle(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b):i_dr_obj(tPen, tBrush, p, b){begin.x = begin.y = end.x = end.y = 0;}; virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const { std::wstring str; wchar_t buffor[100]; wsprintf(buffor, L"Circle %i %i; %i %i", begin.x, begin.y, end.x, end.y); str = buffor; str += tPen[this->pen_index].toString(); str += tBrush[this->brush_index].toString(); str += tBrush[this->brush_index].toString(); return str; } virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const { tPen[pen_index].SelectPen(hdc); tBrush[brush_index].SelectBrush(hdc); Ellipse(hdc, begin.x, begin.y, end.x, end.y); } virtual void WmLButtonDown(POINT &mp){ begin = mp; end = mp; } virtual void WmLButtonUp(POINT &mp){ end = mp; if(abs(begin.x - end.x) < abs(begin.y - end.y)){ end.y = begin.y + abs(begin.x - end.x) * ( begin.y < end.y ? 1 : -1); }else{ end.x = begin.x + abs(begin.y - end.y) * ( begin.x < end.x ? 1 : -1); } } virtual bool Select(POINT &ps){ return false; } }; // ######################### DEFINICJA KLASY RECTANGLE (PROSTOKĄT) ######################## class rectangle : public i_dr_obj{ POINT begin; POINT end; public: rectangle(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b):i_dr_obj(tPen, tBrush, p, b){begin.x = begin.y = end.x = end.y = 0;}; virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const { std::wstring str; wchar_t buffor[100]; wsprintf(buffor, L"Rectangle %i %i; %i %i", begin.x, begin.y, end.x, end.y); str = buffor; str += tPen[this->pen_index].toString(); str += tBrush[this->brush_index].toString(); return str; } virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const { tPen[pen_index].SelectPen(hdc); tBrush[brush_index].SelectBrush(hdc); Rectangle(hdc, begin.x, begin.y, end.x, end.y); } virtual void WmLButtonDown(POINT &mp){ begin = mp; end = mp; } virtual void WmLButtonUp(POINT &mp){ end = mp; } virtual bool Select(POINT &ps){ return false; } }; #endif

Kod główny programu

W pliku winmain.cpp trzeba załączyć wcześniej utworzony plik drawing_class_def.h dopisując następującą linijkę kodu:

#include "drawing_class_def.h"

na samym początku kodu. Teraz cała funkcja procedury okna potomnego rysowania WndDrawingProc będzie wyglądała następująco:

// ################################### Procedura okna dokumentu ##################################### LRESULT CALLBACK WndDrawingProc(HWND hWndDraw, UINT msg, WPARAM wParam, LPARAM lParam){ static POINT mousepos; // współrzędne kursora w oknie static i_dr_obj* obj; // wskaźnik na interfejs dodawanego obiektu static std::vector<i_dr_obj*> tObj; // tablica interfejsów obiektów rysowanych static enum state st; // tryb rysowania lub edycji static std::vector<hpen> tPen; // tablica wykorzystywanych pędzli static std::vector<hbrush> tBrush; // tablica wykorzystywanych wypełnień switch(msg){ case WM_CREATE: { obj = NULL; } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWndDraw, &ps); SetBkMode(hdc, TRANSPARENT); for(std::vector<i_dr_obj*>::iterator i = tObj.begin(); i < tObj.end(); i++){ (*i)->Draw(hdc, tPen, tBrush); // rysowanie dodanych obiektów } if(obj){ obj->Draw(hdc, tPen, tBrush); // rysowanie dodawanego obiektu } EndPaint(hWndDraw, &ps); } break; case WM_LBUTTONDOWN: { SetCapture(hWndDraw); switch(st){ case dr_line: // tworzę obiekt typu line { obj = new line(tPen, tBrush, pen(255,0,0,1,pen::ps::solid), brush(0,0,150)); obj->WmLButtonDown(mousepos); } break; case dr_circle: // tworzę obiekt typu circle { obj = new circle(tPen, tBrush, pen(0,255,0,1,pen::ps::solid), brush(0,150,0)); obj->WmLButtonDown(mousepos); } break; case dr_rect: // tworzę obiekt typu rectangle { obj = new rectangle(tPen, tBrush, pen(0, 150, 255, 1, pen::ps::solid), brush(150,0,200)); obj->WmLButtonDown(mousepos); } break; case sel: // trzyb zaznaczania (nie obsłużony jeszcze) { } break; } } break; case WM_LBUTTONUP: { switch(st){ case dr_line: // dodawanie obiektu do kontenera { if(obj){ obj->WmLButtonUp(mousepos); tObj.push_back(obj); } } break; case dr_circle: // dodawanie obiektu do kontenera { if(obj){ obj->WmLButtonUp(mousepos); tObj.push_back(obj); } } break; case dr_rect: // dodawanie obiektu do kontenera { if(obj){ obj->WmLButtonUp(mousepos); tObj.push_back(obj); } } break; case sel: // tryb edycji (nie obsłużony) { } break; } obj = NULL; ReleaseCapture(); } break; case WM_MOUSEMOVE: { mousepos.x = LOWORD(lParam); // pobieranie współrzędnej x mousepos.y = HIWORD(lParam); // pobieranie współrzędnej y wchar_t buffor[100]; // bufor do konwersji na tekst wsprintf(buffor, L"X = %i",mousepos.x); // konwersja na tekst współrzędnej x SendMessage(statusbar, SB_SETTEXT, 0, (LPARAM)buffor); // ustawienie tekstu pierwszej pozycji paska kontrolki statusbar wsprintf(buffor, L"Y = %i",mousepos.y); // konwersja na tekst współrzędnej y SendMessage(statusbar, SB_SETTEXT, 1, (LPARAM)buffor); // ustawienie tekstu drugiej pozycji paska kontrolki statusbar if(wParam & MK_LBUTTON && obj){ obj->WmLButtonUp(mousepos); // zmiana obiektu, podczas ruchu mychy InvalidateRect(hWndDraw, NULL, true); // odświerzanie okna w celu odrysowania nowych ustawień obiektu } } break; case WM_COMMAND: { if((HWND)lParam == toolbar){ switch(LOWORD(wParam)){ case ID_LINE: // komunikat przychodzący od przycisku toolbar-a o identyfikatorze ID_LINE { st = dr_line; SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie linii"); // ustawienie tekstu w ostatnim polu statusbar-u } break; case ID_CIRCLE: // to samo co poprzednio, tylko dla ID_CIRCLE { st = dr_circle; SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie okręgu"); // ustawienie tekstu w ostatnim polu statusbar-u } break; case ID_RECTANGLE: // to samo co poprzednio, tylko dla ID_RECTANGLE { st = dr_rect; SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie prostokąta"); // ustawienie tekstu w ostatnim polu statusbar-u } break; case ID_SELECT: // to samo co poprzednio, tylko dla ID_SELECT { st = sel; SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie edycja"); // ustawienie tekstu w ostatnim polu statusbar-u } break; } } } break; case WM_DESTROY: // tutaj sprzątanko się rozpoczyna { for(std::vector<i_dr_obj*>::iterator i = tObj.begin(); i < tObj.end(); i++){ delete *i; // usuwanie obiektów } // czyszczenie kontenerów tObj.clear(); tPen.clear(); tBrush.clear(); } break; } return DefWindowProc(hWndDraw, msg, wParam, lParam); }

W tej chwili program już rysuje poszczególne obiekty, ale jeszcze nie ma możliwości zaznaczania i zmiany ustawień obiektów. Poniżej zamieszczam screen programu.

Rysujący program Rysowanie
Rys. 3
Widok programu Rysowanie, który jak widać już rysuje.