1. 참고링크 [Bottom] [Top]
2. MFC 처리 프로세스 [Bottom] [Top]
2.1. SDI 시작/종료 프로세스 [Bottom] [Top]
SDI(단일 문서) 프로그램의 시작과 종료 프로세스.
//---------------------------------------------------------- // SDI 시작 프로세스 WinMainCRTStartup() CMySDIApp::CMySDIApp() WinMain() AfxWinMain() CMySDIApp::InitApplication() CMySDIApp::InitInstance() // <===== ENTER ProcessShellCommand() // <===== ENTER CWinApp::OnFileNew() CMySDIDoc::CMySDIDoc() CMainFrame::CMainFrame() CMainFrame::LoadFrame() CMainFrame::PreCreateWindow() CMainFrame::Create() CMainFrame::PreCreateWindow() CMainFrame::OnCreate() // <===== ENTER CMainFrame::OnCreateClient() CMySDIView::CMySDIView() CMySDIView::Create() CMySDIView::PreCreateWindow() CMySDIView::OnCreate() CMySDIDoc::OnChangedViewList() CMainFrame::OnCreate() // =====> LEAVE CMySDIDoc::OnNewDocument() CMySDIView::OnInitialUpdate() ProcessShellCommand() // =====> LEAVE CMySDIApp::InitInstance() // =====> LEAVE //---------------------------------------------------- CMySDIApp::Run() CMySDIApp::PreTranslateMessage() // 무한 반복 실행 CMySDIApp::OnIdle() // 무한 반복 실행 //---------------------------------------------------- //---------------------------------------------------------- // SDI 종료 프로세스 //---------------------------------------------------------- CMainFrame::OnClose() CMySDIDoc::SaveModified() CMySDIDoc::OnCloseDocument() CMainFrame::DestroyWindow() CMainFrame::OnDestroy() CMySDIView::OnDestroy() CMySDIView::~CMySDIView() CMySDIDoc::OnChangedViewList() CMainFrame::~CMainFrame() CMySDIDoc::~CMySDIDoc() CMySDIApp::ExitInstance()
2.2. Dialog 시작/종료 프로세스 [Bottom] [Top]
Dialog(대화 상자) 프로그램의 시작과 종료 프로세스.
//---------------------------------------------------------- // Dialog 시작 프로세스 //---------------------------------------------------------- WinMainCRTStartup() CMyDlgApp::CMyDlgApp() WinMain() AfxWinMain() CMyDlgApp::InitApplication() CMyDlgApp::InitInstance() // <===== ENTER CMyDlgDlg::CMyDlgDlg() DoModal() // <===== ENTER CMyDlgDlg::OnCreate() CMyDlgDlg::PreInitDialog() CMyDlgDlg::OnInitDialog() //---------------------------------------------------------- // Dialog 종료 프로세스 //---------------------------------------------------------- CMyDlgDlg::OnOK() // 방법 1: OK 버튼 CMyDlgDlg::OnCancel() // 방법 2: Cancel 버튼 CMyDlgDlg::OnClose() // 방법 3: 종료[X] 버튼 CMyDlgDlg::OnCancel() //------------------------------------------------ CMyDlgDlg::DestroyWindow() CMyDlgDlg::OnDestroy() DoModal() // =====> LEAVE CMyDlgApp::InitInstance() // =====> LEAVE CMyDlgApp::ExitInstance()
3. 윈도우 제어 [Bottom] [Top]
3.1. 윈도우의 최소 / 최대 크기 지정하기 [Bottom] [Top]
윈도우를 특정 크기 이하 또는 이상으로 바꿀수 없도록 지정하는 소스 코드.
1 // STEP 1: WM_GETMINMAXINFO 메세지 핸들러 추가. 2 3 void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 4 { 5 // STEP 2: 최대/최소 크기 설정. 6 7 // 최대 크기 지정 8 lpMMI->ptMaxTrackSize.x = 600; 9 lpMMI->ptMaxTrackSize.y = 400; 10 11 // 최소 크기 지정 12 lpMMI->ptMinTrackSize.x = 300; 13 lpMMI->ptMinTrackSize.y = 200; 14 15 CFrameWnd::OnGetMinMaxInfo(lpMMI); 16 } 17
3.2. CView 객체 다시 그리기 [Bottom] [Top]
CDocument 클래스와 연결된 CView 상속 클래스의 클라이언트 영역을 다시 그릴 때 필요한 함수.
1 // CDocument 클래스 멤버 함수 2 void UpdateAllViews( CView * pSender, LPARAM lHint = 0L, CObject * pHint = NULL ); 3 4 // CView 클래스 멤버 함수 ( CWnd 클래스 ) 5 void Invalidate( BOOL bErase = TRUE ); 6
3.3. 화면 이중 버퍼링 사용하기 [Bottom] [Top]
CView 상속 클래스에서 클라이언트 영역을 다시 그릴 때 깜박거리는 현상 (Flicker 현상) 을 막기 위하여 이중 버퍼링 사용하기.
1 BOOL CExampleView::OnEraseBkgnd( CDC * pDC ) 2 { 3 // 화면을 지우지 않게 만든다. (Flicker 현상 제거) 4 return FALSE; 5 } 6 7 void CExampleView::OnDraw( CDC * pDC ) 8 { 9 // 프로젝트 마법사에 의해서 생성된 코드 10 CExampleDoc * pDoc = GetDocument(); 11 ASSERT_VALID( pDoc ); 12 if( !pDoc ) 13 { 14 return; 15 } 16 17 // STEP 0: 이중 버퍼링에 필요한 객체와 변수 선언 18 RECT rect; 19 CDC memDC; 20 CBitmap memBitmap, * pOldBitmap; 21 22 // STEP 1: 현재 DC 와 동일한 메모리 DC & 비트맵 생성 23 GetClientRect( &rect ); 24 memDC.CreateCompatibleDC( pDC ); 25 memBitmap.CreateCompatibleBitmap( pDC, rect.right, rect.bottom ); 26 pOldBitmap = memDC.SelectObject( &memBitmap ); 27 28 // STEP 1-1: 메모리 DC 의 화면 지우기 29 memDC.FillSolidRect( &rect, RGB( 0xFF, 0xFF, 0xFF ) ); 30 31 // STEP 2: 메모리 DC 로 그리기 (예제) 32 memDC.FillSolidRect( &pDoc->m_rectangle, pDoc->m_color ); 33 memDC.Ellipse( &pDoc->m_ellipse); 34 35 // STEP 3: 메모리 DC 를 현재 DC 로 복사하기 36 pDC->BitBlt( 0, 0, rect.right, rect.bottom, &memDC, 0, 0, SRCCOPY ); 37 memDC.SelectObject( pOldBitmap ); 38 } 39
3.4. 투명/반투명 윈도우 만들기 [Bottom] [Top]
특정 색상값(Color Key)을 제거한 투명 윈도우나 알파(Alpha)값 조절이 가능한 반투명 윈도우를 만들 때 Windows 2000 부터 추가된 레이어드(Layered) 윈도우를 사용한다.
1 BOOL CExampleDlg::OnInitDialog() 2 { 3 // STEP 1: WS_EX_LAYERED 확장 스타일 설정. 4 ::SetWindowLong( m_hWnd, GWL_EXSTYLE, ::GetWindowLong( m_hWnd, GWL_EXSTYLE ) | WS_EX_LAYERED ); 5 6 // STEP 2: 투명/반투명 속성 설정. 7 8 // Color Key(RGB(255, 0, 255))를 설정한 경우 (투명 윈도우) 9 SetLayeredWindowAttributes( RGB( 255, 0, 255 ), 128, LWA_COLORKEY ); 10 // Alpha(128)를 설정한 경우 (반투명 윈도우) 11 // SetLayeredWindowAttributes( RGB( 255, 0, 255 ), 128, LWA_ALPHA ); 12 // Color Key 와 Alpha 를 같이 설정한 경우 13 // SetLayeredWindowAttributes( RGB( 255, 0, 255 ), 128, LWA_ALPHA | LWA_COLORKEY ); 14 15 return TRUE; 16 } 17
참고1> WS_EX_LAYERED 확장 스타일은 매크로(#define문) 때문에 생각지도 못한 컴파일 오류가 발생할 수 한다. 레이어드 윈도우는 Windows 2000 부터 지원하기 때문에 다음과 같이 stdafx.h 파일을 수정한다.
// stdafx.h 파일 수정 #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0500 // Windows 2000 이상 지원 #endif
참고2> 레이어드 윈도우의 좀 더 색다른 효과를 사용해 보고 싶다면 UpdateLayeredWindow() 함수에 대하여 좀 더 알아보면 된다.
4. 분할 윈도우 처리 [Bottom] [Top]
4.1. 다중 분할 윈도우 만들기 [Bottom] [Top]
상/하, 좌/우가 복합적으로 분할된 다중 분할 윈도우를 생성하는 소스 코드 (아래의 그림과 같이 분할하는 코드).
- 분할 윈도우 객체 선언
1 class CMainFrame : public CFrameWnd 2 { 3 ... // 생략 4 5 // 특성입니다. 6 protected: 7 CSplitterWnd m_wndSplitterMain; // 좌/우 분할 8 CSplitterWnd m_wndSplitterSub; // 상/하 분할 9 10 ... // 생략 11 }; 12
- 분할 윈도우 생성 코드 구현
1 BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/, 2 CCreateContext* pContext) 3 { 4 // 윈도우 클라이언트 영역 크기 얻기 5 CRect rect; 6 GetClientRect( &rect ); 7 8 // STEP 1: 메인 윈도우의 좌/우 분할 9 m_wndSplitterMain.CreateStatic( this, 1, 2 ); 10 11 // 참고 1: 좌/우 윈도우 크기 지정 12 // m_wndSplitterMain.SetColumnInfo( 0, 250, 10 ); 13 // m_wndSplitterMain.SetColumnInfo( 1, 250, 10 ); 14 15 // STEP 1-1: 좌측 분할 윈도우의 View 생성 16 m_wndSplitterMain.CreateView( 0, 0, RUNTIME_CLASS( CViewA ), 17 CSize( 200, 0 ), pContext ) 18 19 // STEP 2: 우측 분할 윈도우의 상/하 분할 20 m_wndSplitterSub.CreateStatic( &m_wndSplitterMain, 2, 1, 21 WS_CHILD | WS_VISIBLE, 22 m_wndSplitterMain.IdFromRowCol( 0, 1 ) ); 23 24 // 참고 2 : 상/하 윈도우 크기 지정 25 // m_wndSplitterSub.SetRowInfo( 0, 250, 10 ); 26 // m_wndSplitterSub.SetRowInfo( 1, 250, 10 ); 27 28 // SETP 2-1: 상/하 분할 윈도우의 View 생성 29 int nHeight = rect.Height() / 2; 30 m_wndSplitterSub.CreateView( 0, 0, RUNTIME_CLASS( CViewB ), 31 CSize( 0, nHeight ), pContext ); 32 m_wndSplitterSub.CreateView( 1, 0, RUNTIME_CLASS( CViewC ), 33 CSize( 0, nHeight ), pContext ); 34 35 return TRUE; 36 } 37
4.2. 분할 윈도우의 스크롤바 없애기 [Bottom] [Top]
1 BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/, 2 CCreateContext* pContext ) 3 { 4 return m_wndSplitter.Create( this, 5 2, 1, // 행 및 열의 개수를 지정 6 CSize(100, 100), // 최소 창 크기를 지정 7 pContext, 8 WS_CHILD | WS_VISIBLE | SPLS_DYNAMIC_SPLIT ); // 기본 스타일 중에서 WS_HSCROLL 와 WS_VSCROLL 를 제거 9 } 10
4.3. 분할 윈도우의 크기 조절하기 [Bottom] [Top]
1 void CMainFrame::OnSize( UINT nType, int cx, int cy ) 2 { 3 CFrameWnd::OnSize( nType, cx, cy ); 4 5 if( m_bSplitterCreated ) // 주의> 창이 열린 경우에만 적용 가능하다. 6 { 7 CRect rect; 8 m_wndSplitter.GetClientRect( &rect ); 9 10 m_wndSplitter.SetRowInfo( 0, rect.Height() / 2, 10 ); // 첫번째 행의 크기를 지정 11 m_wndSplitter.SetRowInfo( 1, rect.Height() / 2, 10 ); // 두번째 행의 크기를 지정 12 13 // 열의 크기를 지정할 경우 SetColumnInfo() 함수 사용 14 15 m_wndSplitter.RecalcLayout(); 16 } 17 } 18
4.4. 분할 윈도우의 Client Edge 지우기 [Bottom] [Top]
분할 윈도우의 Client Edge 는 CWnd 클래스를 상속 받은 다른 클래스와 달리 윈도우 스타일을 사용하지 않고 CSplitterWnd 클래스에서 직접 그린다. 따라서 CSplitterWnd 클래스를 상속 받아서 Client Edge 를 그리는 함수(가상 함수)를 재정의해야 한다.
- 특정 Pane (View) 의 Client Edge 를 지워야 될 경우, 다음의 두 함수를 재정의 한다.
DrawAllSplitBars() 함수
OnPaint() 함수 (WM_PAINT 메시지 재정의)
참고> 두 함수의 자세한 코드는 아래의 파일을 참고한다.
$(VCInstallDir)atlmfc\src\mfc\winsplit.cpp
$(VCInstallDir)atlmfc\include\afxext.h
1 // SplitterWndEx.h : CSplitterWndEx 클래스 선언 2 // 3 4 #pragma once 5 6 7 // CSplitterWndEx 클래스 8 class CSplitterWndEx : public CSplitterWnd 9 { 10 DECLARE_DYNAMIC( CSplitterWndEx ) 11 12 public: 13 CSplitterWndEx(); 14 virtual ~CSplitterWndEx(); 15 16 // 가상함수 재정의 17 protected: 18 virtual void OnDrawSplitter( CDC* pDC, ESplitType nType, const CRect& rect ); 19 20 // 특정 View 의 Client Edge 를 지워야 될 경우 재정의 한다. 21 // virtual void DrawAllSplitBars(CDC* pDC, int cxInside, int cyInside); 22 23 protected: 24 DECLARE_MESSAGE_MAP() 25 26 public: 27 // 특정 View 의 Client Edge 를 지워야 될 경우 재정의 한다. 28 // afx_msg void OnPaint(); 29 }; 30
1 // SplitterWndEx.cpp : CSplitterWndEx 클래스 구현 2 // 3 4 #include "StdAfx.h" 5 #include "SplitterWndEx.h" 6 7 // 상수 선언 8 #define CX_BORDER (1) 9 #define CY_BORDER (1) 10 11 // CSplitterWndEx 클래스 12 13 IMPLEMENT_DYNAMIC( CSplitterWndEx, CSplitterWnd ) 14 15 CSplitterWndEx::CSplitterWndEx() 16 { 17 } 18 19 CSplitterWndEx::~CSplitterWndEx() 20 { 21 } 22 23 BEGIN_MESSAGE_MAP( CSplitterWndEx, CSplitterWnd ) 24 END_MESSAGE_MAP() 25 26 void CSplitterWndEx::OnDrawSplitter( CDC* pDC, ESplitType nType, const CRect& rectArg ) 27 { 28 if( ( 0 == rectArg.left ) && ( 0 == rectArg.top ) ) 29 { 30 // 좌상단(0,0) 에서 시작하는 View 에 대하여 Client Edge 를 지운다. 31 // - 원리: 창의 기본색과 동일한게 Client Edge 를 그린다. 32 33 if( NULL == pDC ) 34 { 35 RedrawWindow( rectArg, NULL, RDW_INVALIDATE | RDW_NOCHILDREN ); 36 return; 37 } 38 ASSERT_VALID(pDC); 39 40 //-------------------------------------------------------------- 41 42 CRect rect = rectArg; 43 44 pDC->Draw3dRect( rect, ::GetSysColor( COLOR_BTNFACE ), ::GetSysColor( COLOR_BTNFACE ) ); 45 rect.InflateRect( -CX_BORDER, -CY_BORDER ); 46 pDC->Draw3dRect( rect, ::GetSysColor( COLOR_BTNFACE ), ::GetSysColor( COLOR_BTNFACE ) ); 47 48 return; 49 } 50 51 // 다른 View 는 동일하게 동작하도록 OnDrawSplitter() 함수를 호출한다. 52 CSplitterWnd::OnDrawSplitter( pDC, nType, rectArg ); 53 } 54 55 // CSplitterWndEx 메시지 처리기입니다. 56
5. View 클래스 처리 (CView) [Bottom] [Top]
5.1. CView 클라이언트 영역의 Client Edge 지우기 [Bottom] [Top]
CView 클라이언트 영역의 Client Edge 를 지우기 위한 두 가지 방법.
- 윈도우 생성 시 스타일로 지정
주위> 한 부분만 추가하면 Client Edge 가 지워지지 않는다.
1 BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs) 2 { 3 if( !CMDIChildWnd::PreCreateWindow(cs) ) 4 return FALSE; 5 6 // 코드 추가 7 cs.dwExStyle &= ~WS_EX_CLIENTEDGE; 8 9 return TRUE; 10 } 11 12 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) 13 { 14 if( !CView::PreCreateWindow(cs) ) 15 return FALSE; 16 17 // 코드 추가 18 cs.dwExStyle &= ~WS_EX_CLIENTEDGE; 19 20 return TRUE; 21 } 22
- 윈도우 초기화 시 지정
1 void CChildView::OnInitialUpdate() 2 { 3 CView::OnInitialUpdate(); 4 ModifyStyleEx( WS_EX_CLIENTEDGE, 0 ); // 코드 추가 5 GetParentFrame()->RecalcLayout(); 6 ResizeParentToFit(); 7 } 8
5.2. CView 클라이언트 영역을 지우지 않기 [Bottom] [Top]
CView 클라이언트 영역을 다시 그릴 때 깜박거리는 현상 (Flicker 현상) 을 막기 위한 두 가지 방법.
- 윈도우 생성 시 지정
1 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) 2 { 3 cs.lpszClass = AfxRegisterWndClass( 4 CS_VREDRAW | CS_HREDRAW, 5 LoadCursor( NULL, IDC_ARROW ), 6 NULL, // 화면 지우지 않기 1 (Flicker 현상 제거) 7 LoadIcon( NULL, IDI_APPLICATION ) ); 8 9 return CView::PreCreateWindow(cs); 10 } 11
- 화면 갱신 시 지정
1 BOOL CChildView::OnEraseBkgnd(CDC* pDC) 2 { 3 return FALSE; // 화면 지우지 않기 2 (Flicker 현상 제거) 4 } 5
5.3. CView 클라이언트 영역의 배경색 바꾸기 [Bottom] [Top]
배경색을 바꾸기 위하여 WM_ERASEBKGND 메세지 핸들러를 Overriding 하거나 OnDraw() 함수에서 처리할 수 있으며, 다음과 같이 윈도우 생성 시 Brush 설정으로 배경색 지정.
1 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) 2 { 3 cs.lpszClass = AfxRegisterWndClass( 4 CS_VREDRAW | CS_HREDRAW, 5 LoadCursor( NULL, IDC_ARROW ), 6 (HBRUSH)GetStockObject(BLACK_BRUSH), // 원하는 색상의 브러쉬 7 // (HBRUSH)( COLOR_WINDOW + 1 ), // 시스템 색상값 사용 8 LoadIcon( NULL, IDI_APPLICATION ) ); 9 10 return CView::PreCreateWindow(cs); 11 } 12
참고> 시스템 색상 상수값은 winuser.h 헤더 파일에 정의 되어 있으며, COLOR_SCROLLBAR 부터 COLOR_MENUBAR 까지 다양한 값들이 정의 되어 있다. 시스템 색상을 지정할 때는 위에서 주석처리된 코드 처럼 + 1 해야 한다.
6. 기타 View 클래스 처리 (CView 제외) [Bottom] [Top]
6.1. CEditView 의 가로 스크롤바 없애기 [Bottom] [Top]
1 BOOL CEditView::PreCreateWindow(CREATESTRUCT& cs) 2 { 3 if( !CEditView::PreCreateWindow( cs ) ) 4 { 5 return FALSE; 6 } 7 8 // 가로 스크롤바 생략 9 cs.style &= ~( WS_HSCROLL | ES_AUTOHSCROLL ); 10 11 return TRUE; 12 } 13
참고> 동일한 방법으로 CEditView 의 스타일을 바꿀 수 있다.
6.2. CEditView 에 문자열 추가하기 [Bottom] [Top]
CListBox 와 같이 문자열을 추가하기 위한 함수.
1 void CEditView::AddString( CString & strMesg ) 2 { 3 CEdit & editCtrl = GetEditCtrl(); 4 5 int nLen = editCtrl.GetWindowTextLength(); 6 7 // 문자열의 줄바꿈 추가 8 strMesg += "\r\n"; 9 10 // 문자열 추가 11 editCtrl.SetSel( nLen, nLen ); 12 editCtrl.ReplaceSel( strMesg ); 13 } 14
6.3. CEditView 의 배경색 바꾸기 [Bottom] [Top]
CEditView 는 Edit 컨트롤을 포함하고 있기 때문에 CView 와 동일한 방법으로 배경색을 바꿀 수 없다. 아래와 같은 방법으로 배경색을 지정할 수 있다.
- 변수/상수 선언
1 // 상수 선언 2 #define MESG_VIEW_BKCOLOR (RGB(0xC0,0xE0,0xFF)) 3 4 // 클래스 선언 5 class CMesgView : public CEditView 6 { 7 ... // 생략 8 9 // 특성입니다. 10 protected: 11 HBRUSH m_hBrush; // HBRUSH 변수 12 13 ... // 생략 14 }; 15
- 코드 구현
1 // WM_CREATE 메세지 추가 2 int CMesgView::OnCreate(LPCREATESTRUCT lpCreateStruct) 3 { 4 if (CEditView::OnCreate(lpCreateStruct) == -1) 5 return -1; 6 7 // STEP 1: BRUSH 생성 8 m_hBrush = CreateSolidBrush( MESG_VIEW_BKCOLOR ); 9 10 return 0; 11 } 12 13 // =WM_CTLCOLOR 메세지 추가 14 HBRUSH CMesgView::CtlColor(CDC* pDC, UINT /*nCtlColor*/) 15 { 16 // 추가: 글자 배경색 지정 17 SetBkColor( pDC->m_hDC, MESG_VIEW_BKCOLOR ); 18 19 // STEP 2: BRUSH 생성 20 return m_hBrush; 21 } 22 23 // WM_DESTROY 메세지 추가 24 void CMesgView::OnDestroy() 25 { 26 CEditView::OnDestroy(); 27 28 // STEP 3: BRUSH 삭제 29 DeleteObject( m_hBrush ); 30 } 31
