사용자 입력 이벤트 처리
윈도우에서 지원하는건 키보드랑 마우스가 있다.
키보드 관련해서는 키보드가 눌렸다.(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앞에 부호는 위아래를 바꾸는것이다.