.도형 정렬

캔버스에 그려진 도형들은 수직적인 순서를 가지고 있어 먼저 그려진 도형이 나중에 그려진 도형에 의해 가려진다. 도형간의 아래 위가 존재하는 이유는 OnPaint에서 배열을 순방향으로 순회하면서 배열의 앞쪽에 있는 도형을 먼저 그리고 뒤쪽의 도형을 나중에 그리기 때문이다. 순서를 변경하는 기능을 정렬이라 하는데 도형의 순서를 조정하여 수직적인 배치 상태를 바꾸는 기능이다. 배열상의 순서만 바꾸고 다시 그리면 화면상의 순서도 바뀌게 된다. 다음 함수는 배열상의 도형을 이동시켜 순서를 변경한다.

 

void MoveObjectInArray(int src,int dest)

{

   DObject *tObject=arObj[src];

   size_t len;

 

   len=abs(src-dest)*sizeof(DObject *);

   if (src > dest) {

      memmove(arObj+dest+1,arObj+dest,len);

   } else {

      memmove(arObj+src,arObj+src+1,len);

   }

   arObj[dest]=tObject;

}

 

이동 방향에 따라 memmove 포인터를 이동시켜 자리를 만들고 자리에 tObject 대입했다. arObj 배열에 기록된 것은 DObject 구조체의 포인터이므로 포인터만 이동시키면 된다. 포인터가 가리키는 곳에 있는 동적 할당된 구조체의 내용을 교환할 필요는 없다. 이것이 바로 포인터를 때의 이점인데 구조체가 아무리 커도 포인터만 조작하면 된다. 팝업 메뉴에 도형을 정렬하는 항목들이 이미 작성되어 있으므로 캔버스의 OnCommand에서 메뉴 항목들의 명령대로 도형을 교체한다.

 

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

{

   switch(LOWORD(wParam)) {

   ....

   case IDM_POPUP_FRONT:

      if (NowSel != -1 && NowSel < arNum-1) {

          MoveObjectInArray(NowSel,NowSel+1);

          NowSel=NowSel+1;

          InvalidateRect(hWnd,NULL,TRUE);

      }

      break;

   case IDM_POPUP_BACK:

      if (NowSel != -1 && NowSel > 0) {

          MoveObjectInArray(NowSel,NowSel-1);

          NowSel=NowSel-1;

          InvalidateRect(hWnd,NULL,TRUE);

      }

      break;

   case IDM_POPUP_MOSTFRONT:

      if (NowSel != -1 && NowSel < arNum-1) {

          MoveObjectInArray(NowSel,arNum-1);

          NowSel=arNum-1;

          InvalidateRect(hWnd,NULL,TRUE);

      }

      break;

   case IDM_POPUP_MOSTBACK:

      if (NowSel != -1 && NowSel > 0) {

          MoveObjectInArray(NowSel,0);

          NowSel=0;

          InvalidateRect(hWnd,NULL,TRUE);

      }

      break;

   }

   return 0;

}

 

모든 정렬 명령은 일단 선택된 도형이 있을 때만 사용할 있으며 또한 정렬 명령별로 선택 도형의 순서값이 조건에 맞아야 한다. 가령 도형을 수직 위쪽으로 이동시키기 위해서는 배열상의 뒤로 이동해야 하는데 도형이 이미 배열의 제일 마지막에 있다면 이상 뒤로 보낼 없을 것이다. 마찬가지로 수직 아래쪽으로 이동하기 위해서는 배열의 앞쪽으로 이동해야 하는데 이미 제일 앞에 있다면 역시 정렬할 없다.

조건에 맞을 경우 이동하려는 위치로 MoveObjectInArray 함수를 호출하면 된다. 함수에 의해 arObj 배열의 포인터들이 순서대로 이동할 것이다. 배열 상에서 도형을 이동시키고 이동한 후의 도형을 선택 상태로 만든 화면을 다시 그리기만 하면 화면에도 정렬 결과가 적용된다.

메인 메뉴에도 동일한 명령들이 있는데 이중으로 코드를 작성할 필요없이 캔버스에게 명령을 전달하면 된다. 정렬에 관련된 모든 처리는 캔버스가 하고 메인 윈도우는 명령의 중계만 뿐이다.

 

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

{

   switch(LOWORD(wParam)) {

   ....

   case IDM_SHAPE_FRONT:

      SendMessage(hCanvas,WM_COMMAND,MAKEWPARAM(IDM_POPUP_FRONT,0),0);

      break;

   case IDM_SHAPE_BACK:

      SendMessage(hCanvas,WM_COMMAND,MAKEWPARAM(IDM_POPUP_BACK,0),0);

      break;

   case IDM_SHAPE_MOSTFRONT:

      SendMessage(hCanvas,WM_COMMAND,MAKEWPARAM(IDM_POPUP_MOSTFRONT,0),0);

      break;

   case IDM_SHAPE_MOSTBACK:

      SendMessage(hCanvas,WM_COMMAND,MAKEWPARAM(IDM_POPUP_MOSTBACK,0),0);

      break;

 

여기까지 코드를 작성하면 정렬 기능은 완성되었다. 남은 작업은 선택된 도형의 위치에 따라 정렬 관련 메뉴 항목을 관리하는 것이다. 정렬할 없는 도형에 대해서는 아예 메뉴 항목을 사용 금지시키는 것이 좋다. 팝업 메뉴는 메뉴를 열기 전에 조건을 점검해 보고 사용할 없는 항목만 사용 금지시키면 된다.

 

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

{

   ....

   if (NowSel == -1 || NowSel >= arNum-1) {

      EnableMenuItem(hPopup, IDM_POPUP_FRONT, MF_BYCOMMAND | MF_GRAYED);

      EnableMenuItem(hPopup, IDM_POPUP_MOSTFRONT, MF_BYCOMMAND | MF_GRAYED);

   }

   if (NowSel == -1 || NowSel <= 0) {

      EnableMenuItem(hPopup, IDM_POPUP_BACK, MF_BYCOMMAND | MF_GRAYED);

      EnableMenuItem(hPopup, IDM_POPUP_MOSTBACK, MF_BYCOMMAND | MF_GRAYED);

   }

 

   TrackPopupMenu(hPopup, TPM_LEFTALIGN, LOWORD(lParam), HIWORD(lParam),

      0, hWnd, NULL);

   DestroyMenu(hMenu);

   return 0;

}

 

선택된 도형이 배열의 제일 끝에 있다면 도형은 화면상의 제일 위에 있으므로 이상 위로 이동할 없다. 메인 메뉴의 관리 코드도 비슷하되 팝업과는 달리 매번 메뉴를 만드는 것이 아니라 메인 윈도우의 상단에 부착되어 있기 때문에 사용 금지된 항목을 명시적으로 허가하는 코드도 필요하다는 것이 다르다.

 

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

{

   ....

   if (NowSel != -1 && NowSel < arNum-1) {

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_FRONT, MF_BYCOMMAND | MF_ENABLED);

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_MOSTFRONT, MF_BYCOMMAND | MF_ENABLED);

   } else {

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_FRONT, MF_BYCOMMAND | MF_GRAYED);

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_MOSTFRONT, MF_BYCOMMAND | MF_GRAYED);

   }

   if (NowSel != -1 && NowSel > 0) {

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_BACK, MF_BYCOMMAND | MF_ENABLED);

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_MOSTBACK, MF_BYCOMMAND | MF_ENABLED);

   } else {

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_BACK, MF_BYCOMMAND | MF_GRAYED);

      EnableMenuItem((HMENU)wParam, IDM_SHAPE_MOSTBACK, MF_BYCOMMAND | MF_GRAYED);

   }

   return 0;

}

 

메인 메뉴는 처음 한번 만들어지고 계속 사용하므로 한번 사용 금지 시킨 메뉴 항목은 별다른 조치를 취하지 않는 상태가 변경되지 않기 때문에 조건이 맞을 경우 사용 허가도 해야 한다.