API 컨트롤

IT/개발공부 / / 2020. 9. 18. 19:18
728x90
반응형

컨트롤이란 사용자와의 인터페이스를 이루는 도구입니다. 인터페이스를 이룬다는 말은 사용자로부터 명령과 입력을 받아들이고 출력 결과를 보여준다는 뜻입니다.

 

컨트롤도 하나의 윈도우입니다. 윈도우를 만들 때는 WNDCLASS 형의 구조체를 정의하고 RegisterClass 함수를 사용하여 등록한 후 CreateWindow 함수를 호출합니다.

 

그러나 컨트롤은 윈도우즈가 운영체제 차원에서 제공해 주기 때문에 윈도우 클래스를 만들 필요 없이 윈도우즈에 미리 정의되어 있는 윈도우 클래스를 사용하기만 하면 됩니다.

미리 정의된 윈도우 클래스에는 다음과 같은 종류가 있습니다.

 

윈도우 클래스 컨트롤
button 버튼, 체크, 라디오
static 텍스트
scrollbar 스크롤 바
edit 에디트
listbox 리스트 박스
combobox 콤보 박스

이 윈도우 클래스들은 시스템 부팅시에 운영체제에 의해 등록되므로 윈도우 클래스를 따로 등록시킬 필요없이 CreateWindow 함수에 인수로 클래스 이름만 주면 됩니다.

 

컨트롤 생성하기

컨트롤을 생성하기 위해서는 2가지 정보가 필요합니다.

첫 번째는 컨트롤의 ID 이고, 두 번째는 컨트롤의 핸들입니다.

 

일반적으로 컨트롤의 ID는 통지메시지를 수신할 때 사용하며, 컨트롤의 핸들은 부모가 컨트롤에게 메시지를 전달할 때 사용됩니다.

#define IDC_BUTTON 1
#define IDC_EDIT 2
#define IDC_LISTBOX 3
HWND hBtn, hEdit, hListBox;

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
   {
      switch( msg )
      {
         case WM_CREATE:
         hEdit = CreateWindow( TEXT("Edit"), TEXT(""),
         WS_CHILD | WS_VISIBLE | WS_BORDER,
         10,10,90,20, hwnd, (HMENU)IDC_EDIT, 0,0);
         hBtn = CreateWindow( TEXT("button"), TEXT("Push"),
         WS_CHILD | WS_VISIBLE | WS_BORDER , 10,40,90,20, hwnd, (HMENU)IDC_BUTTON, 0,0);
         hListBox = CreateWindow( TEXT("listbox"), TEXT(""), 
         WS_CHILD | WS_VISIBLE | WS_BORDER,
         150,10,90,90, hwnd, (HMENU)IDC_LISTBOX, 0,0);
         
         // Edit가 입력 받는 글자 갯수를 제한한다.- 메세지를 보낸다.
         SendMessage( hEdit, EM_LIMITTEXT, 5, 0);
         
         // ListBox 에 항목을 추가한다.
         SendMessage( hListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("AAAA"));
         SendMessage( hListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("BBBB"));
         
         return 0;

컨트롤을 생성하는 예제입니다.

 

통지 메시지 처리하기

만약, 자식에게 이벤트가 발생하게 되면 부모에게 약속된 형태로 이벤트를 전달해 주는데 이를 통지 메시지라 합니다.

 

통지 메시지는 메뉴와 동일하게 WM_COMMAND 에서 처리하게 됩니다. 이 때 컨트롤의 ID, 해당 컨트롤에서 발생된 정보들이 전달되게 됩니다.

case WM_COMMAND:
   switch( LOWORD(wParam) ) // ID 조사.
   {
      case IDC_EDIT: // EditBox 가 보낸경우
      if ( HIWORD(wParam) == EN_CHANGE ) //통지 코드 조사.
      {
         TCHAR s[256];
         GetWindowText( hEdit, s, 256); // Edit 에서 값을 얻는다.
         SetWindowText( hwnd, s); // 부모 윈도우의 캡션을 변경한다.
      }
      break;

      case IDC_BUTTON:
      if ( HIWORD( wParam) == BN_CLICKED ) // 버튼을 누를때 나오는 통지 코드
      {
         TCHAR s[256];
         GetWindowText(hEdit, s, sizeof(s));
         SendMessage( hListBox, LB_ADDSTRING, 0, (LPARAM)s);
      }
      break;
   }
return 0;

위의 예제를 출력해보면 사용자가 edit 컨트롤에 글자를 입력하면 즉시 타이틀바에 출력됩니다. 또한 버튼을 클릭하면 edit 컨트롤에서 글자를 얻어와 리스트 박스에 출력합니다.

 

컨트롤의 원리

컨트롤은 윈도우를 만들 때 정의됩니다. 실제 정의되는 내용은 무엇이고 부모와 자식간의 약속된 메시지 전달이 어떠한 원리로 이루어 지는지 이해하려면 아래 예제를 참고해주세요.

 

#include <windows.h>

void Draw3dRect(HDC hdc, int x, int y, int xx, int yy, BOOL down, int width )
{
   COLORREF clrMain = RGB(192,192,192),
   clrLight = RGB(255,255,255),
   clrDark = RGB(128,128,128);
   HPEN hPen1, hPen2, hOldPen;
   HBRUSH hBrush, hOldBrush;
   
   if(down)
   {
      hPen2 = CreatePen(PS_SOLID,1,clrLight);
      hPen1 = CreatePen(PS_SOLID,1,clrDark);
   }
   else
   {
      hPen1 = CreatePen(PS_SOLID,1,clrLight);
      hPen2 = CreatePen(PS_SOLID,1,clrDark);
   }
   hBrush = CreateSolidBrush( clrMain );
   hOldPen = (HPEN)SelectObject(hdc, hPen1);
   hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
   
   Rectangle(hdc, x , y, xx+1, yy+1);
   
   for(int i=0; i < width; i++)
   {
      SelectObject(hdc, hPen1);
      MoveToEx(hdc, xx - 1, y, 0 );
      LineTo(hdc, x, y);
      LineTo(hdc, x, yy - 1 );
      SelectObject(hdc, hPen2);
      MoveToEx(hdc, x, yy,0);
      LineTo(hdc, xx, yy);
      LineTo(hdc, xx, y);
      x++; y++; xx--; yy--;
   }
   SelectObject(hdc, hOldPen);
   SelectObject(hdc, hOldBrush);
   
   DeleteObject(hPen1);
   DeleteObject(hPen2); 
   DeleteObject(hBrush);
}

// 자식(버튼)이 부모에게 WM_COMMAND를 보낼때 사용할 통지코드(메세지를 보내는 이유)
#define BTN_LCLICK 1
#define BTN_RCLICK 2
#define BTN_LDBLCLK 3
#define BTN_RDBLCLK 4


// 부모가 자식(버튼)을 만들어 놓고 자식에게 보낼수 있는 메세지를 설계한다.
// 부모 - 자식 간의 약속.
#define BM_CHANGESTYLE WM_USER + 10
#define BM_CHANGETHICK WM_USER + 11


//========================================================================
// 자식용 메세지 처리 함수.
LRESULT CALLBACK ChildProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static BOOL bDown = FALSE;
   static int nThick = 2;
   switch( msg )
   {
      // 부모가 보내준 메세지
      case BM_CHANGETHICK:
      nThick = (int)wParam; // wParam에 두께를 보내주기로 약속
      InvalidateRect( hwnd, 0, FALSE);
      return 0;
      
      case WM_LBUTTONDOWN:
      bDown = TRUE; InvalidateRect( hwnd, 0, FALSE );
      SetCapture( hwnd );
      return 0;
      
      case WM_LBUTTONUP:
      if ( GetCapture() == hwnd )
      {
         ReleaseCapture();
         bDown = FALSE;
         InvalidateRect( hwnd, 0, FALSE );
         
         //========================================
         // 자신이 눌렸음을 부모에게 알려준다.
         HWND hParent = GetParent( hwnd );
         UINT id = GetDlgCtrlID( hwnd );
         SendMessage( hParent, WM_COMMAND,
         MAKELONG(id, BTN_LCLICK),// 하위16:id, 상위16:통지코드
         (LPARAM)hwnd);
         }
         return 0;
         
         case WM_PAINT:
         {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint( hwnd, &ps );
         // 자식은 hdc 를 얻고 자신을 그리기 전에 부모에게 hdc 를 전달해준다.
         // 부모가 자식의 색상을 변경할 권한을 주기 위해서
         // 이런 용도로 미리 만들어진 메세지가 WM_CTLCOLORxxx 이다
         // wParam 에는 hdc 를 lParam 에는 자신의 핸들을 넣어 준다.
         HWND hParent = GetParent( hwnd );
         
         
         SendMessage( hParent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hwnd);
         //========================================================
         RECT rc;
         GetClientRect( hwnd, &rc);
         Draw3dRect( hdc, 0, 0, rc.right, rc.bottom, bDown, nThick );
         
         // 부모가 전달한 캡션 문자열을 윈도우 가운데 출력한다
         TCHAR s[256];
         GetWindowText( hwnd, s, 256 );
         SetBkMode( hdc, TRANSPARENT );
         if ( bDown == TRUE )
         OffsetRect( &rc, nThick, nThick ); // 오른쪽 아래로 사각형을
         
         // nThick 만큼 이동
         DrawText( hdc, s, -1, &rc,
         DT_SINGLELINE | DT_CENTER | DT_VCENTER );
         
         //-----------------------------------------------------
         EndPaint( hwnd, &ps );
      }
      return 0;
   
   // user.chol.com/~downboard/mine.txt 에서 Draw3dRect() 복사해 오세요
   }
   return DefWindowProc( hwnd, msg, wParam, lParam);
}


//============================================================================
// 부모용 메세지 처리함수.
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static HWND hChild;
   switch( msg )
   {
      // 자식이 자신을 그리기 전에 보내는 메세지 - 부모에게 색을 변경할 기회를 준다.
      case WM_CTLCOLORBTN:
      {
         HDC hdc = (HDC)wParam;
         HWND h = (HWND)lParam;
         if ( hChild == h ) // 자식이 2개 이상이라면 내가 원하는 자식인지 확인
         {
            SetTextColor( hdc, RGB(255,0,0));
         }
      }
      return 0;
      
      case WM_LBUTTONDOWN:
      {
         static int n = 2;
         ++n;
         
         // 자식에게 메세지를 보내서 두께를 변경하게 한다
         // wParam : 변경할 두께, lParam : not used
         SendMessage( hChild, BM_CHANGETHICK, n, 0);
      }
      return 0;
      
      // 자식(버튼)이 보내는 메세지
      case WM_COMMAND:
      switch( LOWORD(wParam) ) // 자식 ID
      {
         case 1:
         if ( HIWORD(wParam) == BTN_LCLICK ) // 통지코드 조사
         {
            MessageBox( hwnd, TEXT("Click!!"), TEXT(""), MB_OK);
         }
         break;
      }
      return 0;
      
      case WM_CREATE:
      hChild = CreateWindow( TEXT("CHILD"), TEXT("자식"),
      WS_CHILD | WS_VISIBLE | WS_BORDER,
      10, 10, 100, 100, hwnd,
      
      // 부모 윈도우 핸들
      (HMENU)1, // 자식은 메뉴를 가질수 없다. ID로 사용
       0, 0);
       return 0;
       
       case WM_DESTROY:
       PostQuitMessage(0);
       return 0;
    }
    return DefWindowProc( hwnd, msg, wParam, lParam);
}

int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
   // 1. 윈도우 클래스 만들기
   WNDCLASS wc;
   wc.cbWndExtra = 0;
   wc.cbClsExtra = 0;
   wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
   wc.hCursor = LoadCursor(0, IDC_ARROW);
   wc.hIcon = LoadIcon(0, IDI_APPLICATION);
   wc.hInstance = hInst;
   wc.lpfnWndProc = WndProc;
   wc.lpszClassName = TEXT("First");
   wc.lpszMenuName = 0;
   wc.style = 0;
   
   // 2. 등록(레지스트리에)
   RegisterClass(&wc);
   
   //========================================
   wc.lpfnWndProc = ChildProc;
   wc.lpszClassName = TEXT("CHILD");
   RegisterClass( &wc ); // 자식용 윈도우 클래스 등록
   //=========================================
   
   // 3. 윈도우 창 만들기
   HWND hwnd = CreateWindowEx( 0, TEXT("first"), TEXT("Hello"),
   WS_OVERLAPPEDWINDOW, CW_USEDEFAULT , 0, CW_USEDEFAULT, 0,
   0, 0, hInst, 0);
   
   // 4. 윈도우 보여주기
   ShowWindow(hwnd, SW_SHOW);
   UpdateWindow(hwnd);
   
   // 5. Message
   MSG msg;
   while( GetMessage( &msg, 0, 0, 0 ) )
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

 

서브 클래싱

서브 클래싱이란 윈도우 프로시저로 보내지는 메시지를 중간에 가로채는 기법입니다. 중간에서 메시지를 조작함으로써 윈도우 모양을 변경하거나 동작을 감시할 수 있습니다.

 

아래는 숫자만 입력 가능한 에디트 컨트롤을 구현하기 위해 서브 클래싱을 사용한 예제입니다.

WNDPROC old; // 원래의 EditBox의 메세지 처리함수의 주소를 담을 변수
LRESULT CALLBACK foo( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch( msg )
   {
      case WM_CHAR:
      if ( ( wParam >= '0' && wParam <= '9') || wParam == 8 )
      return CallWindowProc( old, hwnd, msg, wParam, lParam);
      
      return 0; // 숫자 이외의 경우는 무시한다.
   }
   // 나머지 모든 메세지는 원래의 함수로 전달한다.
   return CallWindowProc( old, hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static HWND hEdit;\
   switch( msg )
   {
      case WM_CREATE:
      hEdit = CreateWindow( TEXT("edit"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_BORDER
      10,10,200,200, hwnd, (HMENU)1, 0, 0);
      old = (WNDPROC)SetWindowLong( hEdit, GWL_WNDPROC, (LONG)foo );
      SetFocus(hEdit);
      return 0;
      
      case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   }
   return DefWindowProc(hwnd, msg, wParam, lParam);
}

 

공통 컨트롤

공통 컨트롤은 윈도우즈 95 이후에 추가된 컨트롤을 의미하며 개념 및 일반적 사용방법은 표준 컨트롤과 동일합니다.

 

표준 컨트롤과는 약간 차이점이 있는데 표준 컨트롤은 시스템에 내장되어 있기 때문에 언제든지 사용가능합니다. 그러나 공통 컨트롤은 DLL에 의해 제공되기 때문에 이 DLL이 로드되어 있지 않으면 윈도우 클래스가 정의되지 않으며 따라서 컨트롤을 생성할 수 없습니다. 그래서 공통 컨트롤을 사용하기 위해서는 아래의 초기화 작업이 필요합니다.

  1. CommonCtrl.h 를 포함시킨다.
  2. WM_CREATE 에서 InitCommonControls(Ex) 함수를 호출해 준다.
  3. ComCtl32.lib 를 링크시킨다. 전처리 문으로도 가능하다 ( #pragma comment(lib "comctrl32.lib")

아래는 공통 컨트롤의 예제입니다.

// 공용컨트롤 사용시 필요한 h
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static HINSTANCE hInst;
   static HWND hProgress;
   static int Pos = 10;
   
   switch( msg )
   {
      case WM_CREATE:
      // 공용 컨트롤 사용시 컨트롤 초기화...
      InitCommonControls();
      hInst = ((LPCREATESTRUCT)lParam)->hInstance;
      hProgress = CreateWindow(TEXT("msctls_progress32"),TEXT(""),
      WS_CHILD | WS_VISIBLE | WS_BORDER | PBS_SMOOTH,
      10, 10, 300, 20, hwnd, (HMENU)1, hInst, 0);
      
      // 컨트롤 초기화
      SendMessage( hProgress, PBM_SETRANGE32, 0, 100);
      SendMessage( hProgress, PBM_SETPOS, 10, 0);
      return 0;
      
      case WM_LBUTTONDOWN:
      Pos += 10;
      SendMessage(hProgress, PBM_SETPOS, Pos, 0);
      return 0;
      
      case WM_RBUTTONDOWN:
      Pos -= 10;
      SendMessage(hProgress, PBM_SETPOS, Pos, 0);      
      return 0;
      
      case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   }
   return DefWindowProc(hwnd, msg, wParam, lParam);
}
728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기