황동준님의 윈도우 32비트 프로그래밍 16~20

  윈도우 32비트 프로그래밍 16

안녕하세요……..돌팔이 황동준입니다…..

오늘은 메뉴 부분 마지막 강좌인 툴바와 툴팁 대해서 알아보도록 하겠습니다. 먼저 용어를 정의
해보도록 하죠.

툴바가 무엇인지 모르지는 않겠죠? 한글로 도구모음이라고 번역이 되는데 이것은 메뉴 아래에
그림버튼 형식으로 자주 쓰이는 메뉴 아이템을 꺼내 놓은 것입니다.

MFC를 이용해서 프로그램을 만들면 이 툴바를 간단하게 구현할 수 있지만 API는 조금
복잡한 구조를 띕니다. 그렇다고 아주 복잡한 형식은 아닙니다. 제가 쉽게 설명을 드릴테니 잘 보시기 바랍니다. 툴팁은 뭘까요? 툴바에 마우스의
커서를 대고 몇 초간 기다리면 이 버튼이 어떤 기능을 하는지 풍성 도움말이 나올 겁니다. 이것을 툴팁이라고 합니다.

 

자 그러면 툴바와 툴팁을 어떻게 구현하는지 알아 봅시다. 이 툴바와 툴팁을 구현하면 메뉴
부분에 대한 것은 끝난 것 같군요. 다음에는 상태바에 대해  알아보고 그래픽 부분으로 넘어가겠습니다.

툴바에 사용되는 그림은 윈도우즈에서 사용하는 비트맵 파일 포맷을 사용합니다. 비트맵 그림을
사용하니까 리소스 파일에서 이 그림을 정의 해주어야겠죠? 그런데 예전에 알아본 비트맵 그림파일을 정의 해 준 것과는 좀 다릅니다.

500 BITMAP  “icon.bmp”

위와 같은 형태로 정의 해줍니다. 뭐가 다른지 알겠습니까? 우리가 예전에 비트맵 파일을
정의할 때에는 멘 앞에 유저가 임의로 문자열을 정의해서 정의했는데 숫자로 정의하고 있잖아요? 그렇죠? 이것은 툴바를 생성하는 함수에서 비트맵
파라미터 핸들을 숫자로 요구하기 때문입니다. 그렇다면 툴바로 사용할 그림은 어떤 식으로 그려야 할까요? 그것은 보통 16X16으로 그림을
그립니다. 단 버튼이 여러 개일 때에 따로따로 그리는 것인 아니라 하나의 그림 파일로 연결해서 그려야 한다는 것입니다. 예를 들어서 버튼이
4개인 툴바라고 가정합시다. 그러면 그 그림 사이즈는 어떻게 될까요? 64X16이 되겠죠? 가로로 길쭉한 모양의 그림이 되겠네요.

툴바를 만들어줄 함수를 사용하기 위해서는 아래의 헤더 파일을 포함시켜야 합니다.

#include <commctrl.h>

이것은 우리가 CreateWindow() 함수와 같은 어떤 윈도우를 생성할 때 사용하는
함수가 지원하지 못하는 것까지 지원하도록 확장된 형태를 띄고 있는 것입니다.

void InitCommonControls(void);

위에서 선언한 헤더 파일에서 지원하는 컨트롤을 사용하기 위해서는 윈도우를 생성하는
CreatWindow() 함수 다음에 위 InitCommonControls()함수를 사용해야 합니다.

툴바로 사용할 그림은 하나죠? 그러나 그 그림은 16크기로 구분되어져 있을 겁니다. 각
그림의 기능과 속성을 정의해줄 필요가 있습니다. 이 때  TBBUTTON이라는 구조체를 이용해서 그 속성을 지정해줄 수 있습니다.

typedef struct _TBBUTTON {
    int
iBitmap;
    int idCommand;
    BYTE fsState;
    BYTE
fsStyle;
#ifdef _WIN32
    BYTE bReserved[2];
#endif
    DWORD
dwData;
    int iString;
} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;

구조체의 원형을 잘 보여주고 있군요. 각 구조체 멤버의 의미를 알아 봅시다. 우선 첫번째
iBitmap이라는 것은 버튼의 인덱스를 의미하는 것입니다. 예를들어서 46X16 그림이라면 버튼이 4개이니 위 구조체를 배열로 해서 4개를
생성하고 첫번째 버튼의 iBitmap은 0을 두번째는 1을 세번째, 네번째에는 각각 2, 3을 넣어주면 되는 것입니다. 두번째 맴버인
idCommand는 이 버튼이 생성되었을 때 발생될 메시지를 의미하는 것입니다. 다시말해서 아이디를 의미하는거죠. 우리가 앞에서 알아본 메뉴도
아이템을 선택했을 때 아이디가 메시지로 발생됐죠? 그거랑 같은 겁니다. 대부분의 툴바는 메뉴 아이템중에 있는 것을 만드는 경우가 많으므로 메뉴
아이템중 이 기능을 하는 아이템의 아이디를 지정하면 됩니다. 세번째 멤버인 fsState는 말그대로 버튼의 상태를 지정해 주는 것입니다. 버튼의
상태는 아래의 지정된 예약어로서 그 상태를 명시해 줄 수 있습니다.

TBSTATE_ENABLED         일반적인 버튼을
생성합니다.
TBSTATE_PRESSED         눌린 형태의 버튼을 생성합니다.
TBSTATE_INDETERMINATE
  그레이된 버튼을 생성합니다.

네번째 멤버인 fsStyle은 버튼의 스타일을 지정해 줄수 있는데 이때 지정될수 있는
예약어는 다음과 같습니다.

TBSTYLE_BUTTON          일반적인 버튼을
생성합니다.
TBSTYLE_CHECK           토글 버튼을 생성합니다. 한번 선택되면 들어가고 다시

                        선택하면 원래대로 나오는 형태의 버튼이죠.
TBSTYLE_SEP
            버튼과 버튼을 약간 띄어 주는 역할로 메뉴 만드는 속성
                        중
SEPARATOR 기능과 같습니다.

나머지 멤버는 잘 쓰이지 않아 보통 0으로 지정해주면 됩니다.

자 그러면 정리해 봅시다. 비록 그림은 하나지만 그 그림속에 4개의 버튼이 있다면 위
구조체를 배열로서 4개를 선언하고 각각 속성을 지정해주면 되는 것입니다. 저장된 속성은 툴바를 만들어주는 함수의 파라미터로 사용되어 원하는
툴바를 생성해주거든요.

WINCOMMCTRLAPI HWND WINAPI CreateToolbarEx(HWND
hwnd, DWORD ws,
        UINT wID, int nBitmaps, HINSTANCE hBMInst, UINT
wBMID,
        LPCTBBUTTON lpButtons, int iNumButtons, int dxButton, int
dyButton,
            int dxBitmap, int dyBitmap, UINT uStructSize);

툴바를 만들기 위해서 위 CreateToolbarEx() 함수를 사용합니다. 우와~~~
복잡하네요… 그쵸? 그런데 하나씩 알아보면 별것도 아닙니다.

hwnd            메인 윈도우의 핸들을 의미합니다.

ws              윈도우의 스타일을 지정하면 됩니다. 보통

                WS_VISIBLE|WS_CHILD|WS_BORDER|TBSTYLE_TOOLTIPS 로
지정하
                는데 처음 보는것이 있군요. 바로 마지막 속성인데 이것은 이
                툴바에
툴팁을 사용하겠다는 의미입니다.

wID             전체 툴바에 대한 아이디를 지정하면 되는데 보통 -1을
지정합니
                다. 왜냐하면 이 아이디는 잘 쓰이지 않거든요.

nBitmaps        그림버튼의 개수를 지정하면 됩니다. 위에서 알아 본대로면
4를
                지정하면 되겠네요.

hBMInst         인스턴스 핸들을 지정하면 됩니다.

wBMID           툴바로 사용할 그림의 아이디인데 이것은 리소스 파일에서
정의한
                숫자를 지정하면 됩니다. 위와 같은 겨우라면 500을 지정하면

                겠죠?

lpButtons       바로 위에서 배운 툴바 구조체의 주소를 지정해 주면
됩니다.

iNumButtons     역시 툴바 버튼의 개수를 지정하면 됩니다. 4라고 지정하면
되겠
                죠?

dxButton        보통 0이라고 지정하면 됩니다.

dyButton        보통 0이라고 지정하면 됩니다.

dxBitmap        버튼 그림 하나의 가로 크기를 지정하면 됩니다. 16이라고
하면
                되죠.

dyBitmap        버튼 그림 하나의 세로 크기를 지정하면 됩니다. 16이라고
하면
                되죠.

uStructSize     툴바 구조체의 크기를 지정하면 됩니다.

툴바 구조체를 선언해서 값을 채워주고 위 함수를 이용하면 간단하게 툴바가 구현됩니다. 그렇게
복잡하지는 않죠? 그러면 이번에는 툴팁 처리하는 부분을 봅시다.

커서가 툴바에 놓여진채 몇초가 지나면 WM_NOTIFY라는 메시지가 발생됩니다. 그런데
문제는 이 메시지가 꼭 커서가 툴바에 놓여있을 때만 발생하는 것이 아니라 다른일이 있을 때도 발생될 수 있다는 것입니다. 그러면 이것이 툴바에
커서가 있어서 발생한 메시지라는 것을 어떻게 알 수 있을까요? 그것은 LPARAM 값을 보고 알 수 있습니다. LPARAM값을 특수한 구조체인
LPTOOLTIPTEXT로 캐스트 연산을 해서 그 안에 포함된 값이 정의된 값이면 이것이 툴바에 커서가 놓여저서 발생됐다고 판단이 되는것입니다.
어렵습니까?  간단하게 예를 보죠?

LPTOOLTIPTEXT ttText;

case WM_NOTIFY :

ttText =
(LPTOOLTIPTEXT)lParam;
if(ttText->hdr.code ==
TTN_NEEDTEXT)
{
        .
        .
        .
}

자 여기까지입니다. 위의 것을 해석하려면 먼저 TOOLTIPTEXT라는 구조체의
원형을 보는 것이 이해하기가 쉬울겁니다.

typedef struct tagTOOLTIPTEXT {
    NMHDR
hdr;
    LPSTR lpszText;
    char szText[80];
    HINSTANCE
hinst;
    UINT uFlags;
} TOOLTIPTEXT, FAR *LPTOOLTIPTEXT;

멤버중 모르는 변수 타입이 있죠? 바로 NMHDR이라는 타입인데 이것도 구조체입니다. 다음은
이 구조체의 원형입니다.

typedef struct tagNMHDR
{
    HWND
hwndFrom;
    UINT idFrom;
    UINT code;
} NMHDR;

이젠  ttText->hdr.code 구문이 이해 가죠? 이 값이
TTN_NEEDTEXT면 툴바에 커서가 놓여 있어서 툴팁에 들어갈 문자열이 필요하다는 것을 의미합니다. 구체적으로 어떤 버튼에 커서가
놓여있는지는 ttText->hdr.idFrom에 그 아이디가 들어가있습니다. 그러면 유저는 TOOLTIPTEXT의 두번째 멤벙인
lpszText에 문자열의 지정해주면 그 문자열이 풍성 도움말로 나오게 되죠. 풍선 도움말이 나오는 구문을 예로 들어 볼까요?

switch(ttText->hdr.idFrom)
{
    case 100
:

    ttText->lpszText = “Open”;
    break;

}

100이라는 아이디를 가진 버튼에 커서가 놓여 있으면 Open이라는 풍선 도움말을 보내라는
구문입니다. 어렵습니까? 그렇다면 전체 예제 소스를 보시기 바랍니다. 아하~~~ 하고 이해가 갈겁니다. 아래 예제는 네개의 버튼을 가진 툴바와
툴팁을 구현한 예제입니다.

먼저 리소스 파일의 소스입니다.

#include <windows.h>

500 BITMAP  “icon.bmp”

MyMenu MENU
BEGIN
    POPUP
“&File”
    BEGIN
        MENUITEM “&Open”,
100
        MENUITEM “&Close”, 200
        MENUITEM
SEPARATOR
        MENUITEM “&Save”, 300
        MENUITEM “Save
&As”, 400
    END
    POPUP
“&Toolbar”
    BEGIN
        MENUITEM “&Hide”,
600
        MENUITEM “&Show”, 700
    END
END

프로그램 메인 소스입니다.

#include <windows.h>
#include
<commctrl.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
void CreateTOOLBAR(HWND, HINSTANCE);

HWND hToolbar;

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{

        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;
        char szAppName[] = “This program
is to create toolbar”;

        WndClass.style =
NULL;
        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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
“MyMenu”;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass)) return NULL;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        InitCommonControls();
        CreateTOOLBAR(hWnd,
hInstance);
        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return
msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT mesg,
WPARAM wParam, LPARAM lParam)
{
    HDC hDC;
    LPTOOLTIPTEXT
ttText;

    switch(mesg)
    {
        case
WM_COMMAND :

            hDC =
GetDC(hWnd);
            switch(LOWORD(wParam))
            {
                case
100 :

                    TextOut(hDC, 100, 100, “Open
Button Clicked”, 20);
                    break;

                case 200 :

                    TextOut(hDC, 100, 100, “Close
Button Clicked”, 21);
                    break;

                case 300 :

                    TextOut(hDC, 100, 100, “Save
Button Clicked”, 20);
                    break;

                case 400 :

                    TextOut(hDC, 100, 100, “Save As
Button Clicked”, 23);
                    break;

                case 600 :

                    ShowWindow(hToolbar,
SW_HIDE);
                    break;

                case 700 :

                    ShowWindow(hToolbar,
SW_RESTORE);
                    break;
            }
            ReleaseDC(hWnd,
hDC);
            return FALSE;

        case WM_NOTIFY :

            ttText =
(LPTOOLTIPTEXT)lParam;
            if(ttText->hdr.code ==
TTN_NEEDTEXT)
            {
                switch(ttText->hdr.idFrom)
                {
                    case
100 :

                        ttText->lpszText =
“Open”;
                        break;

                    case 200 :

                        ttText->lpszText =
“Close”;
                        break;

                    case 300 :

                        ttText->lpszText =
“Save”;
                        break;

                    case 400 :

                        ttText->lpszText = “Save
As”;
                        break;
                }
            }
            return
FALSE;

        case WM_DESTROY :

            PostQuitMessage(0);
            return
FALSE;
    }
    return DefWindowProc(hWnd, mesg, wParam,
lParam);
}

void CreateTOOLBAR(HWND hWnd, HINSTANCE
hInst)
{
    TBBUTTON tbToolbar[5];

    tbToolbar[0].iBitmap =
0;
    tbToolbar[0].idCommand = 100;
    tbToolbar[0].fsState =
TBSTATE_ENABLED;
    tbToolbar[0].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[0].dwData = 0L;
    tbToolbar[0].iString =
0;

    tbToolbar[1].iBitmap =
1;
    tbToolbar[1].idCommand = 200;
    tbToolbar[1].fsState =
TBSTATE_ENABLED;
    tbToolbar[1].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[1].dwData = 0L;
    tbToolbar[1].iString =
0;

    tbToolbar[2].iBitmap =
0;
    tbToolbar[2].idCommand = 0;
    tbToolbar[2].fsState =
TBSTATE_ENABLED;
    tbToolbar[2].fsStyle =
TBSTYLE_SEP;
    tbToolbar[2].dwData = 0L;
    tbToolbar[2].iString =
0;

    tbToolbar[3].iBitmap =
2;
    tbToolbar[3].idCommand = 300;
    tbToolbar[3].fsState =
TBSTATE_ENABLED;
    tbToolbar[3].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[3].dwData = 0L;
    tbToolbar[3].iString =
0;

    tbToolbar[4].iBitmap =
3;
    tbToolbar[4].idCommand = 400;
    tbToolbar[4].fsState =
TBSTATE_ENABLED;
    tbToolbar[4].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[4].dwData = 0L;
    tbToolbar[4].iString =
0;

    hToolbar =
CreateToolbarEx(hWnd,
        WS_VISIBLE|WS_CHILD|WS_BORDER|TBSTYLE_TOOLTIPS,
        -1,
        5,
        hInst,
        500,
        tbToolbar,
        5,
        0,
0, 16, 16,
        sizeof(TBBUTTON)
    );
}

1부
끝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

  윈도우 32비트 프로그래밍 17

2부를 시작 하겠습니다.

먼저 툴바를 사용하기 위해 아래의 헤더 파일을 포함 시켰습니다. 이미 설명 드린
부분이죠?

#include <commctrl.h>

윈도우 생성하는 CreateWindow() 함수 다음에 아래 함수도 사용해야 한다고 했을
겁니다.

InitCommonControls();

못보던 함수가 하나 있죠? 바로 아래 함수인데 이것은 필자가 만든 함수입니다. 이 함수
내부를 보도록 하죠.

CreateTOOLBAR(hWnd, hInstance);

void CreateTOOLBAR(HWND hWnd, HINSTANCE
hInst)
{
    TBBUTTON tbToolbar[5];

어허~~ 이렇게 이상한일이 발생할 수가????? 버튼이 4개로 구성되어 있다고 했는데 왜
5개로 선언했을까요? 그것은 우리가 앞에서 알아본 버튼 스타일중 TBSTYLE_SEP 을 사용하기 위해서입니다. 이것은 버튼과 버튼을 구분짓는
역할만 하지만 그래도 하나의 경계 버튼이라고 볼수 있는 것입니다. 실제로 사용될 버튼은 네개지만요.

    tbToolbar[0].iBitmap =
0;
    tbToolbar[0].idCommand = 100;
    tbToolbar[0].fsState =
TBSTATE_ENABLED;
    tbToolbar[0].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[0].dwData = 0L;
    tbToolbar[0].iString =
0;

리소스 파일에서 만든 메뉴 아이템중 100 값을 갖는게 있죠? 그것에 대한 버튼입니다. 보면
알겠지만 아이디가 100으로 되어 있잖아요.

    tbToolbar[1].iBitmap =
1;
    tbToolbar[1].idCommand = 200;
    tbToolbar[1].fsState =
TBSTATE_ENABLED;
    tbToolbar[1].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[1].dwData = 0L;
    tbToolbar[1].iString =
0;

여기까지는 크게 어려운 부분이 없죠? 아래를 봅시다.

    tbToolbar[2].iBitmap =
0;
    tbToolbar[2].idCommand = 0;
    tbToolbar[2].fsState =
TBSTATE_ENABLED;
    tbToolbar[2].fsStyle =
TBSTYLE_SEP;
    tbToolbar[2].dwData = 0L;
    tbToolbar[2].iString =
0;

버튼의 인덱스가 2가 되어야 하는데 0이 되고 아이디도 0이죠? 이 버튼은 버튼의 역할을
갖는것이 아니라 버튼과 버튼을 띄어주는 역할을 하기 때문에 0으로 지정한 것입니다.

    tbToolbar[3].iBitmap =
2;
    tbToolbar[3].idCommand = 300;
    tbToolbar[3].fsState =
TBSTATE_ENABLED;
    tbToolbar[3].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[3].dwData = 0L;
    tbToolbar[3].iString =
0;

    tbToolbar[4].iBitmap =
3;
    tbToolbar[4].idCommand = 400;
    tbToolbar[4].fsState =
TBSTATE_ENABLED;
    tbToolbar[4].fsStyle =
TBSTYLE_BUTTON;
    tbToolbar[4].dwData = 0L;
    tbToolbar[4].iString =
0;

나머지는 같군요.

    hToolbar =
CreateToolbarEx(hWnd,
        WS_VISIBLE|WS_CHILD|WS_BORDER|TBSTYLE_TOOLTIPS,
        -1,
        5,
        hInst,
        500,
        tbToolbar,
        5,
        0,
0, 16, 16,
        sizeof(TBBUTTON)
    );

실제로 등록한 값을 가지고 버튼을 생성하는 과정을 보여주고 있습니다. 버튼 개수를 지정하는
곳을 보면 버튼과 버튼을 띄어주는 것까지 합쳐서 5로 지정했음을 알 수 있을겁니다.

}

이 과정으로 인해서 툴바를 만드는 과정은 끝난 것입니다. 그러면 이번에는 툴팁처리 하는
부분을 보죠.

case WM_NOTIFY :

    ttText =
(LPTOOLTIPTEXT)lParam;
    if(ttText->hdr.code ==
TTN_NEEDTEXT)
    {
        switch(ttText->hdr.idFrom)
        {
            case
100 :

            ttText->lpszText =
“Open”;
            break;

            case 200 :

            ttText->lpszText =
“Close”;
            break;

            case 300 :

            ttText->lpszText =
“Save”;
            break;

            case 400 :

            ttText->lpszText = “Save
As”;
            break;
        }
    }
    return FALSE;

아이고 쉬워라~~~~~~~~~~~~~ 이미 다 설명이 된 거군요. 그쵸? 할게 없네요.
아차~ 한가지 빠진 부분이 있군요.

바로 툴바를 보이게 하고 보이지 않게 하고 하는 부분인데 보통 윈도우즈용 프로그램을 보면
도구모음을 보이게 할 수도 있고 보이지 않게도 할 수 있는 메뉴 아이템이 있을 겁니다. 그것을 구현한 부분을 봅시다.

ShowWindow(hToolbar, SW_HIDE);

위 함수는 많이 본거죠? 첫번째 파라미터는 툴바의 핸들을 의미하고 두번째 파라미터는 툴바를
감추겠다는 의미입니다.

ShowWindow(hToolbar, SW_RESTORE);

반대 역할을 하는거군요. 다시 보이게 하는 역할을 합니다.

2부 끝~~~~~~~~~~~~~~~~~~~~

오늘은 여기까지
끝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

  윈도우 32비트 프로그래밍 18

안녕하세요………..돌팔이 황동준입니다……………..

오늘 배울 것은 한가지입니다. 바로 상태바를 만드는 예제인데 툴바를 구현할 때와 크게 다른
점은 없을 겁니다. 역시 이 상태바를 구현할 때도 아래의 헤더 파일과 함수를 사용해야 합니다.

#include
<commctrl.h>
InitCommonControls();

툴바를 구현할 때도 사용했죠?
상태바를 보면 여러개로 나뉘어 있죠? 예를 들어 3개로
나뉘어 있다고 합시다. 프로그램에서 어떻게 나눌 수 있을까요? 이 나눌 좌표를 지정하는 부분이 있습니다. 우리는 이 좌표를 지정하기 위해서 현
작업영역의 크기를 알아 둘 필요가 있는 것입니다. 왜 그런지는 아래 부분에 가면 이해가 갈 겁니다.

현 작업영역의 크기를 알기 위해서 GetClientRect() 함수를 사용했던거 기억납니까?
자 그러면 어떤 함수를 이용해서 상태바를 만들 수 있는지 봅시다. 상태바는 우리가 윈도우를 생성할 때 사용했던 CreateWindow() 함수를
이용해서 만듭니다. 상태바를 만들기 위한 새로운 함수가 있는 것은 아닙니다. 단지 클래스 이름을 등록하는 부분에
STATUSCLASSNAME이라는 정의된 예약어를 지정하면 됩니다.

hStatebar =
CreateWindow(
    STATUSCLASSNAME,
    “”,
    WS_CHILD|WS_VISIBLE,
    0,
0, 0, 0,
    hWnd,
    NULL,
    hInst,
    NULL
);

위와 같은 형식으로 사용하면 되죠. 상태바를 생성하는 함수를 이용해서 생성했으니 이 상태바의
모양을 결정해야 될겁니다. 먼저 파트를 나누어야 겠죠? 파트를 나눌 때에는 상태바에 특수한 메시지를 보냄으로써 할 수 있는데 상태바에 특수한
메시지를 어떻게 보낼 수 있을까요? SendMessage()라는 함수의 첫번째 파라미터에 상태바 핸들을 지정하면 됩니다. 그런데 우리가 이
SendMessage() 함수를 위에서 배운 적이 있나요? 이거 잘 기억이 안나네……… 그럼 알아 보도록 하죠.

유저가 어떤 작업을 하게 되면 항상 메시지가 발생하게 됩니다. 그런데 우리는 이 메시지를
함수를 이용해서 강제적으로 보낼 수 있습니다. 예를 들어 볼까요?

예를들어서 유저가 a라는 키를 누르게 되면 어떤 메시지가 보내집니까? 주 메시지인 mesg에
WM_CHAR가 들어 갈테고 부메시지인 wParam에 ‘a’라는 문자가 기억되어 있을 겁니다. 그렇죠? 그런데 이 메시지를 강제로 보내는 방법이
있습니다. 다시 말해서 ‘a’를 누르지 않아도 똑같은 메시지가 발생될 수 있도록 할 수 있다는 것입니다.

char cChar = ‘a’;
SendMessage(hWnd, WM_CHAR,
(WPARAM)cChar, (LPARAM)0);

위와 같은 구문으로 보낼 수 있습니다. 그런데 위 구문이 맞는지 모르겠네????? 하여간
구문이 맞는지 여부를 따지지 말고 이해만 하면 됩니다. 첫번째 파라미터는 메시지를 받을 윈도우의 핸들이고 두번째 파라미터는 주 메시지, 세
번째, 네번째 파라미터는 부메시지를 지정하면 됩니다. 윈도우즈용 프로그램을 보면 메뉴 아이템중 프로그램 종료하는 것이 있을 겁니다.

SendMessage(hWnd, WM_DESTROY, (WPARAM)0,
(LPARAM)0);

메뉴 아이템이 선택되었을 때 위 구문을 사용하면 종료가 됩니다. 그렇겠죠?

SendMessage() 함수의 쓰임을 알았으니 계속해서 상태바에 대한 부분을 얘기해 보도록
하겠습니다. 위에서도 언급했지만 메시지를 상태바에 보냄으로써 파트를 나룰 수 있는데 이때에 SB_SETPARTS라는 메시지를 보냅니다.
부메시지인 wParam에 파트수와 lParam에 파트를 나눌 좌표를 가지고 있는 배열의 첫주소를 지정하면 됩니다.

예를 하나 들어보죠.

SendMessage(hStatebar, SB_SETPARTS, (WPARAM)3,
(LPARAM)statePart);

위의 구문은 상태바를 세 등분으로 나누고 있는 것을 보여 주고 있는 것입니다. 아마
statePart[0]에 첫번째 파트의 오른쪽 좌표, statePart[1]에 두번째 파트의 오른쪽 좌표, statePart[2]에 마지막
좌표값이 들어 있을 겁니다. 파트를 나누었으니 각 파트에 문자열을 지정하는 방법만 알면 되겠네요.

SendMessage(hStatebar, SB_SETTEXT, (WPARAM)0,
(LPARAM)”First State Window”);

위와 같이 역시 SendMessage() 함수를 이용해서 지정하면 되는데 세번째 파라미터에
파트의 인덱스를 지정하면 됩니다. 차례로 0부터 증가되는 형식이 되죠. 만약에 상태바의 어떤 파트에 어떤 문자열이 있는지 알고 싶으면
SB_GETTEXT라는 메시지를 보내면 알 수 있습니다.

char szBuff[100];
SendMessage(hStatebar,
SB_GETTEXT, (WPARAM)0, (LPARAM)szBuff);

첫벗째 파트에 현재 어떤 문자열이 지정되어 있는지 알 수 있게 해주는 구문입니다. 자 그러면
간단하게 구현한 프로그램을 보도록 합시다. 이 예제는 상태바의 파트를 세개로 나누어서 마지막 파트에 시간이 출력되도록 한 것입니다. 윈도우즈용
프로그램을 보면 이런 형식을 띄고 있는 것이 많을 겁니다. 그러면 소스를 보도록 합시다.

먼저 리소스 파일의 소스입니다.

#include <windows.h>

MyMenu MENU
BEGIN
    POPUP
“&Statebar”
    BEGIN
        MENUITEM “&Hide”,
100
        MENUITEM “&Show”, 200
    END
END

프로그램 소스입니다.

#include <windows.h>
#include
<stdio.h>
#include <time.h>
#include <commctrl.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
void CreateSTATEBAR(HWND, HINSTANCE);
char* TimeCal(void);

HWND hStatebar;

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{

        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;
        char szAppName[] = “This program
is to create statebar”;

        WndClass.style =
NULL;
        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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
“MyMenu”;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass)) return NULL;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        InitCommonControls();
        SetTimer(hWnd,
300, 1000, NULL);
        CreateSTATEBAR(hWnd,
hInstance);
        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return
msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT mesg,
WPARAM wParam, LPARAM lParam)
{
    switch(mesg)
    {
        case
WM_COMMAND :

            switch(LOWORD(wParam))
            {
                case
100 :

                    ShowWindow(hStatebar,
SW_HIDE);
                    break;

                case 200 :

                    ShowWindow(hStatebar,
SW_RESTORE);
                    break;
            }
            return
FALSE;

        case WM_TIMER :

            if(LOWORD(wParam) ==
300)
                SendMessage(hStatebar, SB_SETTEXT,
(WPARAM)2,
                    (LPARAM)TimeCal());
            return
FALSE;

        case WM_DESTROY :

            KillTimer(hWnd,
300);
            PostQuitMessage(0);
            return
FALSE;
    }
    return DefWindowProc(hWnd, mesg, wParam,
lParam);
}

char* TimeCal(void)
{
    static int
i;
    static struct tm *newtime;
    static time_t t;
    static char
cTime[50];
    static char *Total;
    t = time(NULL);
    newtime =
localtime(&t);
    strcpy(cTime,
asctime(newtime));
    cTime[strlen(cTime)-1] = ”;
    Total =
strtok(cTime, ” “);
    for(i=0; i<3; i++)
        Total = strtok(NULL,
” “);
    return Total;
}

void CreateSTATEBAR(HWND hWnd, HINSTANCE
hInst)
{
    RECT rect;
    int statePart[3], n;

    GetClientRect(hWnd, &rect);

    for(n=1; n<=3;
n++)
        statePart[n-1] = rect.right/3*n;

    hStatebar =
CreateWindow(
        STATUSCLASSNAME,
        “”,
        WS_CHILD|WS_VISIBLE,
        0,
0, 0,
0,
        hWnd,
        NULL,
        hInst,
        NULL
    );

    SendMessage(hStatebar, SB_SETPARTS, (WPARAM)3,
(LPARAM)statePart);
    SendMessage(hStatebar, SB_SETTEXT,
(WPARAM)0,
        (LPARAM)”First State
Window”);
    SendMessage(hStatebar, SB_SETTEXT,
(WPARAM)1,
        (LPARAM)”Second State
Window”);
    SendMessage(hStatebar, SB_SETTEXT, (WPARAM)2,
(LPARAM)TimeCal());
}

자 그러면 구체적으로 분석해 봅시다.

InitCommonControls();
SetTimer(hWnd, 300, 1000,
NULL);
CreateSTATEBAR(hWnd, hInstance);

시간을 매초마다 갱신하기 위해서 타이머를 걸어 두었군요. 그리고 필자가 만든 함수가
수행되는데 어떤 일을 하는지 봅시다.

void CreateSTATEBAR(HWND hWnd, HINSTANCE
hInst)
{
    RECT rect;
    int statePart[3], n;

    GetClientRect(hWnd, &rect);

    for(n=1; n<=3;
n++)
        statePart[n-1] = rect.right/3*n;

현재 작업영역의 크기를 이용해서 똑같이 세 부분으로 확인하는 과정입니다.

    hStatebar =
CreateWindow(
        STATUSCLASSNAME,
        “”,
        WS_CHILD|WS_VISIBLE,
        0,
0, 0,
0,
        hWnd,
        NULL,
        hInst,
        NULL
    );

클래스 이름만 다르게 하고 상태바를 생성하는군요. 이미 앞에서 설명을 드린
부분입니다.

    SendMessage(hStatebar, SB_SETPARTS, (WPARAM)3,
(LPARAM)statePart);

지정해 준 좌표를 이용해서 세파트로 나누고 있는 부분입니다.

    SendMessage(hStatebar, SB_SETTEXT,
(WPARAM)0,
        (LPARAM)”First State
Window”);
    SendMessage(hStatebar, SB_SETTEXT,
(WPARAM)1,
        (LPARAM)”Second State
Window”);
    SendMessage(hStatebar, SB_SETTEXT,
(WPARAM)2,
        (LPARAM)TimeCal());
}

각 파트에 문자열을 지정하고 있습니다. TimeCal()이라는 함수는 이미 우리가 타이머를
배울 때 사용했던 함수죠. 잘 기억이 나지 않는다면 앞 부분을 다시 보시기 바랍니다.

case WM_TIMER :

    if(LOWORD(wParam) ==
300)
        SendMessage(hStatebar, SB_SETTEXT, (WPARAM)2,
(LPARAM)TimeCal());
    return FALSE;

매 초가 되면 새로운 시간을 갱신해서 세번째 파트에 시간을 지정해 주고 있습니다.

case WM_COMMAND :

    switch(LOWORD(wParam))
    {
        case
100 :

            ShowWindow(hStatebar,
SW_HIDE);
            break;

        case 200 :

            ShowWindow(hStatebar,
SW_RESTORE);
            break;
    }
    return FALSE;

툴바를 보이고 하고 보이지 않게 했던 구문과 똑같죠? 역시 상태바를 보이게 하고 보이지 않게
하는 역할을 하고 있는 것입니다.

그런데 중요한 설명을 빠뜨리고 설명을 드린 것 같군요. 툴바 때도 그렇고 상태바를 구현할
때에도 마찬가지로 컴파일할 때 comctl32.lib파일을 같이 묶어준 상태에서 컴파일을 해주어야 합니다. 다시 말해서 commctrl.h 헤더
파일을 이용해서 프로그램을 작성하고 나서는 꼭 comctl32.lib 파일을 묶어서 컴파일해야 한다는 얘기입니다. 만약에 그렇지 않으면 에러가
발생할 겁니다. 이 파일은 비주얼C가 인스톨되어 있는 디렉토리내의 lib 디렉토리내에 있습니다.

자 오늘은 여기까지
끝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

  윈도우 32비트 프로그래밍 19

안녕하세요…………돌팔이 황동준입니다……

오늘부터는 그래픽 부분에 대한 강좌를 시작하겠습니다. 오늘은 점, 선, 박스를 윈도우의
작업영역에 어떻게 그릴 수 있는지 부터 알아 보겠습니다. 예제는 총 세개입니다.

먼저 점을 찍는 방법부터 알아 봅시다. 아주 간단합니다. 점 찍는 함수가 있거든요. 그 함수
하나만 알면 됩니다.

COLORREF SetPixel(HDC hDC, int nX, int nY, COLORREF
crColor);

디바이스 컨텍스트 핸들, 찍힐 좌표, 그리고 점의 색깔을 파라미터로 요구한다는 것을 쉽게
짐작할 수 있겠네요. 자 그러면 예제를 보도록 합시다. 아래 예제는 마우스가 클릭된 좌표에 검정색 점을 찍는 예제입니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS 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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC
hDC;
        static int nX, nY;

        switch(message)
        {
                case
WM_LBUTTONDOWN :

                    nX =
LOWORD(lParam);
                    nY =
HIWORD(lParam);
                    hDC =
GetDC(hWnd);
                    SetPixel(hDC, nX, nY, RGB(0, 0,
0));
                    ReleaseDC(hWnd, hDC);
                    return
0;

                case WM_DESTROY :

                    PostQuitMessage(0);
                    return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

마우스가 클릭되었을때 처리하는 부분을 보도록 합시다.

case WM_LBUTTONDOWN :

    nX = LOWORD(lParam);
    nY =
HIWORD(lParam);
    hDC = GetDC(hWnd);
    SetPixel(hDC, nX, nY, RGB(0, 0,
0));
    ReleaseDC(hWnd, hDC);
    return 0;

뭐 설명드릴 껀덕지가 없군요. 이번에는 선을 긋는 방법을 알아 봅시다. 여러분들이 어떤
종이에 선을 긋는다면 어떤 것들이 필요하겠습니까? 먼저 펜이 필요하겠죠? 윈도우의 작업영역에 선을 그릴 때도 펜이 필요합니다.

HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF
crColor);

위 함수를 이용해서 펜을 생성할 수 있습니다. 리턴된 핸들을 SelectObject()라는
함수를 이용해서 디바이스 컨텍스트 핸들로 선택해주면 그 펜을 사용하게 되는거죠.

선택된 펜을 이용해서 작업을 다 했다면 펜 핸들을 반환해주어야 합니다. 디바이스 컨텍스트
핸들을 이용해서 작업을 한 후 반환하는 것과 같은 이치죠.

BOOL DeleteObject(HGDIOBJ hgdiObj);

위 함수를 이용해서 반환해주면 됩니다. CreatePen() 함수의 파라미터에 대해
알아봅시다. 첫번째 파라미터는 펜의 속성을 지정하는 것입니다. 물론 지정된 예약어를 지정해줌으로써 펜을 선택할 수 있는 것입니다. 그 예약어와
의미는 다음과 같습니다.

PS_DASH             점선을 의미합니다.
PS_DASHDOT
         점선과 점의 혼합된 선을 의미합니다.
PS_DASHDOTDOT       점선과 점 두개 단위로 혼합된 선을
의미합니다.
PS_SOLID            실선을 의미합니다.

직접 위 예약어들을 지정하고 테스트 해보면 어떤 것을 의미하는지 알게 될 겁니다. 두번째
파라미터는 선의 두께를 의미하고 세번째 파라미터는 선의 색깔을 의미합니다.

펜을 선택하는 방법을 알았으니 이제 그 다음에 어떤 작업이 필요한지 알아 봅시다.

그려질 선의 시작 좌표가 필요하겠네요. 시작 좌표는 현재 좌표를 이동하는 함수를 이용해서
지정하면 됩니다. 실제로 선을 긋는 함수의 파라미터는 목적 좌표만을 지정하도록 되어 있거든요. 현재 좌표를 이동하고자 하려면 아래 함수를
사용하면 됩니다.

BOOL MoveToEx(HDC hDC, int nX, int nY, LPPOINT
lpPt);

첫번째, 두번째, 세번째 파라미터가 어떤 역할을 하는지 짐작이 가죠? 좌표를 지정하는
것입니다. 그런데 마직막 파라미터는 어떤 역할을 할까요? 아마 앞부분에서 이 POINT 구조체에 대해 설명을 드린 적이 있었을 겁니다. 이것은
새로운 좌표로 이동했을 때 이동되기 전의 좌표를 저장하는 역할을 하는 것입니다.

자 그러면 실제로 선을 긋는 함수에 대해 알아 봅시다.

BOOL LineTo(HDC hDC, int nX, int nY);

파라미터로 그려질 선의 목적 좌표를 지정해 주면 됩니다.

자 그러면 이 함수를 이용해서 만든 예제를 보도록 합시다. 아래 예제는 마우스로 클릭한
상태에서 드래그했을 때 점선으로 선이 보여지다가 마우스의 왼쪽 버튼을 놓았을 때 실선으로 선을 긋는 예제입니다. 여러분들이 사용하고 있는 그래픽
툴들이 대부분 이런식으로 선을 그을 수 있을 겁니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS 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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC
hDC;
        PAINTSTRUCT ps;
        HPEN hPen;
        static BOOL
bMouse;
        static int nStartX, nStartY, nEndX, nEndY;
        static
POINT pt;

        switch(message)
        {
                case
WM_LBUTTONDOWN :

                    bMouse =
TRUE;
                    nStartX =
LOWORD(lParam);
                    nStartY =
HIWORD(lParam);
                    return 0;

                case WM_LBUTTONUP :

                    bMouse =
FALSE;
                    nEndX =
LOWORD(lParam);
                    nEndY =
HIWORD(lParam);
                    InvalidateRect(hWnd, NULL,
TRUE);
                    return 0;

                case WM_MOUSEMOVE :

                    if(bMouse)
                    {
                        nEndX
= LOWORD(lParam);
                        nEndY =
HIWORD(lParam);
                        InvalidateRect(hWnd, NULL,
TRUE);
                    }
                    return 0;

                case WM_PAINT :

                    if(bMouse)
                        hPen
= CreatePen(PS_DASHDOT, 0, RGB(0, 0,
0));
                    else
                        hPen =
CreatePen(PS_SOLID, 0, RGB(0, 0, 0));
                    hDC =
BeginPaint(hWnd, &ps);
                    SelectObject(hDC,
hPen);
                    MoveToEx(hDC, nStartX, nStartY,
&pt);
                    LineTo(hDC, nEndX,
nEndY);
                    DeleteObject(hPen);
                    EndPaint(hWnd,
&ps);
                    return 0;

                case WM_DESTROY :

                    PostQuitMessage(0);
                    return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

자 그러면 위 소스를 보도록 합시다.

case WM_LBUTTONDOWN :

    bMouse = TRUE;
    nStartX =
LOWORD(lParam);
    nStartY = HIWORD(lParam);
    return 0;

먼저 마우스의 왼쪽 버튼이 눌리면 버튼이 눌렸다는 신호를 TRUE로 세팅하고 그
좌표를 기억하고 있군요.

case WM_LBUTTONUP :

    bMouse = FALSE;
    nEndX =
LOWORD(lParam);
    nEndY = HIWORD(lParam);
    InvalidateRect(hWnd, NULL,
TRUE);
    return 0;

마우스의 왼쪽 버튼이 놓여지면 왼쪽 버튼이 눌린 상태가 아니라는 것을 알리기 위해서
FALSE로 세팅하고 마지막 좌표 값을 기억시키는 구문입니다. 그리고 마지막에 InvalidateRect()라는 함수를 사용하였는데 이 함수는
윈도우를 다시 그린다는 것을 의미하는 것입니다. 윈도우가 그려질 때 WM_PAINT라는 메시지가 발생되니 위 함수 때문에 WM_PAINT 메시지
처리 부분이 실행되겠죠?

case WM_PAINT :

    if(bMouse)
        hPen =
CreatePen(PS_DASHDOT, 0, RGB(0, 0, 0));
    else
        hPen =
CreatePen(PS_SOLID, 0, RGB(0, 0, 0));
    hDC = BeginPaint(hWnd,
&ps);
    SelectObject(hDC, hPen);
    MoveToEx(hDC, nStartX, nStartY,
&pt);
    LineTo(hDC, nEndX,
nEndY);
    DeleteObject(hPen);
    EndPaint(hWnd, &ps);
    return
0;

자 WM_PAINT 메시지 처리 부분입니다. 한번 보세요. bMouse값에 따라 펜을 서로
다르게 선택하고 있습니다. 위 WM_LBUTTONUP 메시지에 의하면 bMouse값을 FALSE로 하니 일반 실선의 펜을 선택하고 선을 그린다는
것을 알 수 있을 겁니다. 선을 긋는 함수에 대한 것은 이미 설명 드렸을 겁니다.

case WM_MOUSEMOVE :

    if(bMouse)
    {
        nEndX =
LOWORD(lParam);
        nEndY =
HIWORD(lParam);
        InvalidateRect(hWnd, NULL,
TRUE);
    }
    return 0;

위 메시지는 마우스가 움직일 때 발생되는 메시지라고 설명 드렸죠? 왼쪽 버튼이 눌린 상태에서
드래그 되는지 체크하기 위해서 위 조건문을 사용한 것입니다. 역시 InvalidateRect() 함수에 의해서 WM_PAINT 구문이하가
실행되겠죠? 이번에는 bMouse 값이 TRUE이므로 점선과 점으로 이루어진 선을 그을 겁니다. 그렇게 어렵지 않죠?

이번에는 박스를 어떻게 그리는지 알아 보겠습니다. 박스도 마찬가지입니다. 박스 그리는 함수
하나만 알면 간단하게 해결되죠.

BOOL Rectangle(HDC hDC, int nLeftTop, int nTop, int
nRightBottom,
    int nBottom);

차례로 사각 모서리의 왼쪽 위 좌표, 오른쪽 아래 좌표를 의미하는 것입니다. 사각의 양
끝좌표를 지정해 주어서 사각형을 그리는 함수입니다. 그러면 예제를 보도록 하죠. 위 선 그리는 예제와 같은 기능을 하는 예제인데 단지 박스를
그린다는 것만 다릅니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS 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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC
hDC;
        PAINTSTRUCT ps;
        HPEN hPen;
        static BOOL
bMouse;
        static int nStartX, nStartY, nEndX, nEndY;
        static
POINT pt;

        switch(message)
        {
                case
WM_LBUTTONDOWN :

                    bMouse =
TRUE;
                    nStartX =
LOWORD(lParam);
                    nStartY =
HIWORD(lParam);
                    return 0;

                case WM_LBUTTONUP :

                    bMouse =
FALSE;
                    nEndX =
LOWORD(lParam);
                    nEndY =
HIWORD(lParam);
                    InvalidateRect(hWnd, NULL,
TRUE);
                    return 0;

                case WM_MOUSEMOVE :

                    if(bMouse)
                    {
                        nEndX
= LOWORD(lParam);
                        nEndY =
HIWORD(lParam);
                        InvalidateRect(hWnd, NULL,
TRUE);
                    }
                    return 0;

                case WM_PAINT :

                    if(bMouse)
                        hPen
= CreatePen(PS_DASHDOT, 0, RGB(0, 0,
0));
                    else
                        hPen =
CreatePen(PS_SOLID, 0, RGB(0, 0, 0));
                    hDC =
BeginPaint(hWnd, &ps);
                    SelectObject(hDC,
hPen);
                    Rectangle(hDC, nStartX, nStartY, nEndX,
nEndY);
                    DeleteObject(hPen);
                    EndPaint(hWnd,
&ps);
                    return 0;

                case WM_DESTROY :

                    PostQuitMessage(0);
                    return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

소스 코드가 똑같죠? 함수 하나만 다르군요.

오늘은 여기까지
끝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

  윈도우 32비트 프로그래밍 20

안녕하세요…………..돌팔이 황동준입니다………….

오늘도 역시 그래픽에 관련된 예제인데 크게 세가지를 알아 보겠습니다. 원, 다각형, 그리고
우리가 이제껏 그린 그림의 내부를 원하는 색으로 채워 넣는 예제를 보도록 하겠습니다. 원, 다각형까지 알아 보면 그래픽에 관련된 모든 부분을
설명했을 거라고 생각할텐데 사실은 그렇지 않습니다. 현이나 호, 부채꼴같은 것을 그리는 함수도 있는데 글로서 설명을 드리기가 참 애매하거든요.
이 부분은 여러분들이 관련 서적을 참고해서 스스로 익히시기 바랍니다.

먼저 첫번째 프로그램으로 원을 그리는 예제를 보도록 하겠습니다. 이 원 그리는 예제도
간단합니다. 원 그리는 함수 하나만 알면 되죠.

BOOL Ellipse(HDC hDC, int nLeftTop, int nTop, int
nRightBottom, int nBottom);

파라미터는 앞에서 먼저 알아본 사각형을 그리는 함수와 같습니다. 즉 위 함수를 사용하면 사각
좌표 안에 들어가는 원이 그려지는 것입니다. 아래 예제는 앞에서 알아본 예제와 다른 점은 없습니다. 단지 함수만 원을 그리는 거죠.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS 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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC
hDC;
        PAINTSTRUCT ps;
        HPEN hPen;
        static BOOL
bMouse;
        static int nStartX, nStartY, nEndX, nEndY;
        static
POINT pt;

        switch(message)
        {
            case
WM_LBUTTONDOWN :

                bMouse =
TRUE;
                nStartX = LOWORD(lParam);
                nStartY =
HIWORD(lParam);
                return 0;

            case WM_LBUTTONUP :

                bMouse =
FALSE;
                nEndX = LOWORD(lParam);
                nEndY =
HIWORD(lParam);
                InvalidateRect(hWnd, NULL,
TRUE);
                return 0;

            case WM_MOUSEMOVE :

                if(bMouse)
                {
                    nEndX
= LOWORD(lParam);
                    nEndY =
HIWORD(lParam);
                    InvalidateRect(hWnd, NULL,
TRUE);
                }
                return 0;

            case WM_PAINT :

                if(bMouse)
                    hPen
= CreatePen(PS_DASHDOT, 0, RGB(0, 0,
0));
                else
                    hPen = CreatePen(PS_SOLID,
0, RGB(0, 0, 0));
                hDC = BeginPaint(hWnd,
&ps);
                SelectObject(hDC,
hPen);
                Ellipse(hDC, nStartX, nStartY, nEndX,
nEndY);
                DeleteObject(hPen);
                EndPaint(hWnd,
&ps);
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

설명 드릴부분이 없군요. 그렇죠? 자 이번에는 어떤 특정한 모양의 그림을 그리는 것이 아니라
불규칙한 다각형을 어떻게 그릴 수 있는지 알아 봅시다.

다각형은 꼭지점으로 이루어 졌죠? 이 꼭지점을 지정한 후에 이 꼭지점을 연결해 주는 함수를
이용하게 되면 어떤 다각형이라도 그리는 것이 가능합니다. 물론 지정해준 꼭지점을 이용해서 다각형을 그리는 함수가 있습니다.

BOOL Polygon(HDC hDC, CONST POINT *lpPoints, int
nCount);

두번째 파라미터는 꼭지점 정보가 들어 있는 POINT 구조체의 주소입니다. 세번째 파라미터는
꼭지점의 개수를 의미하는 거죠. 자 그러면 실제로 예제를 봅시다. 아래 예제를 보면 금방 이해가 갈겁니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS 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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC hDC;
        HPEN
hPen;
        static POINT pt[5];
        static int nCount, nX, nY;

        switch(message)
        {
            case
WM_LBUTTONDOWN :

                nX =
LOWORD(lParam);
                nY =
HIWORD(lParam);
                pt[nCount].x =
nX;
                pt[nCount++].y = nY;
                if(nCount ==
5)
                {
                    hDC =
GetDC(hWnd);
                    hPen = CreatePen(PS_SOLID, 0, RGB(0, 0,
0));
                    SelectObject(hDC,
hPen);
                    Polygon(hDC, pt,
5);
                    DeleteDC(hPen);
                    ReleaseDC(hWnd,
hDC);
                    nCount =
0;
                }
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

위 예제가 어떤 역할을 하는 건지 짐작이 갑니까? 바로 윈도우의 작업영역에 마우스의 왼쪽
버튼을 다섯번 클릭하게 되면 클릭된 위치를 꼭지점으로 하는 다각형을 그리는 예제입니다.

case WM_LBUTTONDOWN :

    nX = LOWORD(lParam);
    nY =
HIWORD(lParam);
    pt[nCount].x = nX;
    pt[nCount++].y = nY;

마우스의 왼쪽 버튼을 누르면 그 좌표값을 저장하는 구문입니다.

    if(nCount == 5)
    {
        hDC =
GetDC(hWnd);
        hPen = CreatePen(PS_SOLID, 0, RGB(0, 0,
0));
        SelectObject(hDC, hPen);
        Polygon(hDC, pt,
5);
        DeleteDC(hPen);
        ReleaseDC(hWnd,
hDC);
        nCount = 0;
    }
    return 0;

다섯번 클릭되었을 때 다각형을 그리는 구문이군요. 그렇게 어렵지는 않을 겁니다.

자 우리는 이제껏 다각형이나 선, 원, 사각형등을 그리는 에제만 보았습니다. 이제 마지막으로
이 다각형 안을 원하는 색으로 칠하는 방법을 보도록 하겠습니다. 다각형을 그릴 때에는 펜이라는 개념이 들어 갔기 때문에 원하는 색으로 펜 핸들을
얻었는데 내부를 칠할 때에는 펜을 사용하지 않습니다. 바로 브러쉬를 사용하는데 브러쉬를 사용할 때에는 브러쉬 핸들을 얻어야 합니다. 펜을 사용할
때도 펜 핸들을 얻어서 사용했죠?

HBRUSH CreateSolidBrush(COLORREF crColor);

위 함수를 이용해서 브러쉬 핸들을 얻으면 됩니다. 파라미터는 컬러입니다. 함수 이름에서 알
수 있듯이 이 함수로 얻은 브러쉬로 다각형의 내부를 칠하게 되면 내부를 선택한 브러쉬의 색깔로 완전히 채우게 됩니다. 이 함수를 이용하지 않고
않고 아래 함수를 이용해서 브러쉬 핸들을 얻게 되면 내부를 완전히 칠하지 않고 빗금같은 형식으로 채우게 됩니다.

HBRUSH CreatHatchBrush(int fnStyle, COLORREF
crColor);

첫번째 파라미터에 어떤 값을 넣는냐에 따라서 빗금치는 형식이 다른 브러쉬를 얻을 수
있습니다. 다음은 첫번째 파라미터에 들어갈 수 있는 예약어입니다.

HS_HORIZONTAL       가로방향의 선을 의미합니다.
HS_BDIAGONAL
       빗금을 의미합니다.
HS_VERTICAL         세로방향의 선을 의미합니다.
HS_CROSS
           십자모양의 선을 의미합니다.
HS_FDIAGONAL        빗금을 의미합니다. 위의 빗금과는 방향이
반대입니다.
HS_DIAGCROSS        엑스모양의 선을 의미합니다.

위에 보면 빗금이라고 되어 있는데 말로서 설명하지니 상당히 난감하군요.. 아래에 예제가
있으니 직접 눈으로 확인하시가 바랍니다. 그러면 예제를 볼까요?

아래 예제는 바로 위에서 알아본 예제와 같습니다. 단지 다른 점은 그려진 다각형의 내부를
원하는 색으로 칠한다는 차이점 밖에는 없습니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS 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 =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC
hDC;
        HBRUSH hBrush;
        static POINT pt[5];
        static
int nCount, nX, nY, nPattern;

        switch(message)
        {
            case
WM_LBUTTONDOWN :

                nX =
LOWORD(lParam);
                nY =
HIWORD(lParam);
                pt[nCount].x =
nX;
                pt[nCount++].y = nY;
                if(nCount ==
5)
                {
                    if(nPattern ==
0)
                        hBrush = CreateSolidBrush(RGB(0, 0,
255));
                    else if(nPattern ==
1)
                        hBrush =
CreateHatchBrush(HS_HORIZONTAL,
                            RGB(0, 0,
255));
                    else if(nPattern ==
2)
                        hBrush =
CreateHatchBrush(HS_BDIAGONAL,
                            RGB(0, 0,
255));
                    else if(nPattern ==
3)
                        hBrush =
CreateHatchBrush(HS_VERTICAL,
                            RGB(0, 0,
255));
                    else i

f(nPattern == 4)
                        hBrush
= CreateHatchBrush(HS_CROSS,
                            RGB(0, 0,
255));
                    else if(nPattern ==
5)
                        hBrush =
CreateHatchBrush(HS_FDIAGONAL,
                            RGB(0, 0,
255));
                    else if(nPattern ==
6)
                        hBrush =
CreateHatchBrush(HS_DIAGCROSS,
                            RGB(0, 0,
255));
                    if(++nPattern ==
7)
                        nPattern = 0;
                    hDC =
GetDC(hWnd);
                    SelectObject(hDC,
hBrush);
                    Polygon(hDC, pt,
5);
                    DeleteDC(hBrush);
                    ReleaseDC(hWnd,
hDC);
                    nCount = 0;
                }
         

       return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

우리가 알아본 내부를 칠하는 브러쉬를 어떻게 취하는지를 골고루 보여주고 있는 것입니다.
어려운게 있습니까? 없죠? 너무 쉬어서 미치겠군요.

오늘은 여기까지 끝~~~~~~~~~~~~~~~~~~~~~

추신 >

앞으로는 굉장히 어려워 집니다. 지금까지는 사실은 장난이죠….. 자! 진하게 준비하길
바랍니다. 윈도우즈 프로그래밍은 쉽게 접근하면 굉장히 쉽고 어렵게 접근하면 굉장히 어렵거든요… 여러분들은 굉장히 쉽게 접근하고 있는
것입니다.

zemna

Programmer/Web/Mobile/Desktop

You may also like...