.이동하기

이번 절에서는 객체를 이동, 크기 변경해 보도록 하자. ApiDraw04 프로젝트의 사본을 작성하여 ApiDraw05 프로젝트를 만들고 프로젝트에 객체 편집 코드를 하나씩 만들어 보자. 먼저 테스트의 편의를 위해 타원을 디폴트 툴로 선택해 둔다. 직선보다는 타원이 이동 크기 변경 테스트에 적합하다. OnCreate에서 NowTool 초기값만 바꾸면 된다.

 

LRESULT OnCreate(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

   NowTool=DT_ELLIPSE;

   ....

 

이동과 크기 변경 처리를 위해 다음 개의 전역 변수를 추가한다.

 

DObject dObj;

int SizeCorner;

 

dObj 이동중인 도형에 대한 정보를 가지는 임시 객체이며 SizeCorner 크기 변경시 사용자가 드래그하고 있는 트래커 번호이다. 마우스 메시지 핸들러에서 이동중인 도형의 테두리만 점선으로 그려야 하는데 작업을 대신할 함수를 따로 작성한다.

 

void DrawTemp(const DObject *pObj)

{

   HDC hdc;

   HPEN hPen,hOldPen;

   HBRUSH hOldBrush;

 

   hdc=GetDC(hCanvas);

   SetROP2(hdc,R2_XORPEN);

   hPen=CreatePen(PS_DOT,1,RGB(0,0,0));

   hOldPen=(HPEN)SelectObject(hdc,hPen);

   hOldBrush=(HBRUSH)(SelectObject(hdc,GetStockObject(NULL_BRUSH)));

   switch (pObj->Type) {

   case DT_LINE:

      if ((pObj->Flag & 0x3) == DS_LT || (pObj->Flag & 0x3) == DS_RB) {

          MoveToEx(hdc,pObj->rt.left,pObj->rt.top,NULL);

          LineTo(hdc,pObj->rt.right,pObj->rt.bottom);

      } else {

          MoveToEx(hdc,pObj->rt.left,pObj->rt.bottom,NULL);

          LineTo(hdc,pObj->rt.right,pObj->rt.top);

      }

      break;

   case DT_ELLIPSE:

      Ellipse(hdc,pObj->rt.left,pObj->rt.top,pObj->rt.right,pObj->rt.bottom);

      break;

   case DT_RECTANGLE:

      Rectangle(hdc,pObj->rt.left,pObj->rt.top,pObj->rt.right,pObj->rt.bottom);

      break;

   }

   DeleteObject(SelectObject(hdc,hOldPen));

   SelectObject(hdc,hOldBrush);

   ReleaseDC(hCanvas,hdc);

}

 

함수는 인수로 전달된 pObj 정보에 따라 도형을 그리되 점선 펜과 브러시로 그려 이동중인 임시 객체임을 나타내도록 했다. OnPaint 있는 그리기 코드와 유사하되 함수가 그리는 객체는 어디까지나 임시 객체이므로 이후 도형의 속성이나 더블 버퍼링 등의 처리에서 제외된다. 임시 객체를 그리는 코드를 굳이 함수로 따로 만든 이유는 마우스 핸들러에서 똑같은 동작을 여러 해야 하기 때문이다. 객체를 이동시키기 위해서는 그리기 모드를 XOR 바꿔서 지우고 다시 그리기를 반복해야 하므로 함수로 분리해 놓고 필요할 때마다 호출하는 것이 훨씬 유리하다.

다음은 함수가 전달받는 인수에 대해 고려해 보자. 이동중인 임시 객체를 그리기 위해 필요한 정보는 사실 사각 영역밖에 없다. 직선이나 타원도 외접 사각형을 그려 이동할 위치를 표현할 있지만 도형의 원래 모양을 그리는 것이 좋을 것이다. 그래서 DrawTemp 이동할 임시 객체의 포인터를 전달받고 임시 객체의 정보대로 모양을 그린다. 이후 DObject 구조체가 곡선이나 다각형을 표현할 있도록 확장되더라도 임시 객체의 포인터로부터 원하는 정보를 모두 추출해 있다.

, 함수는 임시 객체를 그리기 위해 어디까지나 도형의 정보를 참조만 뿐이지 도형의 내용을 변경해서는 안되므로 const 포인터를 받도록 했다. 임시 객체를 그리는 함수를 만들었으므로 마우스 핸들러에서는 커서의 이동에 따라 임시 객체를 이동시켜 그리면서 버튼을 놓을 최종적으로 객체의 좌표를 수정하면 된다. 이동 처리의 시작은 마우스 왼쪽 버튼을 누를 때이며 선택 모드일 때만 이동할 있다.

 

LRESULT OnLButtonDown(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

   int TempSel;

 

   if (NowTool==DT_SELECT) {

      TempSel=FindObject(LOWORD(lParam),HIWORD(lParam));

      if (NowSel != TempSel) {

          NowSel=TempSel;

          InvalidateRect(hWnd,NULL,TRUE);

          UpdateWindow(hWnd);

      }

      if (NowSel != -1) {

          oldx=LOWORD(lParam);

          oldy=HIWORD(lParam);

          dObj=*arObj[NowSel];

          DrawTemp(&dObj);

          DragMode=DM_MOVE;

      }

   } else {

   ....

}

 

코드의 순서를 약간 주의해야 하는데 선택툴일 일단 선택 처리한 후에 이동을 시작한다. 그래야 선택이 없는 상태에서 바로 도형을 드래그할 때도 선택과 동시에 이동할 있다. 선택 상태가 바뀌었으면 UpdateWindow 호출하여 트래커를 즉시 그려 이동 대상이 어떤 도형인지를 가급적 빨리 표시하도록 했다. 이동이 시작되면 oldx, oldy 최초 마우스 버튼을 누른 지점을 저장해 두고 dObj 선택 도형의 사본을 작성한다. 구조체끼리이므로 단순 대입에 의해 사본을 쉽게 만들 있다. , 이후 DObject 포인터를 가지게 된다면 이때는 깊은 복사를 하거나 아니면 포인터는 읽기 전용으로만 사용해야 한다.

작성된 사본으로 DrawTemp 일단 호출하여 임시 객체를 그려 놓고 이동을 시작한다. 그래야 마우스 이동시에 임시 객체를 지우고 위치에 다시 임시 객체를 그릴 있다. DragMode 변수에 DM_MOVE 대입하여 현재 도형을 이동중이라는 것을 기록한다. 이후의 이동 처리는 마우스가 움직일 때인 OnMouseMove에서 처리한다.

 

LRESULT OnMouseMove(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

   ....

   if (DragMode==DM_MOVE) {

      DrawTemp(&dObj);

      OffsetRect(&dObj.rt,ex-oldx,ey-oldy);

      oldx=ex;

      oldy=ey;

      DrawTemp(&dObj);

   }

   return 0;

}

 

DragMode DM_MOVE 때만 임시 객체 이동 처리를 하는데 번의 DrawTemp 호출이 있다. 번째 DrawTemp 호출은 이전 위치의 임시 객체를 지우는 것이고 dObj rt 변경한 번째로 함수를 호출하는 것은 변경된 위치에 임시 객체를 다시 출력하는 것이다. 과정이 마우스 버튼을 놓을 때까지 반복되며 버튼을 놓으면 임시 객체의 정보를 이동 대상 객체로 다시 대입한다.

 

LRESULT OnLButtonUp(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

   ....

   if (DragMode==DM_MOVE) {

      arObj[NowSel]->rt=dObj.rt;

      InvalidateRect(hWnd,NULL,TRUE);

   }

   DragMode=DM_NONE;

   ReleaseCapture();

   return 0;

}

 

위치만 이동했으므로 임시 객체의 정보 나머지는 그대로 두고 rt 다시 대입받는데 영역을 객체에 다시 대입함으로써 이동의 최종 결과를 취한 것이다. 메모리상에서 이동을 완료한 작업 영역 전체를 무효화하면 OnPaint에서 변경된 위치에 도형을 다시 깔끔하게 그릴 것이다. 보다시피 이동이란 무척 간단한 동작이다. 임시 객체를 그리는 것이 조금 귀찮을 뿐이지 도형의 rt 영역만 수정하고 다시 그리기만 하면 된다.