Implementacja rysowania obiektów w oknie programu
Stronę tą wyświetlono już: 2279 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ą:
- line - czyli linia;
- circle - czyli okrąg;
- rectangle - czyli prostokąt
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ę.

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:
- opisujące pióro rysowania przypisane do danego obiektu;
- opisujące wypełnienie rysowanego obiektu;
Uwzględniając wyżej wymienione klasy schemat zależności zmieni się do postaci pokazanej na poniższym rysunku.

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:
- dla klasy abstrakcyjnej i_dr_obj:
- i_dr_obj(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b) - konstruktor;
- virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) = 0; - metoda związana z konwersją do tekstu;
- virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tbrush) = 0; - metoda związana z rysowaniem.
- dla klas dziedziczących line, circle rectangle:
- konstruktory poszczególnych klas;
- virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) - metoda związana z konwersją do tekstu;
- virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tbrush) - metoda związana z rysowaniem.
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.


Tytuł:
Architektura oprogramowania bez tajemnic. Wykorzystaj język C++ do tworzenia wydajnych aplikacji i systemów
Autor:
Adrian Ostrowski, Piotr Gaczkowski

Tytuł:
Opus magnum C++ 11. Programowanie w języku C++. Wydanie II poprawione (komplet)
Autor:
Jerzy Grębosz

Tytuł:
Programowanie wieloplatformowe z C++ i wxWidgets 3
Autor:
Bartosz W. Warzocha

Tytuł:
Język C++ i przetwarzanie współbieżne w akcji. Wydanie II
Autor:
Anthony Williams

Tytuł:
C++ dla bystrzaków. Wydanie VII
Autor:
Stephen R. Davis

Tytuł:
Tablice informatyczne. Podstawy C++
Autor:
Radosław Sokół

Tytuł:
Opus magnum C++11. Programowanie w języku C++ (komplet)
Autor:
Jerzy Grębosz

Tytuł:
OpenCV 3. Komputerowe rozpoznawanie obrazu w C++ przy użyciu biblioteki OpenCV
Autor:
Adrian Kaehler, Gary Bradski

Tytuł:
C++ w 24 godziny. Wydanie VI
Autor:
Rogers Cadenhead, Jesse Liberty

Tytuł:
C++ Optymalizacja kodu. Sprawdzone techniki zwiększania wydajności
Autor:
Guntheroth Kurt