.ApiDraw01

번째 예제인 ApiDraw01 전형적인 API 프로젝트의 형태를 하고 있다. 메뉴에서 그리고자 하는 도형을 선택한 마우스로 드래그하여 직선, 사각형, 타원 등을 그리는 정도의 기능만 가지고 있다. 6장의 RopMode 예제와 유사하므로 RopMode 예제를 이해했다면 예제 정도는 어렵지 않게 이해가 것이다. 다만 예제는 차후의 확장성을 고려하여 윈도우 구조나 변수의 타입 설계를 확장 가능하도록 했다는 정도가 다르다. 전체 소스는 다음과 같다.

 

#include <windows.h>

#include "resource.h"

 

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

LRESULT CALLBACK CanvasProc(HWND,UINT,WPARAM,LPARAM);

HINSTANCE g_hInst;

HWND hWndMain;

LPCTSTR lpszClass=TEXT("ApiDraw");

 

// 타입 전역 변수

HWND hCanvas;

enum DTool { DT_LINE, DT_ELLIPSE, DT_RECTANGLE };

enum DMode { DM_NONE, DM_DRAW };

DTool NowTool;

DMode DragMode;

int sx,sy,oldx,oldy;

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

     ,LPSTR lpszCmdParam,int nCmdShow)

{

   HWND hWnd;

   MSG Message;

   WNDCLASS WndClass;

   g_hInst=hInstance;

  

   WndClass.cbClsExtra=0;

   WndClass.cbWndExtra=0;

   WndClass.hbrBackground=NULL;

   WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);

   WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);

   WndClass.hInstance=hInstance;

   WndClass.lpfnWndProc=WndProc;

   WndClass.lpszClassName=lpszClass;

   WndClass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);

   WndClass.style=0;

   RegisterClass(&WndClass);

 

   WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);

   WndClass.lpfnWndProc=CanvasProc;

   WndClass.lpszClassName="Canvas";

   WndClass.lpszMenuName=NULL;

   WndClass.style=CS_DBLCLKS;

   RegisterClass(&WndClass);

 

   hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,

      CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

      NULL,(HMENU)NULL,hInstance,NULL);

   ShowWindow(hWnd,nCmdShow);

  

   while(GetMessage(&Message,0,0,0)) {

      TranslateMessage(&Message);

      DispatchMessage(&Message);

   }

   return (int)Message.wParam;

}

 

// 메인 윈도우의 메시지 처리 함수

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   switch(iMessage) {

   case WM_CREATE:

      hWndMain=hWnd;

      hCanvas=CreateWindow("Canvas",NULL,WS_CHILD | WS_VISIBLE,

          0,0,0,0,hWnd,(HMENU)0,g_hInst,NULL);

      return 0;

   case WM_SIZE:

      if (wParam != SIZE_MINIMIZED) {

          MoveWindow(hCanvas,0,0,LOWORD(lParam),HIWORD(lParam),TRUE);

      }

      return 0;

   case WM_COMMAND:

      switch(LOWORD(wParam)) {

      case IDM_SHAPE_LINE:

          NowTool=DT_LINE;

          break;

      case IDM_SHAPE_ELLIPSE:

          NowTool=DT_ELLIPSE;

          break;

      case IDM_SHAPE_RECTANGLE:

          NowTool=DT_RECTANGLE;

          break;

      }

      return 0;

   case WM_DESTROY:

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

// 캔버스 윈도우의 메시지 처리 함수

LRESULT CALLBACK CanvasProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   int ex,ey;

   HDC hdc;

 

   switch(iMessage) {

   case WM_CREATE:

      NowTool=DT_LINE;

      DragMode=DM_NONE;

      return 0;

   case WM_LBUTTONDOWN:

      sx=LOWORD(lParam);

      sy=HIWORD(lParam);

      oldx=sx;

      oldy=sy;

      DragMode=DM_DRAW;

      SetCapture(hWnd);

      return 0;

   case WM_MOUSEMOVE:

      ex=(int)(short)LOWORD(lParam);

      ey=(int)(short)HIWORD(lParam);

      if (DragMode==DM_DRAW) {

          hdc=GetDC(hWnd);

          SetROP2(hdc,R2_NOTXORPEN);

          SelectObject(hdc,GetStockObject(NULL_BRUSH));

          switch (NowTool) {

          case DT_LINE:

             MoveToEx(hdc,sx,sy,NULL);

             LineTo(hdc,oldx,oldy);

             MoveToEx(hdc,sx,sy,NULL);

             LineTo(hdc,ex,ey);

             break;

          case DT_ELLIPSE:

             Ellipse(hdc,sx,sy,oldx,oldy);

             Ellipse(hdc,sx,sy,ex,ey);

             break;

          case DT_RECTANGLE:

             Rectangle(hdc,sx,sy,oldx,oldy);

             Rectangle(hdc,sx,sy,ex,ey);

             break;

          }

          oldx=ex;

          oldy=ey;

          ReleaseDC(hWnd,hdc);

      }

      return 0;

   case WM_LBUTTONUP:

      DragMode=DM_NONE;

      ReleaseCapture();

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

WinMain에서 개의 윈도우 클래스를 등록하고 있는데 메인 윈도우외에 캔버스를 위한 윈도우 클래스를 따로 등록했다. 메인 윈도우는 WM_CREATE에서 캔버스를 생성하며 WM_SIZE에서 캔버스를 자신의 작업 영역 전체에 가득 채운다. 외에 메인 윈도우가 하는 일이라고는 메뉴가 선택될 그리기 도구를 바꾸는 정도 뿐이다. 마우스로부터 도형을 생성하고 그리는 모든 동작은 캔버스가 하도록 되어 있다.

결국 메인 윈도우는 캔버스를 담는 껍데기 노릇을 뿐이며 자체가 프로그램의 기능을 직접 제공하는 것은 아니다. 이런 식으로 캔버스 윈도우를 따로 분리하는가 하면 차후의 확장성과 관리의 편리를 위해서이다. 현재 상황에서는 메인 윈도우가 직접 그래픽 객체를 관리해도 아무런 문제가 없다. 그러나 툴바나 팔레트 등이 추가될 때는 상황이 달라진다.

왼쪽 그림처럼 차일드가 없을 때는 메인 윈도우의 작업 영역을 기준으로 그래픽 객체의 좌표를 저장하면 된다. 상태에서 (100,100) 작업 영역 좌상단을 기준으로 (100,100) 좌표가 것이다. 그러나 오른쪽 그림처럼 툴바나 팔레트들이 배치되면 이런 차일드도 메인 윈도우의 작업 영역에 배치되므로 그래픽 객체에 저장된 좌표를 그대로 없고 차일드들의 좌표를 계산에 포함시켜야 한다. 객체를 그릴 뿐만 아니라 마우스 메시지의 인수들을 해석할 때도 차일드를 고려해야 하므로 무척 불편하다.

그림을 그리는 캔버스 윈도우를 따로 만들고 캔버스의 작업 영역에 그리기를 하면 이후 툴바나 팔레트들이 추가되더라도 좌표를 조정할 필요가 없다. 메인 윈도우는 캔버스, 툴바 등을 마음대로 재배치할 있는 자유가 생기는 것이다. 이런 이유로 캔버스를 따로 만들고 모든 그리기 동작은 캔버스가 전담하도록 했다.

다음은 전역 변수들을 보자. DTool 열거형 타입은 그리기 도구들이며 현재 직선, 타원, 사각형 등이 정의되어 있다. DTool형의 NowTool 현재 그리기 도구를 기억하며 값은 또한 도형의 타입 식별자로도 사용된다. DMode 열거형은 마우스가 현재 무엇을 하고 있는 중인가를 기억하며 주로 WM_MOUSEMOVE에서 값을 참조한다. sx, sy 도형을 그리기 위해 드래그를 시작한 좌표이며 oldx, oldy 드래그 중의 좌표이다. 변수의 용도는 RopMode 예제와 동일하다.

캔버스 윈도우의 프로시저에는 마우스로 도형을 그리는 일반적인 코드들이 작성되어 있다. 마우스 왼쪽 버튼을 누를 드래그 시작점을 sx, sy 저장해 두고 마우스 이동시 반전 그리기 모드로 도형을 보여주다가 마우스 버튼을 놓을 도형을 확정짓는 방식이다. 예제는 RopMode 예제에 비해 가지 면에서 다른데 우선 그리기 모드가 R2_NOT 아닌 R2_NOTXORPEN이라는 점이 다른데 프로그램은 흑백이 아닌 색상이 있는 도형도 그려야 하기 때문이다.

또한 WM_LBUTTONUP에서 도형을 다시 한번 그리지 않음으로써 임시적으로 그려진 도형을 확정짓지 않아 도형끼리 겹쳐진 부분이 반전된 상태로 남아 있는 문제가 있다. 문제는 그리기 정보가 저장되고 그래픽 객체를 다시 복구할 있게 되면 자연스럽게 해결되므로 걱정하지 않아도 된다.