Macierz translacji i rotacji a obsługa przemieszczania obiektów

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

Wstęp

Przydałoby się, aby nasze obiekty można było przemieszczać zaraz po ich zaznaczeniu. Warto się zastanowić jak powinna wyglądać obsługa takiego przemieszczania obiektów? Ja widzę to w dwóch różnych wersjach: za pomocą strzałek klawiatury i za pomocą myszki. Do zrealizowania pierwszego z nich trzeba będzie utworzyć specjalną grupę akceleratorów, pod które zostanie podpięta odpowiednia funkcja przesuwająca obiekty o określoną wartość. Do drugiego zaś zadania zaprzężona zostanie funkcja SetWorldTransform, która korzysta z wskaźnika na strukturę XFORM.

Macierz translacji i rotacji

Wiele osób naiwnie twierdzi, że w WinApi nie da się obracać w prosty sposób obiektów rysowanych. Nie jest to prawdą, gdyż właśnie wcześniej już wspominana funkcja SetWorldTransform oraz struktura XFORM, która z tą funkcją jest bezpośrednio powiązana umożliwiają obracanie wszystkich obiektów rysowanych w oknie programu. Co prawda na tej stronie nie będę omawiał obrotów, ale ogólne pojęcie użycia macierzy translacji i rotacji zostanie tutaj wyłuszczone.

Przyjrzyjmy się nieco bliżej strukturze XFORM:

Listing 1
  1. typedef struct tagXFORM { /* xfrm */
  2. FLOAT eM11; // komórka macierzy wiersz: 1, kolumna 1
  3. FLOAT eM12; // komórka macierzy wiersz: 2, kolumna 1
  4. FLOAT eM21; // komórka macierzy wiersz: 1, kolumna 2
  5. FLOAT eM22; // komórka macierzy wiersz: 2, kolumna 2
  6. FLOAT eDx; // komórka macierzy wiersz: 1, kolumna 3
  7. FLOAT eDy; // komórka macierzy wiersz: 2, kolumna 3
  8. } XFORM;

Tak, drodzy czytelnicy, ta struktura opisuje pola pewnej szczególnej macierzy, a mianowicie macierzy postaci takiej:

gdzie:

  • eM11 - składowa obrotu równa cos(α);
  • eM12 - składowa obrotu równa -sin(α);
  • eM21 - składowa obrotu równa sin(α);
  • eM22 - składowa obrotu równa cos(α);
  • eDx - przemieszczenie w osi x;
  • eDy - przemieszczenie w osi y.

Jak to działa, załóżmy, że dany jest pewien 2W punkt P, pewien 2W punkt przemieszczenia Ptr oraz kąt α, gdzie mój punkt przemieszczenia jest punktem względem którego będę obracał dany punkt P. W takim to przypadku aby uzyskać wyjściową macierz transformacji trzeba wykonać następujące obliczenia macierzowe:

Mnożenie naszego punktu P przez macierz transformacji Mtr będzie wyglądać następująco:

W programie Rysowanie nie będę się tak mocno zagłębiał w transformacje wykorzystując strukturę XFORM do realizacji przemieszczania obiektów.

Obsługa przemieszczania obiektów za pomocą strzałek klawiatury

Dodawanie nowych akceleratorów

Na sam początek najprostsze, czyli dodanie kolejnych akceleratorów, które wykorzystać należy do obsługi przemieszczania obiektu o zadaną wartość. Na poniższej ilustracji pokazane zostały wszystkie niezbędne akceleratory.

Akceleratory programu Rysowanie
Rys. 1
Widok dodanych akceleratorów do projektu

Kod związany z obsługą nowych akceleratorów

W komunikacie WM_COMMAND należy zamieścić obsługę akceleratorów:

Listing 2
  1. case WM_COMMAND:
  2. {
  3. if((HWND)lParam == toolbar){
  4. UINT n = LOWORD(wParam);
  5. if(n != ID_SELECT && st == state::sel){
  6. SelObjAct.Clear();
  7. InvalidateRect(hWndDraw, NULL, true);
  8. }
  9. switch(n){
  10. case ID_LINE: // komunikat przychodzący od przycisku toolbar-a o identyfikatorze ID_LINE
  11. {
  12. st = dr_line;
  13. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie linii"); // ustawienie tekstu w ostatnim polu statusbar-u
  14. }
  15. break;
  16. case ID_CIRCLE: // to samo co poprzednio, tylko dla ID_CIRCLE
  17. {
  18. st = dr_circle;
  19. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie okręgu"); // ustawienie tekstu w ostatnim polu statusbar-u
  20. }
  21. break;
  22. case ID_RECTANGLE: // to samo co poprzednio, tylko dla ID_RECTANGLE
  23. {
  24. st = dr_rect;
  25. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie prostokąta"); // ustawienie tekstu w ostatnim polu statusbar-u
  26. }
  27. break;
  28. case ID_SELECT: // to samo co poprzednio, tylko dla ID_SELECT
  29. {
  30. st = sel;
  31. SendMessage(statusbar, SB_SETTEXT, 2, (LPARAM)L"Rysowanie edycja"); // ustawienie tekstu w ostatnim polu statusbar-u
  32. }
  33. break;
  34. }
  35. }else if(HIWORD(wParam) == 1){ // obsługa akceleratora
  36. if(st == state::sel){
  37. switch(LOWORD(wParam)){ // id akceleratora
  38. case ID_ACCELERATOR_DELETE: // usuwanie obiektów
  39. {
  40. SelObjAct.Delete(hWndDraw, tObj); // wywołanie metody klasy SelObjAct, która usunie obiekty zaznaczone
  41. }
  42. break;
  43. case ID_ACCELERATOR_UP: // przemieszczanie w górę
  44. {
  45. POINT move = {0,- move_steep};
  46. SelObjAct.Move(hWndDraw, move);
  47. }
  48. break;
  49. case ID_ACCELERATOR_DOWN: // przemieszczanie w dół
  50. {
  51. POINT move = {0, move_steep};
  52. SelObjAct.Move(hWndDraw, move);
  53. }
  54. break;
  55. case ID_ACCELERATOR_LEFT: // przemieszczanie w lewo
  56. {
  57. POINT move = {- move_steep, 0};
  58. SelObjAct.Move(hWndDraw, move);
  59. }
  60. break;
  61. case ID_ACCELERATOR_RIGHT: // przemieszczanie w prawo
  62. {
  63. POINT move = {move_steep, 0};
  64. SelObjAct.Move(hWndDraw, move);
  65. }
  66. break;
  67. }
  68. }
  69. }

Z kolei w pliku select.h należy zamieścić zaraz po instrukcji #include stałą związaną z rozmiarem przesunięcia:

Listing 3
  1. const int move_steep = 10;

Natomiast w klasie select_obj dodać nową następującą metodę:

Listing 4
  1. void Move(HWND hWndDraw, POINT move){
  2. if(!tSelObj.empty()){
  3. if(selrect.left + move.x < 0)
  4. move.x = -selrect.left;
  5. if(selrect.top + move.y < 0)
  6. move.y = -selrect.top;
  7. OffsetRect(&selrect, move.x, move.y); // przemieszczanie prostokąta
  8. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i++){ // itaracja po zaznaczonych elementach
  9. (*i)->Move(move); // przemieszczanie
  10. }
  11. InvalidateRect(hWndDraw, NULL, false); // odświerzanie okna
  12. }
  13. }

Jak widać konieczne jest również wprowadzenie do wszystkich klas dziedziczących po klasie i_dr_obj metody Move. Dla klasy i_dr_obj:

Listing 5
  1. virtual void Move(POINT move) =0; // przemieszczanie obiektu

Dla klasy line, circle oraz rectangle:

Listing 6
  1. virtual void Move(POINT move){
  2. begin.x += move.x;
  3. begin.y += move.y;
  4. end.x += move.x;
  5. end.y += move.y;
  6. }

Przemieszczanie obiektu poprzez wciśnięcie na zaznaczonym obiekcie klawisza myszki

Ponieważ do obsługi przemieszczania obiektów zaznaczonych posługiwał się będę strukturą XFORM, więc zacznę od kodu, który należy zamieścić przed definicją klasy i_dr_obj:

Listing 7
  1. // ###################### MNOŻENIE MACIERZY TRANSFORMACJI ###########################
  2. XFORM operator * (const XFORM &xform, const XFORM &xform2){
  3. XFORM f;
  4. f.eM11 = xform.eM11 * xform2.eM11 + xform.eM21 * xform2.eM12;
  5. f.eM21 = xform.eM11 * xform2.eM21 + xform.eM21 * xform2.eM22;
  6. f.eDx = xform.eM11 * xform2.eDx + xform.eM21 * xform2.eDy + xform.eDx;
  7. f.eM12 = xform.eM12 * xform2.eM11 + xform.eM22 * xform2.eM12;
  8. f.eM22 = xform.eM12 * xform2.eM21 + xform.eM22 * xform2.eM22;
  9. f.eDy = xform.eM12 * xform2.eDx + xform.eM22 * xform2.eDy + xform.eDy;
  10. return f;
  11. }
  12. // ##################### USTAWIANIE MACIERZY TRANSFORMACJI ###########################
  13. void SetXForm(XFORM &xf,float sinA,float cosA,float dx,float dy){
  14. xf.eM11 = cosA;
  15. xf.eM12 = sinA;
  16. xf.eM21 = -sinA;
  17. xf.eM22 = cosA;
  18. xf.eDx = dx;
  19. xf.eDy = dy;
  20. }

Operator mnożenia obsługuje w powyższym kodzie mnożenie dwóch macierzy translacji i rotacji z kolei funkcja SetXForm ma za zadanie ustawiać pola struktury typu XFORM. Do klasy bazowej i_dr_obj należy dodać następujące dwa pola w części chronionej:

Listing 8
  1. XFORM *moving;
  2. XFORM *wtransform;

Zmianie ulegnie również nagłówek konstruktora:

Listing 9
  1. i_dr_obj(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b, XFORM *wtransform):selected(false), pen_index(-1), brush_index(-1), moving(NULL),wtransform(wtransform)

Oraz konstruktorów klas pochodnych. Dla klasy line:

Listing 10
  1. line(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b,XFORM *wtransform):i_dr_obj(tPen, tBrush, p, b, wtransform){begin.x = begin.y = end.x = end.y = 0;};

Dla klasy circle:

Listing 11
  1. circle(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b, XFORM *wtransform):i_dr_obj(tPen, tBrush, p, b, wtransform){begin.x = begin.y = end.x = end.y = 0;};

Dla klasy rectangle:

Listing 12
  1. rectangle(std::vector<hpen> &tPen, std::vector<hbrush> &tBrush, pen &p, brush &b, XFORM *wtransform):i_dr_obj(tPen, tBrush, p, b, wtransform){begin.x = begin.y = end.x = end.y = 0;};

Do klasy bazowej i_dr_obj należy dodać w części publicznej następujące dwie metody:

Listing 13
  1. virtual void SetTransform() = 0;
  2. void SetMovingWorld(XFORM *moving){
  3. this->moving = moving;
  4. }

Dla wszystkich klas pochodnych metoda czysto wirtualna SetTransform powinna wyglądać następująco:

Listing 14
  1. virtual void Move(POINT move){
  2. begin.x += move.x;
  3. begin.y += move.y;
  4. end.x += move.x;
  5. end.y += move.y;
  6. }

Ktoś może zarzucić mi, że powielam tutaj ten sam kod, jednakże ja ograniczam się w tej przykładowej implementacji programu jedynie do bardzo prostych obiektów, bardziej złożone obiekty nie będą miały tylko punktu początkowego i końcowego, ale mogą składać się z wielu punktów, i dlatego ten kod jest w tym przypadku powielany.

Pewnej zmianie ulegnąć musi metoda Draw, która dla klasy line będzie wyglądała teraz w następujący sposób:

Listing 15
  1. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  2. if(moving){
  3. XFORM tr = *moving * *wtransform;
  4. SetWorldTransform(hdc, &tr);
  5. }
  6. tPen[pen_index].SelectPen(hdc);
  7. tBrush[brush_index].SelectBrush(hdc);
  8. MoveToEx(hdc, begin.x, begin.y, NULL);
  9. LineTo(hdc, end.x, end.y);
  10. if(moving){
  11. SetWorldTransform(hdc, wtransform);
  12. }
  13. }

Dla klasy circle:

Listing 16
  1. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  2. if(moving){
  3. XFORM tr = *moving * *wtransform;
  4. SetWorldTransform(hdc, &tr);
  5. }
  6. tPen[pen_index].SelectPen(hdc);
  7. tBrush[brush_index].SelectBrush(hdc);
  8. Ellipse(hdc, begin.x, begin.y, end.x, end.y);
  9. if(moving){
  10. SetWorldTransform(hdc, wtransform);
  11. }
  12. }

I dla klasy rectangle:

Listing 17
  1. virtual void Draw(HDC &hdc, std::vector<hpen> &tPen, std::vector<hbrush> &tBrush) const {
  2. if(moving){
  3. XFORM tr = *moving * *wtransform;
  4. SetWorldTransform(hdc, &tr);
  5. }
  6. tPen[pen_index].SelectPen(hdc);
  7. tBrush[brush_index].SelectBrush(hdc);
  8. Rectangle(hdc, begin.x, begin.y, end.x, end.y);
  9. if(moving){
  10. SetWorldTransform(hdc, wtransform);
  11. }
  12. }

Czas przejść do klasy select_obj, gdzie w sekcji chronionej zamieścić należy następujące trzy nowe pola:

Listing 18
  1. XFORM translate; // macierz przemieszczenia
  2. bool move; // znacznik, informujący, że obiekt jest przemieszczany
  3. POINT lbMousePos; // pozycja myszki, gdy wciśnięty został lewy przycisk

Kolejne zmiany w kodzie metod klasy select_obj:

Listing 19
  1. void WmLButtonDown(HWND hWndDraw, std::vector<i_dr_obj*> &tObj, WPARAM wParam, POINT mousepos){ // obsługa komunikatu wciśnięcia lewego przycisku myszki
  2. if(!(wParam & MK_SHIFT)){ // Gdy nie wciśnięto przycisku Shift na klawiaturze
  3. if(objundercursor){ // i pod mychą znajduje się jakiś obiekt
  4. translate.eDx = translate.eDy = 0; // zeruj pola związane z przemieszczaniem
  5. if(!objundercursor->Selected()){ // jak obietk nie jest zaznaczony to
  6. if(!tSelObj.empty()){ // jeżeli tablica zaznaczonych elementów nie jest pusta
  7. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i++){ // to iteruj po tej tablicy
  8. (*i)->Unselect(); // odznaczając je
  9. }
  10. tSelObj.clear(); // i wyczyść jej zawartość
  11. }
  12. objundercursor->Select(mousepos); // zaznacz niezaznaczony do tej pory obiekt
  13. selrect = objundercursor->GetRect(); // pobierz jego prostokąt
  14. tSelObj.push_back(objundercursor); // wstaw go do tablicy elementów zaznaczonych
  15. InvalidateRect(hWndDraw, NULL, false); // i odświerz okno
  16. }
  17. move = true; // ustawiam znacznik przemieszczania obiektu na true
  18. lbMousePos = mousepos; // zapamiętuję współrzędne kurosra przy wciśnięciu lewego przycisku myszy
  19. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i ++){ // iteruję po tablicy zaznaczonych obiektów
  20. (*i)->SetMovingWorld(&translate); // i ustawiam w nich wskaźnik na macierz przemieszczenia
  21. }
  22. return ; // kończę działania funkcji
  23. }
  24. if(!tSelObj.empty() && (!objundercursor || !objundercursor->Selected())){ // gdy tablica zaznaczonych elementów nie jest pusta i gdy żaden obiekt nie znajduje się pod kursorem myszy lub obiekt takowy istnieje ale nie jest zazaczony to
  25. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i ++){ // iteruj po wszystkich bierzących elementach kontenera zaznaczonych elementów
  26. (*i)->Unselect(); // odznacz wsystkie elementy
  27. }
  28. tSelObj.clear(); // i wyczyść tą tablicę
  29. }
  30. }
  31. if(objundercursor){ // gdy pod kursorem myszy znajduje się jakiś obiekt to
  32. if(objundercursor->Selected()){ // jeżeli ten obiekt jest zaznaczony
  33. if(wParam & MK_SHIFT){ // oraz przycisk shift jest wciśnięty to
  34. objundercursor->Unselect(); // odznacz tenże obiekt
  35. for(std::vector<i_dr_obj*>::iterator j = tSelObj.begin(); j < tSelObj.end(); j++){ // iteruj po kontenerze zaznaczonych obiektów
  36. if(objundercursor == *j){ // jeżeli dany element kontenera jest równy obiektowi znajdującemu się pod kursorem myszy to
  37. tSelObj.erase(j); // usuń ten element z kontenera
  38. break; // i zakończ iterację
  39. }
  40. }
  41. }
  42. }else{ // w przeciwnym przypadku
  43. tSelObj.push_back(objundercursor); // dodaj do tablicy zaznaczonych elementów obiekt znajdujący się pod kursorem myszy i
  44. objundercursor->Select(mousepos); // zaznacz go
  45. }
  46. }else{ // gdy żaden obiekt nie znajduje się pod kursorem myszki to
  47. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i ++){ // iteruj po wszystkich bierzących elementach kontenera zaznaczonych elementów
  48. (*i)->Unselect(); // odznacz wsystkie elementy
  49. }
  50. tSelObj.clear(); // i wyczyść kontener
  51. }
  52. if(!tSelObj.empty()){ // jeżeli kontenera zaznaczonych elementów nie jest pusta to
  53. selrect = tSelObj[0]->GetRect(); // pobieram prostokąt pierwszego elementu kontenera
  54. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin() + 1; i < tSelObj.end(); i++){ // iteruję po kolejnych elementach kontenera
  55. UnionRect(&selrect, &selrect, &(*i)->GetRect()); // i sumuję wszystkie jego elementy
  56. }
  57. }else{ // w przeciwnym przypadku
  58. SetRect(&selrect, mousepos.x, mousepos.y, mousepos.x, mousepos.y); // ustawiam pola struktury selrect na odpowiedające współrzędnym myszki
  59. }
  60. InvalidateRect(hWndDraw, NULL, true); // odświerzam okno rysowania
  61. }
  62. void WmLButtonUp(HWND hWndDraw, std::vector<i_dr_obj*> &tObj){ // obsługa komunikatu zwolnienia przycisku myszki
  63. if(move){ // jeżeli obiekt jest przemieszczany to
  64. move = false; // ustawiam znacznik przemieszczania na fałsz
  65. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i ++){ // iteruję po wszystkich elementach kontenera
  66. (*i)->SetTransform(); // ustawiając transformację (czyli w tym przypadku przemieszczenie obiektu
  67. }
  68. OffsetRect(&selrect, translate.eDx, translate.eDy); // przemieszczam prostokąt zaznaczenia o eDx i eDy
  69. return ; // kończę funkcję
  70. }
  71. if(tSelObj.empty()){ // jeżeli kontener zaznaczonych elementów nie jest pusty to
  72. SetRect(&selrect, min(selrect.right, selrect.left), min(selrect.top, selrect.bottom), max(selrect.right, selrect.left), max(selrect.top, selrect.bottom));
  73. bool sel;
  74. for(int i = tObj.size() - 1; i > -1; i--){ // iteracja po elementach kontenera od tyłu
  75. sel = tObj[i]->Selected(); // pobieram informację, czy obiekt jest zaznaczony
  76. if(tObj[i]->Select(selrect)){ // jeżeli obiekt znajduje się w prostokącie
  77. if(sel){ // i był już wcześniej zaznaczony to
  78. tObj[i]->Unselect(); // odznacz go
  79. for(std::vector<i_dr_obj*>::iterator j = tSelObj.begin(); j < tSelObj.end(); j++){
  80. if(tObj[i] == *j){
  81. tSelObj.erase(j);
  82. break;
  83. }
  84. }
  85. }else{
  86. tSelObj.push_back(tObj[i]);
  87. }
  88. }
  89. }
  90. if(!tSelObj.empty()){
  91. selrect = tSelObj[0]->GetRect();
  92. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin() + 1; i < tSelObj.end(); i++){
  93. UnionRect(&selrect, &selrect, &(*i)->GetRect());
  94. }
  95. }else{
  96. SetRectEmpty(&selrect);
  97. }
  98. InvalidateRect(hWndDraw, NULL, true);
  99. }
  100. }
  101. void WmMouseMove(HWND hWndDraw, std::vector<i_dr_obj*> &tObj, WPARAM wParam, POINT &mousepos){ // obsługa ruchu myszki
  102. if(move){ // gdy obiekt jest przemieszczany to
  103. translate.eDx = mousepos.x - lbMousePos.x; // obliczam przemieszczenie na x-sie
  104. translate.eDy = mousepos.y - lbMousePos.y; // obliczam przemieszczanie na y-ku
  105. if(selrect.left + translate.eDx < 0) // sprawdzam, czy nie przekroczyłem minimalnego położenia na x-sie
  106. translate.eDx = -selrect.left; // poprawne wychylenie
  107. if(selrect.top + translate.eDy < 0) // sprawdzam, czy nie przekroczyłem minimalnego położenia na y-ku
  108. translate.eDy = -selrect.top; // poprawne wychylenie
  109. InvalidateRect(hWndDraw, NULL, false); // odświerzam
  110. }
  111. if(wParam & MK_LBUTTON){
  112. if(tSelObj.empty()){
  113. selrect.right = mousepos.x;
  114. selrect.bottom = mousepos.y;
  115. }
  116. InvalidateRect(hWndDraw, NULL, true); // odświerzanie okna w celu odrysowania nowych ustawień obiektu
  117. }else{
  118. i_dr_obj* tmp = NULL;
  119. for(int i = tObj.size() - 1; i > -1; i--){
  120. if(tObj[i]->CursorOnObject(mousepos)){
  121. tmp = tObj[i];
  122. break;
  123. }
  124. }
  125. if(objundercursor != tmp){
  126. objundercursor = tmp;
  127. InvalidateRect(hWndDraw, false, true);
  128. }
  129. }
  130. }
  131. void WmPaint(HDC &hdc, XFORM *docLuw){ // obsługa rysowania
  132. XFORM tr = translate * *docLuw;
  133. if(move){
  134. SetWorldTransform(hdc, &tr);
  135. }
  136. if(!tSelObj.empty()){
  137. SelectObject(hdc, GetStockObject(NULL_BRUSH));
  138. selframe.SelectPen(hdc);
  139. Rectangle(hdc, selrect.left, selrect.top, selrect.right, selrect.bottom);
  140. }
  141. selpen.SelectPen(hdc);
  142. selbrush.SelectBrush(hdc);
  143. for(std::vector<i_dr_obj*>::iterator i = tSelObj.begin(); i < tSelObj.end(); i++){
  144. (*i)->DrawSelect(hdc); // rysowanie dodanych obiektów
  145. }
  146. if(tSelObj.empty() && selrect.right && selrect.bottom && selrect.top && selrect.left){
  147. selrangepen.SelectPen(hdc);
  148. SelectObject(hdc, GetStockObject(NULL_BRUSH));
  149. RECT r;
  150. SetRect(&r, min(selrect.right, selrect.left), min(selrect.top, selrect.bottom), max(selrect.right, selrect.left), max(selrect.top, selrect.bottom));
  151. Rectangle(hdc, selrect.left, selrect.top, selrect.right, selrect.bottom);
  152. }
  153. if(objundercursor){
  154. selbycursor.SelectPen(hdc);
  155. objundercursor->DrawSelect(hdc);
  156. }
  157. if(move){
  158. SetWorldTransform(hdc, docLuw);
  159. }
  160. }

Przejdźmy teraz do pliku winmain.cpp, gdzie w funkcji WndDrawingProc najpierw utworzyć należy dwie dodatkowe zmienne statyczne typu XFORM opisujące transformację układów współrzędnych:

Listing 20
  1. static XFORM guw = { // g - globalny; u - układ; w -współrzędnych
  2. 1.f, // cos(0)
  3. 0.f, // sin(0)
  4. 0.f, // -sin(0)
  5. 1.f, // cos(0)
  6. 0.f, // przemieszczenie x
  7. 0.f // przemieszczenie y
  8. };
  9. static XFORM docLuw = {1.f, 0.f, 0.f, 1.f, 0.f, 0.f}; // l - lokalny; u - układ; w - współrzędnych

W komunikacie WM_CREATE trzeba ustawić dla kontekstu urządzenia mem odpowiedni tryb graficzny za pomocą funkcji SetGraphicsMode. Jest to konieczne, ponieważ bez tego program nie będzie w stanie wykonywać transformacji związanej z macierzą opisaną w strukturze XFROM.

Listing 21
  1. case WM_CREATE:
  2. {
  3. obj = NULL;
  4. mem = CreateCompatibleDC(GetDC(NULL)); // tworzenie kompatybilnego z systemem kontekstu urządzenia
  5. SetGraphicsMode(mem, GM_ADVANCED); // ustawienie zaawansowanego trybu graficznego
  6. }
  7. break;

Zmiany w komunikacie WM_PAINT:

Listing 22
  1. case WM_PAINT:
  2. {
  3. PAINTSTRUCT ps;
  4. HDC hdc = BeginPaint(hWndDraw, &ps);
  5. FillRect(mem, &wndRect, (HBRUSH)GetStockObject(BLACK_BRUSH)); // zamalowanie bitmapy na czarno
  6. SetWorldTransform(mem, &docLuw); // ustawienie układu współrzędnych związanego z dokumentem
  7. SetBkMode(mem, TRANSPARENT);
  8. for(std::vector<i_dr_obj*>::iterator i = tObj.begin(); i < tObj.end(); i++){
  9. (*i)->Draw(mem, tPen, tBrush); // rysowanie dodanych obiektów
  10. }
  11. if(st == state::sel){
  12. SelObjAct.WmPaint(mem, &docLuw);
  13. }
  14. if(obj){
  15. obj->Draw(mem, tPen, tBrush); // rysowanie dodawanego obiektu
  16. }
  17. SetWorldTransform(mem, &guw); // ustawienie domyślnego układu współrzędnych
  18. BitBlt(hdc, 0, 0, wndRect.right, wndRect.bottom, mem, 0, 0, SRCCOPY); // przerysowanie bitmapy do okna
  19. EndPaint(hWndDraw, &ps);
  20. }
  21. break;

Również w komunikacie WM_LBUTTONDOWN zajdą pewne drobne zmiany:

Listing 23
  1. case WM_LBUTTONDOWN:
  2. {
  3. SetCapture(hWndDraw);
  4. switch(st){
  5. case dr_line: // tworzę obiekt typu line
  6. {
  7. obj = new line(tPen, tBrush, pen(255,0,0,1,pen::ps::solid), brush(0,0,150), &docLuw);
  8. obj->WmLButtonDown(mousepos);
  9. }
  10. break;
  11. case dr_circle: // tworzę obiekt typu circle
  12. {
  13. obj = new circle(tPen, tBrush, pen(0,255,0,1,pen::ps::solid), brush(0,150,0), &docLuw);
  14. obj->WmLButtonDown(mousepos);
  15. }
  16. break;
  17. case dr_rect: // tworzę obiekt typu rectangle
  18. {
  19. obj = new rectangle(tPen, tBrush, pen(0, 150, 255, 1, pen::ps::solid), brush(150,0,200), &docLuw);
  20. obj->WmLButtonDown(mousepos);
  21. }
  22. break;
  23. case sel: // trzyb zaznaczania (nie obsłużony jeszcze)
  24. {
  25. SelObjAct.WmLButtonDown(hWndDraw, tObj, wParam, mousepos);
  26. }
  27. break;
  28. }
  29. }
  30. break;

Po tym wszystkim mamy zrealizowaną obsługę przemieszczania zaznaczonych obiektów, które zostały dynamicznie utworzone.

Załączniki:

Wersja programu Rysowanie omawianej na tej stronie

Komentarze