'3. Implementation/MFC'에 해당되는 글 52건

  1. 2009.04.23 파일 변경 공지 (File Change Notification)
  2. 2009.04.23 다른 프로세스 실행하기 (Launching Other Processes)
  3. 2009.04.23 메시지 펌프 (Message Pumps)
  4. 2009.04.15 Document 를 공유하는 Frame 윈도우 생성하기
  5. 2009.03.30 Command Routing & OnCmdMsg
  6. 2009.03.27 UI Thread 생성하기
  7. 2009.03.27 SDI 에서 OnFileNew 시퀀스 다이어그램
  8. 2009.03.26 단일 문서에 다중 뷰 추가
  9. 2009.03.25 CEvent , CSyncObject, CMultiLock 사용 예
  10. 2009.03.23 동적으로 팝업 메뉴 생성
  11. 2009.03.20 MFC를 이용한 COM Connection Point 구현
  12. 2009.03.11 View 분할하기
  13. 2009.03.05 View 깜박임 문제 해결
  14. 2009.02.20 AfxOleLockApp and AfxOleUnlockApp
  15. 2009.02.18 MDI 샘플: 문서/뷰 아키텍처 사용/미사용 예제
  16. 2009.02.18 GetCurrentMessage() - 메시지 핸들러에서 메시지 정보 가져오기
  17. 2009.02.17 Document Template 과 Resource String과의 관계
  18. 2009.02.05 How to Replace a View in a Splitter Window
  19. 2009.01.20 문서 템플릿과 문서/뷰 만들기 프로세스
  20. 2009.01.19 DECLARE_DYNCREATE 매크로와 RUNTIME_CLASS 매크로의 관계
2009. 4. 23. 00:09

파일 변경 공지 (File Change Notification)


이 장(17) 초기에, ::WaitForSingleObject 에 전달되는 HANDLE 파라미터가 파일 변경 공지 핸들이 될 수 있다고 언급하였다. Win32 API ::FindFirstChangeNotification 이라는 함수를 포함한다. 이 함수는 지정한 디렉토리 또는 그 하위 디렉토리내에서 변경이 일어날 때마다 블락된 쓰레드를 깨우기 위해 사용할 수 있는 핸들을 반환한다 예를 들어, 파일의 이름이 변경되었거나, 파일이 삭제되었거나, 새로운 디렉토리가 생성되었다든지.

 

파일 시스템의 변경이 왼쪽 또는 오른쪽 창에 즉시 반영되도록 하는 기능을 11장의 Wanderer 애플리케이션에 추가하길 원한다고 하자. 그것을 하기 위한 가장 효과적인 방법은 백그라운드 쓰레드를 시작하고 그 쓰레드가 하나 이상의 파일 변경 공지 핸들에 블락되게 하는 것이다. C 드라이브를 모니터하는 쓰레드를 위한 쓰레드 함수는 아래와 같다.

 

UINT ThreadFunc (LPVOID pParam)

{

        HWND hwnd = (HWND) pParam; // Window to notify

        HANDLE hChange = ::FindFirstChangeNotification (_T ("C:\\"),

               TRUE, FILE_NOTIFY_CHANGE_FILE_NAME ¦ FILE_NOTIFY_CHANGE_DIR_NAME);

 

        if (hChange == INVALID_HANDLE_VALUE) {

               TRACE (_T ("Error: FindFirstChangeNotification failed\n"));

               return (UINT) -1;

        }

 

        while (...) {

               ::WaitForSingleObject (hChange, INFINITE);

               ::PostMessage (hwnd, WM_USER_CHANGE_NOTIFY, 0, 2);

               ::FindNextChangeNotification (hChange); // Reset

        }

        ::FindCloseChangeNotification (hChange);

        return 0;

}

 

::FindFirstChangeNotification 에 전달되는 첫 번째 파라미터는 모니터하고자 하는 디렉토리를 나타낸다. 두 번째는 디렉토리 자체만(FALSE) 또는 그 디렉토리와 하위 디렉토리(TRUE)까지 모니터할 지를 명시한다. 그리고 세 번째는 쓰레드에 공지되어야 하는 변경의 종류를 나타낸다. 이 예제에서는, C: 드라이브의 어딘가에서 파일이 생성될 때, 이름이 변경될 때, 지워 질 때 (FILE_NOTIFY_CHANGE_FILE_NAME) 또는 디렉토리가 생성될 때, 이름이 변경될 때, 지워질 때(FILE_NOTIFY_CHANGE_DIR_NAME)에 쓰레드가 깨어 날 것이다. 쓰레드가 깨어날 때, 윈도우에 사용자 정의 메시지를 포스트하고 그 메시지의 pParam에 파일 핸들이 전달된다. 메시지의 lParam 은 드라이브 수 ( C: 드라이브는 2)를 나타낸다. 메시지를 받은 윈도우는 아마 애플리케이션의 top-level 프레임 윈도우 뷰를 업데이트함으로써 그 메시지에 반응할 수 있다. 파일 변경 공지에 의해 깨어난 쓰레드는 변경의 근원에 관해서 또는 디렉토리 트리내에서 변경이 일어난 곳이 어디인지에 관한 정보는 얻을 수 없다는 것을 기억하라, 그래서 파일 변경 공지가 야기된 곳을 찾길 원한다면 파일 시스템을 스캔해야만 한다.

 

쓰레드가 하나의 드라이브 뿐만 아니라 여러 개를 모니터하도록 만들수도 있다. 해야 하는 모든 작업은 각 드라이브마다 각각의 파일 변경 공지 핸들을 얻기 위해 드라이브마다 ::FindFirstChangeNotification 을 호출하고 동시에 모든 파일 변경 공지에 대해 블락되도록 ::WaitForMultipleObjects 를 사용하는 것이다. ::WaitForMultipleObjects CMultiLock::Lock 과 같은 Win32 API 이다. 세 번째 파라미터에 FALSE 를 전달하는 것은 쓰레드를 블락하고 있는 객체들 중 하나라도 신호상태가 되었을 때 쓰레드가 깨어날 것이라고 시스템에게 말하는 것이다.

 

참고 : Programming with MFC 2nd Edition

2009. 4. 23. 00:08

다른 프로세스 실행하기 (Launching Other Processes)


Win32 프로세스는 쓰레드를 실행하는 것과 같이 쉽게 다른 프로세스를 실행시킬 수 있다. 아래 문장은 C 드라이브의 Windows 디렉토리에 있는 Notepad.exe 를 실행한다.

 

STARTUPINFO si;

::ZeroMemory (&si, sizeof (STARTUPINFO));

si.cb = sizeof (STARTUPINFO);

PROCESS_INFORMATION pi;

 

if (::CreateProcess (NULL, _T ("C:\\Windows\\Notepad"), NULL,

        NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {

               ::CloseHandle (pi.hThread);

               ::CloseHandle (pi.hProcess);

}

 

::CreateProcess (선택적으로 경로) 실행가능한 파일의 이름을 받아 그것을 로드하고 실행하는 다재다능한 함수이다. 만약 드라이브 명과 디렉토리 명이 실행가능한 파일 이름에서 빠졌다면, 시스템은 자동적으로 Windows 디렉토리, Windows system 디렉토리, 현재 Path 에 있는 모든 디렉토리, 그리고 선택된 다른 위치에서 그 파일을 찾는다. 파일 이름은 또한 명령어 파라미터를 포함할 수 있다.

 

"C:\\Windows\\Notepad C:\\Windows\\Desktop\\Ideas.txt"

 

::CreateProcess 는 프로세스 핸들(hProcess)과 프로세스의 주 쓰레드 핸들(hThred)을 포함하는 프로세스에 관한 정보를 가지는 PROCESS_INFORMATION 구조체를 채운다. 프로세스가 시작된 후에는 ::CloseHandle 을 이용하여 이 핸들들을 받드시 닫아야 한다. 핸들을 더 이상 사용하지 않는다면, ::CreateProcess 가 반환하는 순간 그것들을 닫을 수 있다.

 

::CreateProcess 로부터 0이 아닌 값이 반환되면 프로세스는 성공적으로 실행되었다는 것을 의미한다. Win32 프로세스는 비동기적으로 시작되고 실행된다, 그래서 ::CreateProcess 는 프로세스가 종료될 때까지 기다리지 않는다. 만일 다른 프로세스를 시작하고 그것이 종료될 때까지 현재 프로세스를 중지시키고 싶다면, 그 프로세스 핸들에 대해 ::WaitForSingleObject 를 호출하라.

 

STARTUPINFO si;

::ZeroMemory (&si, sizeof (STARTUPINFO));

si.cb = sizeof (STARTUPINFO);

PROCESS_INFORMATION pi;

 

if (::CreateProcess (NULL, _T ("C:\\Windows\\Notepad"), NULL,

        NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {

               ::CloseHandle (pi.hThread);

               ::WaitForSingleObject (pi.hProcess, INFINITE);

               ::CloseHandle (pi.hProcess);

}

 

프로세스는 쓰레드처럼 종료 코드를 가진다. 만일 ::WaitForSingleObject WAIT_FAILED 가 아닌 다른 값을 반환한다면, 그 프로세스의 종료 코드를 얻기 위해 ::GetExitCodeProcess 를 호출할 수 있다.

 

가끔 프로세스가 시작된 후 사용자 입력에 반응하기까지 충분한 시간을 기다릴 필요가 있다. 예륻 들어, 프로세스 A가 프로세스 B를 실행시키고 프로세스 B는 윈도우를 생성한다 그리고 프로세스 A는 그 윈도우에게 메시지를 보내길 원한다, 프로세스 A는 프로세스 B에게 윈도우를 생성하고 메시지 처리를 시작하기 위한 시간을 주기위해 ::CreateProcess 가 반환된 후 잠시동안 기다려야 할 것이다. 이 문제는 Win32 ::WaitForInputIdle 함수를 이용하면 쉽게 해결된다.

 

STARTUPINFO si;

::ZeroMemory (&si, sizeof (STARTUPINFO));

si.cb = sizeof (STARTUPINFO);

PROCESS_INFORMATION pi;

 

if (::CreateProcess (NULL, _T ("C:\\Windows\\Notepad"), NULL,

        NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {

               ::CloseHandle (pi.hThread);

               ::WaitForInputIdle (pi.hProcess, INFINITE);

               // Get B's window handle and send or post a message.

               ::CloseHandle (pi.hProcess);

}

 

::WaitForInputIdle 은 지정한 프로세스가 메시지 처리를 시작하고 메시지 큐가 비어 있게 될 때까지 대기한다. 윈도우 핸들을 찾기 위한 코드를 보여주지 않았다 왜냐하면 프로세스 핸들을 윈도우 핸들로 변환하기 위한 간단한 MFC 또는 API 함수가 없기 때문이다. 대신, 소유하고 있는 프로세스의 어떤 알려진 속성을 근거로 그 윈도우를 찾는 ::EnumWindows, ::FindWindow, 또는 관련 함수를 사용해야만 한다.

 

참고 : Programming with MFC 2nd Edition

 

2009. 4. 23. 00:07

메시지 펌프 (Message Pumps)


멀티쓰레딩에 관해 프로그래머가 가지는 일반적인 오해는 멀티쓰레딩이 애플리케이션을 더 빠르게 만든다는 생각이다. 싱글-프로세서 머신의 경우, 그렇지 않다. 하지만, 애플리케이션의 응답성은 높여준다. 멀티쓰레딩이 만들 수 있는 응답성에 관한 차이점을 설명하는 한가지 방법은 메뉴 명령에 따라 수 천개의 타원을 그리는 애플리케이션을 작성하는 것이다. 만약 주 쓰레드에 의해 그림이 그려지고 주 쓰레드는 자신의 메시지 큐를 검사하지 않고 어떤 대기 메시지도 처리하지 않는다면 그리는 루프가 종료될 때까지 입력이 얼어버릴 것이다. 다른 쓰레드에서 그림을 그리도록 동일한 애플리케이션이 작성된다면, 그리는 루프가 실행되는 동안 사용자 입력에 대해 계속해서 응답할 것이다.

 

하지만, 이와 같이 간단한 시나리오에서는 멀티쓰레딩은 약간 과잉이다. 다른 해결책은 주 쓰레드가 타원을 그리는 동안 메시지가 계속해서 흘러가도록 메시지 펌프를 사용하는 것이다. 아래와 같이 그림을 그리는 메시지 핸들러를 가정하라.

 

void CMainWindow::OnStartDrawing ()

{

        for (int i=0; i<NUMELLIPSES; i++)

               DrawRandomEllipse ();

}

 

NUMELLIPSES 가 만약 큰 수라면, 일단 for 루프가 시작되면 프로그램은 오랫동안 멈출 수 있다. 아래처럼, for 루프를 정지시키는 플래그를 설정하는 다른 메뉴 커맨드를 추가할 수 있다.

 

void CMainWindow::OnStartDrawing ()

{

        m_bQuit = FALSE;

        for (int i=0; i<NUMELLIPSES && !m_bQuit; i++)

               DrawRandomEllipse ();

}

 

void CMainWindow::OnStopDrawing ()

{

        m_bQuit = TRUE;

}

 

하지만 동작하지 않을 것이다. 왜 그럴까? 메시지를 펌프하지 않으면 OnStartDrawing 에서 for 루프가 실행되는 한 OnStopDrawing 을 활성화시키는 WM_COMMAND 메시지를 받을 수 없기 때문이다. 사실상, 메뉴는 for 루프가 실행되는 동안에는 풀다운 메뉴가 펼쳐질 수 조차 없다.

 

이 문제는 메시지 펌프를 이용하여 쉽게 해결된다. 아래는 싱글-쓰레드 MFC 프로그램에서 시간이 꽤 걸리는 프로시져를 실행하는 적절한 방법의 예이다.

 

void CMainWindow::OnStartDrawing ()

{

        m_bQuit = FALSE;

        for (int i=0; i<NUMELLIPSES && !m_bQuit; i++) {

               DrawRandomEllipse ();

               if (!PeekAndPump ())

                       break;

        }

}

 

void CMainWindow::OnStopDrawing ()

{

        m_bQuit = TRUE;

}

 

BOOL CMainWindow::PeekAndPump ()

{

        MSG msg;

        while (::PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {

               if (!AfxGetApp ()->PumpMessage ()) {

                       ::PostQuitMessage (0);

                       return FALSE;

               }

        }

        LONG lIdle = 0;

        while (AfxGetApp ()->OnIdle (lIdle++));

        return TRUE;

}

 

PeekAndPump 는 메시지 루프내에서 메시지 루프를 재현한다(enact). OnStartDrawing for 루프내에서 각 반복이 끝나는 시점에 호출되면서, PeekAndPump 는 큐에 메시지가 대기하고 있다고 ::PeekMessage 가 가리킨다면 메시지를 가져와 처리하기 위해 먼저 CWinThread::PumpMessage 를 호출한다. PumpMessage 0을 반환하는 것은 마지막 메시지를 얻었고 WM_QUIT 메시지가 처리되었다는 것을 의미한다, 그리고 WM_QUIT 메시지는 주 메시지 루프(main message loop)가 가져가지 않으면 애플리케이션은 종료되지 않기 때문에 특별한 처리를 위해 ::PostQuitMessage 를 호출한다. 이것이 바로 PumpMessage 0을 반환하면 PeekAndPump가 큐에다가 또다른 WM_QUIT 메시지를 포스트하는 이유이며, 그리고 PeekAndPump 0을 반환하면 OnStartDrawing 에서 for 루프를 종료하는 이유이기도 하다. 만일 WM_QUIT 메시지가 조기에 종료시키지 않는다면, PeekAndPump 는 함수를 반환하기 전에 애플리케이션 객체의 OnIdle 함수를 호출함으로써 프레임워크의 idle 매커니즘을 시뮬레이션 한다.

 

그리기 루프에 삽입된 PeekAndPump를 이용하여, OnStopDrawing 을 활성화 시키는 WM_COMMAND 는 정상적으로 얻어지고 처리된다. OnStopDrawing m_bQuit TRUE로 설정하기 때문에, 그리기 루프는 다음 타원이 그려지기 전에 종료될 것이다.

 

참고 : Programming with MFC 2nd Edition

2009. 4. 15. 09:06

Document 를 공유하는 Frame 윈도우 생성하기

아래 처럼 하면, 동일한 Document 를 공유하는 새로운 Frame 과 View 를 생성하는 샘플 코드이다.

CWinApp * pApp = AfxGetApp();

POSITION pos = pApp->m_pDocManager->GetFirstDocTemplatePosition();

CDocTemplate * pDocTemplate = pApp->m_pDocManager->GetNextDocTemplate(pos);

 

CDocument * pDocument = GetDocument();

 

CFrameWnd * pwndFrame = (CFrameWnd *)RUNTIME_CLASS(CFrameWnd)->CreateObject();

ASSERT(pwndFrame != NULL);

 

CCreateContext context;

context.m_pCurrentFrame = NULL;

context.m_pCurrentDoc = pDocument;

context.m_pNewViewClass = RUNTIME_CLASS(CEditView);

context.m_pNewDocTemplate = pDocTemplate;

 

if( pwndFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, &context) == FALSE)

{

        return;

}

 

pDocTemplate->SetDefaultTitle(pDocument);

pDocTemplate->InitialUpdateFrame(pwndFrame, pDocument, TRUE);



2009. 3. 30. 23:51

Command Routing & OnCmdMsg


Command Message 는 메뉴에서 아이템이 선택되었을 때, 키보드 단축키(accelerator)가 눌려졌을때, 툴바 버튼이 클릭되었을 때 발생되는 WM_COMMAND 메시지를 위한 MFC의 용어이다.

이러한 Command Message는 아래 그림처럼 해당 메시지를 처리하기 전까지 관련된 객체에 라우팅되며 만일 메시지 처리기가 없는 경우에는 DefWindowProc 에게 처리하도록 한다.


일반적으로, File-New, File-Open, and File-Exit 명령어는 application 객체에 매핑되고 CWinApp 는 그러한 메시지를 처리하기 위해 OnFileNew, OnFileOpen, OnAppExit 명령어 핸들러를 제공한다. File-Save 와 File-Save As 는 일반적으로 다큐먼트 객체에 의해 다루어 지며, 다큐먼트 객체는 CDocument::OnFileSave 와 CDocument::OnFileSaveAS 라고 명명된 디폴트 명령어 핸들러를 제공한다. 툴바와 상태바를 보이거나 숨기는 명령어들은 CFrameWnd 멤버 함수를 사용하는 프레임 윈도우에 의해 다루어진다, 그리고 대부분의 다른 명령어들은 다큐먼트나 뷰에 의해서 다루어 진다.

중요한 점은 메시지 핸들러를 놓는 곳은 라우팅 되는 command message 나 사용자 인터페이스 업데이트에만 해당된다는 것이다. WM_CHAR, WM_LBUTTONDOWN, WM_CREATE, WM_SIZE와 같은 표준 윈도우즈 메시지는 그 메시지를 받는 윈도우에 의해서 다루어져야만 한다. 마우스와 키보드 메시지는 일반적으로 View 에 전해지고, 대부분의 다른 메시지는 프레임 윈도우로 전해진다. 다큐먼트 객체와 애플리케이션 객체는 비명령어 메시지(noncommand messages)를 절대로 받지 않는다.

참고로 아래는 CFrameWnd::OnCmdMsg 의 Command Routing을 구현한 코드 예이다.

 BOOL CFrameWnd::OnCmdMsg(...)

{

        // Pump through current view FIRST.

        CView* pView = GetActiveView();

        if (pView != NULL && pView->OnCmdMsg(...))

               return TRUE;

 

        // Then pump through frame.

        if (CWnd::OnCmdMsg(...))

               return TRUE;

 

        // Last but not least, pump through application.

        CWinApp* pApp = AfxGetApp();

        if (pApp != NULL && pApp->OnCmdMsg(...))

               return TRUE;

 

        return FALSE;

}


참고 : Programming Windows with MFC, 2nd Edition
2009. 3. 27. 14:40

UI Thread 생성하기

아래 코드 예는 MFC 를 지웒하는 Win32 프로젝트에서 UI Thread를 생성하고, 이 쓰레드에서 생성한 윈도우에서 왼쪽 마우스를 클릭하면 종료하는 프로그램이다.

 #include "stdafx.h"

#include "Win32Sample.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

 

#include <afxmt.h>

 

// The one and only application object

 

CWinApp theApp;

 

 

// The CMainWindow class

class CMainWindow : public CFrameWnd

{

public:

        CMainWindow ();

 

protected:

        afx_msg void OnLButtonDown (UINT, CPoint);

        DECLARE_MESSAGE_MAP ()

};

 

BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd)

        ON_WM_LBUTTONDOWN ()

END_MESSAGE_MAP ()

 

CMainWindow::CMainWindow ()

{

        Create (NULL, _T ("UI Thread Window"));

}

 

void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point)

{

        PostMessage (WM_QUIT, 0, 0);

}

 

// The CUIThread class

class CUIThread : public CWinThread

{

        DECLARE_DYNCREATE (CUIThread)

 

public:

        virtual BOOL InitInstance ();

        virtual int ExitInstance() {

               delete m_pMainWnd;

               return 0;

        }

};

 

IMPLEMENT_DYNCREATE (CUIThread, CWinThread)

 

BOOL CUIThread::InitInstance ()

{

        m_pMainWnd = new CMainWindow;

        m_pMainWnd->ShowWindow (SW_SHOW);

        m_pMainWnd->UpdateWindow ();

        return TRUE;

}

 

using namespace std;

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

        int nRetCode = 0;

 

        // initialize MFC and print and error on failure

        if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

        {

               // TODO: change error code to suit your needs

               _tprintf(_T("Fatal Error: MFC initialization failed\n"));

               nRetCode = 1;

        }

        else

        {

               // TODO: code your application's behavior here.

 

               CWinThread* pThread = AfxBeginThread (RUNTIME_CLASS (CUIThread), 0, 0, CREATE_SUSPENDED);

               pThread->m_bAutoDelete = FALSE;

               pThread->ResumeThread();

       

               WaitForSingleObject(pThread->m_hThread, INFINITE);  

               delete pThread;

        }

 

        return nRetCode;

}

 


2009. 3. 27. 11:54

SDI 에서 OnFileNew 시퀀스 다이어그램


2009. 3. 26. 09:07

단일 문서에 다중 뷰 추가

MFC(Microsoft Foundation Class) 라이브러리로 만든 SDI(단일 문서 인터페이스) 응용 프로그램에서 각 문서 형식은 단일 뷰 형식과 연결되어 있습니다. 일부 경우 문서의 현재 뷰를 새 뷰로 전환하는 기능을 설정하는 것이 좋습니다.

   단일 문서의 다중 뷰 구현에 대한 추가 절차는 CDocument::AddView와 MFC 샘플 ENROLLCOLLECT를 참조하십시오.

이 기능은 CView에서 파생된 새 클래스와 뷰를 동적으로 전환하는 추가 코드를 기존 MFC 응용 프로그램에 추가하여 구현할 수 있습니다.

이 기능을 구현하는 단계는 다음과 같습니다.

이 항목의 나머지 부분에서는 다음과 같이 가정합니다.

  • CWinApp에서 파생된 개체 이름은 CMyWinApp이고 CMyWinApp는 MYWINAPP.H와 MYWINAPP.CPP에서 선언 및 정의합니다.
  • CNewViewCView에서 파생된 새 개체의 이름이고 CNewView는 NEWVIEW.H 및 NEWVIEW.CPP에서 선언 및 정의합니다.

기존 응용 프로그램 클래스 수정

뷰 간에 전환하는 응용 프로그램에서 뷰와 뷰 전환 메서드를 저장하려면 멤버 변수를 추가하여 응용 프로그램 클래스를 수정해야 합니다.

MYWINAPP.H의 CMyWinApp 선언에 다음 코드를 추가합니다.

 CView* m_pOldView;
CView* m_pNewView;
CView* SwitchView( );

새 멤버 변수 m_pOldViewm_pNewView는 현재 뷰와 새로 만든 뷰를 가리킵니다. 새 메서드(SwitchView)는 사용자가 요청할 때 뷰를 전환합니다. 메서드 본문은 이 항목 뒤 부분에 나오는 전환 함수 구현에서 설명합니다.

응용 프로그램을 마지막으로 수정할 때 전환 함수에 사용된 Windows 메시지(WM_INITIALUPDATE)를 정의하는 새 헤더 파일이 포함되어야 합니다.

MYWINAPP.CPP의 include 섹션에 다음 줄을 삽입합니다.

 #include <AFXPRIV.H>

변경 내용을 저장하고 다음 단계를 계속합니다.

새 뷰 클래스 만들기 및 수정

클래스 뷰의 New Class 명령을 사용하면 편리하게 새 뷰 클래스를 만들 수 있습니다. 이 클래스에 대한 유일한 요구 사항은 CView에서 파생되어야 한다는 것입니다. 이 새로운 클래스를 응용 프로그램에 추가합니다. 프로젝트에 새 클래스를 추가하는 방법에 대한 자세한 내용은 클래스 추가를 참조하십시오.

프로젝트에 클래스를 추가한 후 일부 뷰 클래스 멤버의 액세스 가능성을 변경해야 합니다.

생성자와 소멸자에 대한 액세스 지정자를 protected에서 public로 변경하여 NEWVIEW.H를 수정합니다. 이렇게 하면 클래스가 동적으로 생성 및 소멸되고 뷰가 표시되기 전에 뷰의 모양을 수정할 수 있습니다.

변경 내용을 저장하고 다음 단계를 계속합니다.

새 뷰 만들기 및 연결

새 뷰를 만들고 연결하려면 응용 프로그램 클래스의 InitInstance 함수를 수정해야 합니다. 이 함수를 수정하면 새 뷰 개체를 만드는 새 코드를 추가한 다음 두 개의 기존 뷰 개체를 사용하여 m_pOldViewm_pNewView 모두를 초기화합니다.

새 뷰는 InitInstance 함수 내에 만들어지므로 새 뷰와 기존 뷰는 응용 프로그램이 소멸될 때까지 지속됩니다. 그러나, 응용 프로그램에서는 간단하게 새 뷰를 동적으로 만들 수 있습니다.

ProcessShellCommand를 호출한 후 다음 코드를 삽입합니다.

 ...
CView* pActiveView = ((CFrameWnd*) m_pMainWnd)->GetActiveView();
m_pOldView = pActiveView;
m_pNewView = (CView*) new CNewView;

CDocument* pCurrentDoc = ((CFrameWnd*)m_pMainWnd)->GetActiveDocument();

// Initialize a CCreateContext to point to the active document.
// With this context, the new view is added to the document
// when the view is created in CView::OnCreate().
CCreateContext newContext;
newContext.m_pNewViewClass = NULL;
newContext.m_pNewDocTemplate = NULL;
newContext.m_pLastView = NULL;
newContext.m_pCurrentFrame = NULL;
newContext.m_pCurrentDoc = pCurrentDoc;

// The ID of the initial active view is AFX_IDW_PANE_FIRST.
// Incrementing this value by one for additional views works
// in the standard document/view case but the technique cannot
// be extended for the CSplitterWnd case.
UINT viewID = AFX_IDW_PANE_FIRST + 1;
CRect rect(0, 0, 0, 0); // Gets resized later.

// Create the new view. In this example, the view persists for
// the life of the application. The application automatically
// deletes the view when the application is closed.
m_pNewView->Create(NULL, "AnyWindowName", WS_CHILD, rect, m_pMainWnd, viewID, &newContext);

// When a document template creates a view, the WM_INITIALUPDATE
// message is sent automatically. However, this code must
// explicitly send the message, as follows.
m_pNewView->SendMessage(WM_INITIALUPDATE, 0, 0);
...

변경 내용을 저장하고 다음 단계를 계속합니다.

전환 함수 구현

이전 단계에서 새 뷰 개체를 만들고 초기화하는 코드를 추가하였습니다. 마지막 작업은 전환 메서드 SwitchView를 구현하는 것입니다.

응용 프로그램 클래스의 구현 파일(MYWINAPP.CPP) 끝에 다음 메서드 정의를 추가합니다.

 CView* CMyWinApp::SwitchView( )
{
   CView* pActiveView =
      ((CFrameWnd*) m_pMainWnd)->GetActiveView();

   CView* pNewView= NULL;
   if(pActiveView == m_pOldView)
      pNewView= m_pNewView;
   else
      pNewView= m_pOldView;

   // Exchange view window IDs so RecalcLayout() works.
   #ifndef _WIN32
   UINT temp = ::GetWindowWord(pActiveView->m_hWnd, GWW_ID);
   ::SetWindowWord(pActiveView->m_hWnd, GWW_ID, ::GetWindowWord(pNewView->m_hWnd, GWW_ID));
   ::SetWindowWord(pNewView->m_hWnd, GWW_ID, temp);
   #else
   UINT temp = ::GetWindowLong(pActiveView->m_hWnd, GWL_ID);
   ::SetWindowLong(pActiveView->m_hWnd, GWL_ID, ::GetWindowLong(pNewView->m_hWnd, GWL_ID));
   ::SetWindowLong(pNewView->m_hWnd, GWL_ID, temp);
   #endif

   pActiveView->ShowWindow(SW_HIDE);
   pNewView->ShowWindow(SW_SHOW);
   ((CFrameWnd*) m_pMainWnd)->SetActiveView(pNewView);
   ((CFrameWnd*) m_pMainWnd)->RecalcLayout();
   pNewView->Invalidate();
   return pActiveView;
}

변경 내용을 저장하고 다음 단계를 계속합니다.

뷰 전환 지원 코드 추가

마지막 단계에서는 응용 프로그램에서 뷰 간에 전환해야 할 경우 SwitchView 메서드를 호출하는 코드를 추가합니다. 뷰 간의 전환은 여러 가지 방법으로 수행할 수 있습니다. 사용자가 선택하는 새 메뉴 항목을 추가할 수도 있고 일정한 조건을 만족하는 경우 내부적으로 뷰를 전환할 수도 있습니다.

새 메뉴 항목과 명령 처리기 함수 추가에 대한 자세한 내용은 명령 및 컨트롤 알림에 대한 처리기를 참조하십시오.


출처 : http://msdn.microsoft.com/ko-kr/library/cc468015(VS.71).aspx

2009. 3. 25. 08:02

CEvent , CSyncObject, CMultiLock 사용 예


 // The following demonstrates trivial usage of the CEvent class.
// A CEvent object is created and passed as a parameter to another
// thread.  The other thread will wait for the event to be signaled
// and then exit

UINT __cdecl MyThreadProc(LPVOID lpParameter)
{
   CEvent* pEvent = (CEvent*)(lpParameter);
   VERIFY(pEvent != NULL);

   // Wait for the event to be signaled
   ::WaitForSingleObject(pEvent->m_hObject, INFINITE);

   // Terminate the thread
   ::AfxEndThread(0, FALSE);
   return 0L;
}

void CEvent_Test()
{
   // Create the CEvent object that will be passed to the thread routine
   CEvent* pEvent = new CEvent(FALSE, FALSE);

   // Create a thread that will wait on the event
   CWinThread* pThread;
   pThread = ::AfxBeginThread(&MyThreadProc, pEvent, 0, 0, CREATE_SUSPENDED, NULL);
   pThread->m_bAutoDelete = FALSE;
   pThread->ResumeThread();

   // Signal the thread to do the next work item
   pEvent->SetEvent();

   // Wait for the thread to consume the event and return
   ::WaitForSingleObject(pThread->m_hThread, INFINITE);
   delete pThread;
   delete pEvent;
}

아래 예에서는 생성자에서 new 를 이용하여 파라미터가 있는 생성자를 호출하는 것도 눈여겨보자.

 // This example builds upon the previous one.
// A second thread is created to calculate prime numbers.
// The main thread will signal the second thread to calulate the next
// prime number in the series.  The second thread signals the first
// after each number is calculated. Finally, after several iterations
// the worker thread is signaled to terminate.

class CPrimeTest
{
public:
   CPrimeTest()
      : m_pCalcNext(new CEvent(FALSE, FALSE))
      , m_pCalcFinished(new CEvent(FALSE, FALSE))
      , m_pTerminateThread(new CEvent(FALSE, FALSE))
      , m_iCurrentPrime(0)
   {  
      // Create a thread that will calculate the prime numbers
      CWinThread* pThread;
      pThread = ::AfxBeginThread(&PrimeCalcProc, this, 0, 0, CREATE_SUSPENDED, NULL);
      pThread->m_bAutoDelete = FALSE;
      pThread->ResumeThread();

      // Calcuate the first 10 prime numbers in the series on the thread
      for(UINT i = 0; i < 10; i++)
      {
         // Signal the thread to do the next work item
         m_pCalcNext->SetEvent();
         // Wait for the thread to complete the current task
         ::WaitForSingleObject(m_pCalcFinished->m_hObject, INFINITE);
         // Print the result
         TRACE(_T("The value of m_iCurrentPrime is: %d\n"), m_iCurrentPrime);
      }

      // Notify the worker thread to exit and wait for it to complete
      m_pTerminateThread->SetEvent();
      ::WaitForSingleObject(pThread->m_hThread, INFINITE);
      delete pThread;
   }
   ~CPrimeTest()
   {
      delete m_pCalcNext;
      delete m_pCalcFinished;
      delete m_pTerminateThread;
   }

private:
   // Determines whether the given number is a prime number
   static BOOL IsPrime(INT ThisPrime)
   {
      if(ThisPrime < 2)
         return FALSE;

      for(INT n = 2; n < ThisPrime; n++)
      {
         if(ThisPrime % n == 0)
            return FALSE;
      }
      return TRUE;
   }

   // Calculates the next prime number in the series
   static INT NextPrime(INT ThisPrime)
   {
      while(TRUE)
      {
         if(IsPrime(++ThisPrime))
         {
            return ThisPrime;
         }
      }
   }

   // Worker thread responsible for calculating the next prime
   // number in the series
   static UINT __cdecl PrimeCalcProc(LPVOID lpParameter)
   {
      CPrimeTest* pThis = static_cast<CPrimeTest*>(lpParameter);
      VERIFY(pThis != NULL);

      VERIFY(pThis->m_pCalcNext != NULL);
      VERIFY(pThis->m_pCalcFinished != NULL);
      VERIFY(pThis->m_pTerminateThread != NULL);

      // Create a CMultiLock object to wait on the various events
      // WAIT_OBJECT_0 refers to the first event in the array, WAIT_OBJECT_0+1 refers to the second
      CSyncObject* pWaitObjects[] = { pThis->m_pCalcNext, pThis->m_pTerminateThread };
      CMultiLock MultiLock(pWaitObjects, 2L);
      while(MultiLock.Lock(INFINITE, FALSE) == WAIT_OBJECT_0)
      {        
         // Calculate next prime
         pThis->m_iCurrentPrime = NextPrime(pThis->m_iCurrentPrime);
         // Notify main thread calculation is complete
         pThis->m_pCalcFinished->SetEvent();
       }

      // Terminate the thread
       ::AfxEndThread(0, FALSE);
      return 0L;
   }

   CEvent* m_pCalcNext;      // notifies worker thread to calculate next prime
   CEvent* m_pCalcFinished;   // notifies main thread current calculation is complete
   CEvent* m_pTerminateThread;   // notifies worker thread to terminate

   INT m_iCurrentPrime;   // current calculated prime number
};

출처 : http://msdn.microsoft.com/ko-kr/library/efk30beh.aspx
2009. 3. 23. 18:41

동적으로 팝업 메뉴 생성


MFC 일 경우 OnContextMenu에서 아래와 같이 생성한다.

 void    CCustomCtrl::OnContextMenu(CWnd * pWnd, CPoint point)

{

        CMenu Menu;

        if( Menu.CreatePopupMenu() == TRUE)

        {

               for(i=0; i<(UINT)m_StringArrary.GetCount(); i++)

               {

                       Menu.AppendMenu(MF_STRING | MF_ENABLED, POPUPID_RANGE_START + i, m_StringArrary.GetAt(i));

               }

 

               Menu.AppendMenu(MF_SEPARATOR, 0, "");

               Menu.AppendMenu(MF_STRING | MF_ENABLED, POPUPID_ENABLELOG, POPUPSTR_ENABLELOG);

               Menu.AppendMenu(MF_STRING | MF_ENABLED, POPUPID_DIABLELOG, POPUPSTR_DISABLELOG);

               Menu.AppendMenu(MF_STRING | MF_ENABLED, POPUPID_RESET, POPUPSTR_RESET);

               Menu.AppendMenu(MF_STRING | MF_ENABLED, POPUPID_ACTIVEUIID, POPUPSTR_ACTIVEUIID);

 

               Menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);

        }      

}


그리고 위 메시지를 처리하기 위해 메시지 맵에다가 아래와 같이 선언한다.

 BEGIN_MESSAGE_MAP(CCustomCtrl, COleControl)

        ON_COMMAND_RANGE(POPUPID_RANGE_START, POPUPID_RANGE_END, OnCommandRange)

        ON_COMMAND(POPUPID_DIABLELOG, OnDisableLog)

        ON_COMMAND(POPUPID_ENABLELOG, OnEnableLog)

        ON_COMMAND(POPUPID_RESET, OnResetCommand)

        ON_COMMAND(POPUPID_ACTIVEUIID, OnGetActiveUIIDCommand)

END_MESSAGE_MAP()

ON_COMMAND_RANGE 의 경우는 아래와 같이 처리한다.

 

void CCusomCtrl::OnCommandRange(UINT uiCommandID)

{

        switch(uiCommandID) {

};

}



참고로 BCG에서는 아래와 같이 처리한다.

 

void CQueryBuilderGridCtrl::OnContextMenu(CWnd* pWnd, CPoint point)

{

        CMenu contextMenu;

        if(contextMenu.CreatePopupMenu() == TRUE)

        {

               contextMenu.AppendMenu(MF_STRING | MF_ENABLED, QBGC_ID_SEARCH, _T("&Search") );

               contextMenu.AppendMenu(MF_SEPARATOR, 0, "");

               contextMenu.AppendMenu(MF_STRING | MF_ENABLED, QBGC_ID_INSERT, _T("&Insert") );

               contextMenu.AppendMenu(MF_STRING | MF_ENABLED, QBGC_ID_DELETE, _T("&Delete") );

               contextMenu.AppendMenu(MF_STRING | MF_ENABLED, QBGC_ID_GROUP, _T("&Group") );

               contextMenu.AppendMenu(MF_STRING | MF_ENABLED, QBGC_ID_UNGROUP, _T("&Ungroup") );

 

 

               CBCGPPopupMenu * pPopupMenu = new CBCGPPopupMenu;

               if (!pPopupMenu->Create(this, point.x, point.y, contextMenu.GetSafeHmenu(), FALSE, TRUE))

               {

                       return;

               }

 

               ((CBCGPFrameWnd*)AfxGetMainWnd())->OnShowPopupMenu (pPopupMenu);

        }

}


만약 좌표가 정확치 않은 경우 ClientToScreen 또는 ScreenToClient를 활용한다.

2009. 3. 20. 08:26

MFC를 이용한 COM Connection Point 구현

커넥션은 아래 부분으로 구성되어 있다.

 

소스 (Source)

           인터페이스를 호출하는 객체

싱크 (Sink)

           인터페이스를 구현하는 객체

 

커넥션 포인트는 소스에 의해 노출되는 인터페이스이다. 커넥션 포인트 매커니즘(IConnectionPoint 인터페이스) 통해서, 싱크 인터페이스 포인터가 소스 객체에 전달된다. 싱크 인터페이스 포인터를 이용하여 싱크 객체의 멤버 함수를 호출할 있다.

 


 

MFC CConnectionPoint CCmdTarget 클래스 내에 모델을 구현하였다. 다른 객체에 커넥션 포인트를 노출하기 위해 CConnectionPoint 상속한 클래스가 IConnectionPoint 인터페이스를 구현한다. CCmdTarget 상속한 클래스는 IConnectionPointContainer 구현하고, 그리고 클래스는 모든 이용 가능한 커넥션 포인터를 열거하거나 특정 커넥션 포인트를 찾을 있다.

 

각각의 커넥션 포인트마다 커넥션 포인트를 구현하는 connection part 선언해야 한다. 하나 이상의 커넥션 포인트를 구현한다면, 클래스 내에 single connection map 선언해야 한다. Connection Map ActiveX 컨트롤에 의해 지원되는 커넥션 포인트 테이블이다.

 

class CMyClass : public CCmdTarget

{

           ...

protected:

           // Connection point for ISample interface

           BEGIN_CONNECTION_PART(CMyClass, SampleConnPt)

                     CONNECTION_IID(IID_ISampleSink)

           END_CONNECTION_PART(SampleConnPt)

 

           DECLARE_CONNECTION_MAP()

 

};

 

BEGIN_CONNECTION_PART END_CONNECTION_PART 매크로는 내장 클래스(embedded class) XSampleConnPt (ConnectionPoint 상속한) 선언한다. CConnectinPoint 멤버함수를 재정의 하거나 멤버 함수를 추가하길 원한다면, 매크로 사이에 선언하면 된다. 예를 들면, CONNECTION_IID 매크로는 매크로 사이에 선언될 CConnectionPoint::GetIID 멤버함수를 재정의한다.

 

두번째 예제에서는, 구현 파일에 코드가 추가된다. 코드는 connection map 구현한다.

 

BEGIN_CONNECTION_MAP(CMyClass, CMyBaseClass)

        CONNECTION_PART(CMyClass, IID_ISampleSink, SampleConnPt)

END_CONNECTION_MAP()

 

하나 이상의 커넥션 포인트를 가지고 있다면, BEGIN_CONNECTION_MAP END_CONNECTION_MAP 매크로 사이에 CONNECTION_PART 매크로를 추가하면 된다.

 

마지막으로, 클래스의 생성자에 EnableConnection 호출하는 코드를 추가한다.

 

CMyClass::CMyClass()

{

        EnableConnections();

        ...

}

 

Dl 코드가 추가되면, CCmdTarget 상속한 클래스는 ISampleSink 인터페이스에 대한 커넥션 포인트를 노출시킨다.

 


 

 

보통, 커넥션 포인트는 “multicasting” 지원한다 동일한 인터페이스에 연결된 다수의 싱크에게 브로드캐스팅. 아래 예는 커넥션 포인트에 대한 각각의 싱크를 반복하면 멀티캐스트하는 방법을 보여준다.

 

void CMyClass::CallSinkFunc()

{

        const CPtrArray* pConnections = m_xSampleConnPt.GetConnections();

        ASSERT(pConnections != NULL);

 

        int cConnections = pConnections->GetSize();

        ISampleSink* pSampleSink;

        for (int i = 0; i < cConnections; i++)

        {

               pSampleSink = (ISampleSink*)(pConnections->GetAt(i));

               if(pSampleSink != NULL)

                       pSampleSink->SinkFunc();

        }

}

 

예제는 CConnectionPoint::GetConnections 호출하여 SampleConnPtr 커넥션 포인트에 대한 현재 커넥션들을 가져온다. 그러고 나서 커넥션을 탐색하면서 모든 커넥션에 대해 ISampleSink::SinkFunc 호출한다.


추가 (2009-06-08) : 연결 지점 (Connection point) 이 제대로 동작하려면 소스 파일의 BEGIN_INTERFACE_MAP 에 IID_IConnectionPointContainer 를 선언해 주어야 한다.


BEGIN_INTERFACE_MAP(CMyClass, CDocument)

        INTERFACE_PART(CMyClass, IID_ITestExeuction, Dispatch)

        INTERFACE_PART(CMyClass, IID_IConnectionPointContainer, ConnPtContainer)

END_INTERFACE_MAP()


관련글 : http://codemuri.tistory.com/entry/%EB%B2%88%EC%97%AD-ActiveX-script-hosting

From : MSDN (ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_vclib/html/bc9fd7c7-8df6-4752-ac8c-0b177442c88d.htm)
2009. 3. 11. 14:05

View 분할하기

CSplitterWnd 를 이용하여 뷰를 분할하는 코드다. 각 분할된 뷰는 하나의 공통된 Document와 연결되도록 한다.

OnCreate 에서 다음과 같이 정의한다. 여기서 View 는 new로 할당되는 포인터이어야 하고, 삭제는 CSplitterWnd에서 자동으로 하기 때문에 new로 할당은 하더라도 삭제는 하지 않음에 주의한다.

 int CSplitSampleView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

        if (CView::OnCreate(lpCreateStruct) == -1)

               return -1;

 

        // TODO:  Add your specialized creation code here

        if (!m_wndSplitter.CreateStatic (this, 2, 1))

        {

               ASSERT(FALSE);

               return FALSE;

        }

 

        m_pView1 = new CEditView;

        m_pView2 = new CEditView;

 

        CCreateContext context;

        context.m_pCurrentDoc = GetDocument();

 

        DWORD dwStyle = WS_CHILD | WS_VISIBLE;

        BOOL blSuccess = m_pView1->Create(NULL, _T("test1"), dwStyle, CRect(0,0,0,0), &m_wndSplitter, m_wndSplitter.IdFromRowCol (0, 0), (CCreateContext*)&context);

        if(blSuccess == FALSE)

 

        {

 

               return FALSE;

 

        }

        m_pView1->SendMessage(WM_INITIALUPDATE);

 

       

        blSuccess = m_pView2->Create(NULL, _T("test2"), dwStyle, CRect(0,0,0,0), &m_wndSplitter, m_wndSplitter.IdFromRowCol (1, 0), (CCreateContext*)&context/*lpCreateStruct->lpCreateParams*/);

        if(blSuccess == FALSE)

        {

               return FALSE;

 

        }

        m_pView2->SendMessage(WM_INITIALUPDATE);

 

        return 0;

}


그리고 OnSize 에서 다음과 같이 한다.

 void CSplitSampleView::OnSize(UINT nType, int cx, int cy)

{

        CView::OnSize(nType, cx, cy);

 

        if (m_wndSplitter.GetSafeHwnd () != NULL)

        {

               m_wndSplitter.SetWindowPos (this, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW);

        }

}


2009. 3. 5. 03:12

View 깜박임 문제 해결


웹사이트를 돌아다니다가 아래 글을 발췌하였다.

View에서 깜박임 문제 해결

* 메모리 DC 이용
* WM_ERASEBKGND 메시지에서 return FALSE

메모리 DC를 이용하는 방법은 익숙하나, WM_ERASEBKGND 메시지 처리는 잘 하지 않았다. 혹 View에서 깜박임이 신경쓰인다면 이 메시지를 주의깊게 다뤄보자.

 BOOL CMyView::OnEraseBkgnd(CDC * pDC)
{
  return FALSE;
  // return CView::OnEraseBkgnd(pDC);
}


참고 : http://rnd.vitzro.com/
2009. 2. 20. 12:46

AfxOleLockApp and AfxOleUnlockApp

The Microsoft Foundation Class Library provides additional facilities for controlling application behavior when external clients have references to the application's objects. Besides maintaining a count of references to each object, servers maintain a global count of active objects. The global functions AfxOleLockApp and AfxOleUnlockApp update the application's count of active objects. If this count is nonzero, the application does not terminate when the user chooses Close from the system menu or Exit from the File menu. Instead, the application's main window is hidden (but not destroyed) until all pending client requests have been completed. Typically, AfxOleLockApp and AfxOleUnlockApp are called in the constructors and destructors, respectively, of classes that support Automation.

FROM : MSDN
2009. 2. 18. 22:54

MDI 샘플: 문서/뷰 아키텍처 사용/미사용 예제



문서/뷰 아키텍처 미사용

응용 프로그램 마법사에서 생성된 MDI 응용 프로그램과 달리 이 샘플 응용 프로그램에서는 프레임워크의 문서 및 뷰 지원을 사용하지 않습니다. 따라서 이 응용 프로그램에서는 문서 템플릿을 사용하지 않으며 응용 프로그램의 InitInstanceAddDocTemplate을 호출하지 않습니다.

그러나 이 응용 프로그램은 프레임워크의 MDI 지원을 완전히 사용합니다. CMainFrameCMDIFrameWnd에서 파생되었으며 CBounceWndCHelloWndCMDIChildWnd에서 파생되었습니다. 또한 Window 메뉴에 있는 Tile과 같은 명령은 프레임워크의 기본 CMDIFrameWnd 구현에 의해 처리됩니다.

CBounceWndCHelloWnd는 모두 Create를 재정의하지만 프레임워크의 MDI 지원을 사용하는 데 이 재정의가 꼭 필요한 것은 아닙니다. Create의 재정의를 통해 창의 기본 커서 및 아이콘을 변경하는 방법을 이해할 수 있습니다. 창의 기본 커서 또는 아이콘을 변경하려면 AFXRegisterWndClass를 호출한 다음 Create를 호출할 때 WNDCLASS의 이름을 전달하여 새 WNDCLASS를 등록해야 합니다.


문서/뷰 아키텍처 사용



출처 : MSDN
2009. 2. 18. 22:24

GetCurrentMessage() - 메시지 핸들러에서 메시지 정보 가져오기


이 함수에 대해서, MSDN에 아래와 같이 나와 있다.

Returns a pointer to the message this window is currently processing. Should only be called when in an OnMessage message-handler member function.


MFC 에서 OnMessage 핸들러내에서 GetCurrentMessaeg() 를 호출하면 MSG 타입의 구조체를 얻을 수 있어 현재 처리중인 명령의 WPARAM과 LPARAM을 알 수 있다. 

아래는 이 함수를 사용한 MSDN 예제 중의 일부이다.

#define IDM_BLACK                       20

#define IDM_RED                         21

#define IDM_GREEN                       22

#define IDM_BLUE                        23

#define IDM_WHITE                       24

 

BEGIN_MESSAGE_MAP(CHelloWnd, CMDIChildWnd)

        //{{AFX_MSG_MAP(CHelloWnd)

        ON_COMMAND(IDM_BLACK, OnColor)

        ON_COMMAND(IDM_RED, OnColor)

        ON_COMMAND(IDM_GREEN, OnColor)

        ON_COMMAND(IDM_BLUE, OnColor)

        ON_COMMAND(IDM_WHITE, OnColor)

        //}}AFX_MSG_MAP

END_MESSAGE_MAP()

 

 

void CHelloWnd::OnColor()

{

        m_nIDColor = LOWORD(GetCurrentMessage()->wParam);

        m_clrText = colorArray[m_nIDColor - IDM_BLACK];

 

        // Force the client area text to be repainted in the new color

        Invalidate();

}


여기서 한가지 더, 같은 핸들러를 이용하여 같은 종류의 명령어를 처리하는 방법을 눈여겨 보자.
2009. 2. 17. 12:16

Document Template 과 Resource String과의 관계

App InitInstance 들여다 보면 다음과 같이 Document Template 추가하는 코드를 있다. AddDocTemplate 파라미터로,

           첫째는 IDR_??? 같은 Resource ID,

           둘째는 Document 클래스

           셋째는 Child Wnd 클래스

           넷째는 View 클래스를

넘겨 준다.

 

//example for CMultiDocTemplate

BOOL CMyApp::InitInstance()

{

        // ...

        // Establish all of the document types

        // supported by the application

 

        AddDocTemplate( new CMultiDocTemplate( IDR_SHEETTYPE,

                            RUNTIME_CLASS( CSheetDoc ),

                            RUNTIME_CLASS( CMDIChildWnd ),

                            RUNTIME_CLASS( CSheetView ) ) );

       

        // ...

}

 

예제의 경우 IDR_SHEETTYPE 이라는 리소스 아이디를 넘겨주는데, 아이디와 연결된 리소스 문자열이 아래와 같다고 하자.

 

// MYCALC.RC

STRINGTABLE PRELOAD DISCARDABLE

BEGIN

    IDR_SHEETTYPE "\nSheet\nWorksheet\nWorksheets (*.myc)\n.myc\n MyCalcSheet\nMyCalc Worksheet"

END

 

‘ \n ’으로 구분된 문자열을 있는데, 각각이 뜻하는 바는 CDocTemplate GetDocString() 함수와 관련이 있으며, DocStringIndex 관련된 설명이 MSDN 자세하게 나와있다.

 

class CDocTemplate {

……

           enum DocStringIndex

           {

                     windowTitle,        // default window title

                     docName,            // user visible name for default document

                     fileNewName,        // user visible name for FileNew

                     // for file based documents:

                     filterName,         // user visible name for FileOpen

                     filterExt,          // user visible extension for FileOpen

                     // for file based documents with Shell open support:

                     regFileTypeId,      // REGEDIT visible registered file type identifier

                     regFileTypeName,    // Shell visible registered file type name

           };

          

           virtual BOOL GetDocString

( CString& rString,  enum DocStringIndex index ) const;

 

……

};

 

  • CDocTemplate::windowTitle   Name that appears in the application window's title bar (for example, "Microsoft Excel"). Present only in the document template for SDI applications.

à 디폴트 윈도우 제목

  • CDocTemplate::docName   Root for the default document name (for example, "Sheet"). This root, plus a number, is used for the default name of a new document of this type whenever the user chooses the New command from the File menu (for example, "Sheet1" or "Sheet2"). If not specified, "Untitled" is used as the default.

à 디폴트 다큐먼트 이름

  • CDocTemplate::fileNewName   Name of this document type. If the application supports more than one type of document, this string is displayed in the File New dialog box (for example, "Worksheet"). If not specified, the document type is inaccessible using the File New command.

à 여러 문서 타입을 지원할 경우, File New 명령을 위한 다큐먼트 이름. Template 여러 개일 경우 File New 명령을 호출하면 Template 선택할 있는 대화상자가 나타난다. 이때 표시되는 이름이며, 만약 이름이 지정되어 있지 않으면 대화상자에 나타나지 않는다.

  • CDocTemplate::filterName   Description of the document type and a wildcard filter matching documents of this type. This string is displayed in the List Files Of Type drop-down list in the File Open dialog box (for example, "Worksheets (*.xls)"). If not specified, the document type is inaccessible using the File Open command.

à 파일 대화상자의 리스트에 표시되는 필터 문자열

  • CDocTemplate::filterExt   Extension for documents of this type (for example, ".xls"). If not specified, the document type is inaccessible using the File Open command.

à 다큐먼트의 확장자

  • CDocTemplate::regFileTypeId   Identifier for the document type to be stored in the registration database maintained by Windows. This string is for internal use only (for example, "ExcelWorksheet"). If not specified, the document type cannot be registered with the Windows File Manager.

à 윈도우에서 관리되는 등록 데이터베이스에 저장되는 다큐먼트의 ID. 지정되지 않으면 다큐먼트가 윈도우즈 파일 관리자에 등록될 수 없다.

  • CDocTemplate::regFileTypeName   Name of the document type to be stored in the registration database. This string may be displayed in dialog boxes of applications that access the registration database (for example, "Microsoft Excel Worksheet").

à 등록 데이터베이스에 저장되는 다큐먼트의 이름. 이름은 등록 데이터 베이스 대화상자에 표시된다


regFileTypeIdregFileTypeName을 지정한 경우, CWinApp::RegisterShellFileTypes 함수를 호출하여 윈도우즈 파일 관리자에 등록한다. 


이와 같이, 각각의 의미를 제대로 이해하고 사용하자.

2009. 2. 5. 00:10

How to Replace a View in a Splitter Window



The SPLIT32 sample demonstrates the following Microsoft Foundation Class (MFC) programming concepts:


A ReplaceView() function can be created which replaces a view in a CSplitterWnd pane with another. The function replaces a view but uses the same CDocument object. This function can be helpful if a programmer wants to keep the same frame window and document for a splitter pane, and only wants to delete a view in a pane and replace it with another.
The CDocument variable m_bAutoDelete can be used to prevent the destruction of a CDocument object if all views of the document are destroyed.
The CWnd::PreCreateWindow() function can be used to prevent the main application from having a thick frame, thus preventing the user from resizing the main application window.
By trapping the WM_SETCURSOR, WM_MOUSEMOVE, and WM_LBUTTONDOWN messages of a CSplitterWnd, you can fix the splitter bars in a splitter window so that they can't be moved by the user.
CSplitterWnd panes can contain regular CWnd objects and are not limited to the use of CViews. This sample places a dialog in the first pane.
CWnd::CenterWindow() can be used to position windows at the center of the screen.
The following files are available for download from the Microsoft Download Center:


Split32.exe (http://download.microsoft.com/download/vc40std/sample/14/win98/en-us/split32.exe)

For additional information about how to download Microsoft Support files, click the following article number to view the article in the Microsoft Knowledge Base:
119591  (http://support.microsoft.com/kb/119591/EN-US/ ) How to Obtain Microsoft Support Files from Online Services
Microsoft scanned this file for viruses. Microsoft used the most current virus-detection software that was available on the date that the file was posted. The file is stored on security-enhanced servers that help to prevent any unauthorized changes to the file.

NOTE: Use the -d option when running SPLIT32.EXE to decompress the file and recreate the proper directory structure.

NOTE: The following Microsoft Software Library samples, which also replace various views, are also available:


VWRPLC32 (MDI)
COLLECT (MDI)
ENROLL (SDI)
VSWAP32 (SDI)
To understand what the sample does, first execute the program. You will see a main application window with a splitter window (CSplitterWnd) in its client area. A selection dialog will be displayed in the first pane and a view will be displayed in the second pane.

Click on the View 1 and View 2 buttons. Notice that the view in the second pane will switch from one to the other. The view is changing, but after looking at the source you'll see that the document stays the same.


From : http://support.microsoft.com/kb/149257

2009. 1. 20. 08:14

문서 템플릿과 문서/뷰 만들기 프로세스

문서 만들기 단계


프레임창 만들기 단계


뷰 만들기 단계


문서 생성 및 열기 시퀀스 다이어그램



Doc/View 프레임워크 클래스 다이어그램


참고 : MSDN 및 MFC 코드
2009. 1. 19. 13:30

DECLARE_DYNCREATE 매크로와 RUNTIME_CLASS 매크로의 관계

MFC로 생성한 SDI 또는 MDI 기반의 애플리케이션의 경우 CWinApp::InitInstance()에 보면 다음의 코드가 있다.

 

             CMultiDocTemplate* pDocTemplate;

             pDocTemplate = new CMultiDocTemplate(

                           IDR_TABBEDTYPE,

                           RUNTIME_CLASS(CTabbedViewDoc),

                           RUNTIME_CLASS(CChildFrame), // custom MDI child frame

                           RUNTIME_CLASS(CTabbedViewView));

             AddDocTemplate(pDocTemplate);

 

RUNTIME_CLASS 매크로 내에 CDocument를 상속한 CTabbedViewDoc 클래스가 선언되어 있다. 그리고 CTabbedViewDoc 클래스 선언부에는 반드시 DECLARE_DYNCREATE 매크로가 선언되어 있다.

 

DECLARE_DYNCREATE 매크로를 정의한 코드를 살펴보면,

 

#define DECLARE_DYNCREATE(class_name) \

             DECLARE_DYNAMIC(class_name) \

             static CObject* PASCAL CreateObject();

 

 

그리고, 내부에 선언된 DECLARE_DYNAMIC 매크로를 살펴보면

 

#define DECLARE_DYNAMIC(class_name) \

public: \

             static const CRuntimeClass class##class_name; \

             virtual CRuntimeClass* GetRuntimeClass() const; \

 

 

 

CTestClass 내에 위 매크로를

 

DECLARE_DYNCREATE(CTestClass)

 

과 같이 선언했다고 가정했을 경우 아래와 같은 코드가 추가되는 것과 같다.

 

class CTestClass

{

             // ….

Static const CRuntimeClass classCTestClass;   // ##은 문자열 결합

virtual CRuntimeClass * GetRuntimeClass() const;

static CObject * PASCAL CreateObject();

 

// ….

}

 

 

그리고 RUNTIME_CLASS 매크로의 정의 코드를 살펴보면 다음과 같다.

 

#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

#ifdef _AFXDLL

#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())

#else

#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)

#endif

 

위와 같이 선언되어 있는데, APP의 경우 DLL이 아니므로 결국

 

#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

 

와 같다. 그러면 다시 처음에 소개된 코드에서 매크로를 원 코드로 변환하면 아래와 같다.

 

             CMultiDocTemplate* pDocTemplate;

             pDocTemplate = new CMultiDocTemplate(

                           IDR_TABBEDTYPE,

((CRuntimeClass*)(&class_name::classCTabbedViewDoc)),

((CRuntimeClass*)(&class_name::classCChildFrame)),

((CRuntimeClass*)(&class_name::classCTabbedViewView)) );

             AddDocTemplate(pDocTemplate);

 

 

위와 같이 CMultiDocTemplate에 인자로 넘어가는 값은 DECLARE_DYNCREATE 매크로에 의해 선언된 멤버 변수의 포인터를 넘기는 것이고, 이 포인터는 CMultiDocTemplate의 부모 클래스인 CDocTemplate 의 생성자에서 다음과 같이 저장된다.

 

CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,

             CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)

{

             // … 중략

             m_pDocClass = pDocClass;

             m_pFrameClass = pFrameClass;

             m_pViewClass = pViewClass;

             // … 중략

}

 

CDocTemplate은 위의 멤버 변수들을 통해서 필요할 때 각각의 클래스에 해당하는 새로운 인스턴스를 생성한다.

 

아래 코드를 살펴보면, CDocTemplate::CreateNewFrame 내에서 CreateObject 함수 호출을 통해서 CFrameWnd 인스턴스를 생성하고, 그 인스턴스를 통해서 CFrameWnd::LoadFrame 을 호출하면 CDocTemplate::CreateNewFrame 에서 넘겨준 CreateContext 인자를 이용하여 CFrameWnd::LoadFrame 함수 내에서 Create를 호출하여 View를 생성한다. 그리고 CDocTemplate::CreateNewDocument() 내에서 CreateObject 함수를 호출하여 Document 인스턴스를 생성한다.

 

CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)

{

             // … 중략

             CCreateContext context;

             context.m_pCurrentFrame = pOther;

             context.m_pCurrentDoc = pDoc;

             context.m_pNewViewClass = m_pViewClass;

             context.m_pNewDocTemplate = this;

 

             CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();

             // … 중략

 

             // create new from resource

             if (!pFrame->LoadFrame(m_nIDResource,

                                        WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles

                                        NULL, &context))

             {

                           TRACE(traceAppMsg, 0, "Warning: CDocTemplate couldn't create a frame.\n");

                           // frame will be deleted in PostNcDestroy cleanup

                           return NULL;

             }

 

             // it worked !

             return pFrame;

}

 

 

BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,

             CWnd* pParentWnd, CCreateContext* pContext)

{

             // … 중략

             if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,

               pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext)) // 뷰 생성

             {

                           return FALSE;   // will self destruct on failure normally

             }

 

             // … 중략

             return TRUE;

}

 

 

CDocument* CDocTemplate::CreateNewDocument()

{

             // 중략

             CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();

             // 중략

             return pDocument;

}

 

긴 내용이지만 위의 내용을 짧게 요약하면 다음과 같다.

 

DECLARE_DYNCREATE 매크로는 RUNTIME_CLASS 매크로에서 필요로 하는 멤버 변수(class##class_name) MFC 프레임워크 내에서 동적으로 인스턴스를 생성하기 위한 멤버 함수(CreateObject)를 제공하기 위해서 선언을 하는 것이다.