프로그래밍 공부/C++ 프로그래밍

Direct X 5일차💻 <WINAPI> #3

데브준우 2022. 6. 19. 18:25

라디오버튼 만들기

#define ID_BUTTON1 100 
#define ID_RADIO1 101 
#define ID_RADIO2 102 
#define ID_RADIO3 103 
#define ID_RADIO4 104 
#define ID_RADIO5 105 
#define ID_RADIO6 106 
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);
		CreateWindow(L"button", L"Group1-1", BS_AUTORADIOBUTTON |WS_CHILD | WS_VISIBLE, 5, 70, 120, 30, hWnd, (HMENU)ID_RADIO1, g_Inst, NULL);
		CreateWindow(L"button", L"Group1-2", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 5, 100, 120, 30, hWnd, (HMENU)ID_RADIO2, g_Inst, NULL);
		CreateWindow(L"button", L"Group1-3", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 5, 130, 120, 30, hWnd, (HMENU)ID_RADIO3, g_Inst, NULL);

		CreateWindow(L"button", L"Group2-1", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 150, 70, 120, 30, hWnd, (HMENU)ID_RADIO4, g_Inst, NULL);
		CreateWindow(L"button", L"Group2-2", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 150, 100, 120, 30, hWnd, (HMENU)ID_RADIO5, g_Inst, NULL);
		CreateWindow(L"button", L"Group2-3", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 150, 130, 120, 30, hWnd, (HMENU)ID_RADIO6, g_Inst, NULL);
		break;

 

라디오 버튼 또한 CreateWindow를 통해 만들어 준다.

다만 어제 만든 버튼과는 다르게 BS_AUTORADIOBUTTON 이라는 스타일도 적용 해주어야한다.

 

 

이렇게 잘 동작 하지만 문제가 하나 있다. 

클릭했을때 1그룹, 2그룹 따로따로 선택하게 하고 싶은데 이 상태로는 그게 안된다는 것

 

	case WM_CREATE:
		//프로그램 이름, 버튼 이름, 스타일 (자식형태|or연산으로 합침)
		CreateWindow(L"button", L"Test", WS_CHILD | WS_VISIBLE, 5, 5, 120, 60, hWnd, (HMENU)ID_BUTTON1, g_Inst,NULL);
		CreateWindow(L"button", L"Group1-1", BS_AUTORADIOBUTTON |WS_CHILD | WS_VISIBLE | WS_GROUP, 5, 70, 120, 30, hWnd, (HMENU)ID_RADIO1, g_Inst, NULL);
		CreateWindow(L"button", L"Group1-2", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 5, 100, 120, 30, hWnd, (HMENU)ID_RADIO2, g_Inst, NULL);
		CreateWindow(L"button", L"Group1-3", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 5, 130, 120, 30, hWnd, (HMENU)ID_RADIO3, g_Inst, NULL);

		CreateWindow(L"button", L"Group2-1", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP, 150, 70, 120, 30, hWnd, (HMENU)ID_RADIO4, g_Inst, NULL);
		CreateWindow(L"button", L"Group2-2", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 150, 100, 120, 30, hWnd, (HMENU)ID_RADIO5, g_Inst, NULL);
		CreateWindow(L"button", L"Group2-3", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 150, 130, 120, 30, hWnd, (HMENU)ID_RADIO6, g_Inst, NULL);
		break;

답은 간단했다.

버튼 그룹의 첫번째 인스턴스를 생성할때 스타일에 WS_GROUP을 넣어주면 됐다.

 

이제 그룹별로 아주 잘된다.

 

그리고 디폴트값으로 항상 한 버튼이 눌려있는것을 원한다면

		CreateWindow(L"button", L"Group1-1", BS_AUTORADIOBUTTON |WS_CHILD | WS_VISIBLE | WS_GROUP, 5, 70, 120, 30, hWnd, (HMENU)ID_RADIO1, g_Inst, NULL);
		CreateWindow(L"button", L"Group1-2", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 5, 100, 120, 30, hWnd, (HMENU)ID_RADIO2, g_Inst, NULL);
		CreateWindow(L"button", L"Group1-3", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 5, 130, 120, 30, hWnd, (HMENU)ID_RADIO3, g_Inst, NULL);
		CheckRadioButton(hWnd, ID_RADIO1, ID_RADIO3, ID_RADIO1);

CheckRadioButton 함수로 설정을 해주면 된다.

난 첫번째 버튼과 3번째 버튼 사이에서 첫번째가 디폴트로 눌려있게 했다.

 

Edit칸과 콤보박스 만들기

CreateWindow(L"edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 5, 170, 400, 300, hWnd, (HMENU)ID_EDIT, g_Inst, nullptr);
		{
			HWND hCombo = CreateWindow(L"combobox", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWN, 5, 480, 100, 200, hWnd, (HMENU)ID_COMBO, g_Inst, nullptr);
			SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"Menu1");
		}

그냥 같은 함수를 써서 버튼과 같이 안의 인자값들만 다 넣어주면 되는데 인자값이 이것저것 너무나 많다..

콤보박스는 하위에 Menu1이라는 메뉴를 넣기위해 핸들러로 받아서 메세지를 넘겨주는 방식이다.

 

콤보 박스와 메뉴가 전부 다 뜬다.

 

팝업창 만들기

 

메뉴 리소스를 추가 했던거 처럼 리소스 추가 - 다이얼로그를 눌러준다.

 

그러면 이러한 창이 뜨는데, 오른쪽 도구상자에서 이것저것 드래그& 드랍으로 만들어 줄 수 있다.

난 버튼을 하나 가져와서 속성-캡션에서 버튼 이름을 바꿔주었다.

 

속성창을 찾을 수 없다면 창을 오른쪽 클릭 하면 띄울 수 있다.

 

 

메뉴를 눌러 팝업창 띄우기

아까만든 File메뉴의 project를 누르면 팝업창을 띄우게 해보자.

이 다이얼로그도 하나의 윈도우 창이기 때문에 메세지를 받아서 처리해야한다.

INT_PTR CALLBACK DlgHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		case WM_CLOSE:
			EndDialog(hWnd, IDCANCEL);
		case WM_COMMAND:
			switch (wParam)
			{
			case IDOK:
				EndDialog(hWnd, IDOK);
				break;
			case IDCANCEL:
				EndDialog(hWnd, IDCANCEL);
				break;
			}
			break;
		break;
	}
	return false;
}

 

이런식으로 다이얼로그의 메세지를 따로 처리하는 함수를 만들어 줘야한다.

IDOK와 IDCANCEL 은 각각 확인/취소 버튼을 눌렀을때 들어오는 wParam값인데, 다이얼로그를 종료시키도록 코드를 입력했다.

 

	case WM_COMMAND:
		switch (wParam)
		{
			case ID_BUTTON1:
				//윈도우의 타이틀바에 글자를 출력
				SetWindowText(hWnd, L"버튼 클릭");;
			case ID_LOAD_PROJECT:
				DialogBox(g_Inst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, nullptr);
				break;
			
		}
		break;

 

그리고 아까 만들어둔 case문에 ID_LOAD_PROJECT를 wParam값으로 받으면 다이얼로그 박스를 실행시켜준다.

메뉴를 만들때와 똑같이 2번째 인자로 MAKEINTRESOURCE함수에 다이얼로그의 정의된 이름을 넣어주면 된다.

 

잘 작동한다.

 

팝업창을 띄울때는 모달과 모달리스가 있는데

모달은 위의 코드로 만들면 되고, 팝업창이 생성됐을때 뒤의 부모 프로그램을 제어 할 수 없게된다.

모달 방식

 

하지만 모달리스 방식은 반대로 뒤의 부모 프로그램을 제어 할 수 있지만 생성 코드가 다르다.

 

HWND hDlg = CreateDialog(g_Inst, MAKEINTRESOURCE(IDD_DIALOG1),hWnd, DlgHandler);
ShowWindow(hDlg,SW_SHOW);

이런식으로 다이얼로그의 인스턴스를 생성하고 핸들러에 값을 담은다음 ShowWindow함수 인자로 넘겨주면 된다.

 

모달리스 방식

 

윈도우 창에 비트맵 띄우기

 

아까 다이얼로그의 리소스를 추가 시켜줬던 것 처럼 Bitmap을 클릭후 가져오기를 누른다.

 

 

그럼 아까 다이얼로그가 뜬것처럼 가져온 비트맵 이미지가 뜬다.

 

이제 코드를 작성 해주자.

	case WM_PAINT:
	{	
		PAINTSTRUCT ps;
		//비긴페인트,앤드페인트는 WM_PAINT에서만 처리 가능
		HDC hDC = BeginPaint(hWnd, &ps);

		//비트맵 리소스를 가져오고
		HBITMAP Image = LoadBitmap(g_Inst, MAKEINTRESOURCE(IDB_BITMAP1));
		BITMAP bitMap;

		//가져온 비트맵 리소스로부터 비트맵 정보를 가져옴
		GetObject(Image, sizeof(BITMAP), &bitMap);
		//현재 DC로부터 호환되는 DC를 생성을하고
		HDC imageDC = CreateCompatibleDC(hDC);
		// 소스 dc를 만드는 과정..
		SelectObject(imageDC, Image);
		//카피해서 생성한다.
		BitBlt(hDC, 0, 0, bitMap.bmWidth, bitMap.bmHeight, imageDC, 0, 0, SRCCOPY);
		DeleteDC(imageDC);

		EndPaint(hWnd, &ps);
	}

사진 하나를 띄우려면 이런 프로세스가 필요 하다는데

처음 윈도우창 만들때처럼 이해가 안간다..

나중에 만들게 되면 파봐야 겠다.

화면에 그림이 잘 출력 되는것을 볼 수 있다.

 

페인트 할때마다 리소스를 가져오고 비트맵 정보를 가져오는것을 방지 하기 위해 코드를 수정했다.

 

HINSTANCE g_Inst = NULL;
HBITMAP Image;
BITMAP bitMap;

LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_CREATE:
		//비트맵 리소스를 가져오고
		Image = LoadBitmap(g_Inst, MAKEINTRESOURCE(IDB_BITMAP1));
		//가져온 비트맵 리소스로부터 비트맵 정보를 가져옴
		GetObject(Image, sizeof(BITMAP), &bitMap);
		break;

	case WM_PAINT:
	{	
		PAINTSTRUCT ps;
		//비긴페인트,앤드페인트는 WM_PAINT에서만 처리 가능
		HDC hDC = BeginPaint(hWnd, &ps);
		//현재 DC로부터 호환되는 DC를 생성을하고
		HDC imageDC = CreateCompatibleDC(hDC);
		// 소스 dc를 만드는 과정..
		SelectObject(imageDC, Image);
		//카피해서 생성한다.
		BitBlt(hDC, 0, 0, bitMap.bmWidth, bitMap.bmHeight, imageDC, 0, 0, SRCCOPY);
		DeleteDC(imageDC);

		EndPaint(hWnd, &ps);
	}
		break;
		case WM_CLOSE:
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
	
}

HBITMAP과 BITMAP을 전역변수로 생성하고 창이 생성될때 리소스와 정보를 가져오도록 했다.

훨씬 더 깔끔해졌다.

 

움직이는 캐릭터 띄우기 #1

 

일단 위의 이미지를 가져와서 아까 비트맵을 띄웠던 과정과 똑같이 비트맵 리소스를 가져온다.

 

조금 편하게 작업하기 위해  AnimationSprite라는 클래스를 만들어 주었다.

 

#pragma once
#include <Windows.h>
class AnimationSprite
{
	RECT myDrawRC;
	HBITMAP Image;
	BITMAP Bitmap;
public:
	//생성자
	AnimationSprite(HINSTANCE hInst,int intResource);
	void Draw(const HDC& hDC);
};

 

필요한 변수들과 생성자와 함수를 만들고,

생성자를 오른쪽클릭해서 빠른 작업및 리팩토링 - 선언/정의 만들기로 들어간다.

 

#include "AnimationSprite.h"

AnimationSprite::AnimationSprite(HINSTANCE hInst,int intResource)
{
	Image = LoadBitmap(hInst, MAKEINTRESOURCE(intResource));
	//가져온 비트맵 리소스로부터 비트맵 정보를 가져옴
	GetObject(Image, sizeof(BITMAP), &Bitmap);
	myDrawRC.left = 0;
	myDrawRC.top = 0;
	myDrawRC.right = 54;
	myDrawRC.bottom = 94;
}

void AnimationSprite::Draw(const HDC& hDC)
{
	HDC imageDC = CreateCompatibleDC(hDC);
	SelectObject(imageDC, Image);
	BitBlt(hDC, 0, 0, myDrawRC.right - myDrawRC.left,myDrawRC.bottom - myDrawRC.top, imageDC, 0, 0, SRCCOPY);
	DeleteDC(imageDC);

}

정의에서는 아까 winmain 메세지 프로시저에 전달하는 함수에서 만들었던 과정들을 생성자와 함수로 처리하기 위해 위와 같이 사이즈를 계산해서 만들어 주었다.

 

#include <Windows.h>
//스마트 포인터를 위해 메모리 헤더파일 인클루드
#include <memory>
#include "resource.h"
// 아까 만들어둔 클래스 헤더파일도 인클루드
#include "AnimationSprite.h"

HINSTANCE g_Inst = NULL;
//스마트 포인터로 AnimationSprite형 변수를 선언해준다.
std::shared_ptr<AnimationSprite> Character = nullptr;

LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_CREATE:
    	//아까만든 이미지를 가리키는 Shared_Ptr 생성
        //정의해준 매개변수를 넣어준다.
		Character = std::make_shared<AnimationSprite>(g_Inst, IDB_BITMAP3);
		break;

	case WM_PAINT:
	{	
		PAINTSTRUCT ps;
		//비긴페인트,앤드페인트는 WM_PAINT에서만 처리 가능
		HDC hDC = BeginPaint(hWnd, &ps);
        
        //캐릭터의 Draw함수 호출
		Character->Draw(hDC);
        
		EndPaint(hWnd, &ps);
	}
		break;
		case WM_CLOSE:
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

메인 스크립트도 위 주석의 설명에 따라 코드를 만들었다.

 

실행을 해보면 아까 캐릭터의 맨 앞의 모습이 잘려서 잘 나온다.

 

이번엔 저 보라색 값을 빼주자.

 

#include "AnimationSprite.h"
//라이브러리 추가
#pragma comment(lib,"Msimg32.lib")

AnimationSprite::AnimationSprite(HINSTANCE hInst,int intResource)
{
	Image = LoadBitmap(hInst, MAKEINTRESOURCE(intResource));
	//가져온 비트맵 리소스로부터 비트맵 정보를 가져옴
	GetObject(Image, sizeof(BITMAP), &Bitmap);
	myDrawRC.left = 0;
	myDrawRC.top = 0;
	myDrawRC.right = 54;
	myDrawRC.bottom = 94;
}

void AnimationSprite::Draw(const HDC& hDC)
{
	HDC imageDC = CreateCompatibleDC(hDC);
	SelectObject(imageDC, Image);
	//BitBlt(hDC, 0, 0, myDrawRC.right - myDrawRC.left,myDrawRC.bottom - myDrawRC.top, imageDC, 0, 0, SRCCOPY);
    
    //RGB값을 빼주는 라이브러리 함수
	TransparentBlt(hDC, 0, 0, myDrawRC.right - myDrawRC.left, myDrawRC.bottom - myDrawRC.top, 
    imageDC, 0, 0, myDrawRC.right, myDrawRC.bottom, RGB(255, 0, 255));
    
	DeleteDC(imageDC);

}

 

라이브러리를 사용해야 한다.

라이브러리를 추가 시키고, TransparentBlt 이라는 함수를 사용해준다.

맨 마지막 보라색RGB값을 넣어주었다.

 

실행시켜보면 보라색 값이 잘 빠진것을 볼 수 있다.

 

 

마치며..

CPP에다 끝없는 WINAPI를 사용하다 보니 모르는것 투성이다.

대충 돌아가는 구조를 알겠지만 혼자 짜보라고 하면 한세월 걸릴것같다.

 

Direct X로 가는 길을 왜이리 멀고도 험한지..

Direct X로 가는여정 5일차 끝