3 장. 마우스와키보드메시지 1
1. 능동적입력받기와수동적입력받기 2 학습목표 이벤트위주로직개념을파악한다 메시지개념을파악한다 능동적입력받기 절차적로직프로그램에서입력받기 언제무엇을입력받을지는프로그램의절차적로직에서결정 예 : 텍스트모드 C 프로그램에서문자열을입력받는경우 수동적입력받기 이벤트위주로직프로그램에서입력받기 윈도우환경에서마우스나키보드입력은 Windows 시스템이받아서프로그램에게메시지형태로전달한다 언제어떤순서로메시지가전달될지미리알수없다 프로그램은전달되어오는메시지를받아서처리해야한다
3 WM_KEYDOWN 키보드의키가눌러지는순간프로그램에게전달되는메시지 Caps Lock, Shift, Ctrl 키를포함키보드의거의모든키에대해서메시지가발생한다 WM_KEYUP 키보드의키가눌러진상태에서놓여지는순간프로그램에게전달되는메시지 Caps Lock, Shift, Ctrl 키를포함키보드의거의모든키에대해서메시지가발생한다 WM_CHAR 입력된문자에대해발생하는메시지 Caps Lock, Shift, Ctrl 과같은문자가아닌키에대해서는메시지가발생하지않는다
4 WM_LBUTTONDOWN 마우스왼쪽버튼이눌려지는순간발생하는메시지 WM_LBUTTONUP 마우스왼쪽버튼이놓여지는순간발생하는메시지 WM_LBUTTONDBLCLK 마우스왼쪽버튼이더블클릭하면발생하는메시지 WM_MOUSEMOVE 마우스포인터가이동할때발생하는메시지 WM_RBUTTONDOWN 마우스오른쪽버튼이눌려지는순간발생하는메시지
5 키보드마우스메시지처리 메시지를처리하기위한메시지핸들러를만들어야한다. Class Wizard에서메시지핸들러를재정의한다 키보드, 마우스메시지핸들러는뷰클래스 (CView 클래스의하위클래스 ) 에정의한다 뷰클래스의역할 윈도우의클라이언트영역에무엇을그리는일 키보드마우스메시지처리
2. 마우스메시지 6 학습목표 이벤트위주로직개념을파악한다 마우스메시지를처리한다 예제프로그램 : Mouse1 소스 : "03-1 Mouse1.zip" 주요작업 1) Application Wizard를이용해프로젝트골격생성 2) ClassWizard에서마우스메시지핸들러재정의 WM_LBUTTONDOWN, WM_RBUTTONDOWN 3) 메시지핸들러구현 CMouse1View::OnLButtonDown() CMouse1View::OnRButtonDown()
7 void CMouse1View::OnLButtonDown(UINT nflags, CPoint point) CClientDC dc(this); CString str; str.format("%d,%d", point.x, point.y); if (nflags & MK_LBUTTON) str += " left"; if (nflags & MK_RBUTTON) str += " right"; if (nflags & MK_CONTROL) str += " ctrl"; if (nflags & MK_SHIFT) str += " shift"; dc.textout(point.x, point.y, str); void CMouse1View::OnRButtonDown(UINT nflags, CPoint point) OnLButtonDown(nFlags, point);
8
9 void CMouse1View::OnLButtonDown(UINT nflags, CPoint point) Class Wizard로재정의한 WM_LBUTTONDOWN 메시지핸들러 Windows 시스템으로부터 WM_LBUTTONDOWN 메시지가전달되면 MFC 골격에의해서저절로이메시지핸들러가호출된다 nflags는눌려진버튼에대한정보 point는마우스포인터좌표 CClientDC dc(this); 화면에무엇을그리기위해서는디바이스컨텍스트객체가필요 WM_PAINT 메시지와관계없는곳에서는 CPaintDC 클래스를사용할수없고 CClientDC 클래스의객체를생성해야한다 즉 CView::OnPaint() 에서만 CPaintDC 의객체를생성할수있고그이외의곳에서는 CClientDC 를사용한다고볼수있다 this는뷰객체
10 if (nflags & MK_LBUTTON) str += " left"; if (nflags & MK_RBUTTON) str += " right"; if (nflags & MK_CONTROL) str += " ctrl"; if (nflags & MK_SHIFT) str += " shift"; nflags는눌려진버튼에대한정보 위와같은상수와 bitwise and 로어떤버튼이눌려졌는지알수있다 MK_LBUTTON 마우스왼쪽버튼 MK_RBUTTON 마우스오른쪽버튼 MK_CONTROL 컨트롤키 MK_SKIFT 쉬프트키 OnLButtonDown(nFlags, point); 메시지핸들러는메시지가전달될때 MFC 골격에의해서호출되는것이보통이지만, 메시지핸들러도결국은메소드이므로이와같이직접호출할수도있다
문제 : 원인분석 11 Mouse1 예제에서윈도우가다른윈도우아래로감춰졌다가다시노출되면그부분이지워진다. 왜지워지는지설명하라. 지워지지않도록수정하라.
3. 마우스드래그 1 12 학습목표 이벤트위주로직개념을파악한다 마우스메시지를처리한다 마우스드래그를처리한다
13 예제프로그램 : Drag 소스 : "03-2 Draw1.zip" 주요작업 1) Application Wizard를이용해프로젝트골격생성 2) ClassWizard에서마우스메시지핸들러재정의 WM_KEYDOWN, WM_KEYUP WM_MOUSEDOWN 3) CDragView에멤버변수추가 CRect m_ellipse; CPoint m_prev; 4) CDragView의생성자에서멤버변수초기화 5) 메시지핸들러구현 CDragView::OnLButtonDown() CDragView::OnLButtonUp() CDragView::OnMouseMove() 6) CDragView::OnDraw() 구현
14
15 CDragView에멤버변수추가 m_ellipse는원을그리기위한좌표를저장 m_prev는마우스의이전위치를저장 class CDragView : public CView... protected: CRect m_ellipse; CPoint m_prev;...
16 CDragView 의생성자에서멤버변수초기화 m_prev 와 m_ellipse 각각생성자를호출하여초기화 CDragView::CDragView() : m_prev(0,0), m_ellipse(0,0,50,50) m_ellipse 위치에원을그림 CDC::SelectStockObject() 는 1 장 8 절을참고 void CDragView::OnDraw(CDC* pdc) pdc->selectstockobject(gray_brush); pdc->ellipse(m_ellipse);
17 void CDragView::OnLButtonDown(UINT nflags, CPoint point) if (m_ellipse.ptinrect(point)) SetCapture(); SetCursor(LoadCursor(NULL, IDC_SIZEALL)); m_prev = point; BOOL CRect::PtInRect(CPoint point) 좌표 point가 CRect 내부이면 true 아니면 false를리턴 typedef int BOOL; if (m_ellipse.ptinrect(point)) if 마우스가 m_ellipse의내부에서눌러졌으면 true
18 CWnd::SetCapture() 마우스포인터의위치와상관없이이후 ReleaseCapture() 를호출할때까지모든마우스메시지를현재윈도우에전달하게함 보통은마우스포인터가위치한그윈도우에메시지를전달함 즉마우스가윈도우밖으로나가도계속마우스메시지를현재윈도우에전달하게함 SetCursor(LoadCursor(NULL, IDC_SIZEALL)); 마우스포인터모양을 IDC_SIZEALL 모양으로설정 predefined cursor IDC_ARROW IDC_CROSS IDC_HAND IDC_HELP IDC_IBEAM IDC_ICON IDC_NO IDC_SIZE IDC_SIZEALL IDC_SIZENESW IDC_SIZENS IDC_SIZENWSE IDC_SIZEWE IDC_UPARROW IDC_WAIT m_prev = point; 현재마우스포인터위치를저장
19 void CDragView::OnMouseMove(UINT nflags, CPoint point) if (GetCapture() == this) CSize offset = point - m_prev; m_ellipse += offset; m_prev = point; Invalidate(); CWnd* CWnd::GetCapture() SetCapture() 한윈도우에대한포인터를리턴 CSize offset = point - m_prev; 현재마우스위치에서이전위치를빼어거리를구함 CSize = CPoint - CPoint
20 m_ellipse += offset; 그거리만큼 m_ellipse를이동 CWnd::Invalidate(BOOL erase = TRUE); CWnd의전체영역을무효화하여결국 OnDraw() 메소드가호출되도록강제한다 erase가 true이면무효화된영역을 Windows 시스템이윈도우배경색으로먼저지우고 WM_PAINT 메시지를보낸다 위의코드에서는 this->invalidate() 를호출하였다. CDragView는 CView를상속받고, CView는 CWnd를상속받는다 CDragView::OnMouseMove() 가리턴된후 WM_PAINT 메시지가보내지고 CDragView::OnDraw() 가호출된다
21 void CDragView::OnLButtonUp(UINT nflags, CPoint point) if (GetCapture() == this) ReleaseCapture(); ReleaseCapture() 마우스메시지캡춰를취소함
문제 : 원인분석 22 Drag 프로그램에서원내부가아니고아래그림과같은위치에서마우스버튼을눌러도원이드래그된다. 왜그런지설명하라.
4. 키보드메시지 23 학습목표 이벤트위주로직개념을파악한다 키보드메시지를처리한다 예제프로그램 : Key1 소스 : "03-3 Key.zip" 주요작업 1) Application Wizard를이용해프로젝트골격생성 2) ClassWizard에서키보드메시지핸들러재정의 WM_KEYDOWN, WM_KEYUP, WM_CHAR 3) CKey1View에메소드추가 void Display(const char* msg); 4) 메시지핸들러구현 CKey1View::OnKeyDown() CKey1View::OnKeyUp() CKey1View::OnChar()
키하나를누르면차례대로 WM_KEYDOWN, WM_CHAR, WM_KEYDOWN 메시지가발생한다 24
25 void CKey1View::Display(const char* msg) CRect rect; CClientDC dc(this); dc.selectstockobject(system_fixed_font); ScrollWindow(0,-20); GetClientRect(&rect); dc.textout(0,rect.bottom-20, msg); ValidateRect(NULL); void CWnd::ScrollWindow(int dx, int dy, CRect* rect=null); rect 영역을 x 축으로 dx 만큼 y 축으로 dy 만큼스크롤한다 rect 가 NULL 이면전체클라이언트영역을스크롤한다 스크롤되어새로노출된영역은무효화된다 void CWnd::ValidateRect(LPCRECT* rect); rect 영역을유효화 ( 무효화취소 ) 한다 rect 가 NULL 이면전체클라이언트영역이유효화된다
26 void CKey1View::OnKeyDown(UINT nchar, UINT nrepcnt, UINT nflags) CString s; s.format("%-20s 0x%02x", "WM_KEYDOWN", nchar); Display(s); void CKey1View::OnKeyUp(UINT nchar, UINT nrepcnt, UINT nflags) CString s; s.format("%-20s 0x%02x", "WM_KEYUP", nchar); Display(s); void CKey1View::OnChar(UINT nchar, UINT nrepcnt, UINT nflags) CString s; s.format("%-20s %4c", "WM_CHAR", nchar); Display(s);
27 Virtual Key Code OnKeyDown() 과 OnKeyUp() 의첫번째파라미터는 virtual key code virtual key code 상수는 Visual Studio의 include 디렉토리아래 "winuser.h" 파일에정의되어있음 virtual key code는 ASCII 코드와다르다 OnChar() 의첫번째파라미터는 ASCII 코드
문제 28 CKey1View::Display() 에서 ValidateRect(NULL); 라인을지우고컴파일하여실행시키면클라이언트영역에제대로그려지지않는다. 왜그런지설명하라. 윈도우가다른윈도우아래로감춰졌다가다시노출되면그부분이지워진다. 지워지지않도록수정하라.
5. 마우스드래그 2 29 학습목표 이벤트위주로직개념을파악한다 마우스메시지를처리한다 마우스드래그를처리한다 무효화를통한화면갱신을파악한다 앞의마우스드래그 1 의 Drag 예제를개선한다 예제프로그램 : Draw 소스 : "03-4 Drag2.zip" 주요작업 1) CDrawView::OnLButtonDown() 개선 2) CDrawView::OnMouseMove() 개선 3) ClassWizard에서 WM_KEYDOWN 메시지핸들러재정의 4) CDrawView::OnKeyDown() 메시지핸들러구현
30 void CDragView::OnLButtonDown(UINT nflags, CPoint point) CRgn rgn; rgn.createellipticrgnindirect(m_ellipse); if (rgn.ptinregion(point)) SetCapture(); SetCursor(LoadCursor(NULL, IDC_SIZEALL)); m_prev = point; CRgn::CreateEllipticRgnIndirect(LPCRECT rect); 복잡한모양의영역을나타내기위한 CRgn 클래스의메소드 rect 내부에꽉차게그려지는원영역을생성 BOOL CRgn::PtInRegion(CPoint point); point 가영역의내부에포함되면 true 아니면 false 를리턴
void CDragView::OnMouseMove(UINT nflags, CPoint point) if (GetCapture() == this) InvalidateRect(m_ellipse); // (1) CSize offset = point - m_prev; m_ellipse += offset; m_prev = point; InvalidateRect(m_ellipse); // (2) 31
32 CWnd::InvalidateRect(LPCRECT* rect) typedef const CRect* LPCRECT; rect 영역을무효화함 첫번째 InvalidateRect(m_ellipse) 는이전 m_ellipse 위치를무효화하고 두번째 InvalidateRect(m_ellipse) 는이동된 m_ellipse 새위치를무효화한다 이전에그렸던원을지워야하기때문에이전 m_ellipse 위치를무효화 새위치에원을다시그려야하기때문에이동된 m_ellipse 새위치를무효화 무효화된영역이발생하면 Windows 시스템은 WM_PAINT 메시지를해당프로그램에게전달하여, 결국 OnDraw() 메소드가실행됨 OnDraw() 에서는무효화된영역구분없이전부그리지만, 실제로화면에그려지는것은무효화된영역내부만이고, 영역외부에그려지는것은무시됨 클라이언트영역전체를다시그리는것에비해서빠르고, 화면깜빡거림이적다
void CDragView::OnKeyDown(UINT nchar, UINT nrepcnt, UINT nflags) InvalidateRect(m_ellipse); // (1) switch (nchar) // (2) case VK_HOME: m_ellipse = CRect(0,0,50,50); break; case VK_UP: m_ellipse += CPoint(0,-10); break; case VK_DOWN: m_ellipse += CPoint(0,10); break; case VK_LEFT: m_ellipse += CPoint(-10,0); break; case VK_RIGHT: m_ellipse += CPoint(10,0); break; ; InvalidateRect(m_ellipse); // (3) 33
34 CDragView::OnKeyDown() 메소드 (1) 은바로전에 CDrawView::OnDraw() 에서그려진원의좌표영역 (m_ellipse) 을무효화한다 (2) 는마우스가이동된만큼 m_ellipse 좌표값을수정한다 (3) 은다음에그려질좌표영역 (m_ellipse) 을무효화한다 위와같이 OnKeyDown() 메소드에서는그릴영역을무효화만하고그리지는않고리턴하면 Windows 시스템은무효화된영역이발생했음을감지하고 WM_PAINT 메시지를보낸다 WM_PAINT 메시지를받으면 MFC 프레임웍에의해서 CDragView::OnDraw() 메소드가호출된다 CDragView::OnDraw() 에서그리는것은무효화된영역내부로만 clipping 된다. 즉위의 (1) 과 (3) 에서무효화된사각형내부에만그려지고나머지영역은무시된다
6. Graphs 예제 35 학습목표마우스로사각형을그리는예제를작성한다 예제프로그램 : Graphs 소스 : "03-5 Graphs.zip" 주요작업 1) Application Wizard를이용해프로젝트골격생성 2) CGraphsView에멤버변수추가 CPoint m_point1, m_point2; 3) ClassWizard에서마우스메시지핸들러재정의 WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE 4) 마우스메시지핸들러구현 5) OnDraw 구현
1) Application Wizard 를이용해프로젝트골격생성메뉴 File New 36
37
38
2) CGraphsView 에멤버변수추가 CPoint m_point1, m_point2; 39
3) ClassWizard 에서마우스메시지핸들러재정의메뉴 View ClassWizard 40
41 멤버변수초기화 CGraphsView::CGraphsView() : m_point1(0,0), m_point2(0,0) // TODO: add construction code here OnDraw() 메소드 #include <math.h> CRect MakeRect(CPoint p1, CPoint p2) return CRect(min(p1.x,p2.x), min(p1.y,p2.y), max(p1.x,p2.x), max(p1.y,p2.y)); void CGraphsView::OnDraw(CDC* pdc) pdc->rectangle(makerect(m_point1,m_point2));
42 마우스메시지 void CGraphsView::OnLButtonDown(UINT nflags, CPoint point) m_point1 = point; m_point2 = point; SetCapture(); void CGraphsView::OnLButtonUp(UINT nflags, CPoint point) if (GetCapture() == this) ReleaseCapture(); void CGraphsView::OnMouseMove(UINT nflags, CPoint point) if (GetCapture() == this) InvalidateRect(MakeRect(m_point1,m_point2)); m_point2 = point; InvalidateRect(MakeRect(m_point1,m_point2));
7. Graphs 예제수정 43 학습목표마우스가그려진사각형위를지나가면사각형이반전되도록 Graphs 를수정한다 예제프로그램 : Graphs 소스 : "03-6 Graphs.zip"
44 CGraphsView 에멤버변수추가 class CGraphsView : public CView protected: // create from serialization only CGraphsView(); DECLARE_DYNCREATE(CGraphsView) // Attributes public: CGraphsDoc* GetDocument(); CPoint m_point1, m_point2; BOOL m_ptinrect;
45 OnDraw() 메소드 void CGraphsView::OnDraw(CDC* pdc) if (m_ptinrect) pdc->selectstockobject(gray_brush); pdc->rectangle(makerect(m_point1,m_point2)); 그려진사각형위에마우스가있을때는 m_ptinrect 가 true 이므로사각형을 GRAY_BRUSH 로그린다
46 OnMouseMove() 메소드 void CGraphsView::OnMouseMove(UINT nflags, CPoint point) if (GetCapture() == this) InvalidateRect(MakeRect(m_point1,m_point2)); m_point2 = point; InvalidateRect(MakeRect(m_point1,m_point2)); else CRect r = MakeRect(m_point1,m_point2); if (m_ptinrect!= r.ptinrect(point)) InvalidateRect(r); m_ptinrect = r.ptinrect(point); 마우스가사각형내부에있다가외부로나가거나, 외부에있다가내부로들어올때 if (m_ptinrect!= r.ptinrect(point)) 문이 true 된다