. CShowMsg

이제 컨트롤의 본체를 보도록 하자. 컨트롤은 C++ 클래스로 표현되며 ShowMsg.h 파일에 클래스가 정의된다.

 

class CShowMsg

{

private:

     int x;

     int y;

     TCHAR *str;

 

public:

     HWND hWnd;

     CShowMsg();

     ~CShowMsg();

 

     BOOL Create(int x,int y,int w,int h,DWORD style,UINT id,HWND hParent);

     void ChangeString(TCHAR *nstr);

     virtual LRESULT OnMessage(UINT iMessage,WPARAM wParam,LPARAM lParam);

};

 

전역 변수들은 모두 이 클래스의 멤버변수로 선언되었으며 컨트롤 내부의 변수이므로 private 액세스 속성을 가진다. 외부로 공개해도 되는 멤버라면 public 속성으로 선언해도 상관없다. 원래부터 있던 변수들 외에 윈도우 핸들값인 hWnd를 추가로 가지는데 도우미 클래스의 AddObject 함수가 객체 맵을 작성할 때 hWnd 멤버에 윈도우 핸들을 대입한다.

멤버변수 외에 생성자, 파괴자가 있고 세 개의 함수가 있는데 이 함수들의 코드는 ShowMsg.cpp 파일에 도우미 클래스와 함께 정의되어 있다.

 

CShowMsg::CShowMsg()

{

}

 

CShowMsg::~CShowMsg()

{

     if (_RegisterHelper.arObj)

          _RegisterHelper.RemoveObject(hWnd);

}

 

BOOL CShowMsg::Create(int x,int y,int w,int h,DWORD style,UINT id,HWND hParent)

{

     CreateWindow("ShowMsgCtrl",NULL, style,

          x,y,w,h,hParent,(HMENU)id,GetModuleHandle(NULL),this);

     return TRUE;

}

 

void CShowMsg::ChangeString(TCHAR *nstr)

{

     lstrcpy(str,nstr);

     InvalidateRect(hWnd,NULL,TRUE);

}

 

LRESULT CShowMsg::OnMessage(UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     HDC hdc;

     PAINTSTRUCT ps;

     RECT crt;

 

     switch(iMessage) {

     case WM_CREATE:

          x=50;

          y=50;

          str=(TCHAR *)malloc(128);

          lstrcpy(str,"String");

          return 0;

     case WM_KEYDOWN:

          GetClientRect(hWnd,&crt);

          switch (wParam) {

          case VK_LEFT:

              if (x > 0)

                   x--;

              break;

          case VK_RIGHT:

              if (x < crt.right-50)

                   x++;

              break;

          case VK_UP:

              if (y > 0)

                   y--;

              break;

          case VK_DOWN:

              if (y < crt.bottom-10)

                   y++;

              break;

          }

          InvalidateRect(hWnd,NULL,TRUE);

          return 0;

     case WM_LBUTTONDOWN:

          if (lstrcmp(str,"String") == 0) {

              ChangeString("문자열");

          } else {

              ChangeString("String");

          }

          SetFocus(hWnd);

          return 0;

     case WM_PAINT:

          GetClientRect(hWnd,&crt);

          hdc=BeginPaint(hWnd, &ps);

          Rectangle(hdc,0,0,crt.right,crt.bottom);

          TextOut(hdc,x,y,str,lstrlen(str));

          EndPaint(hWnd, &ps);

          return 0;

     case WM_DESTROY:

          free(str);

          return 0;

     }

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

}

 

생성자는 특별히 초기화할 것이 없다. 보통 멤버변수를 초기화하는 작업을 하지만 이 컨트롤은 WM_CREATE WM_DESTROY에서 초기화 및 해제를 하고 있으므로 생성자는 그냥 빈둥빈둥 놀고 있으면 된다. 파괴자는 도우미 클래스의 RemoveObject 함수를 호출하여 객체 맵에서 자신을 제거하는 역할을 한다.

Create 함수는 CreateWindow 함수를 호출하여 이 컨트롤의 윈도우를 생성하는 일을 하는데 이 부분이 다소 혼란스럽다. 객체는 이미 만들어졌는데 윈도우는 왜 또 만드는지 말이다. 그러나 객체와 윈도우는 분명히 다르다. 객체가 윈도우를 표현하지만 객체를 만들었다고 해서 윈도우가 만들어지는 것은 아니다. 그래서 객체가 생성될 때 윈도우는 따로 생성해야 한다. 윈도우를 클래스로 표현하는 MFC에서도 이는 마찬가지다. CButton 클래스의 객체를 선언한다고 해서 버튼이 만들어지는 것이 아니라 이 객체의 Create 함수를 따로 불러야 한다.

만약 생성자에서 윈도우까지 같이 생성하도록 하고 싶다면 그렇게 할 수도 있다. 그러나 객체가 생성되는 시점에는 윈도우의 위치를 정하기도 어렵고 부모 윈도우의 핸들도 모르기 때문에 객체 변수를 곧바로 생성하는 것이 불가능해진다. 생성자에서 윈도우까지 만들도록 작성했다면 이 객체는 동적으로 생성하는 수밖에 없으므로 활용성이 떨어지게 된다. 그래서 좀 번거롭더라도 객체를 먼저 만들고 윈도우는 Create 함수로 따로 만들도록 하였다.

Create 함수는 CreateWindow로 윈도우를 생성할 때 마지막 인수 lpParam으로 this 포인터를 넘겨주는 막중한 임무를 띤다. 이 포인터가 전달되어야 ShowMsgProc에서 객체를 등록할 때 객체 포인터와 윈도우의 대응 관계를 맵으로 작성할 수 있으며 이후의 메시지가 제대로 객체를 찾을 수 있다. 인수로 전달받은 style CreateWindow 호출시 그대로 넘겨 주며 id는 메뉴 핸들 대신 지정한다. 메뉴 핸들이 컨트롤에서는 ID값으로 대신 사용된다는 것은 잘 알고 있을 것이다.

ChangeString 일반함수는 컨트롤화를 하기 전의 함수와 동일하다. 다만 클래스의 멤버가 되었으므로 CShowMsg::이 앞에 붙어 있다는 점만 다르다. 클래스의 멤버함수는 암시적으로 this 포인터를 전달받으며 이 포인터로 hWnd를 참조할 수 있으므로 윈도우 핸들을 따로 인수로 전달 받을 필요가 없어졌다.

OnMessage 함수는 클래스 내부의 메시지 프로시저 역할을 한다. 클래스화를 하기 전의 모양과 거의 동일하되 첫 번째 인수 hWnd가 불필요해졌다. 클래스의 멤버함수이므로 hWnd를 인수로 전달받지 않아도 곧바로 사용할 수 있다. hWnd는 곧 this->hWnd와 동일하다. 코드도 컨트롤화를 하기 전의 코드와 완전히 동일하다. 다만 WM_LBUTTONDOWN SetFocus가 추가되었고 WM_DESTROY에서 PostQuitMessage를 호출하지 않는다는 점만 다를 뿐이다.

다음은 이 컨트롤을 사용하는 메인 윈도우의 코드를 보자. ShowMsgTest.cpp의 코드는 다음과 같다.

 

#include <windows.h>

#include "ShowMsg.h"

 

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

HINSTANCE g_hInst;

HWND hWndMain;

LPCTSTR lpszClass=TEXT("ShowMsgTest");

 

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=(HBRUSH)(COLOR_WINDOW+1);

     WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);

     WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);

     WndClass.hInstance=hInstance;

     WndClass.lpfnWndProc=(WNDPROC)WndProc;

     WndClass.lpszClassName=lpszClass;

     WndClass.lpszMenuName=NULL;

     WndClass.style=CS_HREDRAW | CS_VREDRAW;

     RegisterClass(&WndClass);

 

     hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,

          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

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

     ShowWindow(hWnd,nCmdShow);

     hWndMain=hWnd;

    

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

          TranslateMessage(&Message);

          DispatchMessage(&Message);

     }

     return (int)Message.wParam;

}

 

CShowMsg msg, msg2;

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

{

     HDC hdc;

     PAINTSTRUCT ps;

 

     switch(iMessage) {

     case WM_CREATE:

          msg.Create(10,10,100,100,WS_CHILD | WS_VISIBLE,1,hWnd);

          msg2.Create(210,10,100,100,WS_CHILD | WS_VISIBLE,2,hWnd);

          return 0;

     case WM_LBUTTONDOWN:

          msg.ChangeString("text");

          return 0;

     case WM_RBUTTONDOWN:

          CShowMsg *msg3;

          msg3=new CShowMsg;

          msg3->Create(0,0,0,0,WS_CHILD | WS_VISIBLE,3,hWnd);

          Sleep(500);

          delete msg3;

          return 0;

     case WM_PAINT:

          hdc=BeginPaint(hWnd, &ps);

          EndPaint(hWnd, &ps);

          return 0;

     case WM_DESTROY:

          PostQuitMessage(0);

          return 0;

     }

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

}

 

도우미 클래스의 생성자에서 컨트롤의 윈도우 클래스를 등록하므로 별도의 추가 코드없이 곧바로 컨트롤을 사용할 수 있다. 두 개의 컨트롤 객체를 전역으로 선언하였으며 WM_CREATE에서 객체의 Create 함수를 호출하여 윈도우를 생성하도록 하였다. 이렇게 생성하기만 하면 컨트롤은 알아서 실행된다. 문자열을 변경할 때는 객체의 ChangeString 함수를 호출하였다.

WM_RBUTTONDOWN에서는 객체를 동적으로도 만들 수 있는지 테스트하고 있는데 객체형의 포인터 변수를 선언한 후 new 연산자로 생성한다. 그리고 Create 함수로 객체를 생성하였으며 0.5초 후에 delete 연산자로 객체를 해제했다. 객체는 이런 식으로 동적 생성이 쉽다. 제대로 객체들이 만들어지는지 테스트해보자.

두 개의 객체가 이상없이 생성되었다. 마우스 오른쪽 버튼을 클릭하면 동적으로 객체를 생성했다가 해제하는데 화면상으로 보이지는 않는다.