.클립보드

ApiDraw11에서는 클립보드로 도형을 복사하고 붙여 넣는 실습을 보자. 프로젝트의 사본을 작성한 다음 전역 변수를 추가한다.

 

UINT ClipFormat;

int PasteX, PasteY;

 

ClipFormat ApiDraw 도형에 대한 클립보드 포맷 ID이다. 윈도우즈가 ApiDraw 도형 포맷을 리가 없으므로 응용 프로그램이 클립보드 포맷을 직접 등록해서 사용해야 한다. 메인 윈도우가 생성될 클립보드 포맷을 등록하도록 하자.

 

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

{

   ....

   ClipFormat=RegisterClipboardFormat("ApiDraw1.0_ClipboardFormat_"

      "RegisteredBy_KoreaKimSangHyung_When_20041108_03:58:12");

   return 0;

}

 

아주 이름으로된 문자열을 주고 ApiDraw 대한 클립보드 포맷의 ID 발급받았다. 문자열에는 응용 프로그램의 이름과 버전, 만든 사람의 이름과 날짜 등이 포함되어 있으므로 우연히 같은 이름으로 포맷을 등록하는 일은 없을 것이다. 이후 ApiDraw ClipFormat으로 자신의 도형을 클립보드에 넣을 있고 클립보드에 이런 형식의 데이터가 들어 있는지 조사할 수도 있으며 다시 꺼낼 수도 있다. 포맷을 아는 유일한 프로그램은 ApiDraw뿐이지만 ApiDraw끼리는 포맷을 인식하므로 개의 ApiDraw 클립보드로 서로의 자료를 교환할 있다.

PasteX, PasteY 붙여 넣기를 도형이 생성될 위치인데 똑같은 도형을 복사해서 붙인다 하더라도 위치만은 달라야 한다. 모든 정보를 완전히 똑같이 복사해 버리면 도형이 겹쳐서 일일이 분리해야만 것이다. 그래서 다음 붙여 넣을 좌표는 별도의 변수로 관리하며 붙이기를 때마다 우하향으로 일정한 거리만큼 이동하도록 한다. 복사 함수는 다음과 같이 작성한다.

 

void Copy()

{

   HGLOBAL hmem;

   int size;

   BYTE *ptr;

  

   if (NowSel == -1) {

      return;

   }

   size=sizeof(DObject);

   if (arObj[NowSel]->Type >= DT_TEXT && arObj[NowSel]->Type <= DT_META) {

      size += arObj[NowSel]->Len;

   }

 

   hmem=GlobalAlloc(GHND,size);

   ptr=(BYTE *)GlobalLock(hmem);

   memcpy(ptr,arObj[NowSel],sizeof(DObject));

   if (arObj[NowSel]->Type >= DT_TEXT && arObj[NowSel]->Type <= DT_META) {

      memcpy(ptr+sizeof(DObject),arObj[NowSel]->Text,arObj[NowSel]->Len);

   }

   GlobalUnlock(hmem);

   if (OpenClipboard(hWndMain)) {

      EmptyClipboard();

      SetClipboardData(ClipFormat,hmem);

      CloseClipboard();

      PasteX=arObj[NowSel]->rt.left+16;

      PasteY=arObj[NowSel]->rt.top+16;

   }

}

 

일단은 선택된 도형이 있어야 한다. 선택된 도형의 DObject 구조체를 클립보드에 복사하되 텍스트 부가 데이터를 가진 객체들은 데이터도 클립보드에 같이 저장해야 한다. 붙여 넣는 쪽에서 클립보드 내의 객체에 대한 모든 정보를 있도록 해야 하므로 도형 하나에 대한 완전한 정보를 어떤 형식으로든 클립보드에 넣어야 하는 것이다. 일반 도형은 DObject객체만 복사하고 텍스트 등은 DObject 다음에 Len 길이만큼의 데이터도 같이 넣어 두었다.

붙여 넣는 코드는 DObject Type으로부터 여분의 데이터가 있는지를 있고 Len으로부터 길이를 있으므로 무조건 뒤에 덧붙여 놓기만 하면 된다. PasteX, PasteY 복사한 도형의 좌표보다 오른쪽 아래로 16픽셀만큼 이동한 좌표를 대입하여 복사 곧바로 붙여 넣을 원본과 조금 다른 위치에 도형이 생성되도록 했다. 다음은 붙여 넣기를 하는 함수를 작성해 보자.

 

void Paste()

{

   HGLOBAL hmem;

   DObject *pNew;

   RECT nrt;

 

   if (IsClipboardFormatAvailable(ClipFormat)) {

      OpenClipboard(hWndMain);

      hmem=GetClipboardData(ClipFormat);

      pNew=(DObject *)GlobalLock(hmem);

      nrt.left=PasteX;

      nrt.top=PasteY;

      nrt.right=nrt.left+pNew->rt.right-pNew->rt.left;

      nrt.bottom=nrt.top+pNew->rt.bottom-pNew->rt.top;

      PasteX+=16;

      PasteY+=16;

      AppendObject(pNew->Type,&nrt);

      arObj[arNum-1]->LineWidth=pNew->LineWidth;

      arObj[arNum-1]->LineColor=pNew->LineColor;

      arObj[arNum-1]->PlaneColor=pNew->PlaneColor;

      arObj[arNum-1]->Len=pNew->Len;

      arObj[arNum-1]->FontSize=pNew->FontSize;

      arObj[arNum-1]->FontColor=pNew->FontColor;

      lstrcpy(arObj[arNum-1]->FontFace,pNew->FontFace);

 

      if (pNew->Type >= DT_TEXT && pNew->Type <= DT_META) {

          arObj[arNum-1]->Text=(TCHAR *)malloc(pNew->Len);

          memcpy(arObj[arNum-1]->Text,(BYTE *)pNew+sizeof(DObject),pNew->Len);

      }

      GlobalUnlock(hmem);

      CloseClipboard();

      NowSel=arNum-1;

      InvalidateRect(hWndMain,NULL,TRUE);

   }

}

 

붙여 넣기를 하려면 클립보드에 ClipFormat 형식의 데이터가 들어 있어야 한다. 텍스트나 HTML, 비트맵 따위의 데이터가 들어 있다면 데이터는 ApiDraw 직접 인식할 없다. ApiDraw 오로지 자신의 고유 포맷인 ClipFormat 인식할 뿐이다. 클립보드로부터 DObject 데이터를 구하고 PasteX, PasteY로부터 붙여 넣을 좌표를 계산한 AppendObject 일단 도형을 추가한다. 그리고 도형의 속성은 모두 클립보드에 저장된 속성대로 재설정한다. 텍스트, 비트맵, 메타인 경우는 추가 데이터도 같이 읽어와야 한다.

arObj 도형을 추가한 InvalidateRect 호출하면 새로 추가된 도형이 화면에 그려진다. 복사 붙여넣기 코드가 작성되었으므로 이제 적당한 메뉴에서 함수들을 부르기만 하면 된다. 메인 메뉴의 편집 팝업 아래에 있는 복사, 잘라내기, 붙여 넣기에서 함수들을 호출한다.

 

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

{

   ....

   case IDM_EDIT_CUT:

      Copy();

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

      break;

   case IDM_EDIT_COPY:

      Copy();

      break;

   case IDM_EDIT_PASTE:

      Paste();

      break;

 

잘라내기는 복사한 삭제하는 것과 동일하므로 별도의 코드를 따로 작성하지 않아도 된다. 여기까지 코드를 작성한 테스트해 보면 동작할 것이다. 선택된 도형의 유무와 클립보드에 들어 있는 데이터의 형식에 따라 메뉴 항목을 적절히 관리한다.

 

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

{

   ....

   if (NowSel == -1) {

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

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

   } else {

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

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

   }

   if (IsClipboardFormatAvailable(ClipFormat)) {

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

   } else {

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

   }

   return 0;

}

 

복사, 잘라내기는 선택된 도형이 있을 때만 사용할 있고 붙여 넣기는 클립보드에 ClipFormat 있을 때만 사용할 있다. 툴바의 버튼들도 동일한 방법으로 관리한다.

 

void OnIdle()

{

   static DTool OldTool=(DTool)-1;

 

   if (NowSel == -1) {

      SendMessage(hToolBar,TB_SETSTATE,IDM_EDIT_CUT,MAKELONG(0,0));

      SendMessage(hToolBar,TB_SETSTATE,IDM_EDIT_COPY,MAKELONG(0,0));

   } else {

      SendMessage(hToolBar,TB_SETSTATE,IDM_EDIT_CUT,MAKELONG(TBSTATE_ENABLED,0));

      SendMessage(hToolBar,TB_SETSTATE,IDM_EDIT_COPY,MAKELONG(TBSTATE_ENABLED,0));

   }

   if (IsClipboardFormatAvailable(ClipFormat)) {

      SendMessage(hToolBar,TB_SETSTATE,IDM_EDIT_PASTE,MAKELONG(TBSTATE_ENABLED,0));

   } else {

      SendMessage(hToolBar,TB_SETSTATE,IDM_EDIT_PASTE,MAKELONG(0,0));

   }

 

   if (OldTool == NowTool) {

      return;

   }

 

조건은 메뉴의 경우와 완전히 동일하다. 버튼과는 달리 클립보드 관련 명령은 언제든지 바뀔 있기 때문에 OnIdle 호출될 때마다 점검해 보아야 한다.