.선택하기

ApiDraw04 프로젝트에서는 기본적인 도형의 편집 기능을 작성해 본다. ApiDraw 그림을 그리기만 하는 페인팅 프로그램이 아니라 그려진 도형을 자유롭게 이동, 크기 변경, 추가, 삭제할 있는 벡터 드로잉 툴이다. 드로잉 툴과 페인팅 툴의 차이점은 이미 그려진 그림을 편집할 있는가 아닌가인데 그림판은 그려진 도형을 다른 그림에 영향을 주지 않고 옮기기 어렵다. 이미 그려져 있는 도형을 편집하려면 먼저 대상 도형을 선택하는 기능부터 작성해야 한다. 선택을 하는 시점은 의심의 여지없이 마우스 왼쪽 버튼을 누를 때이고 OnPaint에서는 도형이 선택되어 있다는 것을 표시해야 한다. 하고자 하는 작업의 코드는 아마도 다음과 같을 것이다.

 

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

{

   // 클릭한 위치의 도형을 선택한다.

}

 

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

{

   // 선택된 도형의 상태를 그린다.

}

 

OnLButtonDown 선택뿐만 아니라 도형을 그릴 때도 사용되므로 현재 모드가 선택 상태인지 아니면 그리기 상태인지를 구분할 있어야 한다. 선택을 하는 모드는 도형을 그리는 모드와 다르며 마우스를 클릭할 때인 OnLButtonDown 모드에 따라 다른 동작을 해야 것이다. 이런 모드를 기억하는 변수는 NowTool이며 WM_COMMAND에서 IDM_SHAPE_SELECT 선택시 NowTool 선택 모드로 변경한다.

 

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

{

   switch(LOWORD(wParam)) {

   case IDM_FILE_EXIT:

      DestroyWindow(hWnd);

      break;

   case IDM_SHAPE_SELECT:

      NowTool=DT_SELECT;

      break;

   ....

 

OnLButtonDown NowTool 변수값을 보고 직선을 그릴 것인지, 타원을 그릴 것인지 아니면 선택을 것인지를 결정할 것이다. 선택을 하는 시점(OnLButtonDown) 선택을 보여 주는 시점(OnPaint) 다르므로 선택된 도형이 어떤 것인지를 별도로 기억해야 한다. 윈도우즈 프로그램은 아무리 사소한 것이라도 모든 것을 기억해야 한다. 도형을 저장하는 자료 구조가 배열이므로 선택된 도형의 첨자를 기억하면 것이다. 첨자만 기억하면 첨자로부터 선택 도형의 종류와 영역 등을 모두 구할 있다. 정수형의 전역 변수 NowSel 선언하고 OnCreate에서 -1 초기화한다.

 

int NowSel;

 

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

{

   ....

   NowSel=-1;

   return 0;

}

 

NowSel -1 특이값을 가진다는 것은 선택된 도형이 없다는 뜻이다. 변수가 배열의 첨자이므로 -1 정상적인 값과 구분되는 특이값으로 사용될 있다. 상태에서 3 도형을 선택하면 NowSel 3 것이다. ApiDraw 도형 하나만 선택할 있으므로 정수형 변수 하나만으로 선택 상태를 기억할 있다. 만약 복수 개의 도형을 선택할 있다면 선택된 도형들의 첨자를 저장할 정수형 배열이나 연결 리스트가 필요할 것이다. 아니면 DObject 구조체에 bSelect 등의 멤버를 추가하고 멤버에 개별 도형의 선택 상태를 기억하도록 수도 있다.

선택 모드가 정의되었고 선택 상태를 기억할 변수가 만들어졌으므로 이제 실제 선택 코드를 작성해 보자. 마우스 왼쪽 버튼을 누르면 커서 아래의 도형을 선택 상태로 만들어야 하는데 작업을 하려면 먼저 특정 좌표 아래에 있는 도형이 누구인가를 조사해야 한다. 다음 함수는 주어진 좌표로부터 도형의 첨자를 찾는다.

 

int FindObject(int x, int y)

{

   int idx;

   POINT pt;

 

   pt.x=x;

   pt.y=y;

   for (idx=arNum-1;idx>=0;idx--) {

      if (PtInRect(&arObj[idx]->rt,pt)==TRUE) {

          return idx;

      }

   }

   return -1;

}

 

arObj 배열을 순회하면서 x, y 도형 내부에 있는 번째 도형을 찾아 첨자를 리턴하며 만약 조건에 맞는 도형이 발견되지 않을 경우 -1 리턴한다. 주어진 좌표가 도형 내부인지를 조사하기 위해 PtInRect 함수를 사용했다. 함수의 동작에서 눈여겨 것은 arObj 배열을 순회할 끝에서부터 앞쪽으로 순회를 한다는 점이다. 최근에 추가된 도형이 화면의 위쪽에 있으므로 가급적이면 사용자 시선쪽에 가까운 도형이 선택되어야 하기 때문이다. 실제로 도형을 선택하는 OnLButtonDown 코드는 다음과 같이 작성한다.

 

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);

      }

   } else {

      sx=LOWORD(lParam);

      sy=HIWORD(lParam);

      oldx=sx;

      oldy=sy;

      DragMode=DM_DRAW;

   }

   SetCapture(hWnd);

   return 0;

}

 

현재 모드가 DT_SELECT FindObject 클릭한 지점의 도형을 찾아 본다. 검색된 도형의 첨자를 NowSel 바로 대입하지 않고 TempSel이라는 중간 변수에 대입한 비교하는 이유는 똑같은 도형을 선택할 불필요한 화면 그리기를 하지 않도록 하기 위해서이다. FindObject 검색한 결과가 현재 선택 도형인 NowSel 다를 때만 화면을 다시 그리도록 했다. 만약 처리를 하지 않으면 도형 위에서건 영역에서건 마우스를 클릭할 때마다 전체 화면이 계속 다시 그려질 것이다.

화면을 다시 그릴 때는 무효화만 시키면 된다. OnPaint에서 NowSel 값을 참조하여 선택 도형을 적절히 그려줄 것이다. 화면에 어떤 변화가 발생할 화면을 직접 건드릴 필요가 없으며 OnPaint 참조하는 하나를 변경하고 무효화한다. UpdateWindow 호출은 선택 상태를 즉시 그리도록 하는데 선택 곧바로 이동하거나 크기를 변경할 수도 있기 때문이다. 이동할 어떤 도형이 이동 대상인지 분명히 표시해야 하므로 선택 상태가 바뀔 때는 곧바로 화면을 갱신하도록 했다.

여기까지 코드를 작성하면 내부적으로 선택은 완벽하게 이루어지며 선택에 의해 NowSel 관리되고 있지만 아직 선택 상태가 화면에 보이지는 않는다. 화면에 선택 상태를 출력하기 전에 선택을 변경하는 코드들이 제대로 동작하는지부터 테스트해 보자. 선택이 변경되는 시점에 다음 테스트 코드를 작성한다.

 

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);

          TCHAR str[128];

          wsprintf(str,"NowSel=%d",NowSel);

          SetWindowText(GetParent(hWnd),str);

      }

   } else {

 

NowSel 변수값을 메인 윈도우의 타이틀 바에 출력해 봤는데 앞에서 만든 코드들이 이상없이 동작한다면 NowSel 변수가 정확한 선택 상태를 기억할 것이다. 이런 식으로 새로 작성한 코드들은 중간 테스트를 보고 다음 단계로 진행해야 신뢰성있는 코드를 작성할 있다. 굳이 디버거를 돌려 보지 않더라도 타이틀바에 변수값만 찍어보는 간단한 방법으로 확인 가능하다. 테스트 코드이므로 점검이 끝난 후에는 물론 삭제해야 한다.