F.R.I.D.A.Y.

Win32 프로그램 생성하기 7: 사진 암호 part2. 마우스 입력 본문

DEV/C C++

Win32 프로그램 생성하기 7: 사진 암호 part2. 마우스 입력

F.R.I.D.A.Y. 2021. 4. 3. 02:16
반응형

 마우스 입력을 받도록 프로그램을 구성해보겠습니다.


마우스 입력

 우리는 마우스 왼쪽 버튼[# Windows에서는 물리버튼의 위치를 말하지 않습니다. 오른손잡이는 왼쪽 버튼이 되겠지만, 왼손잡이들에게는 오른쪽 버튼이 되기 때문이죠. 그래서 여기에서 설명하는 버튼도 Windows에서 설명하는 것처럼 왼쪽 버튼이 주버튼, 오른쪽이 보조버튼입니다.]의 입력만 받도록 하겠습니다.

 

WM_LBUTTONDOWN

 마우스 왼쪽 버튼이 눌렸을 때 발생하는 메시지는 WM_LBUTTONDOWN입니다. 이 메시지의 WPARAM과 LPARAM은 다음과 같은 값을 가지고 있습니다.

wParam

 마우스 왼쪽 버튼이 눌린 시점에 어떤 가싱 키가 추가로 눌려있는지를 확인할 수 있는 플래그 집합이 들어 있습니다. 이 플래그 집합에서는 CTRL, SHIFT, 마우스 왼쪽, 휠, 오른쪽, 보조버튼 1, 보조버튼 2의 눌림 상태를 확인할 수 있습니다.

lParam

 32비트로 구성되어 있습니다. 이 값에서 하위 16비트는 x좌표, 상위 16비트는 y좌표를 가지고 있습니다.

 

 

WindowProc에 바인딩하기

 WM_PAINT를 처리했던 switch 구문에 아래 코드를 추가해줍니다.

		case WM_LBUTTONDOWN:
			{
				POINT temp;
				temp.x = LOWORD(lParam);
				temp.y = HIWORD(lParam);
				return OnLButtonDown(temp);
			}

POINT는 좌표를 저장하는데 최적화된 구조체입니다. 구조체 멤버로 x와 y가 들어있습니다.

 

LOWORD, HIWORD

 코드를 보면 LOWORD와 HIWORD라는 함수[# 사실 매크로입니다.]가 있습니다. 두 매크로 함수는 32비트 값에서 상위/하위 16비트 값을 추출하는 함수입니다. 만일 두 매크로를 이용하지 않았다면 아래의 코드로 대신해야 했습니다.

temp.x = lParam & 0xFFFF;
temp.y = (lParam >> 16) & 0xFFFF;

// temp.y = lParam >> 16 & 0xFFFF;
// 우선순위가 >> 연산자가 높아서 괄호를 사용하지 않아도 되지만
// 더 명확히 우선 순위를 두기 위해서 괄호를 사용했습니다.

 더 간편해졌다고 생각할 수 있죠.

 

OnLButtonDown

 이 함수는 마우스 왼쪽이 눌림을 감지했을 때 처리하기 위해 가져온 녀석입니다. 마우스 입력이므로 마우스 클릭이 발생한 좌표를 받기 위해 POINT 타입 인자 하나를 가지고 있습니다.

LRESULT OnLButtonDown(POINT pos){

    return 0;
}

 

 제대로 바인딩이 되었는지 확인하기 위해 메시지창을 하나 띄워보겠습니다. OnLButtonDown 함수 안에 넣어주면 됩니다.

MessageBox(nullptr, L"Click", L"ClickMessage", MB_OK);
더보기

# MessageBox 각 인자 설명

  • HWND hWnd
     이 메시지의 소유 윈도우 핸들입니다. 만일 프로그램의 윈도우 핸들을 넘기면, 메시지를 닫지 않았을 때 다른 작업은 할 수 없습니다.
  • LPCWSTR lpText
     메시지에 표시할 설명입니다.
  • LPCWSTR lpCaption
     메시지 타이틀입니다.
  • UINT uType
     메시지 특성입니다. 여기에서 비트 OR 연산으로 메시지의 버튼 종류를 제어하거나 경고/안내 등의 메시지 이미지를 띄울 수 있습니다.

 자세한 내용은 MS DOCS를 참고하세요.

 

 

MessageBox function (winuser.h) - Win32 apps

Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, such as status or error information. The message box returns an integer value that indicates which button the user clicked.

docs.microsoft.com

 메시지에 소유 윈도우 핸들을 입력하지 않았기 때문에 윈도우의 클라이언트 영역[# 타이틀바와 주변 테두리를 제외한 내부 회색 공간]을 누르면 메시지가 계속해서 생성됩니다.

 

 바인딩은 잘된 것을 보았습니다. 이제 사진 암호의 핵심, 잠금을 해제할 때 사용할 위치를 만들겠습니다.

 

좌표 저장 구조체 ITEM

 암호를 구성하기 위해선 두 가지 옵션이 필요합니다. 하나는 암호의 위치를 가리키는 좌표 변수와, 해당 암호를 입력한 뒤 다음 암호로 넘어갈 수 있는가를 확인할 수 있는 변수입니다. 이미지로 설명하면 다음과 같습니다.

 윈도우의 각 좌표를 순서대로 클릭하면 접근이 성공하는 것과 같은 설계인 것이죠. 따라서 구조체는 POINT와 bool 변수를 가지고 있도록 구성합니다.

typedef struct _ITEM {
	POINT pos;
	bool isNext;
}ITEM;

 이제 암호 위치를 저장할 변수를 선언합니다. 격자를 50px 단위로 생성했으므로, 편하게 50px 단위로 구성하겠습니다.

ITEM password[5] = {
	{50, 50},
	{100, 100},
	{150, 150},
	{200, 200},
	{250, 250}
};

 여기까지 따라오면 아래와 같은 코드가 구성됩니다.

더보기

# 중간 코드 점검

typedef struct _ITEM {
	POINT pos;
	bool isNext;
}ITEM;

ITEM password[5] = {
	{50, 50},
	{100, 100},
	{150, 150},
	{200, 200},
	{250, 250}
};

LRESULT OnLButtonDown(POINT pos) {


	return 0;
}

 

오차 범위 만들기

 이렇게 구성하고 암호를 비교하는 코드를 구성하면 접근하기 굉장히 어려워집니다. 정확히 해당 공간을 클릭하는 짓을 다섯 번이나 반복해야하니까요. 안그래도 작은 픽셀인데 어떻게 다섯 번이나 할 수 있을까요? 그러니 우리는 오차 범위를 만들어서 지정된 위치와 비슷한 위치를 클릭하면 지정된 위치를 클릭한 것과 같도록 구성해야합니다.

 

 오차 범위를 구성하는 방법은 다양합니다만, 여기에서는 간단히 지정된 점을 기준으로 반지름 r의 원 내부를 클릭했을 때 통과한 것으로 구성하겠습니다.

이 원 안을 클릭하면 통과하도록 구성

 

 여기에선 약간의 수학 공식이 필요합니다. 원의 방정식이 말이죠.

 이 방정식을 따라 아래의 코드를 구성합니다.

#include <cmath>

// ...

bool isDetect(POINT p1, POINT p2, double range = 20.0) {
	double result;
	result = pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2);

	if (result < pow(range, 2)) return true;
	return false;
}

 pow 함수[# 사용하기 위해서는 math.h나 cmath를 파일 머리에 추가해야합니다. 여기에서는 C++ 기반으로 하는 cmath를 추가했습니다.]는 첫 인자의 n제곱을 반환하는 함수입니다. 두 번째 인자가 n입니다. 기본 값으로 범위 20 안에 들어오면 넘어가는 것으로 했습니다.

 

OnLButtonDown 구성하기

 이제 OnLButtonDown을 구성하겠습니다. 반복문을 이용할 것이며, 처음 파악해야할 것은 이번 입력이 어느번째 입력인가입니다. 총 다섯 번에 걸쳐 순서대로 입력이 이루어져야 합니다.

 

 OnLButtonDown 함수 안에 아래의 내용을 채우겠습니다.

for (int i = 0; i < 5; ++i) {
	if (password[i].isNext) continue;

}

 password[i]번째의 isNext를 확인해서 넘어갈 수 있다면 다음 체크로 넘어갑니다. 만일 isNext가 false인 변수를 만난다면 해당 변수가 우리가 찾고자 하는 클릭 위치를 가지고 있을 겁니다.

if (isDetect(password[i].pos, pos)) {
	password[i].isNext = true;
	break;

}

 만일 오자 범위 안에 존재한다면 정상으로 판단하고 현재 password의 isNext 요소를 true로 변경해 다음 암호에 접근할 수 있도록 합니다. 만일 정상으로 판단되지 않으면 모든 암호를 취소하고 다시 입력할 수 있도록 아래처럼 isNext를 false를 돌려줍니다.

if (isDetect(password[i].pos, pos)) {
	password[i].isNext = true;
	break;

}
else {
	for (int k = 0; k < i; ++k) {
		password[i].isNext = false;
	}
	break;
}

 

 다음으로 모든 입력이 성공했는지를 확인하는 코드를 작성합니다.

	bool isCorrect = true;
	for (int i = 0; i < 5; ++i) {
		isCorrect = password[i].isNext;
		if (!isCorrect) break;
	}

	if (isCorrect) {
		MessageBox(nullptr, L"Correct", L"Login", MB_OK);
		for (int i = 0; i < 5; ++i) password[i].isNext = false;
	}

 password 배열의 모든 isNext가 true인 경우에는 아래의 if 구문이 통과하고 메시지가 나오도록 구성되어 있습니다. 모두 성공한 경우 다시 isNext 변수를 false로 수정해줍니다.

 

 


최종 코드

 아래 링크에서 "Win32 Basic.cpp" 파일을 검색하세요.

 

mijien0179/tistorySampleCode

티스토리 샘플코드. Contribute to mijien0179/tistorySampleCode development by creating an account on GitHub.

github.com

 

# index

728x90
반응형
Comments