관리 메뉴

Kim's Programming

WinApi - 출력(2/2) 본문

Programming/Windows API

WinApi - 출력(2/2)

Programmer. 2015. 8. 29. 12:29

DrawText


TextOut은 한 줄만 출력하므로 기능이 너무 단순합니다. 이보다 조금 더 기능이 많은 문자열 출력 함수로 DrawText라는 함수가 있습니다.


1
int DrawText( HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
cs


이 함수는 사각영역을 정의하여 영역 안에 문자열을 출력할 수 있으며 여러 가지 포맷을 설정하는 기능이 있습니다. 윈도우즈에서 사각영역을 정의할 떄는 다음과 같이 정의된 RECT 구조체를 이용합니다.

1
2
3
4
5
6
7
typedef struct_Rect
{
    LONG left;
    LONG top;
    LONG right;
    LONG bottom;
}
cs

이 구조체는 왼쪽 위의 좌표와 오른쪽 아래의 좌표를 정의함으로써 직사각형 영역을 나타냅니다. 좌상단과 우하단의 좌표만 알면 우상단, 좌하단의 좌표는 자동으로 구할 수 있으므로 두 개의 좌표만으로 사각영역을 간편하게 표현할 수 있습니다. 단 직사각형만 표현할 수 있으며 마름모나 평행사변형같은 사각형은 표현할 수 없습니다.


DrawText의 4번째 인수 lpRect는 이 구조체의 포인터이며 문자열이 출력될 사각영역을 지정합니다. 첫 번째 인수는 물론 hdc이며 두 번쨰 인수가 출력할 문자열, 세 번째 인수는 출력할 문자열 길이이되 이 값이 -1이면 널 종료 문자열로 간주합니다. TextOut과는 달리 널 종료 문자열을 인식하므로 문자열 상수를 곧바로 출력할 수 있습니다. 다섯 번쨰 인수 uFormat은 DrawText 함수가 문자열을 출력할 방법을 지정하는 플래그이며 다음과 같은 여러 가지 플래그의 조합을 지정합니다.


 값

 설명

 DT_LEFT

 수평 왼쪽 정렬합니다.

 DT_RIGHT

 수평 오른쪽 정렬합니다.

 DT_CENTER

 수평 중앙 정렬합니다.

 DT_BOTTOM

 사각영역의 바닥에 문자열을 출력합니다.

 DT_VCENTER

 사각영역의 수직중앙에 문자열을 출력합니다.

 DT_WORDBREAK

 사각영역의 오른쪽 끝에서 자동 개행되도록 합니다.

 DT_SINGLELINE

 한 줄로 출력합니다.

 DT_NOCLIP

 사각영역의 경계를 벗어나도 문자열을 자르지 않고 그대로 출력합니다.

수평, 수직정렬 플래그와 자동 개행 플래그 등이 정의되어 있습니다. 함수 테스트를 위해 DrawText라는 예제를 하나 더 만들고 WndProc을 다음과 같이 작성해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<Windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("DrawText");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = CreateSolidBrush(RGB(255,255,255));
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_QUESTION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, NULL, 00))
    {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return (int)Message.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rt = { 100100400300 };
    TCHAR *str = TEXT("문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 문자열 ");
    switch (iMessage)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        DrawText(hdc, str, -1&rt, DT_CENTER | DT_WORDBREAK);
        EndPaint(hWnd, &ps);
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

사각 영역 rt를 (100,100) (400,300)으로 정의하고 출력할 문자열 str에 긴 문자열을 대입해 두었습니다. 그리고 길이값을 -1로 두어서 문자열 끝까지 출력했으며 중앙 정렬 및 자동개행 플래그를 주었습니다. 이렇게 한 소스의 결과는 다음과 같은 결과로 출력됩니다.


지정한 사각영역 안에 문자열이 자동 개행되며 여러 줄로 출력이 되었습니다. 좌우 정렬 변경 및 한 줄로 출력 등을 할 수 있습니다. DrawText는 여러 줄을 한꺼번에 출력할 수 있어 편리할 것 같지만 예상 외로 TextOut만큼 자주 사용되지는 않습니다.


여러 가지 출력


문자열뿐 아니라 박시지 박스나 사운드 연주 또한 전부 출력의 일종입니다.


그래픽 출력


지금 까지 문자열 출력하는 방법과 크게 다르지 않습니다. 다음과 같은 그래픽 출력 함수들을 사용합니다.


1
2
3
4
5
COLORREF SetPixel(hdc, nXPos, nYPos, clrref)
DWORD MoveToEx(hdc,x,y,lpPoint)
BOOL LineTo(hdc,xEnd,yEnd)
BOOL Rectangle(hdc, nLeftRect, nTopRect, nRightRect, nBottomRect)
BOOL Ellipse(hdc, nLeftRect, nTopRect, nRightRect, nBottomRect)
cs

모든 GDI 함수의 첫 번째 인수는 항상 DC핸들인 hdc이므로 TextOut함수와 마찬가지로 DC핸들로부터 얻어야 그래픽을 출력할 수 있습니다. DC의 개념만 알고 있으면 함수의 이름 자체가 아주 설명적이므로 직관적으로 이해할 수 있을 것입니다. 우선 SetPixel 함수는 화면에 점을 출력하는데 (nXPos, nYPos)좌표에 clrref 색상으로 점을 출력합니다. 다른 출력 함수들과는 달리 DC의 색상 정보를 쓰지 않고 인수로 색상을 전달받는다는 점이 독특한데 점이라는 것은 색상 하나만으로 표현되므로 매번 다르게 지정하는 경우가 많기 때문입니다. 점은 가장 기본적인 그래픽 요소이지만 점만으로 표현할 수 있는 것이 거의 없어 윈도우즈 환경에서는 잘 사용되지않습니다.


선을 그을 때는 MoveToEx함수와 LineTo함수를 같이 사용해야 합니다. GDI 텍스트 모드의 커서에 해당하는 CP를 항상 유지하는데 LineTo함수는 CP에서부터 지정한 좌표까지 선을 그으며 CP를 끝점으로 이동시킵니디다. 그래서 LineTo를 연속적으로 호출하면 계속 이어지는 선을 그을 수 있습니다. MoveToEx는 CP를 지정한 좌표(x,y)로 이동시키며 이동 전의 CP좌표를 lpPoint에 대입하는데 이전 CP값이 필요하지 않은 경우 lpPoint에 NULL을 전달하면 됩니다. 통상 이전 좌표가 필요없기 때문에 NULL을 지정합니다. A,B 에서 C,D까지 선을 긋고 싶다면 다음과 같이 두 함수를 연속적으로 호출합니다.

1
2
MoveToEx(A,B,NULL);
LineTo(hdc,C,D);
cs

MoveToEx 함수로 CP를 (A,B)좌표로 이동시킨 후 LineTo 함수를 호출하면 현재 좌표인 (A,B)에서 LineTo함수의 인수로 전달된 (C,D) 좌표로 선인 그어집니다. 선 하나를 긋기 위해 두 개의 함수 호출이 필요한데 Line(hdc, x1, y1, x2, y2)형식의 레퍼 함수를 만들어 한 번에 선을 그을 수도 있습니다. 인수만 중계하는 것 뿐이므로 이 정도 래퍼 함수는 여러분이 왼쪽으로 만들어도 1분안에 만들 수 있습니다.

1
2
3
4
5
void Line(HDC hdc, int x1 ,int y1 ,int x2 ,int y2)
{
    MoveToEx(hdc,x1,y1,NULL);
    LineTo(hdc,x2,y2)
}
cs

사각형을 그리는 Rectangle 함수와 타원을 그리는 Ellipse 함수는 둘 다 인수가 동일합니다. Rectangle함수는 지정한 두 점 (left,top)과 (Right, Bottom)을 대각선으로 하는 사각형을 그리며 사각형 내부를 채우기도 합니다. Ellipse 함수는 함수는 지정한 사각형에 내접하는 타원을 그립니다. 타원의 표표지정에 줌심점과 장단축의 반지름을 쓰지 않고 외접 사각형을 쓰는 이유는 윈도우즈 환경의 주 입력장치가 마우스이기 때문입니다. 마우스의 이동 경로를 따라 타원을 쉽게 그리기 위해 중심과 반지름 대신 외접 사각형을 지정하도록 되어 있습니다. 만약(x,y)중심에 반지름 r의 타원을 그리고 싶다면 다음과 같이 호출하면됩니다. 약간의 응용만 하면 얼마든지 원하는 모양의 타원을 그릴 수 있으며 이런 기능을 자주 사용한다면 래퍼 함수를 하나 만들어놓고 사용하면 됩니다.

1
Ellipse(hdc,x-r,y-r,x+r,y+r);
cs

이 함수들을 테스트하기 위해서는 다음과 같이 작성하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<Windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("ellipse");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = CreateSolidBrush(RGB(255,255,255));
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_QUESTION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, NULL, 00))
    {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return (int)Message.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    switch (iMessage)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        SetPixel(hdc, 1010, RGB(25500));
        MoveToEx(hdc, 5050, NULL);
        LineTo(hdc, 50040);
        Rectangle(hdc, 50100200180);
        Ellipse(hdc, 220100400200);
        EndPaint(hWnd, &ps);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

다음 소스의 결과는 다음과 같습니다.


BeginPaint 함수를 호출하여 DC 핸들을 먼저 얻은 후 그래픽 출력 함수들을 연속적으로 호출하였습니다. SetPixel함수를 이용 RGB255,0,0(빨간색)색으로 점을 찍었습니다. 또 MoveToEx와 LineTo함수로 선을 그었고 Rectangle함수와 Ellipse함수로 사각형과 타원을 각각 그려보았습니다.


메세지 출력


메세지박스는 조그만 별도의 윈도우를 열어서 사용자에게 정보를 전달하거나 질문을 하는 장치이며 MessageBox 함수 호출 한번으로 간단하게 만들 수 있습니다. 다음은 MessageBox함수원형입니다.

1
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
cs

첫 번째 인수 hWnd는 메시지 박스의 오너(Owner)윈도우입니다. 오너 윈도우란 메세지박스를 소유한 윈도우를 말하는데 메세지 박스는 화면 중앙에 나타나며 메세지 박스가 떠 있는 동안 오너 윈도우는 사용할 수 없는 상태가 됩니다. 사용자가 메세지 내용을 완전히 읽고 메세지 박스를 닫아야만 오너윈도우를 사용할 수 있습니다. 두번쨰 인수 lpText는 메세지 박스에 출력할 문자열이며 세 번쨰 인수 lpCaption은 메세지 박스의 타이틀 바에 나타날 제목 문자열입니다. 네 번째 인수 uType은 메세지 박스에 어떤 종류의 버튼이 나타날 것인가를 지정하는 여러 가지 플래그들이며 다음 표와 같은 값들이 가능합니다.


 값 

 설명 

 MB_ABORTRETRYIGNORE

 메세지 박스에 Abort, Retry, Ignore 세 개의 버튼이 나타납니다.

 MB_OK

 메세지 박스에 OK 버튼 하나만 나타납니다.

 MB_OKCANCEL

 메세지 박스에 OK, Cancel 두개의 버튼이 나타납니다.

 MB_RETRYCANCEL

 메세지 박스에 Retry, Cancel 두개의 버튼이 나타납니다.

 MB_YESNO

 메세지 박스에 Yes, No 두 개의 버튼이 나타납니다.

 MB_YESNOCANCEL

 메세지 박스에 Yes, No, Cancel 세 개의 버튼이 나타납니다.

메세지 박스의 모양은 몇개를 꼽아서 이렇게 생겼습니다.


버튼의 종류를 지정하는 이런 값 외에 아이콘을 출력하는 다음과 같은 플래그도 사용할 수 있습니다. 버튼종류 플래그와 아이콘 플래그를 OR연산자로 연결하여 uType 인수에 지정합니다.


 값 

 아이콘

 MB_ICONEXCLAMATION, MB_ICONWARNING

 

 MB_ICONINFORMATION, MB_ICONASTERISK

 

 MB_ICONQUESTION

 

 MB_ICONSTOP, MB_ICONERROR, MB_ICONHAND

 

(위 아이콘은 운영체제 마다 다를 수 있으며 위의 아이콘은 윈도우10 기준입니다.)


예를들어서 OK,CANCEL(한글 윈도우에서는 확인, 취소) 두 개의 버튼을 나타내게 하고 에러 아이콘을 나타내고 싶다면 uType인수를 MB_OKCANCEL|MB_ERROR로 지정합니다. 메세지 박스를 테스트 하기 위해 다음 소스를 컴파일 해보았습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<Windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MB");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = CreateSolidBrush(RGB(255,255,255));
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_QUESTION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, NULL, 00))
    {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return (int)Message.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    switch (iMessage)
    {
    case WM_LBUTTONDOWN:
        MessageBox(hWnd, TEXT("마우스 왼쪽 버튼 클릭 감지."), TEXT("메세지 박스"), MB_OK | MB_ICONHAND);
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

간단한 전달 내용을 문자열로 출력을 하며 OK버튼 하나만 표시하여 메세지 박스를 닫을 수 있도록 하였습니다. 컴파일 하면 다음과 같습니다.



전달사항을 단순히 전달하는 용도 외에 사용자에게 질문을 하고 대답을 입력받는 용도로도 사용할 수 있습니다. 이 때는 메세지 박스를 호출한 후 사용자가 어떤 버튼을 눌렀는지를 살펴보면 되는데 MessageBox함수는 리턴값으로 사용자가 누른 버튼 값을 돌려줍니다.


값 

 설명

 IDABORT

 Abort 버튼을 눌렀습니다.

 IDCANCEL

 Cancel 버튼을 눌렀습니다.

 IDIGNORE

 Ignore 버튼을 눌렀습니다.

 IDNO

 No 버튼을 눌렀습니다.

 IDOK

 OK 버튼을 눌렀습니다.

 IDRETRY

 Retry 버튼을 눌렀습니다.

 IDYES

 Yes 버튼을 눌렀습니다.

만약 프로그램을 진행하냐 안하냐를 묻는 것을 만들기 위해서 다음과 같은 코드를 작성할 수 있습니다.

1
2
3
4
5
6
7
if(MessageBox(hWnd,TEXT("계속 진행하시겠습니까?"),TEXT("질문"), MB_YESNO)==IDYES)
{
    //프로그램 계속 처리
}else
{
    //중지 처리
}
cs


MessageBox 함수를 호출하여 질문을 먼저 하되 메세지 박스에는 Yes, No 두개의 버튼만을 배치합니다. 그리고 그 리턴값을 살펴보고 IDYES가 리턴되었으면 즉 사용자가 YES버튼을 눌렀으면 프로그램을 진행하고 그렇지 않으면 중지하는 것입니다. 메세지 박스는 사용 방법이 쉬우며 디버깅용으로 자주 사용합니다. 특정 시점에서 변수값을 확인해보고 싶다면 메세지 박스로 변수값을 출력해서 확인할 수 있습니다.


메세지 비프


문자열이나 그래픽, 메세지 박스처럼 눈에 보이는 출력도 있지만 비프음을 내는 들리는 종류의 출력도 있습니다. 스피커를 톨해서 간단한 비프음을 출력할 떄는 MessageBeep함수를 사용합니다.

BOOL MessageBeep( UINT uType);

한 개의 인수만 있는데 이 인수로 어떤 종류의 음을 낼 것인가를 지정합니다.



 값

 설명

 0XFFFFFFFF

 PC의 스피커를 통해 음을 냅니다.

 MB_ICONASTERISK

 ASTERISK 비프음

 MB_ICONEXCLAMATION

 EXCLAMATION 비프음

 MB_ICONHAND

 HAND 비프음

 MB_ICONQUESTION

 QUESTION 비프음

 MB_OK

 시스템 디폴트 비프음


WM_LBUTTONDOWN 메세지 처리 루틴에 MessageBeep함수 호출문을 삽입해 놓으면 마우스 왼쪽 버튼을 누를 떄마다 비프음이 들립니다. MessageBeep함수는 간단한 디버깅 목적으로 자주 사용되는데 메세지가 발생하는지, 흐름이 특정 지점을 통과하는지 소리를 이용하여 확인할 수 있어 편리합니다. 시스템이 정의하는 비프음 같은 단순한 음 말고 다른 소리를 출력하고 싶다면 PlaySound라는 멀티 미디어 함수를 사용해야 합니다. 윈도우의 타이틀 바에 문자열을 출력하는 SetWindowText함수도 값 확인을 위한 디버깅 목적이로 흔히 사용되며 콘솔 환경의 printf 역할을 합니다. 타이틀 바는 문자열을 대입해 놓기만 하면 다시 그리는 작업을 하지 않아도 되므로 값을 찍어보는 용도로 편리합니다. 예를 들어 어떤 정수 value의 값을 실행중에 확인하려면 다음과 같이 합니다.

1
2
3
TCHAR str[128];
wsprintf(str,TEXT("value=%d"),value);
SetWindowText(hWnd,str);
cs

윈도우 핸들과 출력할 문자열을 인수로 전달하면 이 문자열이 타이틀 바에 계속 남아있습니다. 물론 타이틀 바는 디버깅을 위해 존재하는 것이 아니므로 임시적인 용도로만 사용해야합니다. 타이틀 바에 출력된 문자열을 읽을 떄는 GetWindowText 함수를 사용합니다.



'Programming > Windows API' 카테고리의 다른 글

WinApi - 출력(1/2)  (0) 2015.08.27
WinApi - 분석(2/2)  (0) 2015.08.26
WinApi - 분석(1/2)  (1) 2015.08.26
WinAPI - 첫번째 시작 - 프로젝트 만들기  (0) 2015.08.25
WinAPI - 시작하기전에 알고 갑시다.  (0) 2015.08.24