본문 바로가기
코스웨어/14년 스마트컨트롤러

2014.09.12 업무일지] 김대희

by 알 수 없는 사용자 2014. 9. 12.
728x90
반응형

  • 키보드 입력
운영체제와 관계없이 어느 PC에서든 주 입력장치는 키보드가 되겠다.
윈도우즈는 이러한 키보드 입력을 어떻게 받아 들이는지 알아본다

먼저 유니코드라는 것을 짚고 넘어가야 한다.
영문자는 1byte( char )다 문제는 영어 이외에 문자들을 표현하려면
세상 모든 문자들을 2byte로 통일해야 한다. 
그래서 영어 한글 일어 등등 그외 문자들을 모두 2byte로 통일 시켜놓을 게 유니코드다.

콘솔환경에서 char 를 쓴것 처럼 윈도우 프로그램에서는 TCHAR를 쓴다.
참고로
TCHR은 char를 의미하고 1byte이며
TCHAR는 유니코드로써 2byte다

WM_CHAR
어느 창에 키보드입력을 받았을 때 윈도우즈는 포커스를 가진 프로그램에게
키보드 메시지(WM_CHAR, WM_KYEDOWN)를 보낸다 

여기서 포커스란?
멀티 태스킹이 가능한 윈도우즈 운영체제 에서는 여러개의 창을 띄울 수가 있다.
하지만 키보드가 눌려졌을 때 어느 창에 입력을 받아야 할까?
우리가 흔히 알고있는 맨위의 창, 또는 상태바 색깔이 진한 창에 키보드 입력이 되야 한다고 생각한다.
'이것이 포커스를 가졌다.' 라는 개념이 되겠다.

창 내에서도 포커스를 가진 박스가 존재하는데 그림으로 쉽게 설명하면 다음과 같다.

위와 같은 포커스를 가지는 이유는 사용자가 한명이며

운영체제에서 처리되는 프로그램 역시 하나이기 때문이다.


예제를 살펴보자


#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass=TEXT("First");

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
      ,LPSTR lpszCmdParam,int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst=hInstance;
  
  WndClass.cbClsExtra=0// 특수목적에 사용되는 예약 영역..안쓸 때는 '0'
  WndClass.cbWndExtra=0;
  WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
  WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
  WndClass.hInstance=hInstance;
  WndClass.lpfnWndProc=WndProc; // Winproc을 등록하는 함수포인터
  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);
  
  while (GetMessage(&Message,NULL,0,0)) {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return (int)Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
  static RECT area = {1001001366-100100+30};
  HDC hdc;
  PAINTSTRUCT ps;
  static TCHAR str[256];
  int len;

  switch (iMessage) {
  case WM_CHAR:
    len = lstrlen(str);//lstrlen에서 'l'이 붙는 이유는 유니코드 시스템을 사용하므로
    str[len] = (TCHAR)wParam;
    str[len+1= 0;
    InvalidateRect(hWnd,&area,FALSE); //화면의 리프레시
    return 0;
  case WM_PAINT:
    hdc = BeginPaint(hWnd,&ps);
    TextOut(hdc,100,100,str,lstrlen(str));
    EndPaint(hWnd,&ps);
    return 0;
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

실행 결과 화면 : 



WinProc함수만 살펴보도록 한다.

먼저 문자배열의 크기는 256으로 설정해 두었다.


WM_CHAR는 문자열을 입력받아 배열에 조립하는 역할만 하고
문자열을 정작 출력하는 함수는 WM_PAINT가 되겠다.
모든 출력은 이 WM_PAINT함수에서 이루어 진다.

WM_CHAR로 입력받은 문자는 아스키코드로써 wParam에 전달된다.
(wParam은 WinProc의 세번째 인자다)
만약 's'를 입력했으면 아스키코드 '53'이 전달되어 이 값을 그대로 화면에 출력하게 되는 것.

len = lstrlen(str);//lstrlen에서 'l'이 붙는 이유는 유니코드 시스템을 사용하므로
str[len] = (TCHAR)wParam;

str[len+1= 0;

위 부분은 눌려진 키의 아스키코드가 배열에 저장되는 처리과정이다.
받는 메시지는 그에맞게 캐스팅을 해주어야 하며
문자열의 끝을 알리는 NULL을 넣어주고 있는 셈이다.


이렇게 반복적으로 문자열을 입력하면 WM_CHAR의 기능은 끝나며

WM_PAINT의 출력기능만이 남았다.


만약에 'asd'를 순차적으로 입력했다면

s와d 부터는 출력이 되지 않는다.

이유는 문자열이 다시 입력되도 다시 입력된 문자열이 자동으로 출력되는 일은 없다.


그래서 문자가 새로 입력 될 때마다 갈제로 WM_PAINT를 발생 시켜줘야 하는데

이때 InvalidateRect 함수를 호출하는 것이다.


문자열을 조립한 후 이 함수를 호출하여 키보드가 입력 될 때 마다 화면을 다시 그리도록 하는 것.

이 함수를 빼먹으면 문자열을 받아들이지만 새로 받은 문자는 호출이 되지 않는다.

InvalidateRect 함수는 추후에 다시 다룸



  • 무효영역


빨간 박스를 무효영역이라 한다.


가려졌던 계산기의 영역이 복구되어 드러난 것이 보인다.

그리고 가려진 계산기의 버튼들이 보이지 않으므로 무효영역이라 한다.


앞에 있던 창이 다시 옮겨지면 뒤에 있는 창이 모두 정상적으로 출력되므로

이때는 유효영역이 되는 셈이다.








WM_KEYDOWN
키보드에 모든 키가 아스키코드를 갖고있지는 않다.
예를 들어 shift키나 방향키 같은 경우 위와 같은 WM_KEYDOWN 함수로 받아야 하겠다.

그래서 wParam으로는 가상코드가 전달되는데 아래가 그 표다


만약 wParam에 VK_END가 전달 되면 사용자가 END키를 누른 것이 된다.

숫자 또는 영문의 값은 아스키 코드와 동일하며 그것들은 가상 키 코드가 없다.

단, 영문은 대문자 코드와 일치하므로 if(wParam == 'Z') 라고 비교해보면 되겠다.





TranslateMessage

이 함수는 키보드에 키가 눌려졌을 때 어떤 키가 눌려졌는지 메시지를 만드는 역할을 한다.



  • 마우스 입력
윈도우즈는 GUI운영체제다 그래픽 인터페이스 이므로 마우스가 많이 사용된다
주 입력장치는 '키보드'이지만 그래픽툴이나 캐드 등 복잡한 프로그램에서는 마우스가 빈번히 사용 된다.

키보드를 입력을 메시지로 받아 처리하는 것 처럼 마우스 역시 클릭 더블클릭을 메시지로 처리한다.

WM_PAINT를 무효영역을 다시 출력해주는 함수이지만
마우스 커서가 창을 지나갈때는 제외다. 이 커서는 윈도우즈에서 따로 관리, 출력하는 개체다.







  • 윈도우 관리 메시지

생성과 파괴

WM_CRAETE, WM_DISTROY를 winMain, winProc 어디에 쓰느냐 에따라 시점이 달라진다
winMain에서는 윈도우 생성루, 파괴후 시점이며
winProc에서는 윈도우 생성중, 파괴중 시점이다


작업영역

#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass=TEXT("Client");

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
      ,LPSTR lpszCmdParam,int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst=hInstance;
  
  WndClass.cbClsExtra=0// 특수목적에 사용되는 예약 영역..안쓸 때는 '0'
  WndClass.cbWndExtra=0;
  WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
  WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
  WndClass.hInstance=hInstance;
  WndClass.lpfnWndProc=WndProc; // Winproc을 등록하는 함수포인터
  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);
  
  while (GetMessage(&Message,NULL,0,0)) {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return (int)Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  static RECT rt;

  switch (iMessage) {
  case WM_CREATE:// 윈도우 생성
    GetClientRect(hWnd,&rt);
    return 0;
  case WM_PAINT: 
    hdc = BeginPaint(hWnd,&ps);
    SetTextAlign(hdc,TA_CENTER);
    TextOut(hdc,rt.right/2,rt.bottom/2,TEXT("Center TEXT"),11);
    EndPaint(hWnd,&ps);
    return 0;
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

실행결과 : 


윈도우는 작업영역과 비작업영역으로 구분되어 있다.

작업영역은 위 그림에서 윈도우의 하얀바탕을 말하고 비 작업영역은 당연히 그 외를 말한다.

이를 구분해야 할 필요가 있는 이유는 프로그래밍의 전반적인 영향은 작업영역에 미치기 때문이다.


일반적으로 비작업영역은 프로그래밍의 대상이 아니며 운영체제가 알아서 띄운다.

특수한 경우는 빼고..


글자를 출력할때 좌표를 입력하는데 이 기준이 되는 부분은 아래 그림과 같다.



WM_CREATE에서 GetClientRect 함수를 이용해서 작업영역의 좌표를 구했다.

그리고 TextOut함수를 이용해서 작업영역의 반을 나누어서 중앙에 글자를 출력하도록 했다.








WM_SIZE


위처럼 중앙에 출력했을지라도 윈도우 창의 크기를 변경했을때에는 더이상

글자는 중앙에 위치 한 것이 아니게 된다.

윈도우 창의 크기가 변경될 때 마다 다시 출력하는 메시지가 WM_SIZE다


IParam의 하위워드에는 변경된 후의 폭, 상위워드에는 높이가 전달되며

wParam에는 이 메시지가 발생한 이유를 나타내는 플래그가 전달된다.


플래그는 아래와 같다.


위의 소스에서 WinProc만 다음과 같이 수정해본다.


LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  static RECT rt;

  switch (iMessage) {
  case WM_SIZE:
    GetClientRect(hWnd,&rt);
    InvalidateRect(hWnd,NULL,TRUE);
  case WM_PAINT: 
    hdc = BeginPaint(hWnd,&ps);
    SetTextAlign(hdc,TA_CENTER);
    TextOut(hdc,rt.right/2,rt.bottom/2,TEXT("Center TEXT"),11);
    EndPaint(hWnd,&ps);
    return 0;
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

실행결과 : 

창 크기를 바꿔도 글은 항상 중아에 있을 것이다.


WM_CREATE대신 WM_SIZE에서 윈도우창의 크기가 변경될 때 마다 작업영역을 조사하고

화면을 다시 그리므로 문자열은 윈도우 크기에 따라 중앙에 위치하게 된다.




728x90