Implementacja zaznaczania dynamicznie dodawanych obiektów

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

Wstęp

Pierwsze co należy zrobić zanim przystąpi się do obsługi zadania związanego z zaznaczaniem naszych obiektów w programie Rysowanie, konieczne jest zastanowienie się, jak powinno wyglądać takie zaznaczanie obiektów? Oto kilka zasad, jakie ja postawiłem sobie aby mój program spełniał:

  • wyróżnienie obiektu, nad którym znajduje się kursor myszki;
  • zaznaczenie obiektu klikniętego lewym przyciskiem myszki, który znajduje się pod kursorem myszki;
  • jeżeli pod kursorem myszki nie ma żadnego obiektu w chwili kliknięcia lewym przyciskiem myszy to odznacz wszystkie obiekty i umożliw zaznaczanie obiektów po upuszczeniu lewego przycisku myszy, które znajdą się w prostokącie wyznaczonym przez punkt kliknięcia oraz punkt zwolnienia lewego klawisza myszki;
  • jeżeli wciśnięty został klawisz shift to umożliw dodawanie do zaznaczenia pojedynczych obiektów.

Do realizacji tego zadania zostanie utworzona specjalna klasa o nazwie select_obj, która będzie odpowiedzialna za realizację zadań związanych z zaznaczaniem i edycją obiektów. Poniżej zamieszczam mały diagram UML zależności klas w programie.

Schemat UML
Rys. 1
Diagram UML relacji klas w programie Rysowanie

Klasa select_obj jest powiązana z klasą i_dr_obj za pośrednictwem pola tSelObj oraz konstruktora i metod związanych z obsługą zaznaczania obiektów.

Mechanizm wyrywania obiektu pod kursorem myszki

Podstawowe pytanie, jakie należy sobie zadać, to: jak wykrywać obiekt znajdujący się pod kursorem myszki? Odpowiedź na to pytanie nie jest prosta, ponieważ ten mechanizm jest zależy od rodzaju obiektu. Dla linii trzeba wykryć, czy obiekt znajduje się nad tą linią z pewną dopuszczalną tolerancją błędu, dla prostokąta czy punkt kursora znajduje się w jego wnętrzu, podobnie rzecz się ma z okręgiem, ale oba te obiekty muszą realizować swoje zadania w nieco inny sposób.

Wykrywanie kursora nad linią

Jak wykryć kursor myszy nad linią a ściślej rzecz ujmując nad odcinkiem? Pierwszym etapem jest przeprowadzenie pewnych obliczeń wstępnych, polegających na rzutowaniu punktu kursora myszki mousepos na linię przechodzącą przez punkty begin i end. I tu pojawia się pierwszy wyjątek. Wyjątek ten polega na tym, że punkty begin oraz end nie mogą się pokrywać, ponieważ gdy się pokrywają nie da się rzutować punktu. Z tego względu trzeba najpierw sprawdzić czy współrzędne punktu begin są równe współrzędnym punktu end i jeżeli tak, to należy sprawdzić czy punkt kursora myszy mousepos nie jest w pewnej odległości mniejszej od zadanej wartości. Do obliczeń należy wykorzystać stary dobry wzór Pitagorasa:

Dobra - ktoś powie, o co tutaj chodzi? Czyżby twierdzenie Pitagorasa uległo zmianie od czasów, gdy ukończyłem podstawówkę? Co to w ogóle za wzór jest?! Spokojnie to jest twierdzenie Pitagorasa zapisane w nieco innej bo wektorowej postaci. Trzeba mieć świadomość, że iloczyn skalarny dwóch wektorów jest równy sumie iloczynu poszczególnych składowych. A więc powyższy wzór można rozpisać do bardziej normalnej postaci:

Skoro obsłużony został ten paskudny wyjątek, teraz można zakasać rękawy i obliczyć prostopadły punktu kursora myszki mousepos na linię. Do tego celu należy wykorzystać wzory z strony Programowanie → Algorytmy obliczeniowe → Rzutowanie punktu na prostą. Wzorów tych nie będę tutaj przytaczał napiszę tylko, że po rzutowaniu należy obliczyć odległość tegoż punktu od kursora myszy i jeżeli jest on mniejszy od pewnej zadanej wartości to należy jeszcze sprawdzić czy punkt zrzutowany znajduje się pomiędzy końcami linii.

Poniżej zamieszczam fragment kodu klasy line, który dotyczy wykrywania obiektu tego typu znajdującego się pod kursorem myszy:

Listing 1
  1. virtual bool CursorOnObject(POINT &ps) const {
  2. if(begin.x == end.x && begin.y == end.y){ // gdy punkty się pokrywają
  3. if((begin.x - ps.x) * (begin.x - ps.x) > ZN_SIZE * ZN_SIZE){ // oraz kwadrat odległości od kursora od punktu jest większa od zadanej tolerancji
  4. return false; // to zwracaj fałsz bo obiket jest poza zasięgiem zaznaczenia
  5. }
  6. return true; // w przeciwnym razie zwracaj prawdę
  7. }
  8. float u = float((ps.x - begin.x)*(end.x -begin.x)+(ps.y-begin.y)*(end.y-begin.y)) / (pow(begin.x - end.x, 2.f) + pow(begin.y - end.y, 2.f)); // współrzynnik umożliwiający znalezienie punktu zrzutowanego na linię
  9. POINT pt; // zmienna punktu zrzutowanego na linię
  10. pt.x = begin.x + (end.x - begin.x) * u; // współrzędna x zrzutowanego punktu
  11. pt.y = begin.y + (end.y - begin.y) * u; // współrzędna y zrzutowanego punktu
  12. if(pt.x >= min(begin.x, end.x) && pt.x <= max(begin.x, end.x) && pt.y >= min(begin.y, end.y) && pt.y <= max(begin.y, end.y)){ // jak punkt zawiera się w zadanym zakresie wartości to
  13. if((pt.x - ps.x) * (pt.x - ps.x) + (pt.y - ps.y) * (pt.y - ps.y) <= ZN_SIZE * ZN_SIZE){ // jak kwadrat odległości jest mniejszy lub równy kwadratowi maksymalnego odchylenia to
  14. return true; // zwracaj prawdę
  15. }
  16. }
  17. return false; // w przeciwnym przypadku zwracaj fałsz
  18. }

Wykrywanie kursora nad kołem

W przypadku koła, czyli klasy circle znów trzeba się posilić twierdzeniem Pitagorasa, jednakże do tego celu będzie potrzebny punkt centralny, od którego kwadrat odległości dzielącej go od punktu kursora myszki go dzieli. Kod realizujący tę część zadania będzie wyglądał następująco:

Listing 2
  1. virtual bool CursorOnObject(POINT &ps) const {
  2. POINT cp = GetCenterPoint(); // metoda zwracająca współrzędne punktu centralnego
  3. UINT ray = abs(begin.x - end.x) * 0.5; // promień okręgu
  4. if((cp.x - ps.x) * (cp.x - ps.x) + (cp.y - ps.y) * (cp.y - ps.y) > ray * ray){ // jak kwadrat odległości kursora od punktu centralnego jest większy od kwadratu promienia okręgu to
  5. return false; // zwracaj fałsz
  6. }
  7. return true; // w przeciwnym przypadku zwracaj prawdę
  8. }

Wykrywanie kursora nad prostokątem

W przypadku prostokąta sprawa jest jeszcze prostsza, ponieważ istnieje pewna funkcja PtInRect, która sama odwali za nas brudną robotę. Kod realizujący to zadanie będzie miał następującą postać:

Listing 3
  1. virtual bool CursorOnObject(POINT &ps) const {
  2. RECT r; // zmienna pomocnicza
  3. SetRect(&r, min(begin.x, end.x), min(begin.y, end.y), max(begin.x, end.x), max(begin.y, end.y)); // ustawienie prostokąta
  4. return PtInRect(&r, ps); // zwraca prawdę, gdy punkt jest w prostokącie a fałsz gdy nie jest
  5. }

Zmiany w kodzie programu

Zmiany w kodzie klasy i_dr_obj oraz klas dziedziczących po niej

Oto zmieniony nieco kod klasy i_dr_obj oraz klas po niej dziedziczących:

Listing 4
  1. // ############### DAFINICJA INTERFEJSU KLAS DZIEDZICZĄCYCH ###############
  2. class i_dr_obj{ // i - interface; dr - draving; obj - object (interfejs rysowanego obiektu)
  3. protected:
  4. bool selected;
  5. int pen_index; // index pędzla rysowania
  6. int brush_index; // index wypełnienia rysowania
  7. public:
  8. i_dr_obj(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b):selected(false), pen_index(-1), brush_index(-1){
  9. for(int i = 0; i < tPen.size(); i++){ // dla każdego elementu kontenera tPen
  10. if((pen)tPen[i] == p){ // sprawdzam, czy nie ma już utworzonego pióra o takich samych parametrach
  11. pen_index = i; // jeżeli jest takie pióro to zapamiętuję jego index
  12. break; // i kończę zabawę z iterowaniem
  13. }
  14. }
  15. if(pen_index == -1){ // jak pen_index jest równe -1 to znaczy, że nie ma w tPen takiego pióra więc
  16. pen_index = tPen.size(); // przypisuję index ostatniego elementu
  17. hpen hp(p); // tworzę nową definicję pióra
  18. tPen.push_back(hp); // dodaję definicję pióra
  19. }
  20. for(int i = 0; i < tBrush.size(); i++){ // dla każdego elementu kontenera tBrush
  21. if((brush)tBrush[i] == b){ // sprawdzam, czy nie ma już utworzonego pędzla o takich samych parametrach
  22. brush_index = i; // jeżeli taki pędzel już istnieje to zapamiętuję jego index
  23. break; // i kończę zabawę z iterowaniem
  24. }
  25. }
  26. if(brush_index == -1){ // jak brush_index jest równy -1 to znaczy, że w tBrush nie ma takiego pędzla
  27. brush_index = tBrush.size(); // więc przypisuję index ostatniego elementu kontenera (który zaraz dodam)
  28. hbrush hbr(b); // tworzę ten element
  29. tBrush.push_back(hbr); // i dodaję go do kontenera
  30. }
  31. };
  32. virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const = 0; // każda definicja klasy musi obsługiwać tę metodę
  33. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tbrush) const = 0; // każda definicja klasy musi obsługiwać tę metodę
  34. virtual void DrawSelect(HDC &hdc) const = 0; // rysowanie zaznaczenia
  35. virtual void WmLButtonDown(POINT &mp) = 0;
  36. virtual void WmLButtonUp(POINT &mp) = 0;
  37. virtual RECT GetRect() const = 0;
  38. bool Selected() const {return selected;}; // określa, czy obiekt został zaznaczony
  39. void Unselect() { selected = false;};
  40. virtual bool Select(POINT &ps) = 0; // każda definicja klasy musi obługiwać tę metodą
  41. virtual bool Select(RECT &r) = 0;
  42. virtual bool CursorOnObject(POINT &mp) const = 0; // sprawdza, czy kursor jest nad obiektem
  43. virtual ~i_dr_obj(){};
  44. };
  45. // ######################### DEFINICJA KLASY LINE (LINIA) ########################
  46. class line : public i_dr_obj{
  47. POINT begin;
  48. POINT end;
  49. public:
  50. 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;};
  51. virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  52. std::wstring str;
  53. wchar_t buffor[100];
  54. wsprintf(buffor, L"line %i %i; %i %i", begin.x, begin.y, end.x, end.y);
  55. str = buffor;
  56. str += L" ";
  57. str += tPen[this->pen_index].toString();
  58. str += L" ";
  59. str += tBrush[this->brush_index].toString();
  60. return str;
  61. }
  62. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  63. tPen[pen_index].SelectPen(hdc);
  64. tBrush[brush_index].SelectBrush(hdc);
  65. MoveToEx(hdc, begin.x, begin.y, NULL);
  66. LineTo(hdc, end.x, end.y);
  67. }
  68. virtual void DrawSelect(HDC &hdc) const {
  69. Rectangle(hdc, begin.x - ZN_SIZE, begin.y - ZN_SIZE, begin.x + ZN_SIZE, begin.y + ZN_SIZE);
  70. Rectangle(hdc, end.x - ZN_SIZE, end.y - ZN_SIZE, end.x + ZN_SIZE, end.y + ZN_SIZE);
  71. }
  72. virtual void WmLButtonDown(POINT &mp){
  73. begin = mp;
  74. end = mp;
  75. }
  76. virtual void WmLButtonUp(POINT &mp){
  77. end = mp;
  78. }
  79. virtual bool Select(POINT &ps){
  80. if(CursorOnObject(ps)){
  81. selected = true;
  82. return true;
  83. }
  84. return false;
  85. }
  86. virtual bool Select(RECT &r){
  87. if(PtInRect(&r, begin) && PtInRect(&r, end)){
  88. selected = true;
  89. return true;
  90. }
  91. return false;
  92. }
  93. virtual RECT GetRect() const {
  94. RECT r;
  95. SetRect(&r, min(begin.x, end.x), min(begin.y, end.y), max(begin.x, end.x), max(begin.y, end.y));
  96. return r;
  97. }
  98. virtual bool CursorOnObject(POINT &ps) const {
  99. if(begin.x == end.x && begin.y == end.y){ // gdy punkty się pokrywają
  100. if((begin.x - ps.x) * (begin.x - ps.x) > ZN_SIZE * ZN_SIZE){ // oraz kwadrat odległości od kursora od punktu jest większa od zadanej tolerancji
  101. return false; // to zwracaj fałsz bo obiket jest poza zasięgiem zaznaczenia
  102. }
  103. return true; // w przeciwnym razie zwracaj prawdę
  104. }
  105. float u = float((ps.x - begin.x)*(end.x -begin.x)+(ps.y-begin.y)*(end.y-begin.y)) / (pow(begin.x - end.x, 2.f) + pow(begin.y - end.y, 2.f)); // współrzynnik umożliwiający znalezienie punktu zrzutowanego na linię
  106. POINT pt; // zmienna punktu zrzutowanego na linię
  107. pt.x = begin.x + (end.x - begin.x) * u; // współrzędna x zrzutowanego punktu
  108. pt.y = begin.y + (end.y - begin.y) * u; // współrzędna y zrzutowanego punktu
  109. if(pt.x >= min(begin.x, end.x) && pt.x <= max(begin.x, end.x) && pt.y >= min(begin.y, end.y) && pt.y <= max(begin.y, end.y)){ // jak punkt zawiera się w zadanym zakresie wartości to
  110. if((pt.x - ps.x) * (pt.x - ps.x) + (pt.y - ps.y) * (pt.y - ps.y) <= ZN_SIZE * ZN_SIZE){ // jak kwadrat odległości jest mniejszy lub równy kwadratowi maksymalnego odchylenia to
  111. return true; // zwracaj prawdę
  112. }
  113. }
  114. return false; // w przeciwnym przypadku zwracaj fałsz
  115. }
  116. };
  117. // ######################### DEFINICJA KLASY CIRCLE (OKRĄG) ########################
  118. class circle : public i_dr_obj{
  119. POINT begin;
  120. POINT end;
  121. public:
  122. 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;};
  123. POINT GetCenterPoint() const {
  124. POINT pt;
  125. pt.x = (begin.x + end.x) * 0.5;
  126. pt.y = (begin.y + end.y) * 0.5;
  127. return pt;
  128. }
  129. virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  130. std::wstring str;
  131. wchar_t buffor[100];
  132. wsprintf(buffor, L"circle %i %i; %i %i", begin.x, begin.y, end.x, end.y);
  133. str = buffor;
  134. str += L" ";
  135. str += tPen[this->pen_index].toString();
  136. str += L" ";
  137. str += tBrush[this->brush_index].toString();
  138. return str;
  139. }
  140. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  141. tPen[pen_index].SelectPen(hdc);
  142. tBrush[brush_index].SelectBrush(hdc);
  143. Ellipse(hdc, begin.x, begin.y, end.x, end.y);
  144. }
  145. virtual void DrawSelect(HDC &hdc) const {
  146. Rectangle(hdc, begin.x - ZN_SIZE, begin.y - ZN_SIZE, begin.x + ZN_SIZE, begin.y + ZN_SIZE);
  147. Rectangle(hdc, end.x - ZN_SIZE, end.y - ZN_SIZE, end.x + ZN_SIZE, end.y + ZN_SIZE);
  148. }
  149. virtual void WmLButtonDown(POINT &mp){
  150. begin = mp;
  151. end = mp;
  152. }
  153. virtual void WmLButtonUp(POINT &mp){
  154. end = mp;
  155. if(abs(begin.x - end.x) < abs(begin.y - end.y)){
  156. end.y = begin.y + abs(begin.x - end.x) * ( begin.y < end.y ? 1 : -1);
  157. }else{
  158. end.x = begin.x + abs(begin.y - end.y) * ( begin.x < end.x ? 1 : -1);
  159. }
  160. }
  161. virtual bool Select(POINT &ps){
  162. if(CursorOnObject(ps)){
  163. selected = true;
  164. return true;
  165. }
  166. return false;
  167. }
  168. virtual bool Select(RECT &r){
  169. if(PtInRect(&r, begin) && PtInRect(&r, end)){
  170. selected = true;
  171. return true;
  172. }
  173. return false;
  174. }
  175. virtual bool CursorOnObject(POINT &ps) const {
  176. POINT cp = GetCenterPoint(); // metoda zwracająca współrzędne punktu centralnego
  177. UINT ray = abs(begin.x - end.x) * 0.5; // promień okręgu
  178. if((cp.x - ps.x) * (cp.x - ps.x) + (cp.y - ps.y) * (cp.y - ps.y) > ray * ray){ // jak kwadrat odległości kursora od punktu centralnego jest większy od kwadratu promienia okręgu to
  179. return false; // zwracaj fałsz
  180. }
  181. return true; // w przeciwnym przypadku zwracaj prawdę
  182. }
  183. virtual RECT GetRect() const {
  184. RECT r;
  185. SetRect(&r, min(begin.x, end.x), min(begin.y, end.y), max(begin.x, end.x), max(begin.y, end.y));
  186. return r;
  187. }
  188. };
  189. // ######################### DEFINICJA KLASY RECTANGLE (PROSTOKĄT) ########################
  190. class rectangle : public i_dr_obj{
  191. POINT begin;
  192. POINT end;
  193. public:
  194. 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;};
  195. virtual std::wstring toString(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  196. std::wstring str;
  197. wchar_t buffor[100];
  198. wsprintf(buffor, L"rectangle %i %i; %i %i", begin.x, begin.y, end.x, end.y);
  199. str = buffor;
  200. str += L" ";
  201. str += tPen[this->pen_index].toString();
  202. str += L" ";
  203. str += tBrush[this->brush_index].toString();
  204. return str;
  205. }
  206. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  207. tPen[pen_index].SelectPen(hdc);
  208. tBrush[brush_index].SelectBrush(hdc);
  209. Rectangle(hdc, begin.x, begin.y, end.x, end.y);
  210. }
  211. virtual void DrawSelect(HDC &hdc) const {
  212. Rectangle(hdc, begin.x - ZN_SIZE, begin.y - ZN_SIZE, begin.x + ZN_SIZE, begin.y + ZN_SIZE);
  213. Rectangle(hdc, end.x - ZN_SIZE, end.y - ZN_SIZE, end.x + ZN_SIZE, end.y + ZN_SIZE);
  214. }
  215. virtual void WmLButtonDown(POINT &mp){
  216. begin = mp;
  217. end = mp;
  218. }
  219. virtual void WmLButtonUp(POINT &mp){
  220. end = mp;
  221. }
  222. virtual bool Select(POINT &ps){
  223. if(CursorOnObject(ps)){
  224. selected = true;
  225. return true;
  226. }
  227. return false;
  228. }
  229. virtual bool Select(RECT &r){
  230. if(PtInRect(&r, begin) && PtInRect(&r, end)){
  231. selected = true;
  232. return true;
  233. }
  234. return false;
  235. }
  236. virtual bool CursorOnObject(POINT &ps) const {
  237. RECT r; // zmienna pomocnicza
  238. SetRect(&r, min(begin.x, end.x), min(begin.y, end.y), max(begin.x, end.x), max(begin.y, end.y)); // ustawienie prostokąta
  239. return PtInRect(&r, ps); // zwraca prawdę, gdy punkt jest w prostokącie a fałsz gdy nie jest
  240. }
  241. virtual RECT GetRect() const {
  242. RECT r;
  243. SetRect(&r, min(begin.x, end.x), min(begin.y, end.y), max(begin.x, end.x), max(begin.y, end.y));
  244. return r;
  245. }
  246. };

Dodanie nowego kodu klasy select_obj

Do projektu dodać należy plik nagłówkowy o nazwie select.h a w nim znajdzie się kod klasy obsługującej zaznaczanie obiektów:

Listing 5
  1. #ifndef SELECT_H
  2. #define SELECT_H
  3. #include "drawing_class_def.h"
  4. class select_obj{
  5. protected:
  6. i_dr_obj* objundercursor; // wskaźnik na obiekt znajdujący się pod kursorem w trybie zaznaczania i edycji
  7. hpen selpen; // pióro dla znaczników obiektów zaznaczonych
  8. hbrush selbrush; // kolor wypełnienia znaczników obiektu zaznaczonego
  9. hpen selframe; // pióro ramki zaznaczenia
  10. hpen selbycursor; // pióro rysowania znaczników obiektu, gdy ten jest pod kursorem myszki
  11. hpen selrangepen; // pióro używane do rysowania prostokąta zakresu zaznaczanych elementów
  12. std::vector<i_dr_obj*> tSelObj; // tablica zaznaczonych obiektów
  13. RECT selrect; // zaznaczenie lub obszar zaznaczany
  14. public:
  15. select_obj():objundercursor(NULL), selpen(255,0,0,1,pen::ps::solid), selbrush(brush(255,255,255)), selframe(150,150,150, 1, pen::ps::dot), selbycursor(0,0,255, 2, pen::ps::solid), selrangepen(0,150,255,1,pen::ps::dot){
  16. SetRectEmpty(&selrect);
  17. }
  18. void WmLButtonDown(HWND hWndDraw, std::vector<i_dr_obj*> &tObj, WPARAM wParam, POINT mousepos){ // obsługa komunikatu wciśnięcia lewego przycisku myszki
  19. if(!(wParam & MK_SHIFT) && !tSelObj.empty() && (!objundercursor || !objundercursor->Selected())){
  20. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i ++){
  21. (*i)->Unselect();
  22. }
  23. tSelObj.clear();
  24. }
  25. if(objundercursor){
  26. if(objundercursor->Selected()){
  27. if(wParam & MK_SHIFT){
  28. objundercursor->Unselect();
  29. for(std::vector<i_dr_obj*>::iterator j = tSelObj.begin(); j < tSelObj.end(); j++){
  30. if(objundercursor == *j){
  31. tSelObj.erase(j);
  32. break;
  33. }
  34. }
  35. }
  36. }else{
  37. tSelObj.push_back(objundercursor);
  38. objundercursor->Select(mousepos);
  39. }
  40. }else{
  41. tSelObj.clear();
  42. }
  43. if(!tSelObj.empty()){
  44. selrect = tSelObj[0]->GetRect();
  45. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin() + 1; i < tSelObj.end(); i++){
  46. UnionRect(&selrect, &selrect, &(*i)->GetRect());
  47. }
  48. }else{
  49. SetRect(&selrect, mousepos.x, mousepos.y, mousepos.x, mousepos.y);
  50. }
  51. InvalidateRect(hWndDraw, NULL, true);
  52. }
  53. void WmLButtonUp(HWND hWndDraw, std::vector<i_dr_obj*> &tObj){ // obsługa komunikatu zwolnienia przycisku myszki
  54. if(tSelObj.empty()){
  55. SetRect(&selrect, min(selrect.right, selrect.left), min(selrect.top, selrect.bottom), max(selrect.right, selrect.left), max(selrect.top, selrect.bottom));
  56. bool sel;
  57. for(int i = tObj.size() - 1; i > -1; i--){
  58. sel = tObj[i]->Selected();
  59. if(tObj[i]->Select(selrect)){
  60. if(sel){
  61. tObj[i]->Unselect();
  62. for(std::vector<i_dr_obj*>::iterator j = tSelObj.begin(); j < tSelObj.end(); j++){
  63. if(tObj[i] == *j){
  64. tSelObj.erase(j);
  65. break;
  66. }
  67. }
  68. }else{
  69. tSelObj.push_back(tObj[i]);
  70. }
  71. }
  72. }
  73. if(!tSelObj.empty()){
  74. selrect = tSelObj[0]->GetRect();
  75. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin() + 1; i < tSelObj.end(); i++){
  76. UnionRect(&selrect, &selrect, &(*i)->GetRect());
  77. }
  78. }else{
  79. SetRectEmpty(&selrect);
  80. }
  81. InvalidateRect(hWndDraw, NULL, true);
  82. }
  83. }
  84. void WmMouseMove(HWND hWndDraw, std::vector<i_dr_obj*> &tObj, WPARAM wParam, POINT &mousepos, state &st){ // obsługa ruchu myszki
  85. if(wParam & MK_LBUTTON){
  86. if(st == state::sel && tSelObj.empty()){
  87. selrect.right = mousepos.x;
  88. selrect.bottom = mousepos.y;
  89. }
  90. InvalidateRect(hWndDraw, NULL, true); // odświerzanie okna w celu odrysowania nowych ustawień obiektu
  91. }else if(st == state::sel){
  92. objundercursor = NULL;
  93. for(int i = tObj.size() - 1; i > -1; i--){
  94. if(tObj[i]->CursorOnObject(mousepos)){
  95. objundercursor = tObj[i];
  96. break;
  97. }
  98. }
  99. InvalidateRect(hWndDraw, false, true);
  100. }
  101. }
  102. void WmPaint(HDC &hdc){ // obsługa rysowania
  103. if(!tSelObj.empty()){
  104. SelectObject(hdc, GetStockObject(NULL_BRUSH));
  105. selframe.SelectPen(hdc);
  106. Rectangle(hdc, selrect.left, selrect.top, selrect.right, selrect.bottom);
  107. }
  108. selpen.SelectPen(hdc);
  109. selbrush.SelectBrush(hdc);
  110. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i++){
  111. (*i)->DrawSelect(hdc); // rysowanie dodanych obiektów
  112. }
  113. if(tSelObj.empty() && selrect.right && selrect.bottom && selrect.top && selrect.left){
  114. selrangepen.SelectPen(hdc);
  115. SelectObject(hdc, GetStockObject(NULL_BRUSH));
  116. RECT r;
  117. SetRect(&r, min(selrect.right, selrect.left), min(selrect.top, selrect.bottom), max(selrect.right, selrect.left), max(selrect.top, selrect.bottom));
  118. Rectangle(hdc, selrect.left, selrect.top, selrect.right, selrect.bottom);
  119. }
  120. if(objundercursor){
  121. selbycursor.SelectPen(hdc);
  122. objundercursor->DrawSelect(hdc);
  123. }
  124. }
  125. void Clear(){
  126. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i++){
  127. (*i)->Unselect();
  128. }
  129. tSelObj.clear();
  130. objundercursor = NULL;
  131. SetRectEmpty(&selrect);
  132. }
  133. };
  134. #endif

Plik select.h należy załączyć do pliku winmain.cpp.

Zmiany w funkcji procedury okna rysowania

Oto kod procedury okna rysowania, który również musiał ulegnąć zmianie:

Listing 6
  1. // ################################### Procedura okna dokumentu #####################################
  2. LRESULT CALLBACK WndDrawingProc(HWND hWndDraw, UINT msg, WPARAM wParam, LPARAM lParam){
  3. static POINT mousepos; // współrzędne kursora w oknie
  4. static i_dr_obj* obj; // wskaźnik na interfejs dodawanego obiektu
  5. static std::vector<i_dr_obj*> tObj; // tablica interfejsów obiektów rysowanych
  6. static enum state st; // tryb rysowania lub edycji
  7. static std::vector<hpen> tPen; // tablica wykorzystywanych pędzli
  8. static std::vector<hbrush> tBrush; // tablica wykorzystywanych wypełnień
  9. static select_obj SelObjAct;
  10. switch(msg){
  11. case WM_CREATE:
  12. {
  13. obj = NULL;
  14. }
  15. break;
  16. case WM_PAINT:
  17. {
  18. PAINTSTRUCT ps;
  19. HDC hdc = BeginPaint(hWndDraw, &ps);
  20. SetBkMode(hdc, TRANSPARENT);
  21. for(std::vector<i_dr_obj*>::iterator i = tObj.begin(); i < tObj.end(); i++){
  22. (*i)->Draw(hdc, tPen, tBrush); // rysowanie dodanych obiektów
  23. }
  24. if(st == state::sel){
  25. SelObjAct.WmPaint(hdc);
  26. }
  27. if(obj){
  28. obj->Draw(hdc, tPen, tBrush); // rysowanie dodawanego obiektu
  29. }
  30. EndPaint(hWndDraw, &ps);
  31. }
  32. break;
  33. case WM_LBUTTONDOWN:
  34. {
  35. SetCapture(hWndDraw);
  36. switch(st){
  37. case dr_line: // tworzę obiekt typu line
  38. {
  39. obj = new line(tPen, tBrush, pen(255,0,0,1,pen::ps::solid), brush(0,0,150));
  40. obj->WmLButtonDown(mousepos);
  41. }
  42. break;
  43. case dr_circle: // tworzę obiekt typu circle
  44. {
  45. obj = new circle(tPen, tBrush, pen(0,255,0,1,pen::ps::solid), brush(0,150,0));
  46. obj->WmLButtonDown(mousepos);
  47. }
  48. break;
  49. case dr_rect: // tworzę obiekt typu rectangle
  50. {
  51. obj = new rectangle(tPen, tBrush, pen(0, 150, 255, 1, pen::ps::solid), brush(150,0,200));
  52. obj->WmLButtonDown(mousepos);
  53. }
  54. break;
  55. case sel: // trzyb zaznaczania (nie obsłużony jeszcze)
  56. {
  57. SelObjAct.WmLButtonDown(hWndDraw, tObj, wParam, mousepos);
  58. }
  59. break;
  60. }
  61. }
  62. break;
  63. case WM_LBUTTONUP:
  64. {
  65. switch(st){
  66. case dr_line: // dodawanie obiektu do kontenera
  67. {
  68. if(obj){
  69. obj->WmLButtonUp(mousepos);
  70. tObj.push_back(obj);
  71. }
  72. }
  73. break;
  74. case dr_circle: // dodawanie obiektu do kontenera
  75. {
  76. if(obj){
  77. obj->WmLButtonUp(mousepos);
  78. tObj.push_back(obj);
  79. }
  80. }
  81. break;
  82. case dr_rect: // dodawanie obiektu do kontenera
  83. {
  84. if(obj){
  85. obj->WmLButtonUp(mousepos);
  86. tObj.push_back(obj);
  87. }
  88. }
  89. break;
  90. case sel: // tryb edycji
  91. {
  92. SelObjAct.WmLButtonUp(hWndDraw, tObj);
  93. }
  94. break;
  95. }
  96. obj = NULL;
  97. ReleaseCapture();
  98. }
  99. break;
  100. case WM_MOUSEMOVE:
  101. {
  102. mousepos.x = LOWORD(lParam); // pobieranie współrzędnej x
  103. mousepos.y = HIWORD(lParam); // pobieranie współrzędnej y
  104. wchar_t buffor[100]; // bufor do konwersji na tekst
  105. wsprintf(buffor, L"X = %i",mousepos.x); // konwersja na tekst współrzędnej x
  106. SendMessage(statusbar, SB_SETTEXT, 0, (LPARAM)buffor); // ustawienie tekstu pierwszej pozycji paska kontrolki statusbar
  107. wsprintf(buffor, L"Y = %i",mousepos.y); // konwersja na tekst współrzędnej y
  108. SendMessage(statusbar, SB_SETTEXT, 1, (LPARAM)buffor); // ustawienie tekstu drugiej pozycji paska kontrolki statusbar
  109. if((wParam & MK_LBUTTON) && st != state::sel){
  110. if(obj){
  111. obj->WmLButtonUp(mousepos); // zmiana obiektu, podczas ruchu mychy
  112. }
  113. InvalidateRect(hWndDraw, NULL, true); // odświerzanie okna w celu odrysowania nowych ustawień obiektu
  114. }
  115. SelObjAct.WmMouseMove(hWndDraw, tObj, wParam, mousepos, st);
  116. }
  117. break;
  118. case WM_COMMAND:
  119. {
  120. if((HWND)lParam == toolbar){
  121. UINT n = LOWORD(wParam);
  122. if(n != ID_SELECT && st == state::sel){
  123. SelObjAct.Clear();
  124. InvalidateRect(hWndDraw, NULL, true);
  125. }
  126. switch(n){
  127. case ID_LINE: // komunikat przychodzący od przycisku toolbar-a o identyfikatorze ID_LINE
  128. {
  129. st = dr_line;
  130. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie linii"); // ustawienie tekstu w ostatnim polu statusbar-u
  131. }
  132. break;
  133. case ID_CIRCLE: // to samo co poprzednio, tylko dla ID_CIRCLE
  134. {
  135. st = dr_circle;
  136. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie okręgu"); // ustawienie tekstu w ostatnim polu statusbar-u
  137. }
  138. break;
  139. case ID_RECTANGLE: // to samo co poprzednio, tylko dla ID_RECTANGLE
  140. {
  141. st = dr_rect;
  142. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie prostokąta"); // ustawienie tekstu w ostatnim polu statusbar-u
  143. }
  144. break;
  145. case ID_SELECT: // to samo co poprzednio, tylko dla ID_SELECT
  146. {
  147. st = sel;
  148. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie edycja"); // ustawienie tekstu w ostatnim polu statusbar-u
  149. }
  150. break;
  151. }
  152. }
  153. }
  154. break;
  155. case WM_DESTROY: // tutaj sprzątanko się rozpoczyna
  156. {
  157. for(std::vector<i_dr_obj*>::iterator i = tObj.begin(); i < tObj.end(); i++){
  158. delete *i; // usuwanie obiektów
  159. }
  160. // czyszczenie kontenerów
  161. tObj.clear();
  162. tPen.clear();
  163. tBrush.clear();
  164. }
  165. break;
  166. }
  167. return DefWindowProc(hWndDraw, msg, wParam, lParam);
  168. }

Wygląd programu

Oto wygląd naszego programu, w którym można już zaznaczać poszczególne obiekty.

Program rysowanie z obsługą zaznaczania obiektów
Rys. 2
Widok programu Rysowanie obsługującego już jak widać zaznaczanie elementów.

Komentarze