그림판 기능 구현
#include<Windows.h>
#include<string>
#include<list>
//포인트를 저장하는 리스트 변수
std::list<POINT> Pointlist;
//메세지 프로시저가 이 함수를 호춣함
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//그림그리는 구조체
PAINTSTRUCT ps;
//그림 그리는 핸들러
HDC hdc;
switch (uMsg)
{
//마우스가 움직일때마다 발생하는 메세지
case WM_MOUSEMOVE:
if (wParam == MK_LBUTTON)
{
Pointlist.push_back({ LOWORD(lParam), HIWORD(lParam) });
InvalidateRect(hWnd, nullptr, false);
}
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
1. 일단 좌표값을 저장하기 위한 POINT형 리스트 Pointlist를 선언 해준다.
2. Case문을 통해 WM_MOUSEMOVE라는 메세지를 받았을때 wParam에 MK_LBUTTON 값이 들어온다면 이라는 조건을 만든다.
(MK_LBUTTON은 왼쪽 마우스 클릭을 했을때 wParam에 들어오는 값이다.)
3. Pointlist 변수에 push_back으로 lParam을 LOWORD와 HIWORD로 변환시켜서 넣어준다.
4. InvalidateRect 함수를 통해 새로 갱신 시킨다.
HIWORD 와 LOWORD
마우스를 입력했을때 lParam의 HIWORD와 LOWORD 라는게 있다.
32비트를 반으로 쪼개서 16비트씩 HIWORD에는 y좌표, LOWORD에는 x값의 좌표를 저장한다.
#define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff))
#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff))
LOWORD와 HIWORD의 매크로 정의 부분으로 가보면 비트연산을 통해 나눠놓은것을 볼 수 있다.
InvalidateRect 함수
BOOL InvalidateRect(
[in] HWND hWnd,
[in] const RECT *lpRect,
[in] BOOL bErase
);
이 함수의 원형을 한번 들여다보자.
첫번째 매개변수는 클라이언트 영역을 갱신한 윈도우의 핸들 값 (윈도우 인스턴스를 생성 할때 만들었던)
두번째 매개변수는 화면을 갱신할 영역이다. RECT 구조체 형식으로 적으면 된다.
예를들어 특정 구역만 갱신 하고 싶다면,
RECT r = {20,50,100,90};
InvalidateRect(hWnd,&r,true);
전체를 갱신 하고 싶다면
RECT r;
GetClientRect(hWnd,&r);
InvalidateRect(hWnd,&r,true);
우리는 null 을 사용했었는데, null로 지정해줘도 영역 전체를 갱신한다.
세번째 인자는 배경까지 다시 그릴지 말지 이다.
다시 처음으로 돌아와서 WM_PAINT 쪽에서 저장해놓은 리스트를 선으로 연결시켜 그려줘야한다.
case WM_PAINT:
//비긴페인트,엔드 페인트 사이에서 그림이 그려짐
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc,100, 100,str, len);
TextOut(hdc, 100, 50, Message.c_str(),Message.length());
if(Pointlist.size() > 0)
{
MoveToEx(hdc, Pointlist.begin()->x, Pointlist.begin()->y,NULL);
for (auto iter = ++Pointlist.begin(); iter != Pointlist.end(); iter++)
{
LineTo(hdc, iter->x,iter->y);
}
}
EndPaint(hWnd,&ps);
break;
}
1. 리스트의 사이즈가 0 이상이라면 (null이 아니라면)
2. MoveToEx - LineTo 로 그려준다.
3. 시작지점은 pointlist의 시작지점의 x,y좌표 값에서
4. 반복문을 돌며 그려준다. iter를 pointlist의 시작지점에서 1개씩 계속 더해주는것을 볼 수 있다.
아주 잘 그려진다.
하지만 문제가 하나 있다.
위의 코드로는 마우스 왼쪽버튼을 다시누르면 마지막 점에서 새로 이어져버린다.
이걸 해결 해보자.
//포인트를 저장하는 리스트 변수
std::list<std::list<POINT>> PointData;
std::list<POINT> Pointlist;
//레퍼런스로 넘겨줘서 값 복사가 안일어나게
void DrawLine(HDC& hdc, std::list<POINT> plist)
{
if (plist.size() == 0) return;
MoveToEx(hdc, plist.begin()->x, plist.begin()->y, NULL);
for (auto iter = ++plist.begin(); iter != plist.end(); iter++)
{
LineTo(hdc, iter->x, iter->y);
}
}
POINT형 list를 저장하는 리스트 PointData를 선언한다.
아까 MoveToEx-LineTo 가 한번 더 쓰이기때문에 중복코드를 방지하기 위해 DrawLine 함수를 만들어 주었다.
case WM_LBUTTONUP:
Message = L"마우스 버튼 업";
//마우스를 떼는 순간 여기에 저장됨
PointData.push_back(Pointlist);
Pointlist.clear();
InvalidateRect(hWnd, nullptr, true);
break;
case WM_PAINT:
//비긴페인트,엔드 페인트 사이에서 그림이 그려짐
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc,100, 100,str, len);
TextOut(hdc, 100, 50, Message.c_str(),Message.length());
if(Pointlist.size() > 0)
{
DrawLine(hdc,Pointlist);
}
for (auto iter = PointData.begin(); iter != PointData.end(); iter++)
{
DrawLine(hdc, *iter);
}
EndPaint(hWnd,&ps);
break;
1. 마우스를 떼는 순간 그림을 그렸던 Pointlist 를 PointData 에 넣어준다.
2. 바로 pointlist를 클리어 시켜준다.(다 지워준다)
3. 그리고 WM_PAINT 메세지가 들어오면 pointlist와 pointdata 그림을 전부 그려준다.
4. DrawLine의 두번째 매개변수는 list<POINT> 이기 때문에, 그냥 list<list<POINT>>인 PointData를 넘기면 안된다.
PointData에는 Pointlist가 담겨있으니 pointdata의 주소를 담고있는 iter의 값을 넘겨주면 된다.
이제 마우스를 떼도 따로 잘 그려진다.
버튼 만들기
버튼을 만들어 주기 위해서는 윈도우 창을 만드는 것 처럼 똑같이 인스턴스를 생성해야된다.
#define ID_BUTTON1 100
HINSTANCE g_Inst;
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
//윈도우 창이 생성될때 메세지
case WM_CREATE:
//프로그램 이름, 버튼 이름, 스타일 (자식형태|or연산으로 합침)
CreateWindow(L"button", L"Test", WS_CHILD | WS_VISIBLE, 5, 5, 120, 60, hWnd, (HMENU)ID_BUTTON1, g_Inst,NULL);
break;
//버튼이 눌렸을때
case WM_COMMAND:
switch (wParam)
{
case 0:
//윈도우의 타이틀바에 글자를 출력
SetWindowText(hWnd, L"버튼 클릭");;
break;
}
break;
case WM_CLOSE:
//프로그램 종료됨
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
1. 윈도우 창이 생성되면 CreateWindow로 버튼을 생성되게 했다.
넣어줘야 하는 인자값들은 윈도우 창을 생성시킬때와 같지만 어떤 버튼이 눌렸는지 식별을 해줘야 하기 때문에
define으로 ID_BUTTON1을 100으로 정의 해주고, HMENU로 형변환을 해서 hmenu값이 들어가는 부분에 넣어줬다.
2. 버튼이 눌리면 WM_COMMAND 메세지의 wParam값에 hMenu 값이 들어온다.
여기서 이제 case문으로 이 버튼은 어떤 기능을 수행할지 정해주면된다.
3. SetWindowText함수로 타이틀바에 글자를 출력하게 했다.
잘 바뀌는것을 볼 수 있다.
메뉴 만들기
보통 프로그램을 실행하면 위의 메뉴들은 기본이다.
이 메뉴들을 만드는법을 알아보자.
1. 리소스 파일을 추가 시켜준다.
2. 추가된 리소스 파일로 들어가서 리소스 추가를 통해 Menu를 새로 만들어준다.
3. 그러면 이런 창이 하나 뜨는데, 여기서 직접 입력해서 커스터마이징 할 수 있다.
코드가 아니라 직접 눈으로 보고 입력할 수 있어서 편하다.
그다음 코드를 조금 수정해야 한다.
만든 리소스 파일의 헤더를 포함시켜준다.
그리고 어제는 WNDCLASS로 윈도우 속성에 대해 설정할때 lpszMenuName을 null로 설정해주었는데,
메뉴를 만들고 나면 MAKEINTRESOURCE로 아까 만든 메뉴를 넣어서 메뉴를 만들어 줄 수 있다.
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
메뉴의 이름을 모르겠다면 아까 만든 resource.h로 들어가서 확인 가능하다.
만들어진 메뉴가 아주 잘 작동한다.
응용하는건 어렵겠지만..
기본적인 기능들은 만들 수 있을 것 같다.
이걸로 WINAPI 2번째 정리를 마친다.
'프로그래밍 공부 > C++ 프로그래밍' 카테고리의 다른 글
Direct X 6일차💻 <WINAPI> #마지막 (0) | 2022.06.21 |
---|---|
Direct X 5일차💻 <WINAPI> #3 (0) | 2022.06.19 |
WINAPI 추가정리 (0) | 2022.06.19 |
Direct X 3일차💻 <WINAPI> #1 (0) | 2022.06.19 |
Direct X 2일차💻 C++의 동적 배열 (0) | 2022.06.15 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!