MFC

사용자 입력 이벤트 처리

윤주승 2025. 4. 18. 14:19

윈도우에서 지원하는건 키보드랑 마우스가 있다.

키보드 관련해서는 키보드가 눌렸다.(Down)와 반대인 Up만이 존재한다. (WM_KEYDOWN / UP)

문자를 만났을때는 WM_CHAR이벤트가 발생한다. 이는 입력된 문자를 처리하는 이벤트이다.

예를 들어, 소문자 'c'가 눌렸으면 c에 대한 키코드가 있을 것이고, 이것을 버추얼 키코드로 변환해서 준다.

키보드 입력

  • 키보드와 마우스(HID)는 각각 유일한 입력장치
    - 한 프로그램이 독점하지 말아야 함
  • 모든 키보드 입력은 입력 포커스를 가진 응용 프로그램에 전달되는 것이 원칙
  • 입력한 키 값Virtual key code로 변환해 전달 됨
  • 키를 계속 누르고 있을 경우 지속적으로 메시지는 계속 발생
    - 눌린  횟수가 매개변수로 함께 전달되며 1을 초과할 수 있음

방향키 움직임 -> VK_UP, VK_DOWN, VK_RIGHT, VK_LEFT

GetkeyState()함수는 어떠한 키가 눌렸는지 안눌렸는지 검출하는 함수이다.

헤더파일

void CInputSampleView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
	OutputDebugString(_T("KeyDown()\n"));
	CView::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CInputSampleView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
	CClientDC dc(this);
	m_char = nChar;
	
	RedrawWindow();

	CView::OnChar(nChar, nRepCnt, nFlags);
}

void CInputSampleView::OnPaint()
{
	CPaintDC dc(this); // device context for painting
	dc.SetBkMode(OPAQUE);
	dc.SetBkColor(RGB(255, 255, 255));

	if (m_char != 0) {
		CString tmp;
		tmp.Format(_T("%c"), m_char);

		if (::GetKeyState(VK_SHIFT) & 0x8000) {
			tmp += _T(" (Shift)");
		}
		dc.TextOut(10, 10, tmp);
	}

}

OnKeyDown함수는 내가 물리적으로 눌렀을때 호출되는 함수이다. 이는 화면에는 나오지않고, 디버그 창에 눌렸다는 이벤트 확인용이다.

문자가 입력되면 WM_CHAR 메시지가 호출되는데 nChar은 실제로 입력된 문자(아스키코드 값)이다.

RedrawWindow()로 화면을 다시 그리라고 요청한다.

문자가 입력되면 if문이 실행되어 CString을 통해 문자열을 저장하고, m_char는 정수였기때문에 문자열로 Format되며 출력된다.

마우스 이동

  • 마우스 이동 시 WM_MOUSEMOVE 메시지가 발생하며 마우스 포인터 좌표값을 매개변수로 함께 전달
  • 윈도우 영역을 벗어날 경우 메시지를 수신하지 못함
  • 윈도우 속성에 따라 마우스 메시지를 수신하지 않을 수 있음
  • 터치스크린이나 태블릿 역시 마우스와 동일하게 처리

Drag - n - Drop을 예시로 보면 왼쪽 마우스로 누르고(down) -> 마우스를 움직여서 (move) -> 마우스를 뗀다.(up)

먼저 헤더파일에 CPoint m_ptMove (좌표) 객체를 선언해준다.

		tmp.Format(_T("X:%d, Y:%d"), m_ptMove.x, m_ptMove.y);
		dc.TextOut(10, 50, tmp);
        void CInputSampleView::OnMouseMove(UINT nFlags, CPoint point)
{
	m_ptMove = point;
	RedrawWindow();
	CView::OnMouseMove(nFlags, point);
}

point는 m_ptMove에 저장되어 x축과 y축의 좌표가 마우스를 따라 출력될 것이다.

버튼 클릭

  • 윈도우 환경에서 마우스 버튼은 기본적으로 3개
    - 왼쪽, 중앙 휠, 오른쪽
    - 제어판 설정에 따라 좌우가 바뀔 수 있음(왼손)
  • 모든 버튼은 Down, Up 메시지를 가짐
    - 클릭 이벤트에 대한 처리를 원한다면 Down이 아닌 Up 메시지 활용을 권장
  • 더블 클릭은 마우스 버튼 Down + Up의 연속 발생 시 추가되는 메시지
  • Drag and drop은 마우스 버튼 Down, Move, Up 세 메시지의 연속
  • 더블클릭은 Down -> Up -> Down 이 일정시간 보다 빠를 경우 두번째 Down은 더블클릭으로 본다.

먼저 사각형 하나를 만들겠다.

	CRect m_rtIcon = CRect(10, 100, 110, 200);

x, y길이는 각각 100이다. 그러고 사각형을 회색으로 색칠하면 다음과 같은 결과물이 보인다.

dc.FillSolidRect(&m_rtIcon, RGB(192, 192, 192));

배경이 아닌 회색부분을 드래그 했을때의 출력을 추가해볼것이다.

void CInputSampleView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
	if (m_rtIcon.PtInRect(point)) {
		AfxMessageBox(_T("Icon Clicked"));
	}
	CView::OnLButtonDown(nFlags, point);
}

void CInputSampleView::OnMButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.

	CView::OnMButtonUp(nFlags, point);
}

마우스를 클릭했을때와 클릭을 뗐을 때 함수를 두개 만들었다.

PtInRect는이 사각형 안에 클릭 좌표(point)가 포함되는지를 확인한다.

	BOOL m_bDrag = FALSE;

멤버변수를 Boolean타입으로 선언해주고, 아까 사각형을 다시 활용할 것이다.

	if (m_rtIcon.PtInRect(point)) {
		m_bDrag = TRUE;
	}

사각형을 잡았을때 Boolean타입은 True로 바뀌며

void CInputSampleView::OnMouseMove(UINT nFlags, CPoint point)
{
	m_ptMove = point;
	if (m_bDrag) {
		m_rtIcon.TopLeft() = point;
		m_rtIcon.BottomRight() = m_rtIcon.TopLeft() + CSize(100, 100);
	}
	RedrawWindow();
	CView::OnMouseMove(nFlags, point);
}

아이콘의 좌상단을 기준으로해서 사각형이 따라 움직인다. 사각형을 잡았을때 사각형의 크기는 Size만큼 바뀐다.

사각형을 잡았다가 놓았을때 도형이 그 자리에 위치하고, 다시 잡았을때 움직일 수 있으려면

LButtonUp 이벤트에 로직을 수정해야 한다.

void CInputSampleView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
	if (m_bDrag) {
		m_rtIcon.TopLeft() = point;
		m_rtIcon.BottomRight() = m_rtIcon.TopLeft() + CSize(200, 200);
		RedrawWindow();
	}
	m_bDrag = FALSE;

	CView::OnLButtonUp(nFlags, point);
}

m_bDrag상태를 False로 수정하면서 최종 위치를 갱신한다.

정리하면 도형을 잡으면 LButtonDown이 실행되며 m_bDrag는 True상태가 된다. 이 상태에서 마우스를 움직이면 MouseMove 이벤트가 실행되어 좌표는 좌상단을 가리키게 된다. 버튼을 뗄 경우 LButtonUp 이벤트가 실행된다.
RedrawWindow함수를 사용하지않으면 도형의 움직임을 실시간으로 감지할 수 없게 된다.

도형을 클릭하면 좌상단이 잡히는데, 내가 클릭한 위치의 좌표를 사용하려면 CSize 클래스를 사용할 수 있다.

헤더파일에 CSize m_offset 으로 선언하고 버튼을 클릭했을때 좌표에서 빈 공간의 크기를 빼주어야 한다.

		m_offset = point - m_rtIcon.TopLeft();

마우스 이벤트 추적

  • 윈도우 영역을 벗어나더라도 마우스 메시지를 수신하기 위한 방법
    - 시스템에 하나 뿐인 마우스를 특정 응용 프로그램이 독점하는 결과
  • 주의사항
    - 좌표관련 이슈 발생(윈도우 기준, 스크린 기준)
    - 마우스 캡쳐 해제(릴리즈)에 대해 반드시 고려해야 함
  • SetCapture(),  ReleaseCapture() 함수를 사용해서 윈도우 영역에서 벗어나도 상태를 유지할 수 있음.
  • IPC가 가능해야 바탕화면에도 끌어갈 수 있음.

휠 버튼 처리하기

  • 휠 버튼은 화면 스크롤을 목적으로 사용하는 것이 일반적
    - 앞으로 밀면 스크롤 업
    - 손 안쪽으로 감아 당기면 스크롤 다운
  • 매개변수로 스크롤 범위를 전달 받음
    - 휠 조작에 따른 스크롤 폭은 윈도우 설정에 따름
BOOL CInputSampleView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
	if (::GetKeyState(VK_SHIFT) & 0x8000)
		m_rtIcon.TopLeft().x += -zDelta;
	else m_rtIcon.TopLeft().y += -zDelta;
	m_rtIcon.BottomRight() = m_rtIcon.TopLeft() + CSize(200, 200);
	RedrawWindow();
	return CView::OnMouseWheel(nFlags, zDelta, pt);
}

마우스 휠은  zDelta값을 사용해서 조절할 수 있다. 조건문은 키보드가 눌렸을때는 위 아래가 아닌 좌,우로 움직일 때 실행된다. zDelta앞에 부호는 위아래를 바꾸는것이다.