머 / 리 / 말 저자가 단계별로쉽게배우는 Visual C++ 6.0 책을출판한지도벌써 10년이지났습니다. 그동안많은대학에서교재로사용해주시면서많은교수님들과독자들의많은코멘트를받았습니다. 이제 Visual C++ 6.0은새로운운영체제에서호환성이떨어지고 Microsoft 사에서도더이상업그레이드를하지않고실행상의버그가수정되지않아사용상의많은문제점을가지고있었습니다. Visual C++ 2010이나온지 2년이다되어가는데사용할만한교재가많이출간되지않았습니다. 많은독자들이원하는것은좋은서적을통해단기간내에쉽게 MFC 프로그래밍을마스터하는것이라생각하고본저서를집필하게되었습니다. 본저서의저자가대학강단에서학생들에게 Visual C++ 를 10년이상지도하면서느낀점은 Visual C++ 를이용하여윈도우프로그램을작성하는방법을강의할때기초에서고급기술에이르기까지전반적인내용을상세하고알기쉽게설명한체계적인교재가부족하다는것이었습니다. 기존의 Visual C++ 책들은방대한 MFC의내용을설명하고, 많은실습예제들을수록하였으나, 실습예제를작성하는방법에대해단계별로자세히설명하지않아서처음 Visual C++ 를접하는대부분의학생들이매우고생하면서책과씨름하는것을보았습니다. 이책은처음으로 Visual C++ 을접하는대학초년생, 그리고몇번 Visual C++ 책과씨름하였으나아직도윈도우프로그램을작성하는데문제가있는학생들을위해실습을중심으로단계별로따라하며자연스럽게 MFC를배울수있는가장쉬운 Visual C++ 2010 입문서가될수있도록집필하였습니다. 이책을통해빨리윈도우프로그램을제작하는방법을익히고많은시간은프로그램의기본능력을배양하는데할애하시기바랍니다. 머리말 3
머 / 리 / 말 기존의 Visual C++ 입문서와의차별화된특징을소개하면다음과같습니다. 1 이책은기본적인 C 또는 C++ 의문법적내용은수록하지않았습니다. 대신 Visual C++ 2010을사용하여윈도우프로그래밍을하는방법을알기쉽게수록하고있습니다. 따라서이책을보기위해서는 C/C++ 의기본적인지식이필요합니다. 2 컴퓨터의조작시표시되는실제화면을그대로수록하고장문의설명을탈피하여아주일반적인컴퓨터조작능력만있으면실습예제의단계를따라가면서다양한실습을직접해보고그결과를완전히자기것으로만든후에다음단계로진행되도록구성되어있습니다. 3 Visual C++ 2010을사용하여프로젝트를작성하는데장문의설명중심의나열식이아닌문제해결중심의실습위주로다양하고필요한내용만을선택하여쉽게구성하였습니다. 이러한간단한설명과실습방법이독특하고실용적이기에 C 또는 C++ 언어를배우고처음으로 Visual C++ 2010 을이용하여윈도우애플리케이션을만드는방법을익히기위한학생들에게는최고의입문서가될것입니다. 4 대학에서이책을교재로하여한학기강의를할경우중간고사와기말고사를제외한 14주에한단원씩완성할수있도록구성되어있으며, 각단원마지막부분에는연습문제를두어본인스스로실습과제를통해배운내용을다시한번확인할수있는기회를제공하였습니다. 이책이완성되기까지많은수고를해준대전대학교컴퓨터공학과대학원연구실출신인오병선, 김한신, 전용선연구원과현재연구실에서집필을도와준이새봄, 조진오, 이강록, 차혜경, 이호진연구원들에게고마움을표합니다. 또한이책의출판을위하여적극적으로후원하여주신생능출판사김승기사장님과직원여러분께심심한감사를드립니다. 2013 년 1 월 용운골에서저자씀 4 머리말
차 / 례 제 1 장윈도우프로그래밍의이해 1.1 윈도우프로그래밍의개념 13 1.2 Win32 SDK 윈도우프로그램의기본구조 14 < 실습 1-1> 간단한 Win32 SDK 프로그램만들기 15 1.3 간단한윈도우애플리케이션의분석 26 1.4 윈도우프로그램의기본형식 32 제 2 장 MFC 개요및아키텍처 2.1 MFC의개요 45 2.2 MFC 프로그램의구조 46 2.3 Visual C++ 의시작 48 < 실습 2-1> MFC 응용프로그램마법사익히기 49 2.4 SDI 템플릿 64 2.5 MDI 템플릿 66 2.6 MFC 애플리케이션아키텍처 69 < 실습 2-2> 간단한 MFC 프로젝트만들기 72 제 3 장메시지처리 3.1 메시지처리의기본개념 109 3.2 메시지박스 (Message Box) 112 < 실습 3-1> 메시지박스생성하기 113 3.3 마우스메시지 (Mouse Message) 122 < 실습 3-2> 디지털시계만들기 123 3.4 키보드메시지 (Keyboard Message) 139 < 실습 3-3> 문자를입력하고이동시키기 141 차례 5
CONTENTS 제 4 장대화상자 4.1 CDialogEx 클래스 166 4.2 대화상자기반의프로그램 166 4.3 MFC 기본컨트롤 168 < 실습 4-1> MFC 기본컨트롤 (Control) 사용법익히기 170 4.4 모달 (Modal) 대화상자와모덜리스 (Modaless) 대화상자 214 4.5 공용대화상자 215 < 실습 4-2> 단위변환프로그램만들기 220 제 5 장사용자인터페이스 5.1 메뉴 (Menu) 251 < 실습 5-1> 간단한메뉴와단축키만들기 256 5.2 툴바 (Toolbar) 288 < 실습 5-2> 간단한툴바만들기 289 5.3 상태표시줄 (Status Bar) 296 < 실습 5-3> 상태표시줄에팬을만들고문자열을출력하기 297 5.4 도킹팬 (Docking Pane) 윈도우 308 < 실습 5-4> 사칙연산계산기만들기 308 제 6 장그래픽객체의사용 6.1 GDI와 DC의개념 341 6.2 GDI 객체 343 < 실습 6-1> 직선, 도형및비트맵그리기 354 6.3 GDI+ 의개념 381 < 실습 6-2> 사각형뷰포트에원그리기 390 6 차례
CONTENTS 제 7 장컨트롤및리소스 Ⅰ 7.1 리스트컨트롤 (List Control) 427 < 실습 7-1> 대화상자에 List Control을만들기 429 7.2 트리컨트롤 (Tree Control) 459 < 실습 7-2> 대화상자에서 Tree Control를사용하기 460 제 8 장컨트롤및리소스 Ⅱ 8.1 탭컨트롤 (Tab Control) 489 8.2 슬라이더컨트롤 (Slider Control) 490 8.3 스핀컨트롤 (Spin Control) 490 < 실습 8-1> 도형의종류와색상을대화상자에출력하기 491 8.4 프로그레스바컨트롤 (Progress Bar Control) 519 8.5 IP 주소컨트롤 (IP Address Control) 519 8.6 네트워크주소컨트롤 (Network Address Control) 520 8.7 날짜 / 시간선택컨트롤 (Date Time Picker) 521 8.8 애니메이션컨트롤 (Animation Control) 522 < 실습 8-2> 각종컨트롤을이용한데이터전송 Simulator 작성하기 522 제 9 장고급컨트롤및리본 9.1 MFC Feature 컨트롤 (MFC Feature Controls) 545 < 실습 9-1> 간단한명함제작프로그램만들기 549 9.2 리본 (Ribbon) 581 < 실습 9-2> 리본메뉴및각종리본컨트롤익히기 585 차례 7
CONTENTS 제 10 장 도큐먼트파일입출력및다양한뷰클래스 10.1 도큐먼트 629 10.2 파일입출력 632 10.3 다양한뷰클래스 632 < 실습 10-1> FormView 를이용한문자출력및파일입출력하기 634 10.4 분할윈도우 666 < 실습 10-2> 정적분할윈도우를이용한학생정보프로그램만들기 670 10.5 다중뷰 694 < 실습 10-3> 다중뷰만들기 695 제 11 장 동적연결라이브러리 DLL 11.1 DLL의링크 719 11.2 DLL의종류 721 < 실습 11-1> Implicit 링킹을통한정규 DLL 달력만들기 722 < 실습 11-2> Explicit 링킹을통한정규 DLL 달력만들기 740 < 실습 11-3> 확장 DLL을통한주민등록번호조회프로그램만들기 743 제 12 장 데이터베이스프로그래밍 12.1 데이터베이스프로그램의개요 765 < 실습 12-1> OLE DB를이용한학생관리프로그램만들기 768 8 차례
CONTENTS 제 13 장 그래픽프로그래밍 13.1 베지어곡선 (Bezier Curve) 825 < 실습 13-1> 알고리즘을통한베지어 (Bezier) 곡선그리기 827 < 실습 13-2> 컨트롤포인트의이동및베지어곡선의해상도설정하기 851 제 14 장 네트워크프로그래밍 14.1 네트워크프로그램의개요 873 < 실습 14-1> 채팅프로그램제작하기 874 차례 9
CHAPTER 01 윈도우프로그래밍의이해 1.1 윈도우프로그래밍의개념 1.2 Win32 SDK 윈도우프로그램의기본구조 1.3 간단한윈도우애플리케이션의분석 1.4 윈도우프로그램의기본형식
윈도우프로그래밍의이해 01 Chapter Chapter 01 윈도우프로그래밍의이해 이장에서는윈도우프로그래밍의기본개념과 Win32 SDK를이용하여윈도우프로그램을작성하는방법에대해설명한다. 그리고윈도우프로그램의기본형식에대해자세히알아본다. Win32 SDK를이용한윈도우프로그램의기본구조를이해하면 MFC를사용하여윈도우프로그래밍을작성하는데많은도움이될것이다. 1.1 윈도우프로그래밍의개념 우리가처음프로그래밍언어 (C 또는 C++) 를배울때주로 MS-DOS 나 UNIX 환경에서배우게된다. 그이유는프로그래밍언어 (C 또는 C++) 그자체에전념할수있기때문이다. 이런환경에서프로그래밍을하다가처음윈도우같은그래픽사용자인터페이스환경에서프로그래밍을하려고하면매우어려움을겪게된다. 프로그래밍하는방법이전혀다르기때문이다. DOS 환경에서는프로그래밍의수행절차가프로그래머가구현한순서대로실행되는반면윈도우환경에서의프로그래밍은사용자가발생시키는이벤트에의한메시지를처리하는방식으로실행된다. 이러한윈도우환경에서의프로그래밍을메시지기반 (message driven) 또는이벤트기반 (event driven) 프로그래밍이라고한다. 윈도우시스템의모든애플리케이션은메시지를기반으로하여구동된다. 또는이벤트기반의구조라고하기도한다. 메시지기반프로그래밍은예를들어사용자가왼쪽마우스버튼을눌렀을경우왼쪽마우스버튼을눌렀다는이벤트에대해윈도우시스템은해당애플리케이션에 왼쪽마우스버튼이눌렀다 (WM_LBUTTONDOWN) 라는메시지를보낸다. 이메시지를받은애플리케이션에서는이런특정메시지에대해어떠한일을수행할것인가에대한처리루틴을만들어주어야한다. 다시말해윈도우프로그래밍은애플리케이션에서사용자가발생시키는메시지에대한처리루틴을만들어주는것이프로그래밍하는것이라고말할수있다. 여기서꼭알아야하는것은윈도우가애플리케이션으로메시지를보낸다는점이다. 우리가작성한애플리케이션이윈도우시스템에메시지를보내는것이아니라는점을 13
Visual C++ 2010 MFC 프로그래밍 기억하기바란다. 그뜻은애플리케이션내의어떤함수를윈도우시스템이호출한다는것이며, 이함수의인자는특정메시지를의미하는것으로각자의애플리케이션에위치한이함수는윈도우프로시저라하는것이다. 결론적으로메시지가발생하면윈도우시스템이메시지에해당되는애플리케이션의윈도우프로시저를호출한다는것이다. [ 그림 1-1] 은윈도우애플리케이션의구조를도식화한것이다. [ 그림 1-1] 윈도우애플리케이션의메시지처리개념 윈도우프로그래밍을작성하는방법은크게두가지로나눌수있다. 첫번째는 Win32 SDK를이용하는방법이다. Win32 SDK(Software Development Kit) 는윈도우에서애플리케이션프로그램을개발할때필요한 C언어용표준라이브러리이다. 두번째는 MFC를이용하는방법이다. MFC(Microsoft Foundation Class) 는윈도우에서애플리케이션프로그램을개발할때필요한 C++ 로작성된클래스라이브러리이다. 앞으로여러분들이이책을통해배워야할내용이바로이 MFC에대한내용들이다. 첫번째방법인 Win32 SDK를사용하여윈도우프로그래밍하는경우에는윈도우클래스를만들어등록하고, 프레임윈도우를생성하여그윈도우를화면에보여주고메시지처리하는모든부분을프로그래머가코딩을해주어야한다. 그러나두번째방법인 MFC는매우구조적으로만들어져있어이런대부분의윈도우를만들때필요한코드를자동으로생성해주어서프로그래머가윈도우프로그램을쉽게작성할수있도록해준다. 그러나쉽게윈도우프로그램을작성할수있는반면윈도우클래스를만들어등록하고, 프레임윈도우를생성하여그윈도우를화면에보여주고, 메시지처리하는모든부분이프레임워크 (framework) 안에숨겨져있기때문에윈도우프로그래밍을이해하는데어려움이있다. 그래서 Win32 SDK를이용하여작성한윈도우프로그램의기본구조를이해하면 MFC를사용하여윈도우프로그래밍을작성하는데많은도움이될것이다. 14
윈도우프로그래밍의이해 01 Chapter 1.2 Win32 SDK 윈도우프로그램의기본구조 여기서는 Win32 SDK를이용하여윈도우프로그램을작성하였을때의윈도우프로그램의기본구조에대해설명하겠다. 윈도우프로그램은크게초기화하는부분과메시지를처리하는부분으로나눌수있다. 실제프로그램에서초기화부분은 WinMain() 함수에서담당하고메시지를처리하는부분은 WndProc() 함수에서담당한다. C/C++ 에서프로그램이 main() 함수에서시작해서 main() 함수가끝나면프로그램이종료되듯이윈도우프로그램에서도 WinMain() 함수에서시작해서 WinMain() 함수가끝나면프로그램이종료된다. 초기화부분을담당하는 WinMain() 함수는먼저윈도우클래스를만들어등록하고, 그다음프레임윈도우를생성하여화면에나타낸다. 여기에서말한클래스는 C++ 에서배운클래스의개념이아니고윈도우의종류를나타내는것으로단지윈도우의특징등을정의하고등록한후윈도우를생성한다고생각하면된다. 마지막으로메시지큐로부터메시지를받아와메시지를해당윈도우프로시저로보낸다. 윈도우프로시저에서는윈도우시스템에서들어온메시지를처리한다. 이러한루틴은매우전형적인것으로한번이해한후거의모든애플리케이션에똑같이적용하여사용하면된다. 다음은 WinMain() 함수의원형과초기화내용들이다. int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpszcmdline, int ncmdshow) { 윈도우클래스생성윈도우클래스등록프레임윈도우생성프레임윈도우화면에표시메시지큐로부터메시지를받아해당프로시저로보냄 } WinMain() 함수의주요매개변수에대해살펴보면 WINAPI 형은윈도우애플리케이션이라는의미이고첫번째매개변수인 hinstance은애플리케이션프로그램의 ID이다. 애플리케이션이구동되면윈도우시스템에서애플리케이션에 ID를부여한다. hprevinstance 매개변수는같은프로그램이이전에구동되었을때설정되는인스턴스의핸들인데사실이값은항상 NULL이다. 프로그램의중복실행을방지하기위해만든것이지만윈도우 95이후부터는사용하지않는다. lpszcmdline은프로그램을구동할때같이들어오는매개변수로실행파일의경로등을나타내는문자열포인터이다. ncmdshow는윈도우가 15
Visual C++ 2010 MFC 프로그래밍 처음화면에표시될때최대화, 최소화또는정상상태로보여줄것인지를결정해주는매개변수이다. 메시지를처리하는부분을담당하는 WndProc() 함수는윈도우시스템에서들어온메시지를 switch문을이용하여처리하는루틴이다. 함수이름에 Proc가붙으면주로메시지를처리하는함수로윈도우클래스마다필요하다. 다음은 WndProc() 함수의원형과메시지처리형태를보여주고있다. LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { switch(message) { 해당메시지에대한처리 } } LRESULT는결과값을저장하는 32비트자료형이다. CALLBACK 함수는뒤에서어떤메시지에의해감추어진형태로구동되는함수라는의미로역으로호출받는함수이다. WndProc() 함수는 WinMain() 함수에서직접호출하는코드는없다. WndProc() 함수는 CALLBACK 함수이므로 WinMain() 함수의 while 메시지루프에의하여뒤에서감추어진상태로구동된다. 실제적으로 WndProc() 함수를호출하는함수는메인메시지루프의 DispatchMessage() 함수이다. WndProc() 함수의매개변수를살펴보면첫번째매개변수 hwnd는윈도우의핸들이고, 두번째매개변수 message는 WinMain() 함수에서보내주는메시지이다. wparam와 lparam는메시지와함께필요한정보가들어오는매개변수이다. 실제적으로 Win32 SDK를이용하여간단한프로그램을만들어보자. 실습 1-1 간단한 Win32 SDK 윈도우프로그램만들기 이실습은화면에윈도우를생성하고윈도우에 I love Window Programming! 이라는텍스트를출력하고키보드이벤트에대한메시지를출력하는 Win32 SDK 기반의프로그램을작성하는것이다. 이실습을통해 Win32 SDK를이용한윈도우프로그램이어떻게만들어지고어떻게작동되는지를공부하게될것이다. Win32 SDK를이용한윈도우프로그램을완전히이해하면앞으로배우게될 MFC를사용한윈도우프로그램을작성하는데많은도움이될것이다. 16
윈도우프로그래밍의이해 01 Chapter Step 1) Win32 SDK 프로젝트를만든다. 1 Visual C++ 2010 을실행시켜서 [ 파일 ] 메뉴에서 [ 새로만들기 ]-[ 프로젝트 ] 를선택한다. 2 [ 새로만들기 ]-[ 프로젝트 ] 를선택하면다음과같은컨트롤시트가나온다. 좌측창의 Visual C++ 템플릿중에서 [Win32] 템플릿을선택한다. [Win32] 템플릿을선택하고오른쪽창에서 [Win32 프로젝트 ] 를선택한다. Win32 SDK를이용하여윈도우프로그램을작성한다는의미이므로반드시이항목을선택하여주어야한다. 만약이항목을선택하지않으면프로그램이제대로작동되지않는다. 그리고하단부에 [ 이름 ] 항목에프로젝트이름을 Practice1_1 라입력한다. 그러면 [ 위치 ] 에 default로현재 [Documents]-[Visual Studio 2010]-[Projects] 폴더에 Practice1_1이라는폴더가만들어진다. 다른곳에폴더를만들기를원하면 [ 위치 ] 옆의버튼을눌러원하는디렉터리로변경하면된다. [ 솔루션용디렉터리만들기 ] 항목의체크표시를없애면프로젝트를단순하게만들수있다. 여러개의프로젝트를추가하고자한다면체크표시를그대로둔후새로운프로젝트를추가해서생성할수있다. 여기서는간단하게프로젝트를생성하기위하여 [ 솔루션용디렉터리만들기 ] 항목의체크표시를해제해준다. 그리고버튼을누른다. 17
Visual C++ 2010 MFC 프로그래밍 3 버튼을누르면다음과같은대화상자가나온다. 여기서버튼을선택한후 [ 응용프로그램종류 ] 는 [Windows 응용프로그램 ] 을선택한다. [ 추가옵션 ] 에서 [ 빈프로젝트 ] 를반드시체크하고버튼을누르면프로젝트가만들어진다. 18
윈도우프로그래밍의이해 01 Chapter Step 2) 프로젝트에소스파일을삽입한다. 1 [ 솔루션탐색기 ] 의 [ 소스파일 ] 을오른쪽마우스를눌려나오는메뉴에서 [ 추가 ]-[ 새 항목 ] 을선택한다. 2 [ 추가 ]-[ 새항목 ] 을선택하면다음과같은컨트롤시트가나오는데왼쪽창에서 [ 코 드 ] 를선택한후오른쪽창에서 [C++ 파일 (.cpp)] 항목을선택한다. 그리고하단 부에 [ 이름 ] 항목에파일이름을 Practice1_1 라입력한다. 19
Visual C++ 2010 MFC 프로그래밍 3 버튼을누르면 Practice1_1.cpp 라는파일이프로젝트에삽입된다. 다음과 같이 Practice1_1.cpp 파일이생성되어있는지확인해보자. 20
윈도우프로그래밍의이해 01 Chapter Step 3) Practice1_1.cpp 파일에다음의소스코드를입력한다. 1 화면에윈도우를출력하고그윈도우에 I love Window Programming! 라는문자열을출력하고키보드를눌렀을때 키보드가눌러졌습니다. 라는문자열을출력하고키보드가떼어졌을때 키보드가떼어졌습니다. 란문자열을출력하는소스코드를입력한다. #include <windows.h> LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpszcmdline, int ncmdshow) { static LPTSTR szappname = "EasyText"; // 윈도우클래스이름 static LPTSTR sztitlename = "Practice1_1"; // 타이틀바에출력될문자열 HWND hwnd; // 윈도우핸들 MSG msg; // 메시지구조체 WNDCLASSEX WndClass; // 윈도우클래스구조체 // 1 윈도우클래스구조체 WndClass에값을채워윈도우클래스를등록한다. WndClass.cbSize = sizeof(wndclassex); // 구조체크기 WndClass.style = CS_HREDRAW CS_VREDRAW; // 클래스스타일 WndClass.lpfnWndProc = WndProc; // 윈도우프로시저 WndClass.cbClsExtra = 0; // 윈도우클래스데이터영역 WndClass.cbWndExtra = 0; // 윈도우의데이터영역 WndClass.hInstance = hinstance; // 인스턴스핸들 WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 아이콘핸들 WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // 커서핸들 WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 배경브러시핸들 WndClass.lpszMenuName = NULL; // 메뉴이름 WndClass.lpszClassName = szappname; // 윈도우클래스이름 WndClass.hIconSm = 0; // 기본적인작은아이콘 // 윈도우클래스를등록한다. RegisterClassEx(&WndClass); // 2 프레임윈도우를생성한다. hwnd = CreateWindow( szappname, sztitlename, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL ); // 윈도우생성 API 함수 // 등록된윈도우클래스이름 // 타이틀바에출력될문자열 // 윈도우스타일 // 윈도우좌측상단의 x좌표 // 윈도우좌측상단의 y좌표 // 윈도우의너비 // 윈도우의높이 // 부모윈도우의핸들 // 메뉴또는자식윈도우의핸들 // 애플리케이션인스턴스핸들 // 윈도우생성데이터의주소 21
Visual C++ 2010 MFC 프로그래밍 // 프레임윈도우를화면에표시한다. ShowWindow(hwnd, ncmdshow); UpdateWindow(hwnd); } // 3 메시지큐로부터메시지를받아와메시지를해당윈도우프로시저로보낸다. while(getmessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); // 메시지를번역한다. DispatchMessage(&msg); // 메시지를해당윈도우프로시저로보낸다. } return msg.wparam; LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { HDC hdc; // 디바이스컨텍스트 RECT rect; // RECT 구조체 PAINTSTRUCT ps; // 페인트구조체 LPTSTR szmsg1 = "I love Window Programming!"; // 윈도우에출력될문자열 LPTSTR szmsg2 = " 키보드가눌러졌습니다."; // 키보드를눌렀을때출력될문자열 LPTSTR szmsg3 = " 키보드가떼어졌습니다."; // 키보드를떼었을때출력될문자열 } // 1 커널에서들어온메시지를 switch 문을이용하여처리 switch(message) { case WM_CREATE : // 윈도우가처음생성메시지가온경우 break; case WM_PAINT : // 화면에출력메시지가온경우 hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 10, szmsg1, strlen(szmsg1)); // 윈도우에문자열을출력 EndPaint(hwnd, &ps); break; case WM_KEYDOWN : // 키보드버튼이눌린경우 hdc = GetDC(hwnd); GetClientRect(hwnd, &rect); DrawText(hdc, szmsg2, strlen(szmsg2), &rect, DT_SINGLELINE DT_CENTER DT_VCENTER); ReleaseDC(hwnd, hdc); break; case WM_KEYUP : // 키보드버튼이떼어진경우 hdc = GetDC(hwnd); GetClientRect(hwnd, &rect); DrawText(hdc, szmsg3, strlen(szmsg3), &rect, DT_SINGLELINE DT_CENTER DT_VCENTER); ReleaseDC(hwnd, hdc); break; case WM_DESTROY : // 프로그램종료메시지가온경우 PostQuitMessage(0); break; default : // 그외의메시지가온경우 return DefWindowProc(hwnd, message, wparam, lparam); } return 0; 22
윈도우프로그래밍의이해 01 Chapter Step 4) 프로그램을실행시켜보자. 1 키를누르거나 [ 빌드 ] 메뉴에서 [ 솔루션빌드 ] 를선택하여컴파일과링크를시 킨다. 2 빌드를하면다음과같은문자인코딩에관한에러메시지가발생한다. 이유는 Visual Studio에서는문자인코딩을설정할수있는데유니코드와멀티바이트로나뉘는데기본은유니코드형식으로되어있기때문이다. 유니코드와멀티바이트에대한설명은 2장에서자세히다룰것이다. 3 문자인코딩이유니코드로설정되어있는데다음과같은방식으로멀티바이트문 자인코딩으로바꾸면이에러는해결된다. [ 프로젝트 ] 메뉴에서 [Practice1_1 속성 ] 을선택한다. 23
Visual C++ 2010 MFC 프로그래밍 4 [Practice1_1 속성 ] 을선택하면다음과같은컨트롤시트가나오는데왼쪽창에서 [ 구성속성 ]-[ 일반 ] 을선택한후오른쪽창에서 [ 문자집합 ] 항목에서 [ 멀티바이트문자집합사용 ] 을선택한후버튼을누른후다시 키를누르거나 [ 빌드 ] 메뉴에서 [ 솔루션빌드 ] 를선택하여컴파일과링크를시킨다. 24
윈도우프로그래밍의이해 01 Chapter 5 + 를누르거나 [ 디버그 ] 메뉴에서 [ 디버깅하지않고시작 ] 을선택하여프로그 램을실행시키면다음과같은실행화면이나온다. 6 키보드에서아무키를눌렀다가떼어보면다음과같이중앙에메시지가나올것 이다. [ 키보드가눌린경우 ] [ 키보드가떼어진경우 ] 25
Visual C++ 2010 MFC 프로그래밍 1.3 간단한윈도우애플리케이션의분석 여기서 < 실습 1-1> 에서하나의윈도우를출력하고그윈도우에텍스트를출력하기위해서윈도우프로그램에대해아무것도모르면서 Practice1_1.cpp를작성하였다. 이제작성한윈도우프로그램을자세히분석해보자. 먼저프로그램의처음에다음과같이 windows.h 파일을인클루드한다. # include <windows.h> windows.h 파일은윈도우애플리케이션을작성하는데필요한매크로, 각종 API 함수, 메시지등이선언된헤더파일이다. 메시지와각종 API 함수를사용하기위해서는이 헤더파일을반드시윈도우애플리케이션에포함되어야한다. 1) WinMain() 함수모든윈도우애플리케이션은 WinMain() 함수를포함해야한다. 초기화부분을담당하는 WinMain() 함수는다음과같은세가지기본적인작업을수행한다. 1 운영체제에윈도우를등록한다. 2 메모리에프레임윈도우를생성하고속성을초기화하여화면에표시될수있도록한다. 3 메시지루프를생성하여해당윈도우에대한메시지를메시지큐로부터받아와메시지를해당윈도우프로시저로보낸다. 이들각각의작업에대해자세히살펴보자. 1 윈도우클래스를등록한다. 모든윈도우애플리케이션의시작점은 WinMain() 함수이다. WinMain() 함수는윈도우클래스구조체인 WNDCLASSEX 데이터구조체를생성하고구조체멤버에값을채워초기화한다음, RegisterClassEx() API 함수를호출하여운영체제에등록한다. 이데이터구조체는윈도우스타일, 메시지핸들러의주소, 인스턴스핸들, 윈도우배경색상, 애플리케이션아이콘, 그리고디폴트커서와같은윈도우의특징을정의한다. 즉윈도우클래스는윈도우의기본외관및특성을정의한것이다. 26
윈도우프로그래밍의이해 01 Chapter WNDCLASSEX WndClass; // 윈도우클래스구조체 // 윈도우클래스구조체 WndClass 에값을채워윈도우클래스를생성한다. WndClass.cbSize = sizeof(wndclassex); // 구조체크기 WndClass.style = CS_HREDRAW CS_VREDRAW; // 클래스스타일 WndClass.lpfnWndProc = WndProc; // 윈도우프로시저 WndClass.cbClsExtra = 0; // 윈도우클래스데이터영역 WndClass.cbWndExtra = 0; // 윈도우의데이터영역 WndClass.hInstance = hinstance; // 인스턴스핸들 WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 아이콘핸들 WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // 커서핸들 WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 배경브러시핸들 WndClass.lpszMenuName = NULL; // 메뉴이름 WndClass.lpszClassName = szappname; // 윈도우클래스이름 WndClass.hIconSm = 0; // 기본적인작은아이콘 // 윈도우클래스를등록한다. RegisterClassEx(&WndClass); WndClass 구조체멤버의값을설정할때가장유심히보아야할점은윈도우프로시 저를등록하는부분이다. WndProc() 함수를아직구현하지않았지만이함수를윈도우 프로시저로등록하였다. 2 프레임윈도우를생성하고화면에표시한다. 일단윈도우클래스가등록되면 WinMain() 함수는 CreateWindow() API 함수를호출하여애플리케이션의프레임윈도우를생성한다. CreateWindow() 함수는윈도우이름, 윈도우위치, 윈도우크기에대한정보를넘겨줌으로써윈도우의형태를좀더세부적으로정의한다. 첫번째인수는윈도우클래스에서정의한것과같은이름으로해야한다. // 프레임윈도우를생성한다. hwnd = CreateWindow( ); szappname, sztitlename, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL // 윈도우생성 API 함수 // 등록된윈도우클래스이름 // 타이틀바에출력될문자열 // 윈도우스타일 // 윈도우좌측상단의 x좌표 // 윈도우좌측상단의 y좌표 // 윈도우의너비 // 윈도우의높이 // 부모윈도우의핸들 // 메뉴또는자식윈도우의핸들 // 애플리케이션인스턴스핸들 // 윈도우생성데이터의주소 27
Visual C++ 2010 MFC 프로그래밍 윈도우를생성한후 ShowWindow() 함수와 UpdateWindow() 함수를호출하여윈도우를화면에나타나게한다. ShowWindow() 함수는윈도우를화면에보이거나감추는기능을하는함수이고, UpdateWindow() 함수는윈도우의외관을다시그려주는기능을하는함수이다. // 프레임윈도우를화면에표시한다. ShowWindow(hwnd, ncmdshow); UpdateWindow(hwnd); 3 메시지를윈도우프로시저로보낸다. 마지막으로메시지루프 (message loop) 는해당윈도우에대한메시지를메시지큐로부터받아와메시지를해당윈도우프로시저로보낸다. 이작업은 WinMain() 함수에서 while 루프를사용하여수행된다. // 메시지큐로부터메시지를받아와메시지를해당윈도우프로시저로보낸다. while(getmessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); // 가상키메시지를문자메시지로변환한다. DispatchMessage(&msg); // 메시지를해당윈도우프로시저로보낸다. } GetMessage() 함수는메시지큐에서메시지를꺼내와 MSG 데이터구조체에저장한다. TranslateMessage() 함수는가상키 (virtual-key) 메시지를문자메시지로변환한다. 그리고 DispatchMessage() 함수는내부적으로윈도우프로시저함수를호출하여윈도우프로시저함수에메시지를전달한다. WM_QUIT 메시지를받을때 GetMessage() 함수는 0 을리턴하며, WinMain() 함수가끝나면서프로그램이종료된다. 2) WndProc() 함수윈도우클래스를등록하는가장큰목적중의하나는윈도우를윈도우프로시저에연관시키는것이다. 윈도우프로시저는윈도우가클라이언트영역에표시해야할것과사용자입력에대하여반응하는방법을결정한다. 윈도우프로시저는윈도우시스템에서들어온메시지에대하여 switch문을이용하여메시지종류에따라적절한작업을처리하거나, 디폴트윈도우프로시저에메시지를넘겨줄수있다. 디폴트윈도우프로시저 DefWindowProc() 함수는윈도우시스템에서제공되며, 윈도 28
윈도우프로그래밍의이해 01 Chapter 우의아이콘표시나화면복귀, 전체화면표시, 메뉴리소스표시등일반적인 Win32 애플리케이션의많은행위를구현한다. 만약 DefWindowProc() 함수가메시지를처리하지않으면해당메시지는무시된다. < 실습 1-1> 에서윈도우프로시저는 WndProc() 함수이다. 윈도우프로시저는어떠한이름이라도가질수있다. 물론다른이름과중복되지않아야한다. 윈도우애플리케이션은각각다른이름을갖는하나이상의윈도우프로시저를포함할수있다. 일반적으로윈도우프로시저는 switch 문과 case문을이용하여메시지를처리한다. 이번실습에서 WndProc() 함수는다섯개의메시지를즉, WM_CREATE, WM_PAINT, WM_KEYDOWN, WM_KEYUP, WM_DESTROY를처리한다. 다른메시지가들어오면 DefWindowProc() 함수가호출된다. WM_CREATE 메시지는윈도우가처음생성될때발생하는메시지이고, WM_PAINT 메시지는윈도우를다시그려야할때발생하는메시지이다. 그리고 WM_KEYDOWN 메시지와 WM_KEYUP 메시지는각각키보드의키하나를누르거나뗄때발생하는메시지이다. 마지막으로 WM_DESTROY 메시지는윈도우가종료될때발생하는메시지이다. 특정메시지가언제발생하고, 어떻게메시지를처리해야지어떤결과를얻는지아는것이윈도우프로그래밍을배우는것이라할수있다. LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { HDC hdc; // 디바이스컨텍스트 RECT rect; PAINTSTRUCT ps; LPTSTR szmsg1 = "I love Window Programming!"; LPTSTR szmsg2 = " 키보드가눌러졌습니다."; LPTSTR szmsg3 = " 키보드가떼어졌습니다."; // RECT 구조체 // 페인트구조체 // 윈도우에출력될문자열 // 키보드를눌렀을때출력될문자열 // 키보드를떼었을때출력될문자열 // 1 커널에서들어온메시지를 switch 문을이용하여처리 switch(message) { case WM_CREATE : // 윈도우가처음생성메시지가온경우 break; case WM_PAINT : // 화면에출력메시지가온경우 hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 10, szmsg1, strlen(szmsg1)); // 윈도우에텍스트를출력 EndPaint(hwnd, &ps); break; case WM_KEYDOWN : // 키보드버튼이눌린경우 hdc = GetDC(hwnd); GetClientRect(hwnd, &rect); DrawText(hdc, szmsg2, strlen(szmsg2), &rect, DT_SINGLELINE DT_CENTER DT_VCENTER); ReleaseDC(hwnd, hdc); break; 29
Visual C++ 2010 MFC 프로그래밍 } case WM_KEYUP : // 키보드버튼이떼어진경우 hdc = GetDC(hwnd); GetClientRect(hwnd, &rect); DrawText(hdc, szmsg3, strlen(szmsg3), &rect, DT_SINGLELINE DT_CENTER DT_VCENTER); ReleaseDC(hwnd, hdc); break; case WM_DESTROY : // 프로그램종료메시지가온경우 PostQuitMessage(0); break; default : // 그외의메시지가온경우 return DefWindowProc(hwnd, message, wparam, lparam); } return 0; 3) 윈도우에문자열을출력하는방법윈도우에문자열을출력하기위해서는반드시디바이스컨텍스트핸들을얻어와야한다. WndProc() 함수에서 WM_PAINT 메시지경우에는 BeginPaint() 함수를사용하여디바이스컨텍스트를얻었고, WM_KEYDOWN 과 WM_KEYUP 메시지경우에는 GetDC() 함수를사용하여컨텍스트를얻었다. 디바이스컨텍스트를얻는방법은 4가지방법이있는데이방법들은 6장그래픽처리에서자세히다룰것이다. 지금은디바이스컨텍스트를통해서문자열을출력한다는정도만이해하면된다. 디바이스컨텍스트를얻은후윈도우에문자열을출력하기위해서는 TextOut() 함수나 DrawText() 함수를사용하면된다. TextOut() 함수는화면의지정된위치에문자열을출력하는함수이고, DrawText() 함수는영역을정하고이영역에출력형식에맞게문자열을출력하는함수이다. 윈도우의클라이언트영역의크기를알아내는함수는 CWnd 클래스의 GetClientRect() 멤버함수이다. 이함수는윈도우의클라이언트영역을 RECT 구조체로반환한다. TextOut() 함수 TextOut() 함수는화면의지정된위치에기본적인문자열을출력하는함수이다. 함수의원형은다음과같다. BOOL TextOut(HDC hdc, int nxstart, int nystart, LPCTSTR lpstring, int cbstring); hdc nxstart nystart lpstring cbstring : 디바이스컨텍스트핸들 : 문자열출력시작점 X 좌표 : 문자열출력시작점 Y 좌표 : 출력할문자열 : 문자열의길이 30
윈도우프로그래밍의이해 01 Chapter DrawText() 함수 DrawText() 함수는화면에문자열을출력할때어느영역을정하고이영역에출력형식에맞게문자열을출력하는함수이다. 함수의원형은다음과같다. BOOL DrawText(HDC hdc, LPCSTR lpstring, int nlength, LPRECT lprect, UINT Flags); hdc : 디바이스컨텍스트핸들 lpstring : 출력할문자열 nlength : 문자열의길이 lprect : 출력할영역의주소 Flags : 출력형식플러그이고, 설정할수있는플러그의값은다음과같다. 플러그값 내용 DT_TOP DT_BOTTOM DT_LEFT DT_RIGHT DT_CENTER DT_VCENTER DT_CALCRECT DT_SINGLELINE DT_NOCLIP DT_EXPANDTABS 설정된영역의상단으로정렬 설정된영역의하단으로정렬 설정된영역의좌측으로정렬 설정된영역의우측으로정렬 (DT_SINGLELINE 과함께지정되어야한다 ) 설정된영역의가로중앙에정렬 설정된영역의세로중앙에정렬 (DT_SINGLELINE 과함께지정되어야한다 ) 문자열을출력할사각형의영역을계산행바꿈과라인피드를무시하고한줄로출력클리핑없이문자를출력문자열에탭이포함되어있을때공백을출력 4) MFC에서의 WinMain() 함수와윈도우프로시저 Win32 SDK를이용하여작성한모든윈도우프로그램과마찬가지로 MFC 애플리케이션도 WinMain() 함수를갖는다. 그러나프로그래머는 MFC 애플리케이션에 WinMain() 함수를작성할필요가없다. 이함수는프레임워크 (Framework) 에서제공되며애플리케이션이시작될때호출된다. MFC는클래스가생성하는대부분의메시지를처리하는내부적인메시징시스템을갖고있다. 그러나 MFC안에서메시지를처리할수없을때애플리케이션은디폴트윈도우프로시저, 즉 DefWindowProc() 함수를호출해서해당메시지를처리한다. 이렇게 MFC에서는 WinMain() 함수와윈도우프로시저가프레임워크안에숨어있기때문에이러한개념을모르는상태에서처음 MFC를배우려고하는사람들은윈도우프로그램을작성하는데상당히어려움을겪게된다. 31
Visual C++ 2010 MFC 프로그래밍 1.4 윈도우프로그래밍의기본형식 C/C++ 언어를배운사람이라도 C/C++ 언어를이용한 Win32 SDK로작성된윈도우프로그램을보면무슨내용인지하나도모를것이다. 왜냐하면 C/C++ 언어의표준자료형은없고처음보는자료형들이많이나오기때문이다. 이러한자료형들은 windows.h에정의된윈도우프로그램을위한구조체들이다. 윈도우프로그램에서 H로시작하지않는자료형은모두구조체로생각하면된다. 모르는구조체의내용을알고자한다면간단하게그구조체에커서를놓고 을눌러도움말을이용하면된다. 윈도우프로그래밍을할때도움말을참조하는것은필수적이다. 또한윈도우프로그램을이해하는데가장중요한것중의하나는핸들의개념이다. 윈도우에서모든자원은핸들이관리한다. 핸들이란자원을식별하기위한정수번호이다. 윈도우는핸들이라는번호를윈도우시스템이관리하는모든자원에부여한다. 쉽게설명하면윈도우프로그램은앞에서언급한구조체를이용해모든것을객체화하였다. 이객체들을조정하기위해핸들이필요하다. 자동차나자전거를조종하기위해핸들이필요한것과같은개념이라생각하면된다. Win32 SDK에서는핸들자료형은앞에 H가붙는다. 예를들면 < 실습 1-1> 에서 WndProc() 함수의매개변수에서 hwnd가윈도우에대한핸들이라생각하면된다. 윈도우프로그램은핸들을받거나만들어서사용한다고생각하면된다. 1) 새로운데이터형식 MS-DOS 상에서 C/C++ 언어를공부한사람들은 Practice1_1과같은기본적인윈도우프로그램에서사용되는여러가지데이터형식들이생소할것이다. 그이유는 MS-DOS 와는다른운영체제인윈도우라는인터페이스덕분에여러가지자료형들이정의되었기때문이다. 그럼앞에서작성한프로그램에서나오는몇몇데이터형식에대해서알아보도록하자. 1 MSG 구조체 MSG 구조체는메시지큐에저장되는메시지정보를담고있는구조체이다. WinMain() 함수에서메시지루프를보면, GetMessage() 함수를볼수있다. 이함수는메시지큐에서메시지를가져와서 MSG 구조체에메시지정보를입력하게된다. 그러고나서윈도우프로시저가이메시지를처리하게된다. 32
윈도우프로그래밍의이해 01 Chapter 이구조체는 windows.h 파일에다음과같이정의되어있다. typedef struct tagmsg { HWND hwnd; UINT message; WPARAM wparam; LPARAM lparam; DWORD time; POINT pt; } MSG; hwnd : 이메시지를받아서처리할윈도우에대한핸들을나타낸다. message : 발생된메시지를가지고있으며, 내부적으로정수형으로정의되어있다. wparam : 메시지에대한추가적인정보를담고있으며, 이내용은메시지의종류에따라다른의미를가질수있다. lparam : 메시지에대한추가적인정보를담고있으며, 이내용은메시지의종류에따라다른의미를가질수있다. time : 메시지가발생한시간을담고있다. 우리가생각하는시간이아니라시스템의시간이다. pt : 메시지가발생했을때, 화면상의스크린좌표를담고있다. 2 WNDCLASSEX 구조체 이구조체에는윈도우속성에대한정보를포함한다. 이데이터형식또한 windows.h 파일에정의되어있으며, 다음과같이정의되어있다. typedef struct tagwndclassex { UINT cbsize; UINT style; WNDPROC lpfnwndproc; int cbclsextra; int cbwndextra; HANDLE hinstance; HICON hicon; HCURSOR hcursor; HBRUSH hbrbackground; LPCTSTR lpszmenuname; LPCTSTR lpszclassname; HICON hiconsm } WNDCLASSEX; 33
Visual C++ 2010 MFC 프로그래밍 cbsize : 구조체의크기를나타낸다. style : 윈도우의스타일을지정한다. 정수값의조합으로지정된다. lpfnwndproc : 윈도우프로시저에대한포인터를지정한다. cbclsextra : 윈도우클래스의데이터영역을나타낸다. cbwndextra : 윈도우의데이터영역을나타낸다. hinstance : 프로그램자체에대한즉, 인스턴스에대한핸들을지정한다. hicon : 이윈도우에서사용될아이콘에대한핸들을지정한다. hcursor : 이윈도우에서사용할커서에대한핸들을지정한다. hbrbackground : 윈도우의백그라운드브러시에대한핸들을지정한다. lpszmenuname : 윈도우에서메뉴의이름을지정하며, 리소스에서사용된다. lpszclassname : 윈도우클래스의이름을명시한다. hiconsm : 기본적인작은아이콘에대한핸들을지정한다. 3 PAINTSTRUCT 구조체 이구조체는텍스트나이미지를윈도우의클라이언트영역에그리고자할때, 사용할 정보를포함하며, 다음과같이정의되어있다. typedef struct tagpaintstruct { HDC hdc; BOOL ferase; RECT rcpaint; BOOL frestore; BOOL fincupdate; BYTE rgbreserved[16]; } PAINTSTRUCT; hdc : 디스플레이컨텍스트에대한핸들을지정한다. ferase : 윈도우의백그라운드를다시그릴지지정한다. rcpaint : 그리고자하는영역을사각형구조체를이용해지정한다. frestore : 시스템에예약되어있으며, 내부적으로이용된다. fincupdate : 시스템에예약되어있으며, 내부적으로이용된다. rgbreserved : 시스템에예약되어있으며, 내부적으로이용된다. 34
윈도우프로그래밍의이해 01 Chapter 4 RECT 구조체 이구조체는사각형형태의좌표를지정하며, 왼쪽상단좌표와오른쪽하단좌표를 저장한다. typedef struct tagrect { LONG left; LONG top; LONG right; LONG bottom; } RECT; left : 사각형영역의왼쪽좌표를명시한다. top : 사각형영역의위쪽좌표를명시한다. right : 사각형영역의오른쪽좌표를명시한다. bottom : 사각형영역의아래쪽좌표를명시한다. 2) 헝가리언표기법 (Hungarian Notation) 위에서작성한프로그램을살펴보면, 특이하게지정된변수이름들을볼수있을것이다. 좀이상하게생각되어도나름대로규칙이있게명시되어있는것이다. 꼭이러한변수명명법을사용하라는뜻은아니지만, 다수의윈도우프로그래머들이이러한방법으로프로그래밍하고있으며, 지금까지개발된라이브러리또한이런변수명명법을따르고있다. 또한일정한변수명명법을사용하면, 변수에대한이해를쉽게할뿐아니라, 다른개발자들과프로그래밍언어로써대화가쉬워질것이다. 헝가리언표기법 (Hungarian Notation) 이란 Microsoft의헝가리프로그래머인 Charles Simonyi를기리는뜻으로붙여진이름이며, 변수이름은그데이터형식을의미하는하나이상의소문자로시작한다. 헝가리언표기법은작성한프로그램에서오류나버그가발생하기전에에러를방지할수있게해준다. 왜냐하면, 변수의사용과더불어데이터형식을기술해줌으로써, 데이터형식불일치같은것을포함해코딩오류를사전에방지할수있기때문이다. 다음에제시하는 [ 표 1-1] 은일반적인변수에대한접두어를나타내고있다. 35
Visual C++ 2010 MFC 프로그래밍 [ 표 1-1] 헝가리언표기법에서의접두어 접두어 데이터형식 c by i or n f d b w l dw fn s sz h p char BYTE(unsigned char) int float double BOOL(int) WORD(unsigned long) LONG(long) DWORD(unsigned long) function string null 문자로끝나는문자열 handle pointer 3) 윈도우시스템의이해우리는윈도우라는운영체제에서애플리케이션을개발하고자지금까지공부를했고, 또공부할것이다. 그렇다면, 우리가사용하고있는윈도우라는운영체제에대해좀더이해하고있다면, 더욱뛰어난개발자가될수있다고생각한다. MS-DOS상에서 C 또는 C++ 를이용해프로그래밍하던시절을생각해보자. 그때시절과지금을비교한다면, 너무나도많은것이바뀌었고, 특히프로그램을하는방법이변했다는것을느낄수있을것이다. 그러므로우리가윈도우프로그래밍전문가가되기위해서는윈도우시스템의특징들을확실히이해하여야한다. 이번에는윈도우시스템을개발자의입장에서간단히이해하고배워보기로한다. 1 윈도우는 GUI(Graphic User Interface) 이다. MS-DOS 시절로돌아가생각해보면, 컴퓨터와대화할수있는방법이라고는기본적으로도스커맨드라인과기타입력장치등이있었다. 하지만, 윈도우는그래픽을이용한인터페이스를구현하면서사용자가좀더쉽게컴퓨터를이용하게했으며, 응용프로그램과사용자사이의대화를친밀하게만들었다. 36
윈도우프로그래밍의이해 01 Chapter 또한윈도우는일관된사용자인터페이스를제공한다. 이이야기는윈도우기본프로그램들을다룰줄알고있다면, 다른애플리케이션을기본적으로사용하는데어려움이덜하다는이야기이다. 참으로윈도우는사용자로볼때, 많은것을고려한운영체제라할수있다. 그러면개발자의입장에서생각해보자. 윈도우가 GUI를기본바탕으로하고있기때문에개발자또한도스시절보다좀더보기좋고, 사용이편리한인터페이스를구현할수있다. 반면, 이러한인터페이스는구현하기위한노고도만만치않을것이다. 그리고윈도우는자체적으로공통된인터페이스를 Win32 API에서제공해주기때문에다른응용프로그램들과유사한인터페이스를구현할수있다. 2 윈도우는멀티태스킹을지원한다. 멀티태스킹이란동시에여러가지작업을할수있다는것이다. 예를들면, 컴퓨터로음악을들으면서, Visual C++ 에서프로그램을작성할수있다. 이것이바로멀티태스킹이다. 윈도우초기에는비선점형멀티태스킹을지원했다. 이는여러개의프로그램에대해처리시간을할당하기위해시스템타이머를사용하지않았다는것을의미한다. 하지만, 현재는선점형멀티태스킹을하고있다. 즉, 프로그램자체가병행으로실행되는것처럼보이는 Multi Processing이라는것이다. 개발자입장에서이런면들을생각해보자. 우리가애플리케이션을제작하면, 윈도우시스템자체가제작한프로그램을멀티태스킹으로수행한다. 하지만, 이러한윈도우시스템을이용해한응용프로그램에서작업을세분화해서수행함으로써응용프로그램의퍼포먼스를향상시킬수있다. 이것이바로 Multi Threading 기술이다. 3 윈도우는메시지기반구조 (Message-Driven Architecture) 이다. 윈도우시스템의모든애플리케이션은메시지를기반으로하여구동된다. 예를들면, 화면의윈도우크기를사용자가줄이기나늘리면윈도우시스템은해당애플리케이션으로윈도우크기변경 (WM_SIZE) 메시지를보내게된다. 그리고이메시지를받은애플리케이션은이에대응하는처리를하게되는것이다. 여기서꼭알아야하는것은윈도우가애플리케이션으로메시지를보낸다는점이다. 우리가작성한애플리케이션이윈도우시스템에메시지를보내는것이아니라는점을기억하기바란다. 그뜻은애플리케이션내의어떤함수를윈도우시스템이호출한다는것이며, 이함수의인자는특정메시지를의미하는것으로각자의애플리케이션에위치한이함수는윈도우프로시저라하는것이다. 결론적으로메시지가발생하면, 윈도우시스템이메시지에해당되는애플리케이션의윈도우프로시저를호출한다는것이다. 37
Visual C++ 2010 MFC 프로그래밍 4 윈도우는윈도우프로시저 (Window Procedure) 를호출한다. 프로그램이운영체제를호출한다는것은잘알고있을것이다. 도스시절에는프로그램이시스템에인터럽트라는것을걸어서시스템을호출한뒤시스템자원을이용했기때문이다. 하지만윈도우시스템은반대로운영체제가프로그램을호출한다. 즉, 윈도우가애플리케이션의윈도우프로시저를호출한다는얘기이다. 모든윈도우애플리케이션은윈도우프로시저를가지고있고, 윈도우프로시저는프로그램자체또는동적연결라이브러리에존재하는함수이다. 윈도우시스템은윈도우프로시저를호출하여메시지를윈도우에보내게되며, 윈도우프로시저는메시지에따른처리를한다음제어권을윈도우시스템으로반환한다. 애플리케이션이실행될때즉, 메모리에상주될때, 애플리케이션에대한메시지큐 (Message Queue) 를생성한다. 이메시지큐는윈도우시스템의모든메시지를저장하게된다. 애플리케이션에서메시지큐에저장된메시지를꺼내어적절한윈도우프로시저에전달하는메시지루프를가지고있고, 이루프를통해윈도우프로시저가메시지를처리한다. 하지만, 어떤메시지들은메시지큐에들어가지않고, 직접윈도우프로시저로보내지는것들도있다. 38
윈도우프로그래밍의이해 01 Chapter 연습문제 01. < 실습 1-1> 에서작성한 < 간단한 Win32 SDK 윈도우프로그램 > 을수정하여다음과같은기능을추가할수있도록구현해보자. 생성된윈도우에서마우스버튼을눌렀을경우와마우스가이동중일때윈도우의중앙에문자열을출력해보자. 마우스왼쪽버튼을눌렀을경우에는 마우스가눌러졌습니다. 라는문자열을, 마우스가이동중일경우는 마우스가이동중입니다. 라는문자열을출력한다. 왼쪽마우스버튼을떼었을경우는출력된문자열을지운다. 1) 마우스를제어하기위해서는마우스에대한메시지를먼저알아야할것이다. WM_LBUTTONDOWN WM_LBUTTONUP WM_MOUSEMOVE 마우스왼쪽버튼이눌러졌을때발생하는메시지 마우스왼쪽버튼이떼어졌을때발생하는메시지 마우스가이동중일때발생하는메시지 2) 이러한메시지들에대한처리를윈도우프로시저의 switch 문과 case 문에삽입하여코딩하면된다. 그리고문자열을출력하기위해서는디바이스컨텍스트핸들을얻어와야한다. < 실습 1-1> 의 WM_KEYDOWN 경우에는 GetDC() 함수를이용하였다. 이번연습문제에서도 GetDC() 함수를이용해 DC 핸들을얻고그 DC를이용하여문자열을출력한다. 그리고 DC를사용하고난후에 ReleaseDC() 함수를사용하여 DC를반드시해제해주어야한다. case 문에사용될대략적인코딩은다음과같다. hdc = GetDC(hwnd); // DC를사용한문자열출력루틴삽입 ReleaseDC(hwnd, hdc); 3) 그리고, 화면중앙에문자열을출력하기위해서는그리고자하는영역의사각형구조체좌표를구해 DrawText() 함수를이용해문자열을출력하면된다. < 실습 1-1> 의 WM_KEYDOWN 경우와같이 GetClientRect() 함수를사용하여그리고자하는사각형구조체좌표를구한다. 참고로우리가출력하고자하는영역은윈도우클라이언트영역이될것이다. GetClientRect(hwnd, &rect); 39
Visual C++ 2010 MFC 프로그래밍 4) 왼쪽마우스버튼을떼었을때는어떤문자열을출력하는것이아니라출력된문자열을지워야한다. 왼쪽마우스버튼을떼었을경우출력된문자열을지우기위해서는 Win32 SDK API 함수인 InvalidateRect() 함수를호출하여윈도우의클라이언트영역을다시그리게한다. InvalidateRect(hwnd, NULL, TRUE); InvalidateRect() 함수 InvalidateRect() 함수는윈도우의클라이언트영역을다시그리는함수이다. 이함수는 WM_PAINT를직접호출하진않으나특정영역을윈도우의 update region에더해시스템이 WM_PAINT를호출하도록한다. 시스템은 update region을확인해서비어있지않으면 WM_PAINT 를호출한다. 중요한것은 WM_PAINT를직접호출하지않고시스템이메시지큐에있는다른메시지를다처리한후에 WM_PAINT 가발생하므로 InvalidateRect 호출시바로업데이트가되지않을수있다는것이다. 함수의원형은다음과같다. BOOL InvalidateRect(HWND hwnd, CONST RECT *lprect, BOOL berase); hwnd : 다시그려질영역을설정할윈도우핸들이다. lprect : 다시그려질사각영역의좌표를포함하는 CRect 객체나 RECT 구조체의포인터. 만약 NULL로지정되면전체클라이언트영역을사용하게된다. berase : 다시그려질영역이백그라운드인지를명시. TRUE는원래그려져있는것을지우고그리라는의미이다. 5) 위와같은내용을참고로하여, 다음과같이실행되는프로그램을작성해보기바 란다. [ 마우스가눌린경우 ] [ 마우스가이동하는경우 ] 40
윈도우프로그래밍의이해 01 Chapter 02. 위에서작성한 < 연습 1-1> 에이어서마우스왼쪽버튼에대해다음과같은기능이추가되도록구현해보자. 생성된윈도우에서마우스가눌러졌을경우 마우스가눌러졌습니다. 라는텍스트와함께마우스가눌린점의좌표값을마우스를눌러진위치에출력해보도록하자. 1) 좌표값을얻어오기위해서는 POINT 구조체를알아야한다. POINT 구조체는한점의 x, y 좌표를정의하는구조체이다. 따라서왼쪽마우스버튼을눌렀을때그위치를저장하는변수를 POINT 구조체를사용하여구조체변수를다음과같이선언한다. POINT MousePoint; POINT 구조체한점의 x 좌표와 y 좌표를정의하는구조체이다. typedef struct tagpoint { LONG x; LONG y; } POINT; x : x 좌표값 y : y 좌표값 2) 마우스버튼을눌러졌을때좌표값은 WndProc() 함수의 lparam 매개변수를통해 전달된다. x 좌표값은 LOWORD 매크로, y 좌표값은 HIWORD 매크로를사용하 여마우스좌표값을다음과같이얻을수있다. MousePoint.x = LOWORD(lParam); MousePoint.y = HIWORD(lParam); 3) POINT 구조체로좌표값을얻어오게되면윈도우에이데이터를출력해야한다. 윈도우에출력하기위해서는 wsprintf() 함수를이용하여우선문자열에원하는형 식에맞게출력을한다. wsprintf(szmsg6, "X:%ld, Y:%ld", MousePoint.x, MousePoint.y); 41
Visual C++ 2010 MFC 프로그래밍 4) 마우스가눌린위치에좌표값을출력하기위해서는 wsprintf() 함수로만든문자 열을 TextOut() 함수를이용하여윈도우에출력한다. TextOut(hdc, MousePoint.x, MousePoint.y, szmsg6, strlen(szmsg6)); 5) 위와같은내용을참고로하여, 다음과같이실행되는프로그램을작성해보기바 란다. [ 마우스가눌린경우 ] 42
찾아보기 찾아보기 ᄀ 고유 ID 178 공용대화상자 215 ᄂ 날짜 / 시간선택컨트롤 521 네트워크주소컨트롤 520 논리적인폰트 352 ᄃ 다중뷰 694 단축키 268 대화상자 165 데이터베이스 763 도큐먼트 629 도킹팬 (Docking Pane) 윈도우 308 동적분할윈도우 666 ᄅ 래스터오퍼레이션 349 러버밴드 355 리본 (Ribbon) 581 리본디자이너 586 리본메뉴 582 리소스뷰 62 리스트컨트롤 427 멀티태스킹 37 ᄆ 마우스메시지 122 멀티바이트인코딩 51 멀티태스킹 70 메뉴 251 메시지맵 110 메시지박스 112 메시지처리 110 메시지핸들러 111 명령메시지 110 모달대화상자 214 모덜리스대화상자 214 문맥메뉴 252 물리적인폰트 352 ᄇ 버디컨트롤 490 베지어곡선 823 분할윈도우 666 브러시 (Brush) 345 비트맵 (Bitmap) 350 ᄉ 사용자인터페이스 581 삽입 (<<) 연산자 630 상태표시줄 296 솔루션탐색기 61 스레드 70 스핀컨트롤 490 슬라이더컨트롤 490 ᄋ 애니메이션컨트롤 522 액세스키 268 에디터의사용법 63 윈도우메시지 110 윈도우프로그래밍 13 유니코드인코딩 51 908
INDEX 유니코드 52 응용프로그램마법사 48 인스턴스 64, 68 ᄌ 전방참조 329 정규 DLL 719 정적분할윈도우 666 ᄎ 참조추가 734 추출 (>>) 연산자 630 ᄏ 캐스케이딩메뉴 252 컨트롤통지메시지 110 컨트롤포인트 823 컬러대화상자 219 클래스마법사 48, 78 클래스뷰 61 키보드메시지 139 풀다운메뉴 251 프로그레스바 519 ᄒ 헝가리언표기법 35 확장 DLL 722 A Accept() 888 ActiveX controls 란 59 AFX_EXT_CLASS 720 AfxGetApp() 64, 68 AfxGetMainWnd() 65, 68 AfxMessageBox() 112 Animation Control 522 Application Button 583 AtlTraceErrorRecords() 790 B Brush 클래스 384 Button Control 169 ᄐ 탭컨트롤 489 템플릿 64 툴바 288 트리컨트롤 459 ᄑ 파일대화상자 215 팝업메뉴 252 펜 (Pen) 344 폰트 (Font) 352 폰트대화상자 218 폼뷰 634 폼뷰기반의프로젝트와대화상자기반의 프로젝트의차이점 636 C CALLBACK 16 CArchive 630 CAsyncSocket 873 Category 583 CBitmap::LoadBitmap() 377 CBrush::CreateHatchBrush() 346 CBrush::CreateSolidBrush() 346 CClientDC 343 CCmdTarget 70 CCmdUI 284 CColorDialog 219 CComboBox::AddString() 193 CComboBox::GetCurSel() 205 CComboBox::GetLBText() 246 CComboBox::ResetContent() 193 909
찾아보기 CCommand::MoveFirst() 797 CCommand::MoveLast() 797 CCommand::MoveNext() 791 CCommand::MovePrev() 798 CCreateContext 704 CDataSource::OpenFromInitializationString() 786 CDC::AlphaBlend() 378 CDC::BitBlt() 377 CDC::DrawText() 100 CDC::FillSolidRect() 572 CDC::SetBkColor() 572 CDC::StretchBlt() 378 CDC::TextOut() 83 CDialog 72 CDialogEx 166 CDockablePane::Create() 320 CDocument 72, 629 CFileDialog 216 CFont::CreateFontIndirect() 572 CFontDialog 218 CFrameWnd 71 Check Box 169 ClientToScreen() 363 ClipCursor() 362 CListBox::AddString() 195 CListBox::DeleteString() 206 CListBox::GetCount() 192 CListBox::GetCurSel() 208 CListBox::GetText() 205 CListCtrl::DeleteItem() 453 CListCtrl::GetItemCount 443 CListCtrl::GetItemText() 447 CListCtrl::InsertColumn() 440 CListCtrl::InsertItem() 443 CListCtrl::SetExtendedStyle() 440 CListCtrl::SetItemText() 453 CMDIChildWnd 48 CMDIFrameWnd 47 CMFCPropertyGridColorPropertyl:: 570 CMFCPropertyGridCtrl::AddProperty() 569 CMFCPropertyGridCtrl::EnableDescriptionArea() 569 CMFCPropertyGridCtrl::EnableHeaderCtrl() 569 CMFCPropertyGridCtrl::RemoveAll() 568 CMFCPropertyGridCtrl::SetVSDotNetLook() 568 CMFCPropertyGridProperty::AddOption() 569 CMFCPropertyGridProperty::AddSubItem() 569 CObject 69 COLORREF 데이터형과 RGB 매크로 359 Combo Box 169 Connect() 892 CPen::CreatePen() 345 CPoint 104 CProgressCtrl::SetPos() 533 CProgressCtrl::SetRange() 533 Create() 889 CreateCompatibleDC() 351 CreateFontIndirect() 352 CreateStatic() 679 CreateView() 679 CreateWindow() 27 CRect::SetRectEmpty() 566 CRect 100 CSliderCtrl::GetPos() 515 CSocket 873 CStatusBar::SetPaneText() 304 CString 77 CString::Delete() 147 CString::Empty() 203 CString::GetLength() 147 CString::IsEmpty() 203 CString::ReverseFind() 786 CTime 132 CTime::GetCurrentTime() 132 CTreeCtrl::DeleteItem() 479 CTreeCtrl::Expand() 473 CTreeCtrl::GetItemText() 471 CTreeCtrl::InsertItem() 469 CTreeCtrl::SetItemText() 476 CView 72 CWinApp 71 CWindowDC 343 CWinThread 70 910
INDEX CWnd 71 CWnd::GetDlgItemInt() 327 CWnd::GetWindowRect() 242 CWnd::GetWindowRect() 512 CWnd::MoveWindow() 242 CWnd::ShowWindow() 242 EnableAutomaticButton() 570 D DAO 766 Date Time Picker 521 DBMS 765 DC 342 DDX 186 DefWindowProc() 28 DeleteObject() 344 DispatchMessage() 28 DLL 719 DoDataExchange() 167 DoModal() 214 DrawBezier 387 DrawCurve 387 DrawEllipse 386 DrawLine() 385 DrawPolygon 386 DrawRectangle 386 DrawString() 388 DrawText() 31 E Ellipse 348 event driven 13 Explicit 링킹 718 ExtTextOut() 353 F Format 77 FreeLibrary() 720 G GDI 341 GDI 객체 343 GDI+ 381 GetActiveDocument() 65, 69 GetActiveView() 65, 68 GetClientRect() 100 GetColor() 220 GetDC() 342 GetMessage() 28 GetModuleFileName() 784 GetProcAddress() 720 GetStockObject() 350 GetTextExtent(str) 423 GetWindowDC() 343 GetWindowLong() 458 Graphics 클래스 382 Group Box 169 H HatchBrush 클래스 384 I Implicit 링킹 719 Invalidate() 82 InvalidateRect() 40 IP 주소컨트롤 519 IsBOF() 797 IsEOF() 797 IsStoring() 630 K KillTimer() 137 L LinearGradientBrush 클래스 385 LineTo() 347 911
찾아보기 List Box 169 List Control 427 Listen() 889 LoadLibrary() 720 LV_COLUMN 439 LV_ITEM 443 Pen 클래스 383 POINT 41 PolyBezier() 349 Polygon() 348 Polyline() 348 PtInRect() 863 M MDI 66 message driven 13 MFC 45 MFC 기본컨트롤 168 MFC Edit Browse Control 547 MFC Feature 컨트롤 545 MFC Font ComboBox Control 548 MFC Masked Edit Control 547 MFC Property Grid Control 546 MFC Shell List 546 MFC Shell Tree 546 MFC VSListBox 547 MoveTo() 347 MSG 32 Q Quick Access Toolbar 583 R Radio Button 169 ReadObject() 630 RecalcLayout() 703 Receive() 893 RECT 35 Rectangle 347 ReleaseCapture() 362 ReleaseDC() 342 Ribbon Bar 583 Ribbon Base Element 582 Ribbon Designer 585 O ODBC 766 OLE 54 OLE DB 767 OnCreateClient() 678 OnDraw() 83 OnInitDialog() 167 OnNewDocument() 629 OnOpenDocument() 632 OnSaveDocument() 632 OnTimer() 536 P PAINTSTRUCT 34 Panel 583 S ScreenToClient() 513 SDI 64 SelectObject() 344 Send() 894 Serialize() 631 SetActiveView() 703 SetBkColor() 353 SetBkMode() 353 SetCapture() 362 SetDashStyle() 383 SetDlgCtrlID() 703 SetROP2() 349 SetTextAlign() 353 SetTextColor() 353 SetTextRenderingHint 389 912
INDEX SetTimer() 128 SetWindowLong() 458 SetWindowText() 307 ShowWindow() 28 Slider Control 490 SolidBrush 클래스 384 Spin Control 490 Static Text 168, 169 T Tab Control 489 TabbedTextOut() 353 TC_ITEM 506 TextOut() 30 TranslateMessage() 28 Tree Control 459 TV_INSERTSTRUCT 469 U UpdateData() 203 UpdateWindow() 28 W Win32 SDK 15 WINAPI 15 WinMain() 26 WNDCLASSEX 33 WndProc() 28 WriteObject() 630 913
저자약력 정일홍 애리조나주립대학교컴퓨터공학과공학박사현재 ) 대전대학교컴퓨터공학과교수 저자와의협의에의해인지를생략합니다. 단계별로쉽게배우는 Visual C++ 2010 MFC Programming 정일홍지음 초판인쇄 : 초판발행 : 발행인 : 발행처 : 신고번호 : 신고일자 : I S B N : 2013. 2. 18 2013. 2. 22 김승기생능출판사제 406-2005-000002 호 2005. 1. 21 978-89-7050-743-9(93000) - 경기도파주시문발동 507-12 파주출판도시대표전화 : (031) 955-0761 FAX : (031) 955-0768 홈페이지 : http://www.booksr.co.kr 파본및잘못된책은바꾸어드립니다. 정가 30,000 원