7.차일드 윈도우 컨트롤
본장에서는 버튼,체크버튼,라디오버튼,리스트박스,콤보박스,정적윈도우,스크롤 윈도우 등을 출력하는 방법에 대해서 설명합니다. 이런 윈도우들은 자신이 독립적으로 출력되는 것이아니라 어느 윈도우 내부에 자식윈도우로써 출력됩니다. 윈도우 프로그램에서 하나의 윈도우 밑에 여러 윈도우들이 등록되는 것이 일반적이고 이런 윈도우들도 자신만의 프로시저들이 있으며 이 프로시저들은 메시지를 받고 필요한 메시지를 부모윈도우에 전달합니다. 본장에서는 차일드 윈도우들의 메시지 프로시저를 가로체는 방법과 차일드윈도우로부터 전달된 메시지를 처리하는 방법에 대해서 설명합니다.
차일드(Child) 윈도우 만들기
“모든 윈도우들이 차일드(Child) 윈도우가 될 수있다!” 결국 “윈도우위에 윈도우를 윈도우 밑에 윈도우를 붙일수 있다” 이것은 윈도우 프로그램의 기본입니다. 차일드 윈도우는 정의를 하자면 WinMain에서 만들어진 기본윈도우 안에 새로운 윈도우를 만들 때 이것이 차일드 윈도우인것입니다. 본장에서는 모든 차일드윈도우를 설명하기 보다. 차일드 윈도우로서만 생을 마감해야할 운명을 가진 윈도우에 대해서 설명합니다. 버튼,체크버튼,라디오 버튼등은 자신 혼자서 메인 윈도우로 설정된다는 것이 무의미 합니다. 물론 버튼 하나만 윈도우로 출력할수 있습니다. 그렇지만 버튼이라는 윈도우는 어떤 메인윈도우에서 특정 항목을 실행하기위해서 부수적으로 존재하는 윈도우라고 볼수있지 이것을 주메인 윈도우로 설정한다는 것은 조금 문제가 있다는 것입니다. 본장에서 설명하는 차일드윈도우란 그 성격이 메인 윈도우로 존재하기 보다 메인윈도우의 자식윈도우로 존재하는 것이 적당한 성격의 윈도우를 이야기 합니다.
차일드윈도우를 만들때도 윈도우를 만드는 함수 CreateWindow를 사용합니다. 메인 윈도우에서는 9번째인자 즉 HMENU hMenu를 메뉴 설정 핸들로 사용하였으나 차일드윈도우에서는 이인자를 자신의 윈도우 ID로 사용합니다. 다음은 CreateWindow를 이용하여 버튼을 만든 예제입니다.
CreateWindow("button","버튼",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON , 10,10,100,40,hwnd,(HMENU)1,hinst,NULL);
CreateWindow두번째 인자 즉 DWORD dwStyle 의 값에는 일반적인 윈도우 스타일 WS 계열과 함께 차일드 윈도우의 스타일을 결합시킵니다.
위의 예에서는 WS_CHILD|WS_VISIBLE 와 함께 BS_BUSHBUTTON이라는 버튼스타일중의 하나를 첨부시켰습니다. 또한 HWND hWndParent 에는 부모 윈도우 핸들을 설정시켰습니다. 결국 차일드 윈도우를 만들고자 하면 다음과 같은 형태가 됩니다.
CreateWindow(차일드윈도우이름,차일드윈도우에 출력할 텍스트,
차일드윈도우들이 가지고 있는 스타일과 일반윈도우 스타일결합
x,y,cx,cy,부모윈도우핸들,자기 아이디번호, 인스턴스,윈도우를만들
때 필요한 데이터)
여기에서 “차일드윈도우 이름”이란 윈도우 시스템에서 설정한 이름입니다. 버튼윈도우는 "button"이며 에디터 박스는 "edit" 리스트 박스는 "listbox" 콤보박스는 "combobox" 스크롤 바는 “scrollbar” 로 설정해야 합니다. 차일드윈도우의 스타일은 각각의 윈도우에 따라 다릅니다. button윈도우들은 BS_ 계열의 스타일을 가지고 있고 에디터 박스는 ES_ 계열의 스타일을 가지고 있습니다. 이부분에 대해서는 차일드윈도우를 만들어가면서 하나하나 설명해 가겠습니다.
차일드 윈도우에서 이벤트가 발생되면 이것은 부모 윈도우 전달됩니다. 전달 될 때 스크롤 윈도우를 제외한 대부분의 차일드 윈도우들이 WM_COMMAND로 전달됩니다. 전장에서 WM_COMMAND로 메뉴 ID들이 전달된다고 하였습니다. 그렇다면 차일드윈도우들이 전달하는 WM_COMMAND와 메뉴 ID에서 전달하는 WM_COMMAND가 구별될 필요성이 있는데 이것은 어떻게 구별될까요? 표1이 차일드 윈도우 들이 전달하는 WM_COMMAND와 메뉴에서 전달되는 WM_COMMAND의 차이를 보여줍니다.
(표1 ) 차일드윈도우와 메뉴의 차이
|
LOWORD(wParam) |
HIWORD(wParam) |
lParam |
차일드윈도우 |
차일드 윈도우 ID |
이벤트 메시지 |
윈도우핸들 |
메뉴 ID |
메뉴 ID |
0 |
0 |
차일드 윈도우에서는 LOWORD(wParam)에 자신의 ID를 전달합니다. 즉 CreateWindow에서 9번째 인자 HMENU hMenu 에 설정한 값을 전달합니다. 메뉴에서는 HIWORD(wParam)에 아무값도 없으나 차일드 윈도우에서는 이벤트 메시지값이 전달됩니다. 예를 들어 버튼윈도우에서 마우스로 버튼을 클릭했다면 이때 발생되는 이벤트가 BN_CLICKED 인데 이값이 HIWORD(wParam)에 전달된다는 것입니다. lParam에는 메시지를 보낸 차일드 윈도우의 윈도우 핸들이 전달됩니다. 따라서 차일드윈도우에서 보내는 WM_COMMAND와 메뉴에서 보내는 ID가 분명히 서로 다릅니다. 따라서 차일드 윈도우에 설정된 ID와 메뉴에서 설정된 ID가 중복되더라도 분별은 할수 있습니다. 그러나 일반적으로 메뉴에서 설정된 ID번호를 중복해서 차일드 윈도우에 사용하지은 않습니다. 이것은 좋지 않는 형태입니다. 중복해서 사용한다면 WM_COMMAND에서 차일드윈도우인지 메뉴인지 확인을 하기위해서 또한번 검사를 해보아야 하기 때문입니다.
버튼 윈도우
버튼윈도우는 매우 많은 종류가 있습니다. 표2는 버튼윈도우에서 설정하는 BS_계열 스타일에 의해서 변화되는 버튼의 종류를 나열한것입니다.
표2에 설정한 스타일 외에서 버튼의 속성을 변경하기위한 스타일 들이 있습니다. 사용자가 직접 윈도우를 그릴수 있는 BS_OWNERDRAW 체크버튼이나 라디오버튼에서 문자가 버튼 좌측에 출력되는 BS_LEFTTEXT 스타일들이 이것입니다.
(표2)_ 다양한 버튼 윈도우 스타일
버튼 윈도우 |
추가되는 BS_계열 스타일 |
일반적인 버튼 |
BS_PUSHBUTTON |
윈도우 기본 버튼 |
BS_DEFPUSHBUTTON |
체크 박스 |
BS_CHECKBOX |
라디오 버튼 |
BS_RADIOBUTTON |
3가지 상태 체크박스 |
BS_3STATE |
그룹 박스 |
BS_GROUPBOX |
자동적으로 전환되는 체크박스 |
BS_AUTOCHECKBOX |
자동적으로 전환되는 라디오버튼 |
BS_AUTORADIOBUTTON |
예를 들어서 체크박스를 만들고자 한다면 다음과 같이 할수 있습니다.
CreateWindow("button","체크 박스",
WS_CHILD|WS_VISIBLE|BS_CHECKBOX, 10,50,100,40,hwnd,(HMENU)3,hinst,NULL);
보통 차일드윈도우들은 윈도우 스타일에서 WS_CHILD와 WS_VISIBLE 두 개를 기본적으로 사용하고 그리고 차일드윈도우의 스타일을 첨부합니다.
버튼 윈도우 메시지
버튼 윈도우에서 어떤 이벤트가 발생되었을 경우 BN_계열의 메시지를 보낸다고 설명하였습니다. 표3은 버튼윈도우들이 보내는 메시지 리스트입니다.
(표3) 버튼 윈도우 이벤트 메시지
이벤트 메시지 |
내용 |
BN_CLICKED |
버튼을 좌측 마우스버튼으로 클릭하였음 |
BN_DBLCLK |
버튼을 좌측 마우스버튼으로 더블 클릭하였음 |
BN_DISABLE |
버튼이 비활성화 되었음 |
BN_DOUBLECLICKED |
라디오버튼이나 BS_OWNERDRAW 스타일 버튼에서 좌측 마우스 버튼을 더블클릭하였음 |
BN_HILITE |
사용자가 버튼을 선택하였을 때 발생하는 메시지 |
BN_UNHILITE |
사용자가 버튼에서 다른 윈도우로 전환되었을 때 메시지 |
BN_SETFOCUS |
버튼에 포커스가 주어졌을 때 메시지 |
BN_KILLFOCUS |
버튼에서 포커스가 사라졌을 때 메시지 |
BN_PAINT |
버튼이 다시 그려질 때 발생하는 메시지 |
예를 들어서 ID가 1번인 버튼윈도우를 만들었다면 다음과 같은 방법으로 이벤트에 해당하는 프로그램을 할수 있습니다.
case WM_COMMAND:
if(LOWORD(wParam)==1) //1번으로 설정된 버튼윈도우이면
{
switch(HIWORD(wParam))
{
case BN_CLICKED:
//버튼을 클릭했을 때 수행 내용 작성
break;
case BN_SETFOCUS:
//버튼에 포커스가 주어졌을대 메시지 작성
break;
:
}
}
위의 내용에서 보면 버튼 몇 개정도 만들어 놓고 메뉴 몇 개 정도 만들면 WM_COMAND메시지에서 switch문이 여러개가 아주 복잡하게 연결될것입니다.
이럴경우에는 WM_COMMAND안에 모든 내용을 수행하는 것은 무리입니다. 결국 함수 포인터를 사용하는 것이 편리합니다. 예를 들어 버튼 3개에 대한 각각의 프로세싱을 처리한다면
//버튼 1번 함수
void Button1(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) //버튼 2번함수
void Button2(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
//버튼 3번함수
void Btton3p(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam
void (*Button[3])(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
={Button1,Button2,Button3};
와 같이 설정한후에
case WM_COMMAND:
Button[LOWORD(wParam)(hwnd,HIWORD(wParam),wParam, lParam);
break;
라고 하여 각각의 프로세스를 함수로 나누어서 설정하는 것이 좋습니다.
본내용에 대해서 이해가 안가시는 부분은 C언어의 포인터 부분의 함수포인터를 다시 보시기 바랍니다.
체크버튼,라디오버튼 상태 바꾸기
체크 버튼이나 라디오 버튼을 클릭하면 한번은 선택되었다가 다시 누르면 선택이 해제 됩니다. 이렇게 버튼의 체크상태를 변경할 때 사용하는 방법은 차일드 윈도우에 BM_SETCHECK 라는 메시지를 보내는 것입니다.
일반적으로 체크버튼 상태를 기록하는 BOOL 형의 변수를 설정한다음 초기에는 0으로 설정합니다.
static BOOL radioflag=FALSE;
이함수는 값이 변경되지 말아야 하기 때문에 static로 설정합니다. 이렇게 한후 BN_CLICKED라는 메시지가 오면 이플러그를 0이면 1로 1이면 0으로 전환시킵니다. 그리고 이값을 wParam값으로 버튼윈도우에 보내면 됩니다.
case BN_CLICKED:
radioflag=1-radioflag;
//버튼 윈도우에 메시지르 보낸다.
SendMessage((HWND)lParam,BM_SETCHECK,
(WPARAM)radioflag,(LPARAM)NULL);
break;
버튼 윈도우 예제 ButtonEx
ButtonEx는 버튼과 체크박스,라디오버튼,그룹박스등을 화면에 출력하고 이 차일드로부터 이벤트를 받아서 출력하는 예제입니다.
(프로그램 소스)
//버튼 윈도우 예제
//ButtonEx.c
#include <windows.h>
HINSTANCE hinst;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ButtonEx" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hinst=hInstance;
hwnd = CreateWindow (szAppName,
"버튼윈도우 예제:ButtonEx",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
//버튼에 이벤트에 해당하는 함수
void Button(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
case BN_CLICKED:
MessageBox(hwnd,"버튼 이 클릭되었습니다.","버튼메시지",MB_OK );
break;
}
}
//체크 버튼 이벤트에 해당하는 함수
void CheckButton(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL checkflag=FALSE;
switch(iMsg)
{
case BN_CLICKED:
checkflag=1-checkflag;
SendMessage((HWND)lParam,BM_SETCHECK,(WPARAM)checkflag,(LPARAM)NULL);
break;
}
}
//라디오 버튼에 해당하는 이벤트 함수
void RadioButton(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL radioflag=FALSE;
switch(iMsg)
{
case BN_CLICKED:
radioflag=1-radioflag;
//버튼 윈도우에 메시지르 보낸다.
SendMessage((HWND)lParam,BM_SETCHECK,(WPARAM)radioflag,(LPARAM)NULL);
break;
}
}
//버튼윈도우와 맞게 각 함수의 배열을 만든다.
void (*ButtonProcess[6])(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
={Button,Button,CheckButton,RadioButton,Button,Button};
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
switch (iMsg)
{
case WM_CREATE :
//차일드 윈도우를 만든다.
CreateWindow("button","버튼",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON ,
10,10,100,40,hwnd,(HMENU)1,hinst,NULL);
CreateWindow("button","OK",
WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON, 120,10,100,40,hwnd,(HMENU)2,hinst,NULL);
CreateWindow("button","체크 박스",
WS_CHILD|WS_VISIBLE|BS_CHECKBOX,
10,50,100,40,hwnd,(HMENU)3,hinst,NULL);
CreateWindow("button","라디오버튼",
WS_CHILD|WS_VISIBLE|BS_RADIOBUTTON, 120,50,100,40,hwnd,(HMENU)4,hinst,NULL);
CreateWindow("button","3D State",
WS_CHILD|WS_VISIBLE|BS_3STATE, 10,100,100,40,hwnd,(HMENU)5,hinst,NULL);
CreateWindow("button","Group Box",
WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
120,100,100,40,hwnd,(HMENU)5,hinst,NULL);
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_COMMAND:
//함수 배열 번호와 LOWORD(wParam)-1과 같은 함수 실행
ButtonProcess[LOWORD(wParam)-1]
(hwnd,HIWORD(wParam),wParam,lParam);
break;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 1) ButtonEx 출력 결과
차일드 윈도우 프로시저 가로채기
버튼이나 스크롤바, 리스트박스,에디터박스 등은 CreateWindow에 의해서 위도우가 만들어지면 자신의 기능을 담당하는 기본적인 프로시저들이 함께 등록됩니다. 버튼의 경우 자신을 클릭했으면 버튼이 들어가고 그리고 부모 윈도우에 WM_COMMAND와 HIWORD(lParam) BN_CLICKED라는 메시지를 보내는 기능을 합니다. 즉 버튼이 하는 일반적인 기능을 가진 프로시저가 내장되어 있다는 뜻입니다. 에디터 박스와 리스트박스,콤보 박스, 스크롤바도 각각의 기능에 맞는 프로시저들이 있습니다. 에디터 박스는 글자를 입력하면 화면에 출력하는 기능 및 여러 기능키에의해서 커서가움직이는 기능들이 있으면 리스트박스는 데이터를 한라인씩 기록하고 선택하였을 때 특정 선택값을 알리며 그값을 리턴하는 기능들이 있습니다. 따라서 차일드 윈도우는 단지 CreateWindow에 의해서 도시만 하고 부모윈도우는 모든 내용을 차일드 윈도우에 전담시킨다음 WM_COMMAND를 받아서 이벤트에 해당되는 내용만 처리하면 됩니다.
그러나 때로는 차일드 윈도우 내부로 들어가서 차일드 윈도우에서 받는 메시지를 가로채고 싶을 경우가 있습니다. 예를 든다면 에디터 박스 차일드 윈도우로 들어가서 WM_KEYDOWN를 가로채고 여기에서 특정 키늘 누르면 에디터박스에서 포커스가 사라지게 한다든지 Tab키를 누르면 다음 차일드윈도우로 포커스가 이동하게 한다는지 하는 것은 차일드 윈도우 프로시저에서 처리 해야 합니다.
이렇게 프로시저를 새로운 프로시저를 바꿀때는 GetWindowLong를 사용합니다.
예를 들어 하나의 버튼을 만들고 이버튼의 프로시저를 바꾸고자 한다면 다음과 같이 할수 있습니다.
HWND butwnd;
WNDPROC butProc;
butwnd=CreateWindow("button","버튼 ",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON ,
10,10+i*40,100,40,hwnd,(HMENU)1,hinst,NULL);
butProc=(WNDPROC) SetWindowLong (butwnd, GWL_WNDPROC,
(LONG) ButtonProc) ;
LRESULT CALLBACK ButtonProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
이곳에서 버튼에 해당하는 내용을 기록한다.
:
}
//처리를 하지 못한 것은 기본적인 프로시저로 넘긴다.
return CallWindowProc (butProc, hwnd, iMsg, wParam, lParam) ;
}
SetWindowLong의 두 번재 인자를 GWL_WNDPROC 로 설정하고 ButtonProc를 등록하면 CALLBACK함수인 ButtonProc가 실행됩니다. SetWindowLong 함수에서 새로운 프로시저 함수를 등록하면 이전에 설정된 프로시저 함수가 리턴됩니다. 이함수를 butProc에 받아둡니다. 이이유는 새로 설정된 프로시저 함수에서 처리하지 못한 것들은 기본적으로 내장된 프로시저가 처리하게 하기 위함입니다. 새로운 프로시저를 등록하였을 때 기본적으로 내장된 프로시저를 보관해두지 않는다면 버튼이 눌려졌을 때 화면에서 버튼이 들어간 모양과 버튼이 윈도우에서 버튼에 포커스가 잡혔을 때 버튼위에 가느다란 라인을 그린다든지 하는 잡단한 기능들을 일일이 다해주어야 합니다. 이렇게 하는 것을 방지하기 위해서 SetWindowLong에서 리턴된 이전 프로세스를 보관한후에 필요한 처리만 ButtonProc의 switch(iMsg)에서 처리하고 나머지 처리는 CallWindowProc함수에 의해서 이전에 보관해둔 butProc에 넘겨주는 방식을 이용합니다. 실제 메인윈도우와 연결견 WndProc 또한 필요한 내용만 자신이 처리하고 나머지는 DefWindowProc 에 의해서 시스템에게 모든 권한을 넘겨주는것과 같은 방식입니다.
차일드 윈도우 색상 바꾸기
ButtonEx에서 체크버튼이나 그룹박스 라디오 버튼등은 기본적인 배경색이 회색입니다. 그림1에서의 출력결과를 보듯이 메인윈도우의 배경색은 흰색이며 체크버튼과 그룹박스, 라디오 버튼 등은 회색이기 때문에 보기에 매우 좋지 않습니다. 차일드 윈도우의 색상을 바꾸고자할 경우에는 특정 메시지를 받고 배경으로 설정할 브러쉬를 리턴하면 됩니다. 예를 들어 그림 1에서 체크박스나 그룹박스 등의 색을 흰색으로 설정한다면 WM_CTLCOLORSTATIC 메시를 받고 흰색 브러쉬를 리턴하면 됩니다.
case WM_CTLCOLORSTATIC:
return (LRESULT) CreateSolidBrush(RGB(255,255,255));
CreateSolidBrush는 HBRUSH를 리턴함으로 위와 같이 단축해서 간단하게 표현할수 있습니다.
이외에 차일드 윈도우의 색상을 바꾸고자 한다면 표4와 같은 메시지를 받았을 때 배경색상에 맞는 브러쉬를 리턴하면됩니다.
(표4) 차일드윈도우 색상 정의 메시지
메시지 |
차일드 윈도우 |
WM_CTLCOLORSTATIC |
정적 외곽 박스형 차일드윈도우 |
WM_CTLCOLORMSGBOX |
메시지 박스 |
WM_CTLCOLORBTN |
버튼 |
WM_CTLCOLORDLG |
대화상자 |
WM_CTLCOLORLISTBOX |
리스트 박스 |
WM_CTLCOLOREDIT |
에디터 박스 |
WM_CTLCOLORSCROLLBAR |
스크롤 바 |
위 메시지와 함께 wParam에는 차일드 윈도우의 DC가 lParam에는 차일드 윈도우 핸들이 넘어옵니다.
때로 는 wParam에 의해서 넘겨받은 DC를 이용하여 차일드윈도우에 새로운 내용을 출력할수도 있습니다. 그러나 이부분에서 다소 주의를 요합니다. 잘못하면 차일드윈도우의 형태가 깨질수도 있기 때문입니다.
사용자 그리기
버튼을 만들 때 스타일에 BS_OWNERDRAW를 설정하면 기존에 사용하는 버튼이 아닌 사용자가 버튼을 그릴수 있습니다. BS_OWNERDRAW를 설정하고 버튼윈도우를 만들면 메인윈도우에는 WM_DRAWITEM 메시지가 발생되고 wParam에는 버튼 번호가 lParam에는 버튼의 정보가 들어 옵니다. 이정보는 DRAWITEMSTRUCT 형으로 들어옵니다. 다음은 DRAWITEMSTRUCT 구조체의 형태입니다.
typedef struct tagDRAWITEMSTRUCT {
UINT CtlType;
UINT CtlID;
UINT itemID;
UINT itemAction;
UINT itemState;
HWND hwndItem;
HDC hDC;
RECT rcItem;
DWORD itemData;
} DRAWITEMSTRUCT;
CtlType 은 현재 그리고자 하는 차일드윈도우가 어떤것인가를 알려줍니다. 이 변수에 저장되는 값들은 다음과 같습니다.
ODT_BUTTON : 버튼
ODT_COMBOBOX : 콤보박스
ODT_LISTBOX: 리스트 박스
ODT_MENU: 메뉴
ODT_LISTVIEW: 리스트 뷰
ODT_STATIC: 정적 윈도우
ODT_TAB: 탭컨트롤
CtlID 는 차일드 윈도우일 경우 이윈도우의 ID를 의미합니다. 만약 메뉴에서 사용자 그리기를 한다면 itemID에 ID값이 넘어오고 CtlID에는 0이 설정됩니다.
itemState는 현재 차일드 윈도우나 메뉴의 상태를 알려줍니다. 이값은 다음과 같습니다.
ODS_CHECKED : 차일드윈도우나 메뉴가 체크 되어 있을경우
ODS_DISABLED : 차일드 윈도우나 메뉴가 비활성화 되어있을경우
ODS_FOCUS : 차일드 윈도우에 포커스가 맞추어져 있을경우
ODS_GRAYED : 메뉴가 비활성화 되어 있을경우
ODS_SELECTED : 차일드 윈도우와 메뉴가 선택 되었을경우
ODS_COMBOBOXEDIT : 콤보 박스에 데이터가 입력될 경우
ODS_DEFAULT : 기본적인 상태일경우
hwndItem 은 차일드윈도우의 윈도우 핸들러 이며 hDC 는 차일드 윈도우의 DC
rcItem는 새로 그려줄 윈도우 영역이며 itemData 는 콤보박스나 리스트박스일 경우 현재 설정된 데이터의 값들이 넘어옵니다.
WM_DRAWITEM 메시지가 발생되면 이메시지에와 함께 들어오는 lParam을 DRAWITEMSTRUCT 포인터로 받아서 이구조체를 이용하여 사용자가 직접 그릴수 있습니다. 보통 일반적인 다른 차일드 윈도우들은 사용자가 직접 그리지 않습니다. 그러나 버튼은 사용자가 직접 그리는 경우가 많습니다. 어플리케이션 프로그램에서 비트맵 버튼등을 많이 보셨을 것입니다. 비트맵 버튼은 사용자 그리기 스타일을 첨부하여 버튼을 사용자가 직접 그린것입니다. 비트맵버튼을 출력하는 방법을 먼저 예로 보여 드리겠습니다.
//DRAWITEMSTRUCT형의 drawbut를 설정한다.
LPDRAWITEMSTRUCT drawbut;
//들어간 버튼 나온 버튼 의 비트맵을 저장할 변수 설정
static HBITMAP hBitD,hBitU;
//비트맵을 로드한다.
hBitD=LoadBitmap(hinst,"BUTTOND");
hBitU=LoadBitmap(hinst,"BUTTONU");
case WM_DRAWITEM:
//lParam으로 부터 DRAWITEMSTRUCT핸들을 얻는다.
drawbut=(LPDRAWITEMSTRUCT)lParam;
//비트맵 출력을 위한 메모리 dc를 설정한다.
MemDC=CreateCompatibleDC(drawbut->hDC);
//현재 버튼으리 눌려 진것이면
if(drawbut->itemState & ODS_SELECTED)
{
SelectObject(MemDC,hBitD);
//눌려진 그림을 그린다.
BitBlt(drawbut->hDC,0,0,69,35,MemDC,0,0,SRCCOPY);
}
else
{
//기본적 버튼을 그린다.
SelectObject(MemDC,hBitU);
BitBlt(drawbut->hDC,0,0,69,35,MemDC,0,0,SRCCOPY);
//현재 윈도우가에 포커스가 주어졌을때 박스를 그린다.
if(drawbut->itemState & ODS_FOCUS)
FrameRect(drawbut->hDC,&rect,CreateSolidBrush(RGB(255,255,0)));
}
DeleteDC(MemDC);
return 0 ;
비트맵 버튼을 DC에 로드하는 것은 설명하였습니다. 버튼을 그릴경우에 버튼이 눌려?봉뻑㎰? 버튼이 나와있을 경우 그리고 현재 버튼윈도우에 포커스가 맞추어 졌을경우에 해당되는 것을 모두 설정해 주어야 합니다. 버튼이 눌려진 것은 itemState와 ODS_SELECTED를 AND 연산을 이용하여 확인할수 있으며 현재 버튼에 포커스가 주어진 것을 확인할경우에는 ODS_FOCUS를 AND 연산을 하여 알수 있습니다. 보통 버튼에 포커스가 주어지면 보튼 내부에 작은 박스가 그려집니다. 박스 라인을 그리기 위해서 보통 많이 사용하는 것이 FrameRect입니다. 위의 예제는 이함수를 이용하여 버튼내부에 박스를 노란색으로 그려준 형태입니다.
차일드 윈도우의 활성 비활성 포커스설정
차일드 윈도우들을 비활성화 시켜서 사용하지 못하게 할 경우 또한 다시 활성화 시켜서 사용을 가능하게 할 경우 이때에는 EnableWindow 함수를 사용합니다. EnbaleWindow함수형태는 다음과 같습니다.
BOOL EnableWindow(
HWND hWnd, // 윈도우 핸들
BOOL bEnable // 활성일경우에는 TRUE비활성일경우에는 FALSE
);
프로그램을 제작할 경우 특정 버튼이 어떤 조건에서만 활성화 되게 하는 경우가 많습니다. 예를 든다면 음성을 출력하는 간단한 프로그램에서 Play버튼이 눌려져서 현재 음성이 출력될경우에는 STOP와 PAUSE버튼이 활성화 되어 있게 해야 하고 Play버튼은 다시 비활성으로 바뀌어야 합니다. 이럴경우에는 EnableWindow함수를 이용하여 차일드윈도우를 비활성 또는 활성화 하게 할수 있습니다.
특정 차일드 윈도우에 포커스를 주어지고자 할 경우 이때는 SetFocus 라는 함수를 사용하면 됩니다. SetFocus의 형태는 다음과 같습니다.
HWND SetFocus(
HWND hWnd // 윈도우 핸들
);
일반 프로그램에서 Tab키를 누르거나 Enter키를 누르게 되면 차일드 윈도우들에서 포커스가 순서적으로 이동됩니다. 이럴경우에는 SetFocus함수를 사용합니다.
차일드 윈도우예제 ButtonEx2
ButtonEx2는 그룹박스의 배경색을 흰색으로 설정하고 3개의 기본 버튼가 사용자 그리기 버튼을 만든다음 각버는의 프로시저를 새로 등록한 예제입니다. ButtonEx2는 Enter키를 누르게 되면 버튼 4개로 돌아가면서 포커스가 맞추어 집니다. 또한 F1키를 누르게 되면 첫 번째 버튼이 비활성 되고 Tab키를 누르면 비활성화 된 버튼이 다시 활성화 됩니다.
(프로그램 소스)
//버튼 윈도우 예제
//ButtonEx2.c
#include <windows.h>
HINSTANCE hinst;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK ButtonProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ButtonEx2" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hinst=hInstance;
hwnd = CreateWindow (szAppName,
"버튼윈도우 예제:ButtonEx2",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
//버튼 윈도우 핸들
HWND butwnd[4];
//버튼에서 받은 기본적인 프로시저를 저장하는 변수
WNDPROC butProc[4] ;
//버튼 윈도우 핸들 배열에서 현재 선택된 윈도우 핸들 번호
int butpos=0;
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ,MemDC;
PAINTSTRUCT ps ;
static HBRUSH hBrush;
//들어간 버튼 나온 버튼 의 비트맵을 저장할 변수 설정
static HBITMAP hBitD,hBitU;
//DRAWITEMSTRUCT형의 drawbut를 설정한다.
LPDRAWITEMSTRUCT drawbut;
RECT rect={5,5,64,30};
int i,id;
switch (iMsg)
{
case WM_CREATE :
//비트맵을 로드한다.
hBitD=LoadBitmap(hinst,"BUTTOND");
hBitU=LoadBitmap(hinst,"BUTTONU");
//차일드 윈도우를 만든다.
for(i=0;i<3;i++)
{
id=i+1;
butwnd[i]=CreateWindow("button","버튼",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON ,
10,10+i*40,100,40,hwnd,(HMENU)id,hinst,NULL);
//프로시저 설정
butProc[i]=(WNDPROC) SetWindowLong (butwnd[i],
GWL_WNDPROC,(LONG) ButtonProc) ;
}
id=i+1;
//4번째 버튼은 사용자 그리기로
butwnd[i]=CreateWindow("button","",WS_CHILD|
WS_VISIBLE|BS_OWNERDRAW, 10,10+i*40,69,35,hwnd,(HMENU)id,hinst,NULL);
//프로시저 설정
butProc[i]=(WNDPROC) SetWindowLong (butwnd[i], GWL_WNDPROC,(LONG) ButtonProc) ;
//첫번째 윈도우에 포커스를 맞춘다.
SetFocus(butwnd[butpos]);
CreateWindow("button","Group Box",
WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
120,100,100,40,hwnd,(HMENU)7,hinst,NULL);
//흰색 브러쉬를 만든다. WM_CTLCOLORSTATIC에서 사용
hBrush=CreateSolidBrush (RGB(255,255,255));
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_CTLCOLORSTATIC:
//흰색 브러쉬를 리턴하여 그룹박스의 배경색을 흰색으로 한다.
return (LRESULT) hBrush;
case WM_DESTROY :
PostQuitMessage (0) ;
break;
case WM_DRAWITEM:
//lParam으로 부터 DRAWITEMSTRUCT핸들을 얻는다.
drawbut=(LPDRAWITEMSTRUCT)lParam;
//비트맵 출력을 위한 메모리 dc를 설정한다.
MemDC=CreateCompatibleDC(drawbut->hDC);
//현재 버튼으리 눌려 진것이면
if(drawbut->itemState & ODS_SELECTED)
{
SelectObject(MemDC,hBitD);
//눌려진 그림을 그린다.
BitBlt(drawbut->hDC,0,0,69,35,MemDC,0,0,SRCCOPY);
}
else
{
//기본적 버튼을 그린다.
SelectObject(MemDC,hBitU);
BitBlt(drawbut->hDC,0,0,69,35,MemDC,0,0,SRCCOPY);
//현재 윈도우가에 포커스가 주어졌을때 박스를 그린다.
if(drawbut->itemState & ODS_FOCUS)
FrameRect(drawbut->hDC,&rect,CreateSolidBrush(RGB(255,255,0)));
}
DeleteDC(MemDC);
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
LRESULT CALLBACK ButtonProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
case WM_KEYDOWN:
//리턴 키를 누르면
if(wParam==VK_RETURN)
{
//버튼 윈도우 포스를 증가시키고
butpos++;
//4일경우에는 0으로 전환
if(butpos==4)
butpos=0;
//해당버튼윈도우에 포커스를 맞춘다.
SetFocus(butwnd[butpos]);
return 0;
}
//F1 키이면 첫번째 버튼을 비활성화 시키ㄱ고
else if(wParam==VK_F1)
EnableWindow(butwnd[0],FALSE);
//TAB키이면 첫번째 윈도우를 활성화 시킨다.
else if(wParam==VK_TAB)
EnableWindow(butwnd[0],TRUE);
break;
}
return CallWindowProc (butProc[butpos], hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 2)ButtonEx2 출력 결과
정적 윈도우
정적윈도우란 이것은 단지 하나의 빈 판으로써 키보드 입력이나 또는 마우스 이벤트를 받지않고 어떤 부모 윈도우에 WM_COMMAND를 보내지 않습니다. 그냥 간단하게 윈도우가 설정될뿐입니다. 정적윈도우를 만들경우에는 CreateWindow의 첫 번째 인자에 "static"라고 설정하면 만들 수 있습니다. 이윈도우에 어떤 메시지를 전달하고자 할 경우 이때는 부모 윈도우에서 정적윈도우에 메시지를 보내는 방법을 이용하면 됩니다. 예를 들어서 정적윈도우에 마우스 버튼과 키보드 입력메시지를 전달하고자 할 경우 다음과 같은 방법을 이용합니다.
//정적윈도우 영역 좌표
static RECT procRect={0,0,400,400};
case WM_CREATE :
//정적 윈도우를 만든다.
staticwnd=CreateWindow("static",NULL,
WS_CHILD | WS_VISIBLE | WS_THICKFRAME |WS_VSCROLL, 0,0,400,400,hwnd,(HMENU)3,
((LPCREATESTRUCT)lParam)->hInstance,NULL);
//프로시저 설정
staticProc=(WNDPROC) SetWindowLong (staticwnd, GWL_WNDPROC,
(LONG) StaticProc) ;
break;
case WM_KEYDOWN:
//키입력을 정적 윈도우에 보낸다
SendMessage(staticwnd,iMsg,wParam,lParam);
return 0;
case WM_LBUTTONDOWN:
//좌측 마우스 버튼을 클릭하면 위치를 point에
//저장하고
point.x=LOWORD(lParam);
point.y=HIWORD(lParam);
//point가 정적윈도우 영역에 있으면
//메시지를 보낸다.
if(PtInRect(&procRect,point))
SendMessage(staticwnd,iMsg,wParam,lParam);
return 0;
WM_CREATE에서 정적윈도우를 만든다음 이 윈도우 프로시적을 StaticProc로 등록합니다. WM_KEYDOWN 메시지가 발생되면 바로 정적윈도우로 메시지를 전송하고 WM_LBUTTONDOWN메시지가 발생되면 lParam으로부터 마우스 위치를 point에 저장하고 PtInRect함수를 사용하여 이 point가 정적윈도우 영역에 있는가를 확인합니다. PtInRect함수는 RECT와 POINT를 받아서 POINT변수가 RECT안에 있으면 TRUE값을 그렇지 않을경우에는 FALSE값을 리턴합니다.
정적윈도우를 사용하는 경우는 여러부분에 있으나 주로 많이 사용되는곳이 CD-TITLE를 만들경우입니다. CD-TITLE에서는 보통 박스 형태의 화면에 여러개의 프레임들이 교차되어 나타납니다. 이럴 경우 정적윈도우를 이용하여 여러개의 프레임을 만든다음 이것을 교차시키는 형태를 취합니다. 이방법은 윈도우의 전환에서 속도가 빠르기 때문에 자주 이용됩니다.
에디터 박스
에디터 박스란 문자를 입력하는 기능을 가진 윈도우입니다.
에디터 박스는 CreateWindow의 첫 번째 인자를 "edit"로 하고 ES_ 계열 스타일을 변경함으로써 만들 수 있습니다. 표5는 에디터 박스에서 사용되는 ES_ 계열의 리스트입니다.
에디터 박스 스타일 |
내용 |
ES_AUTOHSCROLL |
문자를 입력하면 수평으로 자동적으로 스크롤 된다. |
ES_AUTOVSCROLL |
문자를 입력할 때 여러 줄 입력시 자동적으로
수직 스크롤이 일어난다. |
ES_CENTER |
문자가 중앙을 깃점으로 정렬된다. |
ES_LEFT |
문자가 좌측을 깃점으로 정렬된다. |
ES_RIGHT |
문자가 우측을 깃점으로 정렬된다. |
ES_MULTILINE |
여러줄을 입력할수 있도록 한다. |
ES_PASSWORD |
비밀번호 형태로 입력이 된다. 이 스타일을 설정
하면 문자를 입력하여도 ‘*’ 코드만 화면에 출력된다. |
ES_READONLY |
읽기전용, 입력이 되지 않는다. |
ES_WANTRETURN |
에디터 박스에서 Enter키를 받는다. |
(표5) 에디터 박스 스타일
예를 들어서 에디터 박스가 여러줄을 입력받으면서 자동으로 수평수직 스크롤이 되면서 스크로 바가 붙은 형으로 윈도우를 만들고자 한다면 다음과 같이 할수 있습니다.
editwnd=CreateWindow("edit",NULL,
WS_CHILD |WS_VISIBLE| WS_HSCROLL |WS_VSCROLL
|WS_BORDER| ES_LEFT|ES_MULTILINE|
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
10,10,200,300,hwnd,(HMENU)1,hinst,NULL);
에디터 박스에서 어떤 이벤트가 발생되었을 때 WM_COMMAND가 발생되면서 HIWORD(wParam)에 저장되는 이벤트는 표6과 같습니다.
(표6) 에디터 박스 이벤트 메시지 리스트
이벤트 메시지 |
내용 |
EN_CHANGE |
에디터 박스에서 데이터가 수정되었을경우 |
EN_ERRSPACE |
에디터 박스에서 더 이상 데이터를 입력할수 없을경우 |
EN_HSCROLL |
수평으로 스크롤이 일어날경우 |
EN_IMECHANGE |
한영 전환이 일어났을경우 |
EN_KILLFOCUS |
포커스가 사라졌을경우 |
EN_SETFOCUS |
현재 포커스가 설정되었을 경우 |
EN_CHANGE는 에디터박스에서 문자를 입력하는 순간에 발생됩니다. 에디터박스는 윈도우 95에서는 한계용량을 가지고 있습니다. 만일 이용량을 초과할경우에는 EN_ERRSPACE메시지가 발생됩니다. 특이 메시지로서 EN_IMECHANGE메시지 인데 이것은 한영전환이 일어 났을 때 발생되는 메시지입니다. IME에 대한 방법은 키보드 컨트롤 부분에서 설명하였습니다. 에디터박스에서는 단지 현재 한영전환이 되었다는 것만 알려줄뿐 현재가 어떤 상태인지는 알려주지 않습니다. IMM 함수를 이용해서 현재 전환 된 모드를 알수 있을것입니다.
에디터 박스에서 데이터를 얻고자 할경우네는 GetWindowText를 데이터를 넣고자 할경우에는 SetWindowText함수를 이용하시면 됩니다. 이함수의 형태는 다음과 같습니다.
int GetWindowText(
HWND hWnd, // 윈도우 핸들
LPTSTR lpString, // 문자열 버퍼
int nMaxCount // 문자열 버퍼의 최대크기
);
BOOL SetWindowText(
HWND hWnd, // 윈도우 핸들
LPCTSTR lpString //문자열 버퍼
);
SetWindowText나 GetWindowText는 에디터 박스에서만 사용하는 함수가 아니라 모든 윈도우에 통용됩니다. 윈도우 타이틀바에 문자열을 설정하고자 할경우에도 이함수를 이용할수 있습니다. 쉽게 보면 윈도우에 메인 문자열을 설정할 때 사용하는 함수라고 보시면 됩니다.
정적 윈도우와 에디터 박스 예제 ExEdit
ExEdit는 정적윈도우를 하나 만들고 이 이 윈도우에서 키보드 입력 및 마우스 가 입력되었을 때 메시지박스를 출력하고 버튼을 클릭하면 에디터박스에서 입력한 데이터가 출력됩니다.
(프로그램 소스)
//C언어 배우기 위한 코딩
//ExEdit.c
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK StaticProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
HINSTANCE hinst;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ExEdit" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hinst=hInstance;
hwnd = CreateWindow (szAppName,
"에디터 박스 예제:ExEdit",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
WNDPROC staticProc;
HWND staticwnd;
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
static HBRUSH hBrush;
POINT point;
//정적윈도우 영역 좌표
static RECT procRect={0,0,400,400};
static HWND editwnd;
char temp[80];
switch (iMsg)
{
case WM_CREATE :
//정적 윈도우를 만든다.
staticwnd=CreateWindow("static",NULL,
WS_CHILD | WS_VISIBLE |
WS_THICKFRAME |WS_VSCROLL,
0,0,400,400,hwnd,(HMENU)3,
((LPCREATESTRUCT)lParam)->hInstance,NULL);
//프로시저 설정
staticProc=(WNDPROC) SetWindowLong
(staticwnd, GWL_WNDPROC,
(LONG) StaticProc) ;
//에디터 박스를 만든다.
editwnd=CreateWindow("edit",NULL,
WS_CHILD |WS_VISIBLE|
WS_HSCROLL|WS_VSCROLL|WS_BORDER|
ES_LEFT|ES_MULTILINE|
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
10,10,200,300,hwnd,(HMENU)1,hinst,NULL);
CreateWindow("button","입력데이터 보기",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON ,
220,10,150,40,hwnd,(HMENU)2,hinst,NULL);
hBrush=CreateSolidBrush (RGB(255,255,0));
return 0 ;
case WM_CTLCOLORSTATIC:
//흰색 브러쉬를 리턴하여 그룹박스의 배경색을 흰색으로 한다.
return (LRESULT) CreateSolidBrush(RGB(255,255,255));
case WM_COMMAND:
switch(LOWORD(wParam))
{
case 2:
//버튼을 클릭하면 입력한 데이터를 출력한다.
if(HIWORD(wParam)==BN_CLICKED)
{
//에디터 박스에서 데이터를 얻는다.
GetWindowText(editwnd,temp,80);
MessageBox(hwnd,temp,"edit 데이터",MB_OK );
}
break;
}
break;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_CTLCOLOREDIT:
return (LRESULT) hBrush;
case WM_KEYDOWN:
//키입력을 정적 윈도우에 보낸다
SendMessage(staticwnd,iMsg,wParam,lParam);
return 0;
case WM_LBUTTONDOWN:
//좌측 마우스 버튼을 클릭하면 위치를 point에
//저장하고
point.x=LOWORD(lParam);
point.y=HIWORD(lParam);
//point가 정적윈도우 영역에 있으면
//메시지를 보낸다.
if(PtInRect(&procRect,point))
SendMessage(staticwnd,iMsg,wParam,lParam);
return 0;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
//정적윈도우 프로시저
LRESULT CALLBACK StaticProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
// 부모 윈도우로 부터 키와 마우스 버튼 메시지를 받아서
// 메시지 박스를 실행시킨다.
case WM_KEYDOWN:
MessageBox(hwnd,"키눌림","키눌림",MB_OK);
break;
case WM_LBUTTONDOWN:
MessageBox(hwnd,"마우스눌림","마우스눌림",MB_OK);
break;
}
return CallWindowProc (staticProc, hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 3)ExEdit 출력 결과
리스트 박스와 콤보박스
리스트 박스와 콤보박스는 그림4와 같은 형태로써 문자열 리스트를 저장하는 차일드 윈도우입니다.
(그림 4) 리스트 박스와 콤보박스 형태
리스트박스와 콤보 박스 또한 CreateWindow 함수를 이용하여 만들며 윈도우 스타일에 각각의 스타일을 첨부할수 있습니다. 표7은 리스트박스에 대한 스타일 이며 표8은 콤보박스에 대한 스타일입니다.
(표 7) 리스트 박스 스타일
윈도우 스타일 |
내용 |
LBS_EXTENDEDSEL |
다중 선택을 할 경우 Shift 키와 마우스를 이용할수 있게 한다. |
LBS_MULTIPLESEL |
여러개의 아이템을 선택할수 있도록한다. |
LBS_OWNERDRAWFIXED |
사용자 그리기, 리스트 박스 크기가 고정된 형태 |
LBS_OWNERDRAWVARIABLE |
사용자 그리기 ,리스트박스 크기가 변함 |
LBS_SORT |
데이터가 가나다 순으로 정렬됨 |
LBS_STANDARD |
표준 리스트 박스 |
(표 8) 콤보 박스 스타일
윈도우 스타일 |
내용 |
CBS_AUTOHSCROLL |
콤보박스에서 데이터를 입력할 때 자동적으로 스크롤이 이루어진다. |
CBS_DROPDOWN |
콤보박스 좌측 다운 버튼을 클릭하면 리스트가 보이는 형태 |
CBS_OWNERDRAWFIXED |
사용자 그리기, 콤보 박스 크기가 고정된 형태 |
CBS_OWNERDRAWVARIABLE |
사용자 그리기 ,콤보박스 크기가 변함 |
CBS_SORT |
데이터가 가나다 순으로 정렬됨 |
CBS_SIMPLE |
리스트박스가 항시보이는 형태 |
표준 형태의 리스트박스를 사용하고자 한다면 LBS_STANDARD 단 한 개를 사용하면 됩니다. 그외 특정 옵션이 있을경우에는 그옵션을 삽입시키면 됩니다. CBS_DROPDOWN이란 콤보박스가 처음에는 에디터 형태의 창만 있다가 다운 버튼을 클릭하면 리스트가 보이는 형태이면 이와 대조적으로 항시 보이는 형태는 CBS_SIMPLE입니다. 다음은 콤보박스와 리스트박스를 만드는 예입니다.
listwnd=CreateWindow("listbox",NULL,
WS_CHILD |WS_VISIBLE| LBS_STANDARD,
0,0,100,100,
hwnd,(HMENU)1,
((LPCREATESTRUCT)lParam)->hInstance,NULL);
combownd=CreateWindow("combobox",NULL,
WS_CHILD |WS_VISIBLE| CBS_DROPDOWN |CBS_AUTOHSCROLL ,
150,0,100,100,
hwnd,(HMENU)2,((LPCREATESTRUCT)lParam)->hInstance,NULL);
리스트 박스와 콤보박스 이벤트
리스트박스에서 어떤 이벤트가 발생되었을때는 LBN_ 계열의 메시지가 발생되면 콤보박스에서 특정 이벤트가 발생되었을 경우에는 CBN_ 계열의 메시지가 발생됩니다. 표9은 리스트박스 이벤트 메시지 이며 표10는 콤보박스 이벤트 메시지입니다.
메시지 |
내용 |
LBN_DBLCLK |
항목을 더블클릭했을경우 |
LBN_ERRSPACE |
메모리가 부족할경우 |
LBN_KILLFOCUS |
포커스 사라질경우 |
LBN_SETFOCUS |
현재 리스트에 포커스가 맞추어 졌을때 |
LBN_SELCANCEL |
리스트박스에서 CANCEL이벤트가 발생되었을때 |
LBN_SELCHANGE |
리스트박스에서 선택항목이 바뀌었을경우 |
(표 9) 리스트 박스 이벤트
(표 10) 콤보 박스 이벤트
메시지 |
내용 |
CBN_DBLCLK |
항목을 더블클릭했을경우 |
CBN_ERRSPACE |
메모리가 부족할경우 |
CBN_KILLFOCUS |
포커스 사라질경우 |
CBN_SETFOCUS |
현재 리스트에 포커스가 맞추어 졌을때 |
CBN_DROPDOWN |
콤보박스에서 다운 버튼을 눌렀을경우 |
CBN_SELCHANGE |
콤보박스에서 선택항목이 바뀌었을경우 |
리스트 박스와 콤보박스 명령 메시지
리스트박스에 데이터를 넣거나 또는 콤보박스에 데이터를 넣거나 삭제하거나 현재 선택된 리스트번호를 알고자 할경우에 리스트 박스 윈도우나 콤보 박스 윈도우에 메시지를 전송함으로써 알수가 있습니다. 이때 사용하는 함수가 SendMessage함수인데 이함수에 의해서 명령이 발생되면 넘겨준 lParam이나 또는 리턴값으로 결과정보를 넘겨줍니다. 표11은 리스트박스 명령 메시지이며 표12는 콤보박스 명령 메시지입니다.
메시지 |
내용 |
결과값 |
LB_ADDSTRING |
새로운 데이터를 입력한다. |
lParam에 입력할 데이터 문자열을 넘겨준다. |
LB_DELETESTRING |
선택된 데이터를 삭제한다. |
wParam에 삭제할 데이터 번호를 지정 |
LB_INSERTSTRING |
데이터를 삽입한다. |
wParam에 삽입할위치 lParam에 상입할 문자열 |
LB_DIR |
파일 리스트를 보여준다. |
wParam 파일속성
lParam에 검색할 파일명 (*.* 포함) |
LB_FINDSTRING |
리스트에서 특정 문자열을 찾는다. |
wParam에 시작번호
lParam에 찾는문자열을
찾았을 경우 찾은 번호는 리턴값에 설정됨 |
LB_GETCURSEL |
현재 리스트박스의 커서
위치를 얻는다. |
현재 선택된 리스트 번호를 리턴값에 설정함 |
LB_GETTEXT |
특정위치의 문자열을 얻는다. |
wParam에 특정위치번호 lParam 에는 해당번호에 있는 문자열이 리턴된다. |
LB_GETTEXTLEN |
특정 위치의 문자열의 길이를 얻는다. |
wParam에는 특정위치번호 리턴값에 길이가 설정된다. |
(표 11) 리스트 박스 명령 메시지
메시지 |
내용 |
결과값 |
CB_ADDSTRING |
새로운 데이터를 입력한다. |
lParam에 입력할 데이터 문자열을 넘겨준다. |
CB_DELETESTRING |
선택된 데이터를 삭제한다. |
wParam에 삭제할 데이터 번호를 지정 |
CB_INSERTSTRING |
데이터를 삽입한다. |
wParam에 삽입할위치 lParam에 상입할 문자열 |
CB_DIR |
파일 리스트를 보여준다. |
wParam 파일속성
lParam에 검색할 파일명 (*.* 포함) |
CB_FINDSTRING |
리스트에서 특정 문자열을 찾는다. |
wParam에 시작번호
lParam에 찾는문자열을
찾았을 경우 찾은 번호는 리턴값에 설정됨 |
CB_GETCURSEL |
현재 콤보박스의 커서
위치를 얻는다. |
현재 선택된 리스트 번호를 리턴값에 설정함 |
CB_GETLBTEXT |
특정위치의 문자열을 얻는다. |
wParam에 특정위치번호 lParam 에는 해당번호에 있는 문자열이 리턴된다. |
CB_GETLBTEXTLEN |
특정 위치의 문자열의 길이를 얻는다. |
wParam에는 특정위치번호 리턴값에 길이가 설정된다. |
(표 12) 콤보박스 설정 메시지
예를 든다면 리스트박스에서 세로운 데이터를 넣고자 한다면 LB_ADDSTRING을 이용하여 다음과 같이 할수 있습니다.
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터1");
위와 같이 하면 “데이터1”이라는 값이 리스트 박스에 삽입되게 됩니다.
마찬가지고 콤보박스는 다음과 같이 합니다.
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터1");
표11과 12을 참조하면 리스트 박스와 콤보박스에 대한 많은 것들을 컨트롤 할수 있을것입니다. 이외에도 많은 LB_ 계열의 명령 메시지와 CB_ 계열의 명령 메시지가 있습니다. 이것들은 도움말을 참조하시기 바랍니다.
하나의 예를 더들어 보겟습니다. 리스트 박스에서 특정 항목을 선택하였을 때 선택된 값을 알아내는 방법입니다.
case WM_COMMAND:
//리스트 박스이고 이벤트가 LBN_SELCHANGE
if(LOWORD(wParam)==1 && HIWORD(wParam)==LBN_SELCHANGE)
{
//선택된 리스트 번호를 num에 얻고
num=SendMessage(listwnd,LB_GETCURSEL,0,0);
//num의 데이터를 temp에 얻는다.
SendMessage(listwnd,LB_GETTEXT,
(WPARAM)num,(LPARAM)temp);
MessageBox(hwnd,temp,"리스트박스",MB_OK );
}
리스트 박스와 콤보박스 컨트롤 예제 ExListBox
ExListBox는 리스트박스와 콤보박스에 데이터를 넣은다음 선택된 데이터가 무엇인지 알아보는 예제입니다.
(프로그램 소스)
//리스트 박스와 콤보박스
//ExListBox.c
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ExListBox.c" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hwnd = CreateWindow (szAppName,
"리스트박스와 콤보박스 예제:ExListBox",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
static HWND listwnd,combownd;
char temp[80];
int num;
switch (iMsg)
{
case WM_CREATE :
//리스트 박스 만들기
listwnd=CreateWindow("listbox",NULL,
WS_CHILD |WS_VISIBLE| LBS_STANDARD,
0,0,100,100,hwnd,(HMENU)1,
((LPCREATESTRUCT)lParam)->hInstance,NULL);
//콤보 박스 만들기
combownd=CreateWindow("combobox",NULL,
WS_CHILD |WS_VISIBLE| CBS_DROPDOWN
|CBS_AUTOHSCROLL ,
150,0,100,100,hwnd,(HMENU)2,
((LPCREATESTRUCT)lParam)->hInstance,NULL);
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터1");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터2");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터3");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터4");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터5");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터6");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터7");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터8");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터9");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터10");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터11");
SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)"데이터12");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터1");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터2");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터3");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터4");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터5");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터6");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터7");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터8");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터9");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터10");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터11");
SendMessage(combownd,CB_ADDSTRING,0,(LPARAM)"데이터12");
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
case WM_COMMAND:
//리스트 박스이고 이벤트가 LBN_SELCHANGE
if(LOWORD(wParam)==1 && HIWORD(wParam)==LBN_SELCHANGE)
{
//선택된 리스트 번호를 num에 얻고
num=SendMessage(listwnd,LB_GETCURSEL,0,0);
//num의 데이터를 temp에 얻는다.
SendMessage(listwnd,LB_GETTEXT,(WPARAM)num,(LPARAM)temp);
MessageBox(hwnd,temp,"리스트박스",MB_OK );
}
//콤보박스도 같은 방법이다
if(LOWORD(wParam)==2 && HIWORD(wParam)==CBN_SELCHANGE)
{
num=SendMessage(listwnd,CB_GETCURSEL,0,0);
SendMessage(combownd,CB_GETLBTEXT,(WPARAM)num,(LPARAM)temp);
MessageBox(hwnd,temp,"콤보박스",MB_OK );
}
break;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 5)ExListBox출력 결과
스크롤바 윈도우 예제 ScrBarEx
ScrBarEx는 스크롤 윈도우를 만들고 이것에 대한 이벤트를 받는 예제입니다. 스크롤 윈도우에서 발생되는 이벤트는 WM_COMMAND가 아닌 WM_VSCROLL이나 WM_HSCROLL로 발생됩니다. 스크롤 이벤트를 컨트롤 하는 방법은 5장에서 설명한 스크롤 바가 붙은 윈도우와 거히 비슷합니다. 다만 차이점은 스크롤 윈도우를 만드는것과 WM_VSCROLL과 WM_HSCROLL이 발생될 때 LOWORD(wParam)에 스크롤 윈도우 ID가 설정된다는 것입니다.
(프로그램 소스)
//스크롤 윈도우 예제
//ScrBarEx.c
#include <windows.h>
HINSTANCE hinst;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ScrBarEx.c" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hinst=hInstance;
hwnd = CreateWindow (szAppName,
"스크롤 윈도우:ScrBarEx",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
HWND scwnd;
static int pos=0;
static HBRUSH hBrush;
int totalline=100;
char temp[80];
switch (iMsg)
{
case WM_CREATE :
scwnd=CreateWindow("scrollbar","버튼",
WS_CHILD|WS_VISIBLE|SBS_VERT ,
10,10,30,200,hwnd,(HMENU)1,hinst,NULL);
SetScrollRange(scwnd,SB_CTL,0,totalline,FALSE);
SetScrollPos(scwnd,SB_CTL,pos,TRUE);
hBrush=CreateSolidBrush (RGB(0,255,0));
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
wsprintf(temp,"현재 위치: %d",pos);
TextOut(hdc,50,50,temp,strlen(temp));
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_VSCROLL:
switch(LOWORD(wParam))
{
case SB_TOP:
pos=0;
break;
case SB_BOTTOM:
pos=totalline-1;
break;
case SB_LINEUP:
pos-=1;
if(pos<0)
pos=0;
break;
case SB_LINEDOWN:
pos+=1;
if(pos==totalline)
pos=totalline-1;
break;
case SB_PAGEUP:
pos-=10;
if(pos<0)
pos=0;
break;
case SB_PAGEDOWN:
pos+=10;
if(pos>=totalline)
pos=totalline-1;
break;
case SB_THUMBTRACK:
pos=HIWORD(wParam);
break;
}
SetScrollPos((HWND)lParam,SB_CTL,pos,TRUE);
InvalidateRgn(hwnd,NULL,FALSE);
break;
//스크롤 버튼 배경색 바꾸기
case WM_CTLCOLORSCROLLBAR:
return (LRESULT) hBrush;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 6)ScrBarEx 출력 결과
<자료출저 : 삼육대학교 이상엽 박사>