안녕하세요 ~ MFC 카테고리의 꽃집총각 입니다.

이번에는 그 동안 함께 멀티터치를 공부하면서 다뤄보았던 예제 프로그램들의 소스를 정리해 보았습니다.

아래 코드를 다운 받으세요 :)


Gesture : WM_GESTURE 메세지를 이용해 화면상의 사각형을 이동, 회전, 확대/축소 하는 예제.

Touch : WM_TOUCH 메세지를 이용해 클라이언트 영역에 터치 입력 궤적을 따라 선을 그려주는 예제.

Manipulation : IManipulationProcessor 인터페이스를 활용해 Microsoft Surface에서와 같은 Manipulation 움직임을 제어하는 예제.

Manipulation Inertia : IManipulationProcessor, IInertiaProcessor 인터페이스를 모두 활용, 조작(Manipulation) 및 관성(Inertia) 모두를 적용한 예제.

조작(Manipulation)과 관성(Inertia) 처리 방법은 아직 포스팅으로 설명 드리지 못한 부분입니다만 샘플 코드 정리하면서 함께 정리해 공개합니다.

모든 코드는 기본적으로 MS에서 제공하는 예제 코드를 기반으로 하고, 제가 포스팅 하면서 추가로 변경한 부분들의 코드도 일부 포함되어 있습니다. 참고 부탁 드립니다. 

신고
크리에이티브 커먼즈 라이선스
Creative Commons License

Intro

안녕하세요 ~ MFC 카테고리의 꽃집총각 입니다.

지난 포스팅에서는 드디어 WM_TOUCH를 이용한 멀티터치 UX의 구현방법에 대한 이야기가 시작되었습니다. channel9에 공개된 예제를 함께 작성해 보면서 WM_TOUCH 메세지를 다루는 방법을 알아보고 있는데요, 어떨 때 WM_TOUCH를 이용해야 하는지, WM_GESTURE가 아닌 WM_TOUCH를 받고 싶을 땐 어떻게 처리해 주어야 하는지, 그리고 메세지를 받을 때마다 호출되는 CWnd::OnTouchInput 등에 대한 설명이 있었습니다.

그리고 실제로 OnTouchInput() 함수에서 터치 메세지를 제어할 예제 프로그램인 ‘TouchPad’ 프로젝트를 생성하고 기본적인 전처리 단계를 알아보았지요. 이에 이어서 오늘은 OnTouchInput() 함수의 구현부터 본격적으로 알아보도록 하겠습니다 ^^

 

Task 3 : 드로잉 코드는 가져다 씁시다. 터치에 집중해야죠 ㅎㅎ

지금 함께 작성해 보려고 하는 TouchPad는 윈도우 그림판처럼 멀티 터치 입력에 대해 라인을 그려주는 예제입니다. 한 발 더 나가서, 서로 다른 입력으로 드로잉 하는 라인은 색상도 다르게 표시되도록 할거예요. 하지만 사용자로부터 입력된 터치 정보의 처리방법에 집중하기 위해, 드로잉 코드는 샘플에서 제공된 클래스를 사용하도록 하겠습니다. 먼저 예제 프로젝트에 아래의 파일을 추가해 주세요.

 이 클래스들을 이용해 우리가 구현할 코드에 대해 간략히 설명해 보기로 하지요. 우리는 각각의 터치 입력에 대해서 라인(stroke. 스트로크라고 부르겠습니다.)을 그려내는 기능을 추가할 것인데, 이를 이해서 두 개의 스트로크 모음을 유지할겁니다. 한 쪽에서는 이미 드로잉이 끝난 스트로크 데이터를 모아 유지하게 되고, 또 다른 나머지 한 쪽에서는 현재 터치입력으로 드로잉하고 있는 데이터를 유지하게 됩니다. 스크린을 터치하는 한 개 혹은 다수의 입력을 받아 m_StrkColDrawing 멤버변수가 유지하는 스트로크 정보에 CPoint 형식의 좌표 데이터를 추가합니다. 그리고 스크린에서 손가락을 떼면, 손가락을 따라 그려지고 있던 스트로크 정보를 m_StrkColDrawing 변수에서 m_StrkColFinished로 이동시킵니다. 이 때 동시에 다수의 터치 입력이 동시에 일어나면 이를 구분하기 위해 서로가 다른 색상을 가진 스트로크를 출력하게 할겁니다.

 그럼 소스코드 상에서의 수정사항을 알아보기로 하지요. 먼저 위에 첨부되어있는 파일들을 다운받아서 프로젝트에 넣은 후, StdAfx.h 헤더파일에 새로 추가된 헤더들을 인클루드 해주세요.

// stdafx.h 파일에 추가해 주세요.
#include "Stroke.h"  
#include "StrokeCollection.h" 

그리고 ChildView.h 파일로 가서 CChildView 클래스에 private 멤버 변수로 아래의 세 변수를 추가해 줍니다.

private:
    int m_iCurrColor;                    // The current stroke color
    CStrokeCollection m_StrkColFinished; // The user finished entering strokes
                                         // after user lifted his or her finger.
    CStrokeCollection m_StrkColDrawing;  // The Strokes collection the user is
                                         // currently drawing.

이 중에서 m_iCurrColor는 드로잉 되는 스트로크의 색상을 결정하기 위한 인덱스로 쓰일 겁니다. 생성자에서 0으로 초기화 해주세요.

CChildView::CChildView() : m_iCurrColor(0)
{
}

드로잉이 다 끝나서 m_StrkColFinished 변수에 담겨 있을 스트로크 정보들을 화면에 출력해 주기 위해 CChileView::OnPaint() 함수에 아래의 코드를 추가해 줍니다.

// 드로잉이 완료된 스트로크 정보를 출력한다.
m_StrkColFinished.Draw(&dc);

 

Task 4 : 터치 데이터를 다뤄보자 (드디어!)

이제 드로잉 코드의 기본적인 설정들은 끝을 냈고, 지금부터 우리는 WM_TOUCH 메세지에 대해 아래의 세 가지 경우에 따른 코딩을 추가해 넣을 것입니다.

  1. 사용자가 스크린에 터치를 시작한 시점의 처리. – Touch Input Down
  2. 터치한 손가락을 스크린 상에서 움직이는 동안의 처리. – Touch Input Move
  3. 스크린에서 이동하던 손가락을 떼어 터치 입력이 끝나는 시점의 처리. – Touch Input Up

각각의 경우를 처리하기 위한 코드를 클래스에 추가해 봅시다. ChildView.h 헤더파일에서 위의 세가지 경우에 호출할 CChildView 클래스의 멤버함수를 선언해 줍니다.

protected:
    // Handlers for different touch input events
    BOOL OnTouchInputDown(CPoint pt, PTOUCHINPUT pInput);
    BOOL OnTouchInputMove(CPoint pt, PTOUCHINPUT pInput);
    BOOL OnTouchInputUp(CPoint pt, PTOUCHINPUT pInput);

그리고 ChildView.cpp 파일에 아래의 구현을 넣어주세요.

// OnTouchInputDown - 멀티터치 입력이 시작될 때 처리를 구현하는 함수.
BOOL CChildView::OnTouchInputDown(CPoint pt, PTOUCHINPUT pInput)
{
    // 새로운 스트로크 객체를 생성하고 Point 정보를 추가합니다.
    COLORREF strokeColor = GetTouchColor((pInput->dwFlags & TOUCHEVENTF_PRIMARY) != 0);
    
    CStroke* pStrkNew = new CStroke(pInput->dwID, strokeColor);
    pStrkNew->Add(pt);
    
    // 새로 생성한 스트로크 객체를 현재 드로잉 중인 스트로크 정보를 보관하는 
    // m_StrkColDrawing 변수에 추가해 줍니다.
    m_StrkColDrawing.Add(pStrkNew);
 
    return TRUE;
}
 
// OnTouchInputMove - 멀티터치 입력이 지속적으로 들어오고 있을 때의 처리를 구현하는 함수.
BOOL CChildView::OnTouchInputMove(CPoint pt, PTOUCHINPUT pInput)
{
    // 한 번에 다수의 터치 입력이 들어올 수 있다.
    // TOUCHINPUT 구조체의 dwID 변수를 이용해 
    // 현재 드로잉 하고 있는 스트로크들의 데이터 집합을 찾아낸다.
    int strokeIndex = m_StrkColDrawing.FindStrokeById(pInput->dwID);
 
    if (strokeIndex >= 0) // 해당 ID의 데이터를 찾아낸 경우.
    {
        CStroke* pStrk =  m_StrkColDrawing[strokeIndex];
    
        // 터치 입력이 들어온 좌표값 Point 정보를 스트로크에 추가해 준다.
        pStrk->Add(pt);
 
        // 스트로크를 다시 그려준다. 
        pStrk->Draw(GetDC());
    }
    
    return TRUE;
}
 
// OnTouchInputUp - 멀티터치 입력이 끝날 때의 처리를 구현하는 함수.
BOOL CChildView::OnTouchInputUp(CPoint pt, PTOUCHINPUT pInput)
{
    // 한 번에 다수의 터치 입력이 들어올 수 있다.
    // TOUCHINPUT 구조체의 dwID 변수를 이용해 
    // 현재 드로잉 하고 있는 스트로크들의 데이터 집합을 찾아낸다.
    int strokeIndex = m_StrkColDrawing.FindStrokeById(pInput->dwID);
 
    if (strokeIndex >= 0) // 해당 ID의 데이터를 찾아낸 경우.
    {
        CStroke* pStrkCopy = m_StrkColDrawing[strokeIndex];
 
        // 찾아낸 스트로크 정보를 m_StrkColDrawing 변수에서 제거하고...
        m_StrkColDrawing.RemoveAt(strokeIndex);
        
        // 제거한 CStroke 클래스의 포인터를 m_StrkColFinished 변수에 추가해준다.
        m_StrkColFinished.Add(pStrkCopy);
    }
    
    return TRUE;
}
 

 각각의 함수 본문에는 터치가 발생한 시점에 걸맞는 드로잉 코드들이 구현되어 있습니다. 샘플 원본에는 영어로 달려있던 주석을 제가 한글로 바꾸고 추가 설명도 좀 덧붙여 두었습니다. 아직까지는 별다른 설명 없이 코드만 보더라도 어렵지 않게 이해하실 수 있을 거예요.

 이 함수들의 본문보다 더 중요한 부분은, 이 함수들을 '언제 호출해 주어야 하느냐' 하는 점입니다. 멀티 터치 데이터의 입력 시점에 따른 상태를 확인하기 위해 주의깊게 확인해야 할 부분은 지난 포스팅에서 선언만 해두고 빈 함수 몸통만 덩그라니 남겨두었던 CChildView::OnTouchInput(...) 함수입니다. 이곳에서 현재 멀티터치 데이터를 조회해 현재 입력 시점이 어떻게 되는지, 혹은 부가적인 다른 정보들을 어떻게 얻어내는지를 알아보는 것이 이번 포스팅의 중요한 목표라고 볼 수 있죠. 아래의 코드를 유심히 봐주세요.

BOOL CChildView::OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput)
{     
    if ((pInput->dwFlags & TOUCHEVENTF_DOWN) == TOUCHEVENTF_DOWN)
    {
        return OnTouchInputDown(pt, pInput); // 터치 시작!
    }
    else if ((pInput->dwFlags & TOUCHEVENTF_MOVE) == TOUCHEVENTF_MOVE)
    {
        return OnTouchInputMove(pt, pInput); // 터치 입력 진행중.
    }
    else if ((pInput->dwFlags & TOUCHEVENTF_UP) == TOUCHEVENTF_UP)
    {
        return OnTouchInputUp(pt, pInput);    // 터치 입력 마무리.
    }
 
    return FALSE;
}

OnTouchInput 함수의 인자로 TOUCHINPUT 구조체의 포인터가 전달되는데, 이 구조체의 멤버 변수인 dwFlag에 담겨있는 flag의 상태를 조회함으로써 현재 터치 입력에 대한 여러 가지 정보들을 알아낼 수 있습니다. 위에 적힌 코드에서도 dwFlag를 이용해 터치 데이터의 추가 정보들을 알아내고 있습니다. 위의 함수 본문을 복사해 OnTouchInput 함수의 구현을 대체해 주세요.

이 중 일련의 멀티터치 입력이 처음 시작될 때의 처리를 위한 OnTouchInputDown(…) 함수를 한 번 보기로 하죠. 여기에서도 TOUCHINPUT 구조체의 dwFlag 를 조회하는 코드를 볼 수 있습니다. 바로 TOUCHEVENTF_PRIMARY 플래그가 켜져 있는지를 확인해 보는 코드네요. 입력중인 터치 정보가 여러 개인 경우, 이 중 가장 먼저 시작된 (PRIMARY) 터치 정보 인지를 확인하는 부분입니다. PRIMARY 터치는 스트로크의 색상을 검정으로 유지하고, 그렇지 않은 경우는 미리 정의된 몇 가지 색상 정보를 rotation해가며 결정해 주기 위해서 PRIMARY 데이터 여부를 확인하고 있습니다. 스트로크의 색상을 결정해주는 GetTouchColor() 함수의 선언 및 구현도 아래와 같이 추가해 주세요.

// in ChildView.h 
private:
    COLORREF GetTouchColor(bool bPrimaryContact);
COLORREF CChildView::GetTouchColor(bool bPrimaryContact)
{
    static COLORREF c_arrColor[] =  // 2번째 색상 정보를 미리 정의하는 static array.
    {
        RGB(255, 0, 0),             // Red
        RGB(0, 255, 0),             // Green
        RGB(0, 0, 255),             // Blue
        RGB(0, 255, 255),           // Cyan
        RGB(255, 0, 255),           // Magenta
        RGB(255, 255, 0)            // Yellow
    };
 
    COLORREF color;
    if (bPrimaryContact)
    {
        // primary contact 는 항상 black으로 표시한다.
        color = RGB(0,0,0);         // Black
    }
    else
    {
        // secondary 색상을 결정 한 후,
        color = c_arrColor[m_iCurrColor];
 
        // array 의 다음 색상으로 인덱스를 이동시켜 준다.
        m_iCurrColor = (m_iCurrColor + 1) % (sizeof(c_arrColor)/sizeof(c_arrColor[0]));
    }
 
    return color;
}

 이제 추가해야 할 코딩의 마지막 입니다. 드로잉을 하면서 추가해 주었던 스트로크 정보들의 해제 처리를 소멸자에 넣어주세요. 멀티터치 데이터의 조작 방식을 알아보기 위한 이번 예제는 동적 메모리 할당의 효율적인 처리 같은 관점에서는 그다지 효율적이지 못합니다. 그래서 실행해보면 다소 반응 속도가 느리다고 느껴지실 수도 있습니다만, 이번 예제가 어디까지나 MFC 프로젝트에서 WM_TOUCH 메세지의 조작 방식을 알아보기 위함임을 감안해 주세요 ㅎㅎㅎ 그래도 양심상 할당한 메모리의 해제는 해주어야겠지요 ^^;… CChildView의 소멸자에 아래 코드를 넣어주시면 이제 코드 추가는 모두 완료입니다!

CChildView::~CChildView()
{
    for (int i = 0; i < m_StrkColDrawing.GetCount(); ++i)
    {
        delete m_StrkColDrawing[i];
    }
 
    for (int i = 0; i < m_StrkColFinished.GetCount(); ++i)
    {
        delete m_StrkColFinished[i];
    }
}

 

Task 5 : 프로그램을 실행하고 직접 스크린을 ‘멀티’터치해 봅니다.

이제 빌드하고 프로그램을 실행해 봅니다. 스크린 터치를 통해서 view 영역에 동시에 서로 다른 라인이 그려지는 기능을 직접 확인해 보세요. 샘플 프로그램을 훌륭하게 서로 다른 panning 제스처를 인식해 각각의 입력에 따른 스트로크를 출력하게 됩니다.

 

  

Outro

이번 글에서는 지난번 포스팅에 이어서 WM_TOUCH 메세지를 사용해 멀티터치 UX를 구현하는 TouchPad 예제를 완성해 보았습니다. WM_TOUCH 메세지가 발생할 때마다 호출되는 CWnd::OnTouchInput() 함수에서

  1. Touch 입력의 시작, 진행 중, 종료 시점을 알아내는 방법
  2. DWORD 타입의 ID값을 이용해 서로 다른 터치 입력을 구분하는 방법
  3. 동시에 여러 개의 멀티터치가 발생할 때, 먼저 일어난(primary) 터치 데이터를 구별하는 방법

… 등을 알아보았습니다. 또한 이를 활용해 동시 다발적인 멀티터치 입력에 대해 각기 다른 panning 제스처로 인식해 궤적을 그려내는 방법을 예제 구현과 함께 알아보았습니다. 이를 통해 WM_TOUCH 메세지를 제어하는 방법을 보다 쉽게 이해할 수 있으셨을 겁니다.

Native 코드로 멀티터치 UX를 구현하는 방법 중에 아직 소개해 드리지 않은 영역이 있습니다. MFC로 제공되는 기능은 아니지만, Com-Interface 형태로 제공되는 Manipulation(MS Surface에서와 같은 위젯 처리 기법. 한번에 여러 개의 제스처를 동시 적용한다.), Inertia(관성)의 제어기법 등이 그것입니다. 다음 포스팅에서는 이러한 부분에 대해서 알아보도록 하겠습니다.

그럼 다음에도 재미있는 내용으로 다시 찾아 뵙도록 하겠습니다. 

감사합니다 ^^*

 

Reference

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro

    안녕하세요 ~ MFC 카테고리의 꽃집총각 입니다.

    지난번 포스팅까지는 멀티터치를 좀 더 손쉽게 구현할 수 있도록 해주는 제스처(GESTURE)를 이용한 방법을 알아보았습니다. 기본적으로는 OS 자체에서 이미 한 번 가공하고 난 데이터를 WM_GESTURE라는 메세지를 전달해 줍니다. 이런 방식을 통해 프로그래머는 가공된 데이터를 바로 사용하기만 하면 된다는 장점도 가지지만, 좀 더 커스텀한 나만의 터치입력 제어처리를 하기에는 다소 제한적이라는 단점도 동시에 얻게 됩니다. 오늘은 이런 경우를 위해, OS의 처리를 아무 것도 거치치 않은 순수 터치입력 데이터인 WM_TOUCH메세지를 이용해 멀티터치 UX를 구현하는 방법을 알아보도록 하겠습니다.

    이전과 동일하게 본문의 내용은 msdn의 channel9에 공개되어 있는 예제를 기본 내용으로 합니다. 원문의 출처는 ( http://channel9.msdn.com/learn/courses/Windows7/Multitouch/Win7MultitouchMFC/Exercise-1-Build-a-Multitouch-Application/ ) 입니다.

    어떨 때 WM_TOUCH를 이용해야 하나요?

    그럼 일단 예제를 살펴보기 전에, WM_GESTOURE로 구현할 수 없는 멀티터치 UX의 예제란 어떤 게 있을지 한 번 이야기 해 보겠습니다. 위에 있는 이미지는 Windows7에 기본으로 포함되어있는 그림판 입니다. 제 PC에서 마우스 두 개를 꽂고, 가상 멀티터치 드라이브를 통해 마우스의 입력을 터치 입력으로 인식하게 변경한 다음, 그림판의 view 영역에 동시에 두 개의 브러시를 통해 그림을 그리고 있는 모습입니다. 멀티터치… 멀티터치니까 당연히 두 개의 panning 제스처 인식이 동시에 처리되는 게 맞겠죠. 제가 마우스가 하나 더 있다면 세 개도 동시에 드로잉 할 수 있을 겁니다. (손은 두 개지만… ㅡ,.ㅡ;..)

    ‘좋아, 나는 팀 블로그에서 제스처를 이용한 멀티터치 프로그래밍 방법을 익혔으니까 이걸 내가 직접 한 번 짜봐야지!’ 하고 마음을 먹어봅니다. 하지만… 어떻게 구현해야 할 지 막상 감이 잡히질 않는군요. panning 제스처니까 CWnd::OnGesturePan(CPoint ptFrom, CPoint ptTo) 함수를 상속받아서 구현하면 될까요? 근데 입력이 동시에 두 개가 들어오면 함수가 어떤 식으로 호출될까요?

    제스처를 통한 구현방법을 사용하는 경우엔 먼저 입력된 한 개의 panning 제스처만 인식이 됩니다. 만약에 제스처를 통해 위에 올린 스크린샷과 같은 저런 터치를 입력하면, OS는 저 것을 두 개의 panning이 아닌 rotate 내지는 zoom 제스처로 번역하게 되겠지요. 그림판에 저렇게 두 개의 라인이 드로잉 되는 모습을 보고 있어서 그렇지, 실제로 저 입력 동작을 지난 번 예제인 사각박스 움직이기 샘플에 입력했다면 당연히 zoom 내지는 rotate의 효과를 기대하게 될 겁니다.

    바로 이런 경우, WM_GESTURE가 아닌 WM_TOUCH 메세지를 사용하면 되겠습니다. 동시에 입력되는 두 개의 터치 데이터를 그대로 전달받아서 각각의 panning 제스처로 인식하도록 직접 처리하면 되겠지요. 그렇게 하려면 어떻게 해야 하는지 지금부터 함께 예제를 작성하면서 알아 보도록 하겠습니다.

    Task 1 : MFC Application 프로젝트를 만들자.

    WM_GESTURE 활용 예제와 유사하게 이번에도 프로젝트 생성부터 단계적으로 알아보도록 하겠습니다. MFC 응용 프로그램 마법사에서 MFC 표준 스타일의 SDI 타입을 지정해 줍니다. wizard의 주요한 설정 화면을 이번에도 스크린샷으로 대신하겠습니다. 지난번과 달리 이번엔 한글판 스크린샷을 찍었네요 ㅎㅎ

     

     

     

     

    Task 2 : 하드웨어 상태 확인 및 Touch WIndow 설정

    하드웨어 상태 확인은 지난번에 [MFC/윈도우 7 멀티터치] #2 : 제스처(gesture)를 이용한 구현(上) 편에서 해주었던 부분과 동일합니다. 자세한 설명은 지난 번 글을 참고해 주시고요, 간단히 CTouchPadApp::InitInstance() 함수에 추가해줄 소스코드만 다시 한번 적어보기로 하지요.

    BYTE digitizerStatus = (BYTE) GetSystemMetrics(SM_DIGITIZER);   
    if ((digitizerStatus & (NID_READY | NID_MULTI_INPUT)) == 0)  
    {   
        AfxMessageBox(L"현재 터치 입력이 불가능한 상태입니다.");   
        return FALSE;   
    }   
        
    BYTE nInputs = (BYTE) GetSystemMetrics(SM_MAXIMUMTOUCHES);   
       
    CString str;  
    str.Format(L"현재 %d개의 터치를 동시 인식할 수 있습니다.", nInputs);  
    AfxMessageBox(str);

    위에 있는 코드를 CTouchPadApp::InitInstance() 에 넣고 프로그램을 실행시켰을 때, 아래와 같은 안내 메세지가 나오는걸 확인해 주세요.

    하드웨어가 터치를 인식할 수 있는 장치인지를 확인하고 난 다음엔 예제 프로그램의 view 영역이 WM_TOUCH 메세지를 받을 수 있게끔 등록해 주는 절차가 필요합니다. 그렇지 않으면 터치 입력이 들어왔을 때 예전과 똑같이 WM_GESTURE만 날아오게 될 테니까요. 터치 메시지를 받는 윈도우로 등록해 주기 위해서는 CWnd::RegisterTouchWIndow() 함수를 호출해 주면 됩니다. 이 처리를 CChildView의 OnCreate() 함수에서 해주기로 합시다.

    Ctrl + Shift + X 키를 눌러서 MFC Class Wizard 창을 띄워줍니다. WM_CREATE 메세지를 처리하는 핸들러를 추가해주세요. 그리고 핸들러에 아래의 간단한 코드를 추가하면 끝입니다 :)

        if (!RegisterTouchWindow())
        {
            ASSERT(FALSE);
        }

    CWnd::RegisterTouchWIndow() 함수를 호출해주면 윈도우를 터치 윈도우로 등록하게 됩니다. 함수의 인자가 default value 때문에 생략되었는데, 터치 윈도우 등록을 해제하고 싶은 경우도 RegisterTouchWIndow( FALSE ) 를 호출해서 처리합니다. 터치 윈도우로 등록되고 나면 더이상 WM_GESTURE 메세지는 발생되지 않으며, 아무 가공도 거치지 않은 저레벨의 순수 터치 입력 데이터를 전달해 주는 메세지인 WM_TOUCH가 발생하게 됩니다.

    헌데 우리는 지금 Win32 프로젝트가 아니라 MFC 프로젝트를 보고 있지요 ㅎㅎ WM_TOUCH를 메시지 프로시저 함수에서 바로 얻어오자는 게 아닌 이상, WM_TOUCH 발생시 호출되는 함수를 알아봐야겠습니다. 우리는 CWnd::OnTouchInput() 함수를 이용하면 되겠군요. 하지만 이 함수는 MFC 클래스 마법사에서는 표시되지 않으니, 직접 손으로 선언과 구현부를 적어주어야 합니다. ChildView.h 파일과 ChildView.cpp에 각각 아래의 코드를 넣어주세요.

    // ChildView.h 파일에 추가.
    // Overrides
    protected:
        virtual BOOL OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput);

    // ChildView.cpp 파일에 추가. BOOL CChildView::OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput) { // TODO: Handle Tocuh input messages here return FALSE; }

    자, 이제 low-level의 터치 데이터를 직접 받아 처리하기 위한 모든 준비가 끝이 났습니다. 이제 사용자가 프로그램의 view 영역에 터치 입력을 할 때마다 OnTouchInput(…) 함수가 호출될 겁니다. 본격적인 터치 데이터의 활용 방법은 다음 포스팅에서 알아보도록 하겠습니다 ㅎㅎ

    Outro

    이번 포스팅에서는 WM_TOUCH를 이용해 멀티터치 UX를 구현해야 하는 경우에 대한 설명, 윈도우를 터치 윈도우로 등록하는 방법, WM_TOUCH가 발생할 때마다 호출되는 CWnd의 멤버함수 등에 대해 알아보았습니다. WM_TOUCH의 프로그래밍 방법을 알아보기 위한 예제인 TouchPad라는 이름의 프로젝트를 생성부터 기본 설정까지 함께 알아보았고요. 다음 포스팅 에서는 예제를 완성시켜 나가면서, low-level 터치 데이터를 조작하는 코드를 함께 알아보도록 하겠습니다.

    무더운 여름날씨에 건강 조심하시고 다음 포스팅에서 뵙도록 하겠습니다.
    감사합니다 ^^*

    Reference

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro

    안녕하세요~ MFC 카테고리의 꽃집총각 입니다.

    이번 포스팅에서는 앞 글에서 이야기 중이었던 예제 프로그램의 나머지 부분을 마저 작성하고, MFC 기반의 프로젝트에서 제스처를 이용해 멀티터치 프로그래밍을 제어하는 방법에 대한 보충 설명을 정리해 보도록 하겠습니다. 앞에 적은 글과 이어지는 내용이니까, 앞부분을 보지 못하신 분들은 ( http://vsts2010.net/292 ) 이 글을 먼저 읽어주세요 ^^ 지난번 글에서는 멀티터치 동작을 확인하기 위한 Drawing 처리까지 함께 알아봤습니다. 앞부분에서 진행됐던 내용을 소제목만 간추려서 알아보면,

    * 제스처를 인식하는 첫 번째 예제 프로그램 *

    1. MFC Application 프로젝트를 만들자
    2. 하드웨어 상태를 알아봅시다
    3. View 영역에 사각형을 그리자!

    …와 같은 내용이었습니다. 두 번째 스텝에서 알아봤던 하드웨어의 멀티터치 인식 가능여부 확인방법을 제외하고는 멀티터치와 직접적으로 연관된 내용은 많이 없었군요. 이번 시간부터는 본격적으로 터치 입력을 제어하는 코드를 함께 알아보겠습니다.

     

    Task 4 : 터치 인식 코드를 넣을 차례!

    자, 이제 드디어 제스처를 제어하는 코드를 알아볼 시간입니다. 사용자 경험(UX;User Experience) 분야에서 가장 뜨거운 이슈인 멀티터치! 뭔가 색다르고 놀라운 구현방식이 있을 거 같지만 실망(?)스럽게도 구현방법은 기존의 MFC 프로그래밍과 별다른 바가 없습니다. 그저 적당한 함수를 오버라이딩 해서 필요한 코드를 넣어주면 그만이거든요. 바로 이 점이 새롭게 변화된 MFC 추가기능들이 마음에 드는 이유 중에 하나입니다. 바로 ‘친숙한 인터페이스’ 말입니다 :)

    MFC의 윈도우 랩핑 클래스인 CWnd에는 이번에 새롭게 추가된 제스처 관련 함수들이 있습니다. 이 중에서 아래의 다섯 가지 제스처 핸들링 함수를 오버라이딩 합니다. 각각의 함수들은 이름에 명시되어 있는 제스처가 입력됐을 때 호출됩니다. 아래의 코드를 ChildView.h 파일의 class CChildView 선언에 넣어줍니다.

    // Overrides
    protected:
        // Gesture handlers
        virtual BOOL OnGestureZoom(CPoint ptCenter, long lDelta);
        virtual BOOL OnGesturePan(CPoint ptFrom, CPoint ptTo);
        virtual BOOL OnGestureRotate(CPoint ptCenter, double dblAngle);
        virtual BOOL OnGesturePressAndTap(CPoint ptFirstFinger, long lDelta);
        virtual BOOL OnGestureTwoFingerTap(CPoint ptCenter);

    기본적으로 터치가 인식되면 애플리케이션으로 WM_GESTURE 메세지가 날아옵니다. Win32 API만을 이용해 제스처를 제어하려면 WndProc 프로시저 함수에서 WM_GESTURE를 잡아서 처리해야 하지만, MFC 애플리케이션의 경우 자체적으로 각각의 제스처에 대한 전용 핸들링 함수들이 나뉘어져 있기 때문에, 제어가 필요한 제스처에 해당하는 함수를 용도에 맞게 따로따로 오버라이딩 해서 사용하면 됩니다.

    Note : 그런데 CWnd의 선언을 살펴보면 (afxwin.h 파일에 있습니다.) 위에 나열된 함수 말고도 OnGesture 함수가 정의되어 있는 것을 확인하실 수 있습니다. 함수의 원형은 아래와 같습니다.

    afx_msg LRESULT OnGesture(WPARAM wParam, LPARAM lParam);

    오호라~ 이것은 WM_GESTURE 메세지가 넘어올 때마다 해당 메세지를 직접 제어할 수 있는 함수인가 보군요~ 함수의 인자로 wParam, lParam이 모두 날아오니 win32 프로젝트에서 하는 것처럼 코딩하고 싶을 땐 이걸 상속받아서 작업하면 되겠구나 ~ … 라고 생각하고, 실제로 테스트 코드도 만들어 봤었지만 안되더군요 @.@… 똑똑한 여러분들은 이유가 무엇인지 바로 찾으셨을 거라고 생각합니다만… 저 함수는 가상 함수가 아닙니다 ^^;… 그저 이름만 보고 상속받아 써야지 했는데, 나중에 보니 재정의 할 수 없는 일반 멤버함수더라고요 ㅎㅎ 아마도 CWnd가 자체적으로 처리하는 코드를 구현한 부분이 아닌가 생각됩니다. 일반적으로는 각각의 제스처마다 독립적으로 호출되는 위의 다섯 가지 함수를 이용하면 되고요, 경우에 따라 부득이하게 WM_GESTURE 메세지를 직접 제어하고 싶을 때엔… 이전에 그랬던 것처럼 WndProc을 직접 제어하도록 하고 그곳에서 WM_GESTURE를 받은 경우에 대한 switch-case 문을 넣어주면 되겠죠 ^^

    이제 위에 소개된 다섯 개의 함수들에서 view 영역에 그려지고 있는 사각형을 제어하는 코드들을 넣어줍니다. 아래에 함수들의 본문 코드가 있습니다.

    BOOL CChildView::OnGesturePan(CPoint ptFrom, CPoint ptTo)
    {
        int dx = ptTo.x - ptFrom.x;
        int dy = ptTo.y - ptFrom.y;
        
        if (dx != 0 || dy != 0)
        {
            m_drawingObject.Move(dx, dy);
            RedrawWindow();
        }
        return TRUE;
    }
     
    BOOL CChildView::OnGestureZoom(CPoint ptCenter, long lDelta)
    {
        if ((m_pCurrentGestureInfo->dwFlags & GF_BEGIN) == GF_BEGIN)
        {
            m_dblZoomRatioStart = m_dblZoomRatioTotal = lDelta;
        }
        else if (lDelta != 0)
        {
            m_dblZoomRatioTotal += lDelta;
            double zoomFactor = (double)m_dblZoomRatioTotal / m_dblZoomRatioStart;
            
            m_drawingObject.Zoom(zoomFactor, ptCenter.x, ptCenter.y);
            
            m_dblZoomRatioStart = m_dblZoomRatioTotal;
            RedrawWindow();
        }
        return TRUE;
    }
     
    BOOL CChildView::OnGestureRotate(CPoint ptCenter, double dblAngle)
    {
        if ((m_pCurrentGestureInfo->dwFlags & GF_BEGIN) == GF_BEGIN)
        {
            // Make the first center, the rotating one
            m_ptCenter = ptCenter;
        }
        else if (dblAngle != 0.)
        {
            m_drawingObject.Rotate(dblAngle * PI / 100.0, m_ptCenter.x, m_ptCenter.y);
            RedrawWindow();
        }
        
        return TRUE;
    }
     
    BOOL CChildView::OnGesturePressAndTap(CPoint ptFirstFinger, long lDelta)
    {
        if ((m_pCurrentGestureInfo->dwFlags & GF_BEGIN) != 0)
        {
            m_drawingObject.ShiftColor();
            RedrawWindow();
        }
        
        return TRUE;
    }
     
    BOOL CChildView::OnGestureTwoFingerTap(CPoint ptCenter)
    {
        m_drawingObject.TogleDrawDiagonals();
        RedrawWindow();
        
        return TRUE;
    }
     

    함수의 이름과 전달되는 인자들 모두 직관적입니다. 함수 본문의 예제 코드들도 그리 어렵지 않군요. 위의 코드들을 복사해 적어준 뒤 빌드하고 프로그램을 실행해 봅니다. 애플리케이션이 뜨면 사각형을 손으로 움직여 보세요. 터치 입력에 따라 사각형은 이동하고, 늘어나고, 회전할겁니다 :)

    네…? 근데 뭔가가 안 된다고요?

     

    Task 5 : 다 되는 거 같은데 회전만 안되네요 ㅡㅜ…

    여기까지 진행하고 테스트를 해보면 다른 제스처는 다 인식을 하는데, 회전(rotate)만 제대로 안 되는 현상을 겪으실 겁니다. 참고로 이번 예제에서 확인하실 수 있는 다섯 가지 제스처에 대한 동작을 간략히 설명 드리면 아래와 같습니다.

    panning 손가락을 스크린에 대고 이동한다.
    zoom 두 손가락을 스크린에 대고 벌리거나 모은다.
    rotate 두 손가락을 스크린에 대고 회전시킨다.
    press and tab 한 손가락을 스크린에 댄 상태에서 다른 손가락으로 스크린을 빠르게 터치한다.
    two finger tab 두 손가락을 동시에 스크린에 붙였다 뗀다.

    그런데 다른 건 다 잘되는데 아마 rotate 제스처만 동작하지 않을 거예요. 그리고 다른 제스처들은 다 인식할 테지만… 사각형 내부에서 일어난 제스처인지를 판단하는 처리가 없었기 때문에, 꼭 사각형 내부가 아니더라도 client 영역 내에서 발생한 유효한 제스처라면 모두 다 인식하는 걸 확인할 수 있으실 겁니다.

    그 이유는 코드가 잘못된 것이 아니라, 윈도우 자체에서 다른 제스처들은 모두 기본적으로 활성화 되어있는데, rotate만은 기본적으로 비활성화 되어있기 때문입니다. 활성화 하려면 다 해두든가 아님 말든가 할 것이지 왜 rotate만 천대(?)하는지 자세한 내막은 모르겠습니다만… 그것이 현실이군요. rotate를 활성화하기 위해, 제스처의 설정(config)를 제어하는 방법을 알아보도록 합시다.

    MFC에서는 제스처의 설정을 쉽게 컨트롤 할 수 있도록 CGestureConfig라는 클래스를 제공합니다. CChildView의 선언( in ChildView.h )에 CGestureConfig 타입의 멤버변수를 하나 추가해줍니다.

    class CChildView 
    {
        // ...
     
    // Fields
    protected:
        // Holds gesture configuration
        CGestureConfig m_gestureConfig;
        
        // ...
    }

    그리고 새롭게 부활한 반가운 인터페이스, 마법사 중의 마법사 MFC Class Wizard를 띄워서 OnCreate 함수를 오버라이딩 합니다. (단축키 Ctrl + Shift + X 입니다 ^^)

    그리고 아래의 코드를 넣어주세요.

     
    int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        if (CWnd::OnCreate(lpCreateStruct) == -1)
            return -1;
     
        // TODO: Add your specialized creation code here
        GetGestureConfig(&m_gestureConfig);
        
        // Only rotation is not enabled by default
        m_gestureConfig.EnableRotate();
        
        SetGestureConfig(&m_gestureConfig);
     
        return 0;
    }
     
     
     
    그리고 나서 다시 한번 빌드하고 실행해 봅니다. 이제 rotate 제스처도 제대로 인식하는 첫 번째 예제 프로그램이 완성 되었습니다 ^^*
     
    자 드디어 첫 번째 예제의 완성입니다~ !! 짝짝짝 ~
     

    Task 6 : panning이 왜 이렇게 뻑뻑하지? 아이폰은 안 그런뎅..

     
    task5 까지가 channel9에서 소개한 MFC gesture 기능의 첫 번째 예제입니다. 필요한 부분과 불필요한 부분을 적절하게 구분해 잘 설명해 두어서, 한번 설명을 따라 작성해 보시는 것만으로 제스처 프로그래밍에 대한 대략적인 흐름을 손쉽게 파악하셨을 거라고 생각합니다.
     
    이대로 끝내기엔 좀 아쉬운 감이 있어서… 예제 코드에 대한 추가 설명을 드리고자 합니다. 사실 글 중간중간에도 제가 추가로 적은 글이 제법 있었지만… 마땅히 말씀드릴 타이밍을 못 찾았던 요것 한 가지만 더 말씀 드리도록 할게요 ㅎㅎ
     
    일단 예제를 조작해 보면 제일 먼저 거슬리는 부분이 바로 panning 제스처에 대한 조작감(?) – 게임개발만 오래 하다 보니 이런 표현이…^^; – 인데요. 아이폰에서 느껴지는 부드러운 반응성에 비해 사각형 움직임이 무척이나 둔하고 불만족스럽다는 걸 느끼실 겁니다. 사각형이 한 번 움직이고 나면 괜찮은데, panning 제스처를 인식해 움직여지기 시작할 때 까지가 뭔가 걸리는 느낌입니다. x, y 한 방향으로만 움직이고, 다른 방향으로는 안 움직이는 경우도 아마 겪으셨을 거예요. 왜 이럴까요? 기기의 한계? 아님 OS 자체의 한계?
     
    이런 현상은 하드웨어의 문제나 OS 기능 자체의 문제가 아닙니다. 이것도 역시 panning 제스처의 옵션에서 GC_PAN_WITH_GUTTER 플래그가 기본적으로 켜져 있기 때문에 나타나는 현상입니다. 저도 처음에 제스처(gesture)를 이용한 멀티터치 예제들을 실행해 보다가 panning의 무딘 반응성이 너무 눈에 거슬려서, 부드러운 움직임을 보려면 결국 WM_GESTURE에서는 한계가 있고, WM_TOUCH를 써야 하는가 보다 했었는데, 간단히 플래그만 조절해주면 좀 더 부드러운 움직임을 느낄 수가 있게 됩니다.

    GC_PAN_WITH_GUTTER에서 gutter는 홈통이나 배수로 등의 뜻으로, panning 제스처가 발생했을 때 가장 메인이 되는 방향으로의 움직임 이외의 방향성에 대한 반응은 특정 임계값을 넘기지 않는 이상 무시하게 하는 효과를 줍니다. 이 때문에 손가락에서의 미세한 움직임들이 무시되고, 반응성이 안좋다는 느낌을 받게 되죠. 이를 해결하기 위해서는 rotate 제스처의 활성화를 위해 오버라이딩 했었던 OnCreate 함수에서 panning에 대한 아래의 설정도 함께 처리해 주면 됩니다.

     
    config.EnablePan( TRUE, 
        GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | 
        GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY |
        GC_PAN_WITH_INERTIA );
     
    pan 제스처를 활성화 하되, GC_PAN_WITH_GUTTER 플래그만을 제외한 나머지 플래그들을 활성화 해주는 코드입니다. 위 코드를 넣고 나서 빌드하고 다시 실행해보면 이전보다 훨씬 더 부드럽게 사각형이 이동되는 것을 느낄 수 있으실 겁니다.

     
     

    Outro

    이것으로 MFC를 이용해서 제스처를 이용한 멀티터치 프로그래밍 방법의 첫 번째 예제를 소개와 설명을 모두 마쳤습니다. 제스처의 설정을 컨트롤 하는 방법, 각각의 제스처에 대한 핸들링 코드를 넣어주는 방법 등을 알아보았는데, 대부분 예전 MFC 프로그래밍의 마우스/키보드 이벤트 처리 등과 비슷한 방식이었기 때문에 그리 어렵지는 않은 난이도였습니다.

    다음 포스팅 에서는 이제 가장 자유롭고 확장성 있는 (… 하지만 대신 좀 더 까다로운 ) WM_TOUCH를 이용한 멀티터치 프로그래밍 방법을 알아 보도록 하겠습니다. 혹시 그 전에 질문 사항들이나, 제스처 프로그래밍 방법들에 대한 추가 학습사항이 있다면 다시 한 번 정리하는 기회를 갖도록 하겠습니다.

    그럼 다음 포스팅에서 다시 인사 드리도록 하겠습니다. 그 때까지 더운 날씨에 모두들 건강하시고 공부 열심히 하세요 ~ ^^*

    감사합니다 ~ 꾸벅 ~

    Reference

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro

    안녕하세요. MFC 카테고리의 꽃집총각입니다.

    한동안 여러 가지 다른 행사와 게으름으로(;;) 포스팅이 많이 늦어졌습니다 ^^; 지난 글에서는 ( [윈도우 7 멀티터치] #1 : 멀티터치 UX를 적용하는 3단계 전략 ) 멀티터치 프로그래밍을 내 애플리케이션에 적용하는 방법을 3단계로 나누어 설명하였습니다. 이 중 첫 번째 단계인 ‘추가 코딩 없이도 기본적으로 얻을 수 있는 기능들’에 대해 자세히 알아보았고, 나머지 두 단계는 기본적인 내용만 소개해 드렸습니다. 이번엔 이중에서 두 번째 단계인 제스처(gesture) 이용한 프로그래밍 방법에 대해 자세히 알아보도록 하겠습니다.

    note : 지난번 까지는 프로그래밍 이야기 보다는 개념적인 설명이 위주였기 때문에 ‘Windows 7 Development’ 카테고리에 글을 올렸습니다만… 이제부터는 본격적으로 구현에 대한 이야기가 시작되니까 ‘MFC’ 카테고리에서 연재하도록 하겠습니다. 앞부분은 어느 언어를 사용하든 멀티터치 프로그래밍을 위해 알아두어야 할 공통적인 내용들이었고요, 제가 앞으로 말씀드릴 부분은 주로 MFC를 위한 구현방법이니까, 이게 맞는 거겠지요. :)

     

    제스처를 인식하는 첫 번째 예제 프로그램

    이제까지는 제가 주로 개념설명이나 이론적인 부분들을 모두 말씀 드리고 나서 예제 코드를 알아봤었는데, 이런 쌍팔년도식 학습법은 요즘은 별로 어울리지 않는 듯 합니다 ^^;… 일단은 돌아가는 예제 프로그램을 간단하게 한 번 작성해 보기로 하지요. 그게 자질구레한 설명도 줄여주고, 공부하기도 좀 더 쉬울 것 같네요 ㅎㅎ

    첫 번째로 구현해볼 예제는 view 영역에 그려진 box를 터치로 조작, 변환하는 프로그램 입니다.

    view 영역에 box를 하나 표시하고, 손가락으로 사각형을 이동, 회전, 확대/축소 할 수도 있고, Press  & Tap 제스처를 통해 색상을 변경할 수도 있고, Twi Finger Tap 제스처로 X표시를 하거나 지울 수도 있는 프로그램입니다. 예제를 따라서 작성해 보면 MFC 애플리케이션에서 어떻게 제스처를 다룰 수 있는지 쉽게 이해하실 수 있습니다. 해당 예제는 Windows 7 SDK에 들어있는 MFC 샘플 중에 하나입니다. 영어에 부담이 없으신 분들은 직접 Windows 7 SDK를 확인해 보셔도 좋을 것 같군요 ^^ ( http://channel9.msdn.com/learn/courses/Windows7/Multitouch/Win7MultiTouchGestureMFC/Exercise-1-Build-a-Multitouch-Gesture-Application/ )

     

    Task 1 : MFC Application 프로젝트를 만들자

    우선 Visual Studio 2010에서 MFC Application 프로젝트를 하나 만드세요. Application Wizard에서는 고전 MFC 스타일의 SDI 타입을 지정해 줍니다. Wizard의 설정 화면을 활자 설명 대신 스크린샷으로 대신하겠습니다.

     

    Task 2 : 하드웨어 상태를 알아봅시다

    본격적인 멀티터치 UX를 구현하기에 앞서, 현재 구동중인 실행환경이 멀티터치가 가능한 상태인지를 확인해야 합니다. 멀티터치를 인식 가능한 하드웨어가 연결되어 있는지, 그리고 사용 가능한 상태인지를 알 수 있어야겠지요. 이런 정보를 얻고자 할 때는 예전부터 익히 사용해오던  ::GetSystemMetrics(…) 함수를 이용하면 됩니다.

    int WINAPI GetSystemMetrics( __in  int nIndex );
    http://msdn.microsoft.com/en-us/library/ms724385(VS.85).aspx

    GetSystemMetrics는 운영체제의 여러 가지 설정 정보들을 얻어올 수 있는 API로 예전부터 자주 사용되던 함수입니다. 윈도우 캡션 영역, 메뉴 영역 등의 너비를 얻을 수 있기 때문에 UI 작업할 때 많이 사용해 보셨을 겁니다. MFC/Win32 API에 새롭게 추가되는 기능들은 상당 부분 이렇게 이전 인터페이스를 그대로 사용하고 있기 때문에 공부하기가 특히 어렵지 않은 느낌이어서 마음에 듭니다 ㅎㅎ 이 함수로 터치인식 정보를 얻고 싶을 땐 아래의 새로운 flag들을 사용합니다.

    • SM_DIGITIGER : 입력장치 상태 확인
    • SM_TABLETPC : 타블렛 pc 여부 확인
    • SM_MAXIMUMTOUCHES : 최대 동시입력 가능한 터치 개수

    이번에 공부하면서 보니, 윈도우 XP가 타블렛 PC edition이란게 따로 있었더군요… SM_TABLETPC는 이런 정보를 확인할 때 쓰는 값이고, 나머지 두 flag는 Vista 이하 OS에서는 인식하지 않습니다. 각 항목의 자세한 값은 MSDN을 참고하시면 되고요, 기본적인 경우라면 아래의 코드를 가져다 사용하시면 되겠습니다. 현재 멀티터치 입력 가능 여부와 동시 입력 개수를 알아보는 코드입니다.

       1: BYTE digitizerStatus = (BYTE) GetSystemMetrics(SM_DIGITIZER);
       2: if ((digitizerStatus & (NID_READY | NID_MULTI_INPUT)) == 0) //Stack Ready + MultiTouch
       3: {
       4:     AfxMessageBox(L"현재 터치 입력이 불가능한 상태입니다.");
       5:     return FALSE;
       6: }
       7:  
       8: BYTE nInputs = (BYTE) GetSystemMetrics(SM_MAXIMUMTOUCHES);
       9:  
      10: CString str;
      11: str.Format(L"현재 %d개의 터치를 동시 인식할 수 있습니다.", nInputs);
      12: AfxMessageBox(str);

    그리고… 멀티터치 프로그래밍 공부가 하고 싶은데 하드웨어가 마땅치 않을 때… 제가 예전에 말씀 드렸던 시뮬레이션 환경 설정 ( http://multitouchvista.codeplex.com/ ) 기억하고 계시죠? 기억이 안 나시는 분은 ( [멀티터치]멀티터치 프로그래밍 환경 구축하기 ) 링크를 참고해 주세요 ^^

    위에 있는 코드를 그대로 복사해서 CTouchGestureDemoApp::InitInstance() 함수에 넣어주세요. 그리고 애플리케이션을 실행하면 아래와 같은 창이 뜹니다.

    아따, 많기도 하여라 @.@… 동시에 255개의 터치를 인식할 수 있군요. 저도 터치 입력 환경을 시뮬레이션 해서 공부하고 있는데, 이런 경우 동시 입력 개수가 255개로 나오게 됩니다 ㅎㅎ

     

    Task 3 : View 영역에 사각형을 그리자!

    샘플을 따라 해보기 위해서 화면에 box를 그려주는 코드를 우리가 일일이 직접 작성할 필요는 없겠죠. 지금은 제스처의 인식 방법을 알아보고 싶은 거니까요. Windows 7 SDK에서 box 드로잉을 해주는 코드를 제공하고 있으니, 이 코드를 사용하도록 하겠습니다. 그리고 우리는 멀티터치 제스처를 이용하는 방법에만 집중하기로 합시다. box를 그려주는 CDrawingObject라는 클래스의 소스는 아래에서 다운 받으실 수 있습니다.

    위의 파일을 다운 받으셔서 프로젝트에 포함하고, stdafx.h 파일의 아래쪽에 헤더파일 인클루드 구문을 넣어줍니다. 기본적으로 작성되어 있는 상태에서 #include <afxcontrolbars.h> 아래에 넣어주면 됩니다.

       1: #include "DrawingObject.h"

    그리고 ChildView.h를 열고 View 클래스의 멤버 변수로 CDrawingObject 객체를 하나 선언해 줍니다.

       1: // The drawing object
       2: CDrawingObject m_drawingObject;

    마찬가지로 View 클래스의 멤버 변수로, box의 확대/축소와 이동을 처리하기 위해 필요한 변수들을 몇 가지 더 추가해 줍니다.

       1: // Needed for drawing object position and size calculations
       2: double m_dblZoomRatioStart;
       3: double m_dblZoomRatioTotal;
       4: CPoint m_ptCenter;

    그리고 box를 그려주기 위해서, CChildView.cpp에 있는 CChildView::OnPaint() 함수의 제일 아래쪽에 box drawing 코드를 넣어줍니다.

       1: m_drawingObject.Paint(&dc);

    그다음, 윈도우 크기를 변경할 때 box의 크기와 위치를 기본값으로 초기화 해주는 코드를 넣어줍니다. box가 화면영역을 벗어나거나 한 경우 초기화 코드가 있으면 좋겠지요. 그러기 위해서 먼저 WM_SIZE 메세지 핸들러를 추가합니다.

    Ctrl + Shift + X 키를 눌러 MFC 클래스 위자드(MFC Class Wizard)를 띄웁니다. MFC 클래스 위자드는 VC++ 6.0에 있던 툴인데, Visual Studio .NET 2002 통합 환경으로 변경되면서 사라졌지만 이번에 Vusial Studio 2010에서 새롭게 부활했습니다. 거의 10년 만이라고 할 수 있군요. 예전에 사용해 보신 분들이라면 아주 익숙한 인터페이스 일겁니다. 여기에서 CChildView의 WM_SIZE 메세지를 선택해서 핸들러를 추가해 줍니다. 아래 스크린샷을 참고하세요.

    MFC 클래스 마법사의 부활은 VS2010의 핵심적인 변화 요소중에 하나입니다. 클래스 마법사의 활용 부분은 다음 기회에 별도의 포스팅으로 자세하게 설명하도록 하겠습니다.

    생성된 CChildView::OnSize 함수에 아래의 코드를 넣어줍니다.

       1: m_drawingObject.ResetObject(cx, cy);

    여기까지 진행했다면 이제 drawing 처리의 완성입니다. 프로젝트를 빌드하고 애플리케이션을 실행하면 아래와 같이 빨간 box가 drawing 되는 모습을 확인하실 수 있습니다.

     

    Outro

    한 번의 포스팅에서 예제의 완성과 추가설명까지 모두 다루려고 했지만 그러기엔 글이 너무 길어질 것 같네요. 이쯤에서 한 번 호흡을 끊고, 다음 포스팅에서 실질적인 제스처 인식 코딩 방법과 추가설명을 정리하도록 하겠습니다. (下편은 바로 올릴 예정이니 조금만 기다려 주세요)

    이번 글에서는 제스처를 인식하는 예제 프로그램의 작성 방법을 단계적으로 소개하고, 하드웨어의 멀티터치 인식 여부 확인 방법을 알아보았고, 아주 짧게나마 VS2010에서 새롭게 부활한 MFC 클래스 마법사에 대한 언급이 있었고, 첫 번째 멀티터치 예제의 작성 방법을 절차적으로 알아보았습니다. 제스처를 이용한 멀티터치 구현 하(下)편에서는 제스처를 인식해 box를 이동, 변환 시키는 코드의 작성과 샘플을 통해 알아볼 제스처 인식 프로그래밍의 부가 정보 및 팁들을 소개해 드리도록 하겠습니다.

    그럼 곧바로 하(下)편으로 찾아 뵙겠습니다.
    감사합니다 ^^*

    Reference

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    안녕하세요. MFC 카테고리의 꽃집총각입니다.

    꽤나 오랜만에 글을 올리는군요 ^^;
    그동안 팀 블로그에 올려 두었던 MFC의 기능 소개 글들을 한 번에 정리할 기회를 갖게 되어 예전 글들을 다시 살펴보며 정리하다가 CTaskDialog 클래스 사용법을 알아볼 수 있는 공개용 예제코드를 작성해 보았습니다. 포스팅을 할 때 사용했던 코드이기 때문에 주요코드들은 모두 블로그에 조각조각 올라와 있기는 하지만 다운받아서 직접 빌드하고 실행해보실 수 있도록 프로젝트 파일과 솔루션 파일 모두 함께 올립니다.


    소스코드는 당연히 Visual Studio 2010 RC 버전에서 제작되었습니다 ^^;...
    아래는 예제 코드를 켭쳐한 이미지 입니다.

    프로그램을 빌드해서 실행하면 대화상자 중앙에 두 개의 버튼이 있습니다.
    왼쪽 버튼을 누르면 간단한 출력 방식인 CTaskDialog::ShowDialog() 함수의 사용법을,
    오른쪽 버튼을 누르면 좀 더 디테일한 방법인 CTaskDialog::DoModal() 함수의 사용법을
    확인해 보실 수 있습니다.

    아래 두 장의 이미지는 각각의 방법들로 띄운 예제 Task Dialog 의 모습입니다.


     CTaskDialog::ShowDialog()를 이용해 출력한 Task Dialog.


    CTaskDialog::DoModal()을 이용해 출력한 Task Dialog.

    학습하시는 데 도움이 되었으면 하는 마음으로 올립니다.

    곧 MFC의 새롭고 재밌는 기능들을 주제로 다시 포스팅을 이어나가도록 하겠습니다.
    조금만 기다려 주세요 ~

    그럼 다음 포스팅에서 뵙겠습니다 ~
    감사합니다 ^^*

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro
    안녕하세요. MFC 카테고리의 꽃집총각 입니다.
    우리는 지금 비주얼 스튜디오 2010에서 새롭에 추가되는 MFC 클래스인 CTaskDialog를 알아보고 있습니다.
    지난 시간까지는 2회의 연재에 걸쳐서 태스크 대화상자가 무엇인지, 그리고 태스크 대화상자를 기존의 MessageBox 처럼 간단한 용도로 사용하고자 할 때엔 어떻게 해야 하는지를 함께 살펴보았습니다. 앞서 연재된 포스팅은 아래의 링크를 따라가시면 읽어보실 수 있습니다.

    이번에는 태스크 대화상자의 세 번째 포스팅으로, 태스크 매니저가 제공하는 다양한 기능들을 본격적으로 활용하기 위한 방법을 알아보기로 하겠습니다.



    태스크 대화상자를 띄우는 또다른 방법 - DoModal() 함수.
    지난번 포스팅에서 소개했던 단순 출력 방식은 CTaskDialog::ShowDialog 함수 하나만 호출해 주면 바로 태스크 대화상자를 띄울 수 있었습니다. 8개의 인자를 통해 기본적인 기능을 어느 정도 제어할 수는 있었지만 애초부터가 심플한 사용을 위한 방법이었으므로, CTaskDialog의 여러가지 강력한 기능들을 모두 제어하기에는 무리가 있었습니다.
    이번에는 CTaskDialog의 객체를 직접 생성하고, CTaskDialog의 멤버함수들을 통해 다양한 기능을 활용하는 방법을 알아보도록 하겠습니다. 이미 우리에게 익숙한 방식인 CDialog의 파생클래스를 출력하던 방식과 유사합니다. 객체를 만들어 DoModal() 함수를 호출하면 창이 뜨고, 창이 떠있는 동안 함수 내부에서 제어를 쥐고 있다가, 사용자가 창을 끄게 되면 리턴값을 내면서 함수의 호출이 종료되는 방식입니다.

    // 객체 생성.
    CTaskDialog dlg( L"본문", L"머릿글", L"타이틀" );
    
    // TODO : DoModal을 호출하기 전, dlg의 멤버함수를 호출해 원하는 기능들을 설정한다.
    
    INT_PTR iRet = dlg.DoModal();
    
    switch( iRet )
    {
    case IDOK:        ... break;
    case IKCANCEL:    ... break;
        ...
    }
    

    기존의 스타일을 그대로 닮아있어서 훨씬 더 친숙하게 느껴집니다 :)
    다만 다른점은, DoModal을 부르기 전에 여러가지 기능을 제공하는 CTaskDialog의 멤버함수를 이용해 태스크 대화상자의 동작 및 기능을 런타임에 디자인 한다는 점입니다. 각각의 기능들은 적절한 이름의 함수로 제공되어 있으며, 매우 직관적이므로 별도의 설명 없이도 쉽게 이해하실 수 있습니다.
    함수들의 레퍼런스는 아무래도 MSDN 페이지를 직접 보시는 것이 좋을 것 같습니다. ( http://msdn.microsoft.com/en-us/library/dd293651(VS.100).aspx ) 사실은 제가 한글로 설명을 덧붙인 함수 참고표를 정리해 봤는데, 굳이 부가설명이 필요 없을 뿐더러 오히려 MSDN 페이지보다 더 읽기 힘들어지기만 하네요. 대신 눈에 쏙쏙 들어오는 예제 코드를 통해 주요 함수들의 사용 방법에 대한 설명을 갈음하도록 하겠습니다.
    예제는 MS VC++ MVP인 Marc Gregoire의 블로그에 공개된 예제 코드를 기반으로 하여, 주석과 인자들을 한글로 수정하고, 강의의 진행에 맞도록 일부 수정한 버전입니다. 원본 코드는 이곳 ( http://www.nuonsoft.com/blog/2009/06/10/ctaskdialog-in-mfc-in-visual-c-2010/ )에서 다운받으실 수 있습니다.

    // 태스크 대화상자 객체 생성.
    CTaskDialog taskDlg( L"본문", L"머릿글", L"타이틀" );
    
    // 본문, 머릿글, 타이틀 텍스트 별도 지정
    taskDlg.SetContent(_T("여기가 본문이 출력되는 위치입니다.\n")
        _T("당연히 여러 줄로 텍스트를 출력할 수 있고,")
        _T("<a href=\"http://vsts2010.net\">하이퍼 링크</a>도 설정할 수 있습니다.") );
    
    taskDlg.SetMainInstruction(_T("여기는 머릿글을 적는 곳입니다.\n")
        _T("머릿글도 여러 줄로 적을 수 있어요."));
    
    taskDlg.SetWindowTitle(_T("이것이 태스크 대화상자 입니다."));
    
    // 3개의 커맨드 버튼 추가. 한 버튼은 권한 승격이 필요한 작업임을 표시.
    taskDlg.AddCommandControl(1, _T("커맨드 버튼 1 "));
    taskDlg.AddCommandControl(2, _T("커맨드 버튼 2 "));
    taskDlg.AddCommandControl(3, _T("커맨드 버튼 3 \n")
        _T("사용자 권한 설정(UAC) 기능의 권한 승격이 필요함을 표시할 수 있습니다."), TRUE, TRUE);
    
    
    // 2개의 라디오 버튼 추가.
    taskDlg.AddRadioButton(4, _T("라디오 버튼 1"));
    taskDlg.AddRadioButton(5, _T("라디오 버튼 2"));
    
    // 보이기, 감추기 버튼으로 사용자가 펼치고 접을 수 있는 추가 메세지 설정.
    taskDlg.SetExpansionArea(
        _T("이 메세지는 사용자가 하단의 '자세히' 버튼을 누르면 추가로 보여지는 메세지 입니다.\n")
        _T("이곳에도 하이퍼 링크가 적용됩니다. <a href=\"notepad.exe\">메모장 열기</a>."),
        _T("자세히"), _T("감추기"));
    
    // 꼬릿말 부분 설명 & 아이콘 설정.
    taskDlg.SetFooterIcon(MAKEINTRESOURCE(IDI_INFORMATION));
    taskDlg.SetFooterText(_T("꼬릿말 출력 위치. \n역시 멀티라인 지원됩니다."));
    
    // 대화상자의 메인 아이콘 설정.
    taskDlg.SetMainIcon(m_hIcon);
    
    // 태스크 대화상자의 프로그레스바 설정.
    taskDlg.SetProgressBarMarquee();
    taskDlg.SetProgressBarRange(0, 100);
    taskDlg.SetProgressBarPosition(0);
    
    // 대화상자 하단의 체크박스 표시 설정 및 텍스트 셋팅.
    taskDlg.SetVerificationCheckboxText(_T("윈도우 시작시 자동 실행"));
    taskDlg.SetVerificationCheckbox(TRUE);
    
    // 하이퍼 링크, 타이머 옵션 추가 설정.
    int options = taskDlg.GetOptions();
    options |= TDF_ENABLE_HYPERLINKS;
    options |= TDF_CALLBACK_TIMER;
    taskDlg.SetOptions(options);
    
    
    // 태스크 대화상자 출력.
    INT_PTR iRes = taskDlg.DoModal();
    
    // 사용자가 선택한 커맨드 버튼 ID와 라디오 버튼 ID 확인하기.
    int iSelectedCommandControlID = taskDlg.GetSelectedCommandControlID();
    int iSelectedRadioButtonID = taskDlg.GetSelectedRadioButtonID();
    

    위의 예제 코드로 생성된 태스크 대화상자의 모습은 아래와 같습니다.

    (그림 1) 예제 코드로 생성된 태스크 대화상자의 모습.


    (그림 2) 예제코드로 생성한 태스크 대화상자. '자세히' 버튼을 눌러 본문이 확장된 상태.


    제가 개인적으로 판단하기에는 주요함수 참고 테이블 보다는 위의 예제 코드가 훨씬 더 사용법을 익히기에 좋아 보입니다. 익스플로러 창을 두 개 띄워서 스크린샷과 소스코드를 대조해서 확인해 보세요. 어지간한 기능은 MSDN을 참고하지 않고도 바로 사용법을 이해하실 수 있을겁니다.



    CTaskDialog의 프로그레스바 컨트롤.
    이 부분까지 포스팅을 유심히 살펴오신 분이시라면 이상한 점을 한 가지 파악하셨을겁니다. 바로 프로그레스바 컨트롤 입니다. 지난 번 포스팅에서도, CTaskDialog::ShowDialog 함수를 이용해 프로그레스바 컨트롤을 억지로 붙여보기만 하고는 제어하는 방법을 오늘로 미루었는데, 오늘의 예제에서도 프로그레스바의 수치 등을 조절하는 함수는 있었지만 여전히 텅 빈 황량한 프로그레스바만 출력되고 있습니다.
    DoModal() 함수를 호출하기 전에 SetProgressBarPosition( ... ) 함수로 프로그레스바 컨트롤의 초기값을 정해줄 수는 있지만, 태스트 대화상자가 한 번 뜨고 나면 DoModal() 함수가 제어를 쥐고 있기 때문에 컨트롤의 값을 조작하도록 손쓸 방법도 마땅치가 않습니다.

    (그림 3) SetProgressBarPosition(...)으로 초기값은 정할 수 있다고 치지만, 더이상 어떻게 컨트롤 한다는 말인가?

    이럴 때 CTaskDialog 클래스의 주요 기능 중의 하나인 타이머를 이용해야 합니다. 프로그레스바 컨트롤을 다루는 방법을 통해 CTaskDialog의 파생 클래스 생성 및 타이머 사용 방법을 알아보도록 하겠습니다.



    CTaskDialog::OnTimer 오버로딩하기.
    대화상자가 떠있는 동안은 DoModal()이 제어권을 갖고 있기 때문에, 우리가 CTaskDialog 객체의 외부에서 추가적인 조작을 가하기는 어렵습니다. 하지만 프로그레스바 컨트롤의 값을 애니메이션 하려면 대화상자가 출력되어 있는 동안에 처리가 이루어 져야 하겠지요.
    이런 경우에는 CTaskDialog를 상속받는 파생 클래스를 제작하고, 가상 함수인 OnTimer를 오버로딩합니다.
    OnTimer의 기본형은 다음과 같습니다.

    • virtual HRESULT OnTimer(_In_ long lTime);

    예제 코드에 보면 SetOption( ... ) 함수를 이용해 기본적인 옵션에 TDF_CALLBACK_TIMER 를 추가해주어 타이머 기능을 활성화 하는 처리를 확인할 수 있습니다. 이렇게 플래그를 통해 타이머를 활성화 시켜주면 OnTimer 함수가 약 200 밀리초 (=0.2초) 주기로 호출됩니다.

    int options = taskDlg.GetOptions(); options |= TDF_CALLBACK_TIMER;   // 타이머 옵션 추가 설정. taskDlg.SetOptions(options);

    타이머가 필요한 대표적인 예가 바로 프로그레스바 컨트롤의 애니메이션 이겠지요. 오버로딩한 OnTimer 함수에서 간단하게 프로그레스바의 값을 진행시키는 코드를 넣어보도록 하겠습니다. 값을 조절하는 건 위에서 언급되었던 SetProgressBarPosition 함수만으로 충분합니다 :)

    class CMyTaskDialog : public CTaskDialog { // ... virtual HRESULT OnTimer(_In_ long lTime); } HRESULT CMyTaskDialog::OnTimer(_In_ long lTime) { static int iProgressPos = 0; iProgressPos += 2; if (iProgressPos >= 100) iProgressPos = 0; SetProgressBarPosition(iProgressPos); return S_OK; }

    위의 코드는 매번 타이머 함수가 호출될 때마다 프로그레스바 컨트롤의 값을 2씩 증가시켜주다가, 컨트롤이 만땅이 되면 꽉 차면 다시 0으로 초기화 해주고 있습니다. 0.2초에 한 번씩 호출이 되는데 2씩 채우니까... 100을 다 채우려면... 그러니까 1초에 다섯번쯤 호출 되는데... 한 번에 2씩이면 초당 10씩인가...... 뭐 아무튼 실행해보면 적당한 속도로 컨트롤이 애니메이션 되는 모습을 확인할 수 있습니다.
    (웃자고 적어본 거 아시죠... 저 덧셈 잘합니다...;;)

    프로그레스바 컨트롤에 대한 이야기가 나왔으니까 프로그레스바의 스타일에 대해 몇가지만 더 이야기하고 마무리 짓도록 하지요.



    Marquee 타입 프로그레스바
    태스크 대화상자의 프로그레스바 컨트롤은 기존의 기본적인 프로그레스바 방식보다는 Marquee 타입으로 많이 쓰일것을 예상하고 디자인된 것으로 보입니다. 프로그레스바 컨트롤을 추가하는 함수의 이름부터가 SetProgressBarMarquee(...) 이니 말입니다. 우리 예제에서는 프로그레스바의 영역을 설정하고 값을 지정해 주어 일반 프로그레스바처럼 사용하였지만, SetProgressBarRange(...) 함수와 SetProgressBarPosition(...)을 주석처리하면 프로그레스바는 기본적으로 Marquee 타입으로 설정되고, 자동으로 애니메이션 됩니다.

    (그림 4) Marquee 타입 프로그레스바. 지렁이 같은게 계속 스멀스멀...

    윈도우 XP의 부팅속도를 가늠하기 위한 암묵적 의사소통 수단이기도 했지요. 지렁이 몇마리...
    바로 그런 식입니다. Marquee 타입 프로그레스바 컨트롤은 내부에 초록색 불빛이 좌에서 우로 이동하는 애니메이션을 반복합니다. 내부적으로 무언가 작업을 처리하고 있음을 나타내는 용도로 사용하는데 적합합니다.



    CTaskDialog::SetProgressBarState(...)로 프로그레스바 상태 설정하기.
    프로그레스바 관련 함수 중에 SetProgressBarState(...)가 있습니다. 이 함수를 이용해 프로그레스바의 상태를 정상 / 일시 정지 / 에러 세가지 상태 중의 하나로 설정할 수가 있습니다. 인자로 아래의 플래그를 주면 됩니다.

    • PBST_NORMAL - 정상. 디폴트값이며, 프로그레스바가 녹색으로 표시됨.
    • PBST_ERROR - 에러. 프로그레스바가 빨강으로 표시됨.
    • PBST_PAUSED - 일시 정지. 프로그레스바가 노랑으로 표시됨.

    상황에 따라 적절히 사용하면 좀 더 친절한 인터페이스를 제공할 수 있겠군요. 아래는 각각의 상태에 대한 프로그레스마 컨트롤의 스크린샷 입니다. 스크린샷을 비교하는 김에 위에 붙였던 Marquee 타입의 스크린샷까지 함께 대조해 보도록 하겠습니다.

    (그림 5) 위에부터 차례대로, Marquee 타입, 기본형 정상상태 / 에러상태 /일시정지 상태



    Outro
    이것으로 MFC 10.0의 새로운 클래스인 CTaskDialog 편을 마무리 하도록 하겠습니다. 사용법은 그렇게 어려울 것도 없는데 막상 풀어서 설명하다보니 글이 꽤나 길어졌습니다. 3차례에 걸친 강좌를 마무리하고 보니, 제가 너무 상세한 설명까지 덧붙여 괜히 글이 장황해 진 것은 아닌가 하는 걱정도 드네요. 분명 내용이 어려울 게 없는데 말입니다 ^^; 하하...
    제 글이 너무 지루하거나 수준이 낮으신 분들은 매번 포스팅마다 가장 아랫부분에 따로 정리해두는 Reference 링크를 참조하시기 바랍니다. 앞으로도 강의는 가능하면 초보 개발자나 학생들도 어렵다는 느낌 없이 쉽게 접하고 내용을 익힐 수 있는 수준으로 이어나갈 예정입니다.

    VisualStudio는 이미 vs2008부터 Native Programmer들을 위한 지원에 많은 노력을 들이고 있다는 점을 확인할 수 있습니다. 그런 부분을 가장 확실하게 보여주는 점이 MFC 라이브러리의 보강 입니다.
    vs2010에서도 MFC는 많은 신기능들을 소개하고 있습니다. 다음 강의에도 좀 더 흥미있는 주제로 포스팅을 이어 나가도록 하겠습니다.
    그럼 다음 강좌에서 또 뵙도록 하겠습니다.
    감사합니다 ^^*


    Reference

    저작자 표시
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro
    안녕하세요. MFC 카테고리의 꽃집총각 입니다. 이번에는 지난번 포스팅 [MFC] 태스크 대화상자(Task Dialog) - (1/3) : 기능 소개 편에 이어서, 사용하기 편을 준비했습니다.
    이번 포스팅에서 다룰 내용은

    1. 비스타 이전 OS에서 태스크 대화상자 이용에 대한 이슈
    2. 태스크 대화상자의 기본적인 구성
    3. 소스코드 한 줄로 태스크 대화상자 사용하기 - ShowDialog 함수.

    이렇게 세 가지 내용을 알아보도록 하겠습니다. 3번 ShowDialog 함수의 소개가 이번 포스팅의 주된 내용입니다. 태스크 대화상자를 사용하기 위해서는 CTaskDialog의 객체를 생성해 DoModal() 함수를 실행하는 방법과, 객체를 생성하지 않고 간단히 CTaskDialog::ShowDialog(...) 함수를 이용하는 방법이 있습니다. 후자는 간편하게 함수호출 한번으로 태스크 대화상자를 이용할 수 있는 장점이 있고, 전자는 보다 디테일한 설정 및 활용이 가능하다는 장점이 있습니다. 오늘은 간단한 사용법인 ShowDialog 함수에 대해 알아보고, CTaskDialog의 객체를 선언하는 방법은 다음 포스팅에서 별도로 다루도록 하겠습니다.



    CTaskDialog::IsSupported() - 비스타 이전 OS들을 위한 대비
    본격적인 CTaskDialog의 기능들을 알아보기 전에, 먼저 한가지 정리해야 할 점이 있습니다. 이전 포스팅에서 짧게 언급한 바 있지만, 태스크 대화상자는 비스타 이전 OS에서는 제공되지 않습니다. 그래서 만약 개발한 응용프로그램이 윈도우 XP 등에서 실행된다면 태스크 대화상자 출력 시점에 오류를 일으킵니다.  

    (그림 1) 윈도우 XP에서 태스크 대화상자 출력을 시도할 때 나오는 오류 화면.


    그렇기 때문에 개발한 프로그램이 비스타 이전 OS에서도 실행되어야 한다면 이런 경우를 위한 별도의 처리를 직접 해주어야만 합니다. 이럴 땐 어쩔 수 없이 기존의 메세지 박스나, CDialog를 상속받은 클래스를 직접 제작해 주어야 겠지요.
    이런 처리를 찾아볼 수 있는 좋은 예가 바로 인터넷 익스플로러 8.0의 '세션 복구 기능' 입니다. 익스플로러 8.0은 비정상 종료되었다가 실행되는 경우 이전에 열려있던 페이지를 자동 복구할 것인지를 물어보는 창을 띄우는데, 실행중인 윈도우가 비스타 이전 버전이라면 일반 대화상자를, 비스타 이후 버전이라면 태스크 대화상자를 출력합니다.

    (그림 2) 인터넷 익스플로러 8.0의 세션복구 기능에서 쓰인 태스크 대화상자.


    (그림 3) 비스타 이전 OS에서는 태스크 대화상자 대신 기존의 대화상자를 출력합니다.


    이런 식으로 OS 차원의 태스크 대화상자 사용 가능여부를 확인하고자 할 때에는, 직접 OS의 버전을 얻어와서 기능 지원 여부를 판별할 수도 있겠지만 CTaskDialog::IsSupported() 정적 함수를 이용해서 쉽게 확인할 수 있습니다.

    if( CTaskDialog::IsSupported() ) // 태스크 대화상자 사용 가능 여부를 확인
    {
        // 태스크 대화상자 호출.
        CTaskDialog::ShowDialog(message, emptyString, title, 0, 0, TDCBF_OK_BUTTON);
    }
    else
    {
        // 지원하지 않는 OS인 경우는 예전 방식으로 처리.
        AfxMessageBox(message);
    }
    
    (코드 1) CTaskDialog::IsSupported() 함수를 통해 태스크 대화상자 사용 가능 여부를 확인.

    참고로 윈도우 서버 2008은 비스타 이전에 나온 OS이므로 태스크 대화상자를 사용할 수 있습니다. 물론 비스타나 윈도우 7 처럼 예쁜 모양은 아니지만요 ^^;

    (그림 4) 윈도우 서버 2008에서 출력되는 태스크 대화상자의 모습. 출처 : http://mariusbancila.ro/blog/2009/03/10/task-dialog-in-mfc/

     


    태스크 대화상자의 기본적인 구성
    태스크 대화상자는 기본적인 구성은 MSDN에 있는 아래의 샘플 스크린샷에 아주 잘 나타나 있습니다.

    (그림 5) 태스크 대화상자의 기본적인 구성. (출처 : MSDN)


    (그림 5)에서 알 수 있듯이 태스크 대화상자는 다양한 컨트롤들과 기능들을 제공합니다. 오늘은 우선 예전 AfxMessageBox() 수준의 심플한 사용방법을 알아보겠습니다. 간단한 메세지를 사용자에게 노출하거나, 기본적인 버튼을 통해 사용자 입력을 받아오고자 할 때는 오늘 소개하는 ShowDialog 함수를 이용하는 것이 좋습니다.


    첫 번째 예제 : 일단 한 번 띄워봅시다
    자, 이제 정말로 태스크 대화상자를 사용하기 위한 방법을 알아보죠. 가장 간단하게 태스크 대화상자를 출력하는 방법은 정적 함수인 CTaskDialog::ShowDialog() 를 사용하는 방법입니다. 이 함수를 사용하면 CTaskDialog의 객체를 만들지 않고도 바로 태스크 대화상자를 띄울 수 있습니다.
    일단 속 시원하게 코드부터 보겠습니다! ShowDialog 정적 함수를 이용한 코드와, 실제 출력된 태스크 대화상자의 모양입니다.

    INT_PTR ret = CTaskDialog::ShowDialog( L"본문 들어가는 곳", L"제목 들어가는 곳", L"타이틀 적는 곳", IDS_STRING102, // 첫 번째 커맨드 버튼. IDS_STRING104 ); // 마지막 커맨드 버튼. if( ret == IDYES ) { // blah blah... }

    (코드 2) 드디어 등장했습니다! 태스크 대화상자를 띄우는 첫 번째 코드입니다!

    (그림 6) (코드 2)를 통해 출력한 태스크 대화상자의 모습.


    태스크 대화상자의 설명을 위해 두 차례나 걸친 포스팅에서 수없이 떠들었던 것에 비해, 너무나도 단순합니다. 그냥 함수 호출 하나 했더니 뜨는군요. 제목, 본문, 타이틀을 설정하는 것은 인자로 직접 문자열을 넣어주고 있으므로 추가 설명이 없이도 쉽게 확인하실 수 있습니다.
    스크린샷을 보면 우리의 첫 번째 태스크 대화상자에서는 세 개의 커맨드 버튼을 가지고 있습니다. 커맨드 버튼은 예, 아니요, 확인, 취소 등과 같은 기존 대화상자의 기본적인 버튼 이외에 자유로운 출력 문자열을 설정할 수 있는 의사 입력 버튼 입니다. 태스크 대화상자의 가장 중앙부에 위치하며, 사실상 태스크 대화상자에서 가장 중요한 컨트롤이라고 할 수 있습니다. ShowDialog 함수를 이용할 때 커맨트 버튼을 넣기 위해서는 응용 프로그램의 리소스 파일에 있는 스트링 테이블을 참조합니다. 스트링 테이블에 커맨드 버튼의 설명으로 사용할 문자열들을 차례로 입력하고, 해당 스트링 아이디의 처음과 마지막 값을 ShowDialog의 네 번째, 다섯 번째 인자로 넣어줍니다.

    (그림 7) 커맨드 버튼을 추가하기 위해 스트링 테이블을 사용하는 예시.


    (그림 6)이 우리의 첫 번째 대화상자 예시에 쓰인 스트링 테이블 입니다. 태스크 대화상자에는 스트링 테이블의 문자열이 출력되고, 사용자가 해당 커맨드 버튼을 선택한 경우 ShowDialog의 리턴값으로 스트링 ID가 반환됩니다. 예제의 세 번째 커맨드 버튼처럼 화면에 여러 줄의 텍스트를 출력하고 싶다면 문자열 안에 개행문자를 이용해 여러 줄의 텍스트를 넣어주면 됩니다.



    CTaskDialog::ShowDialog(...) 함수를 좀 더 자세히 알아봅시다.
    ShowDialog 함수의 인자는 모두 8개 입니다. 예제에서는 다섯개의 인자만을 사용했고 나머지 인자들은 기본값이 쓰였습니다. 함수의 전체적인 기본형은 아래와 같습니다.  

    static INT_PTR CTaskDialog::ShowDialog(
       const CString& strContent,
       const CString& strMainInstruction,
       const CString& strTitle,
       int nIDCommandControlsFirst,
       int nIDCommandControlsLast,
       int nCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON,
       int nTaskDialogOptions = TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS,
       const CString& strFooter = _T("")
    );

    다섯 번째 인자까지는 설명이 되었으니 나머지 인자를 보자면,
    • nCommonButtons : 태스크 대화상자 오른쪽 하단에 출력되는 기본 버튼들을 설정합니다. 기본값으로 '예', '아니오' 두 개의 버튼이 출력됩니다.
    • nTaskDialogOptions : 태스크 대화상자의 여러가지 옵션을 조절할 수 있는 인자입니다. 옵션에 대한 플래그 값은 아래에서 보다 자세히 설명합니다.
    • strFooter : 태스크 대화상자의 가장 아래쪽에 출력되는 꼬릿말 부분입니다.
    기본 버튼으로 설정할 수 있는 플래그는 총 여섯가지이고, CommCtrl.h에 정의되어 있습니다.
    • TDCBF_OK_BUTTON - 확인
    • TDCBF_CANCEL_BUTTON - 취소
    • TDCBF_YES_BUTTON - 예
    • TDCBF_NO_BUTTON - 아니오
    • TDCBF_RETRY_BUTTON - 재시도
    • TDCBF_CLOSE_BUTTON - 닫기
    태스크 대화상자의 옵션을 조절할 수 있는 플래그는 총 열 여섯가지이고, 역시 CommCtrl.h 파일에 정의되어 있습니다. 이 중에서, ShowDialog() 함수와 함께 쓰일 수 있을 만한 플래그 몇 가지만을 정리해 보면 아래와 같습니다.
    • TDF_ENABLE_HYPERLINKS
      하이퍼 링크를 활성화 합니다. 텍스트에 하이퍼 링크를 넣고 싶을 때에는 문자열에서 <a href=\"http://vsts2010.net\">하이퍼 링크</a>처럼 HTML 문법으로 설정할 수 있습니다.
    • TDF_ALLOW_DIALOG_CANCELLATION
      태스크 대화상자의 오른쪽 상단에 창 닫기 버튼이 생기고, Esc키나 Alt + F4로도 창을 끌 수 있게 됩니다. 닫기버튼이나 키보드 조작으로 창을 닫은 경우는 IDCANCEL이 리턴됩니다.
    • TDF_USE_COMMAND_LINKS
      커맨드 버튼을 사용하도록 설정합니다. 이 플래그가 설정되지 않으면 커맨드 버튼으로 설정한 버튼들도 기본 버튼들처럼 출력됩니다.
    • TDF_USE_COMMAND_LINKS_NO_ICON
      커맨드 버튼에 아이콘을 출력하지 않고 텍스트만 표시하게 설정합니다.
    • TDF_SHOW_PROGRESS_BAR
      프로그레스바 컨트롤을 표시합니다.
    • TDF_SHOW_MARQUEE_PROGRESS_BAR
      프로그레스바 컨트롤을 Marquee 형식으로 출력합니다.
    • TDF_POSITION_RELATIVE_TO_WINDOW
      태스크 대화상자를 부모 윈도우의 중앙 위치에 나타나도록 합니다. 이 플래그가 설정되지 않으면 바탕화을 기준으로 중앙 위치에 출력됩니다.
    • TDF_RTL_LAYOUT
      태스크 대화상자에 출력되는 텍스트들을 오른쪽에서 왼쪽으로 출력합니다.
    • TDF_CAN_BE_MINIMIZED
      태스크 대화상자의 오른쪽 상단에 최소화 버튼이 생기고, 버튼을 누르면 창이 최소화 됩니다.
    위의 아홉가지 플래그 정도가 ShowDialog 함수를 이용한 간단한 사용시에도 유용하게 쓰일만한 기능들 입니다. 이 중에 프로그레스바와 관련된 플래그 같은 경우, TDF_SHOW_PROGRESS_BAR를 넣어주면 실제로 프로그레스바가 표시되지만, 프로그레스바의 값을 조절한다거나, 오류상태를 표시하는 등의 세밀한 처리를 하는 것은 어렵습니다. 이런 경우는 ShowDialog 함수를 통한 호출보다는 직접적으로 CTaskDialog 의 객체 혹은 CTaskDialog 파생클래스의 객체를 만들어서 처리해야 합니다. TDF_SHOW_MARQUEE_PROGRESS_BAR 플래그로 Marquee 형식을 출력할 수도 있지만 이런 경우도 대게는 타이머를 설정하고 자체적으로 동작이 진행중임을 표시하기 위해 주로 사용하므로, 이또한 ShowDialog를 통한 출력에는 생뚱맞습니다.

    (그림 8) 예제 대화상자에 꼬릿말과 프로그레스바를 추가한 모습.


    위의 (그림 8) 스크린샷을 봅시다. 꼬릿말은 그럭저럭 쓸만 하다고 하지만, 텅 빈 프로그레스바는 보는 이의 마음까지 황량하게 만듭니다. 아무래도 좀 더 추가적인 처리가 필요해 보입니다. ShowDialog 함수를 통해서도 프로그레스바 컨트롤을 출력할 수는 있지만, 이를 세밀하게 제어하는 것은 무리입니다. 이를 포함해 여러가지 컨트롤의 처리에 대한 내용은 다음 포스팅에 설명하도록 하겠습니다.



    Outro
    이번 포스팅 에서는 태스크 대화상자 미지원 OS를 식별하는 방법과, 태스크 대화상자의 기본적인 구성, 그리고 메시지 박스 수준의 간단한 태스크 대화상자 출력방법을 알아보았습니다. 정적 함수 ShowDialog()를 사용하면 AfxMessageBox()를 이용하는 것보다 좀 더 다양하게 머릿말, 본문, 꼬릿말 등으로 구분에 메세지를 설정할 수 있고, 모던한 디자인의 레이아웃을 갖춘 태스크 대화상자를 출력할 수 있습니다.
    다음 포스팅에서는 태스크 대화상자가 제공하는 다양한 컨트롤 들을 활용할 수 있는 방법을 알아보도록 하겠습니다. 리소스 에디터로 직접 다이얼로그를 디자인하던 불편함에서 벗어나 함수호출 몇 줄만으로 다양한 기능을 추가하는 방법들을 정리할 예정입니다. 보다 미리 관련 내용을 확인하고 싶으신 분들은 MSDN(http://msdn.microsoft.com/en-us/library/bb760441(VS.85).aspx)과 다음의 페이지( http://www.nuonsoft.com/blog/2009/06/10/ctaskdialog-in-mfc-in-visual-c-2010/ )를 참고하시기 바랍니다.
    그럼 다음 포스팅에서 뵙겠습니다.
    감사합니다 ^^*


    Reference
    저작자 표시
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro
    안녕하세요. MFC 카테고리의 꽃집총각입니다. 이번에는 앞서 말씀 드렸던 대로, MFC 10.0에서 새로 추가되는 클래스 중에 하나인 CTaskDialog에 대해서 알아보도록 하겠습니다. 이번 포스팅에서는 CTaskDialog클래스와 이를 이용해 구현할 수 있는 기능인 태스크 대화상자(Task Dialog; 이하 한글 표기만을 사용합니다)에 대한 전반적인 개념을 정리하고, 다음시간 부터는 실제 사용방법 및 주의 사항 등을 정리해 보도록 하겠습니다.


    태스크 대화상자 소개
    태스크 대화상자(Task Dialog)는 윈도우 비스타 버전에서 새롭게 선보인 컨셉의 대화상자 입니다. 태스크 대화상자를 이용해 개발자들은 기존의 메세지 박스를 이용해 구현하던 기능을 보다 손쉽고 강력하게 처리할 수 있습니다. 이전부터 널리 사용되고 있는 AfxMessageBox(...)를 이용해서도 간한단 정보의 노출이나 사용자 의사선택을 처리할 수 있었지만, 추가적인 기능을 확장해 넣기에는 번거로운 점이 많았습니다. 어떻게든 예, 아니오로 대답할 수 있는 연속적인 질문을 생각해내 메세지 박스를 연거푸 출력하거나, 리소스 에디터를 열고 새로운 대화상자를 직접 만들어 주어야 했습니다. 하지만 태스크 대화상자를 이용하면 개발자가 대화상자를 커스터마이징 하기 위해 필요한 거의 대부분의 기능을 옵션으로 제공하고 있어, 손쉽고 풍부한 기능확장을 아주 심플하게 해낼 수 있습니다.

    원래 길게 말해봐야 한 번 보는게 더 와닿는 법입니다. 아래의 스크린샷이 바로 태스크 대화상자의 예시 입니다.

    (그림 1) Internet Explorer 8.0의 세션 복구 기능에서 등장하는 태스크 대화상자.


    (그림 2) 지난번 강좌에서 설명한 리스타트 매니저에서 사용되는 태스크 대화상자.


    (그림 3) 윈도우 7의 업데이트 옵션 설정할때 등장하는 태스크 대화상자.


    아마도 윈도우 비스타나 윈도우 7을 사용해보신 분이라면 이곳 저곳에서 많이 보셨던 형식의 대화상자 일겁니다. 위에서 예를 든 세 가지 스크린샷도 모두 프로그래머가 별도로 만든 것이 아니라, 운영체제나 인터넷 익스플로러 등에서 쓰인 녀석들 입니다. Visual Studio 2010의 MFC기반 응용 프로그램에서는 이러한 태스크 대화상자를 몇 줄 안되는 코드만으로 쉽게 제어할 수 있는 강력한 클래스를 제공합니다. 그 클래스가 바로 CTaskDialog 입니다 :)


    CTaskDialog로 할 수 있는 일들.
    거의 예/아니오 내지는 확인/취소 정도의 선택밖에 제공할 수 없었던 기존의 메세지 박스와는 달리 태스크 대화상자는 아래와 같은 풍부한 커스터마이징 옵션을 제공합니다.

    • 사용자 지정 아이콘
    • 메인 헤더 텍스트 (멀티라인 지원)
    • 본문 텍스트 (당연히 멀티라인 지원)
    • 프로그레스바 컨트롤 표시 가능.
    • 라디오 버튼 표시 가능.
    • 커맨드 버튼 - 대화상자의 가장 중앙에 나타나는, 사용자의 의사 선택을 받는 버튼 - 표시 가능.
    • 추가적인 본문 텍스트를 보이거나 숨길 수 있게 해주는 확장/축소 버튼 표시 가능.
    • 체크박스 표시 가능.
    • 꼬릿말 텍스트 (멀티라인 지원)
    • 대부분의 컨트롤 및 텍스트에 하이퍼 링크 지원.
    • 타이머 기능 제공.

    위처럼 다양한 옵션을 제공하기 때문에 어지간한 대화상자 UI의 구성은 번거롭게 다이얼로그 리소스에 버튼을 배열하고 CDialog 파생클래스를 만들어 일일이 코딩을 해야 하는 수고를 들이지 않아도 CTaskDialog 를 이용해 아주 심플하게 처리할 수 있게 되었습니다. 개발 편의성 뿐만 아니라 실용성 측면에서도 실제 비스타 이후 버전의 윈도우 자체에서도 널리 쓰이고 있는 것을 보면 그 실용성은 입증 되었다 볼 수 있겠네요.
    각각의 기능들에 대한 자세한 사용 방법은 다음 강좌에 보다 본격적으로 다루도록 하겠습니다.



    태스크 대화상자와 운영체제, API들의 관계
    이번 강좌에서는 이 부분을 좀 더 일찍 정리하고 싶었습니다. 실제 기능의 사용에는 크게 상관없지만 그래도 기본 개념의 정리가 중요한 법인데, 지난 번 강좌에서는 너무 늦게 말은 한 것 같아서요. 노파심에 다시 한 번 운영체제 및 API들과 기능간의 관계를 정리해 보겠습니다.

    1. 꼭 MFC에서만 할 수 있는 건 아닙니다. MFC는 보다 편리하게 쓸 수 있도록 도와줍니다.
      태스크 대화상자는 기존의 메세지 박스보다 확장성 측면에서 훨씬 유용하게 쓰일 수 있는 새로운 컨셉의 대화상자 입니다. 이 태스크 대화상자는 윈도우 비스타에서 처음 소개되었으며, 태스크 대화상자를 제어할 수 있는 Windows API들도 함께 제공 되었습니다. MFC에서는 이러한 API들을 쉽고 편하게 사용할 수 있도록 랩핑한 클래스를 제공하는 겁니다. 그래서 굳이 MFC를 사용하지 않아도 태스트 대화상자를 이용할 수는 있지만, MFC의 CTaskDialog를 이용해 보다 편하게 작업을 할 수 있습니다.
    2. 윈도우 비스타 이전 버전에서는 사용할 수 없습니다.
      하지만 애석한 점 한가지는, 비스타 이전 버전의 윈도우에 대한 대비책은 딱히 제공되지 않는다는 점입니다. 지난번 포스팅에서 소개했었던 리스타트 매니저도 마찬가지로 비스타 이후 버전에서만 동작했었습니다. 하지만 리스타트 매니저는 응용 프로그램을 윈도우 XP에서 실행해도 기능을 하지 않을 뿐 오류를 내지는 않았지만, 태스크 대화상자를 사용하는 응용 프로그램을 윈도우 XP에서 실행한다면 태스크 대화상자 출력시점에 에러를 내게 됩니다. 화면상에 출력되어야 하는 기능이니 리스타트 매니저 처럼 오동작 없이 넘어가기가 어렵기 때문입니다.


    Outro

    늘 마찬가지지만 제가 설명해 드리는 MFC의 새로운 기능들은 이해하기 쉽고 빠르게 개발에 적용할 수 있는 기능들 입니다. MFC의 기본적인 방향성 자체가 보편적인 기능을 손쉽게 다룰 수 있게 제공되는 라이브러리 이기도 하고요. 그래서 초보 개발자나, 학생 분들도 어렵지 않게 내용을 따라오실 수 있다고 생각합니다. 그래도 설명 중 잘 이해가 되지 않는 부분이나 잘못된 점이 있다면 피드백을 부탁 드리겠습니다.

    그럼 이번 포스팅에서는 간략하게 기능소개 정도로 글을 마치고, 다음 글에서 본격적으로 태스크 대화상자를 사용하는 방법을 다뤄 보도록 하겠습니다. 다음 글을 기다리기가 지루하신 분들은 MSDN( http://msdn.microsoft.com/en-us/library/bb760441(VS.85).aspx )이나 다음의 글( http://www.nuonsoft.com/blog/2009/06/10/ctaskdialog-in-mfc-in-visual-c-2010/ )을 참고하세요.
    그럼 다음에 뵙겠습니다.
    감사합니다 ^^*


    Reference

    저작자 표시
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    Intro
    안녕하세요. MFC 카테고리의 꽃집총각 입니다.
    지난번 [MFC] 리스타트 매니저(Restart Manager) - (2/3) : 사용하기 편에 이어서 오늘은 리스타트 매니저 시리즈의 마지막인 ‘활용하기’ 편입니다. 이번 시간에는 실제로 리스타트 매니저의 동작을 확인해 볼 수 있는 샘플을 만드는 과정을 정리하고, 실제 동작에 관련된 이슈들을 몇 가지 정리해 보겠습니다. 


    예제 프로그램작성 – 1. MFC Application Wizard 설정 하기.

    자, 우선은 VIsual Studio 2010을 열어서 Ctrl + Shift + N 을 힘차게 누질러서 새 프로젝트 생성 창을 띄우고, MFC Application을 선택해 MFC 어플리케이션 위자드(Application Wizard;응용 프로그램 마법사)를 띄웁니다.

    (그림 1) MFC Application을 선택해 새 프로젝트를 생성합니다.

    어플리케이션 위자드가 뜨면 왼쪽 메뉴에서 Application Type을 선택하고, 기본 비주얼 스튜디오(Visual Studio) 형식으로 되어 있는 프로젝트 스타일 항목을 오피스(Office) 형식으로 변경해 줍니다. 비주얼 스튜디오 스타일의 여러 가지 도킹 pane들은 이번 예제에는 크게 쓸모가 없으니까, 괜히 걸리적 거리기만 하거든요 ^^;


    (그림 2) MFC Application Wizard > Application Type 항목 설정.

    Document Template Properties 항목을 선택하고, 파일 확장자(File extension) 란에 임의의 확장자를 입력해줍니다. 파일 확장자를 명시하게 되면 별도의 코딩을 추가하지 않아도 생성된 프로젝트에 자동 생성되는 소스코드 부분에 Document의 저장/로딩 처리가 추가됩니다. 파일 확장자를 입력하면 오른쪽에 있는 필터 이름(Filter name)칸도 자동으로 채워집니다. 저는 custom-document-format의 뜻으로 cdf라고 적어봤어요.

    ( 그림 3) MFC Application Wizard > Document Template Properties 항목 설정.

    그리고 가장 중요한 부분! Advanced Features 항목에 가서 리스타트 매니저 지원 사항들을 확인합니다. 리스타트 매니저와 관련된 세 개의 체크사항들이 기본적으로 체크되어 있기 때문에 수정을 할 필요는 없지만 그래도 올바로 체크되어 있는지 확인해야 겠지요. 


    (그림 4) MFC Application Wizard > Advanced Features 항목 설정.

    자, 이제 마지막으로, Generate Class 페이지로 가서 뷰 클래스의 부모 클래스를 CView –> CEditView로 변경해 줍니다. CEditView를 상속받은 클래스는 기본적으로 뷰 영역이 메모장 프로그램처럼 캐럿이 깜박이는 에디트 컨트롤 형식으로 되어있어서, 별다른 처리 없이도 문서의 저장을 확인할 수 있는 문자열 형식의 데이터를 입력할 수 있게 됩니다.


    (그림 5) MFC Application Wizard > Generated Class 항목 설정.

    이제 어플리케이션 마법사의 모든 설정이 끝났습니다. Finish를 눌러 프로젝트를 생성합니다.


    예제 프로그램작성 – 2. 크래시 발생 버튼 추가하기.

    리스타트 매니저가 올바르게 동작하는지 알아보기 위해서는 프로그램이 비정상 종료 되어야 합니다. 샘플 프로그램의 리본 UI에 간단하게 패널 하나와 버튼 하나를 추가하고, 핸들링 함수를 추가해서 아래와 같이 잘못된 포인터 연산을 하는 코드를 넣어줍니다. 어플리케이션 위자드에서 오피스 형식의 Application Type을 선택하셨다면 아마 기본적으로 리본 UI가 붙어있을겁니다. 그렇지 않다면 툴바든, 마우스 입력이든 상관 없이 아무 이벤트나 받아서 고의적으로 예외를 발생시키는 코드를 넣어주면 됩니다.

      
    (그림 6) 리본에 누르면 터지는 버튼을 넣어줍니다.

     

     예제 프로그램 작성 – 3. 문서 자동 저장 간격 조절하기.
    지난번에 리스타트 매니저 사용 팁을 정리하면서, 문서 데이터 자동 저장 기능의 기본 저장 간격 시간은 5분이라고 말씀 드린 적이 있습니다. 우리는 매우 바쁜 사람이니까, 이 시간을 당겨보죠. 이왕이면 프로그램을 띄우고 나서 바로 확인할 수 있도록 아주 짧게 잡아보겠습니다. 한 10초 정도면 나쁘지 않겠죠?

    (그림 7) CWinApp 파생클래스의 생성자에서 m_nAutosaveInterval의 값을 10000 미리세크(=10초)로 설정해줍니다.

    시간 설정에 대한 내용은 지난회 포스팅이었던 [MFC] 리스타트 매니저(Restart Manager) - (2/3) : 사용하기 편을 참고하시면 됩니다.

     

    예제 프로그램 실행!
    이제 빌드를 하고 프로그램을 실행시켜서 에디트 뷰에 블라블라 테스트용 잡담을 늘어놓은 후, 10초가 지나 임시 문서가 만들어졌을 만한 충분한 시간을 제공한 뒤에 ‘누르면 폭발!’ 버튼을 야심차게 눌러주면 프로그램이 크래시가 나고 리스타트 매니저가 동작하면서 프로그램을 다시 띄워 주겠지요? 하지만 실제로 실행해보시면 아마 리스타트 매니저는 커녕 그냥 프로그램만 죽어버리고, 프로그램을 닫든지 디버깅을 하든지 니 맘대로 하라는 쓸쓸한 대화상자만 출력되고 말 겁니다.


    (그림 8) 크래시가 났지만, 재시작도 문서 복구도 아무 것도 일어나지 않았습니다.
    우리는 대 사기극에 휘말린 걸까요?


    (그림 9) ‘그래, 내컴은 닷넷 디버거가 깔려 있어서 그럴 거야! 일반 사용자들은 이렇지 않겠지!’
    라는 일말의 희망도 부질없습니다. 닷넷 미설치 PC에서는 위와 같은 창이 출력됩니다.

    이 시점에서 몇 가지 더 알아두어야 할 것이 있습니다. 우리의 샘플 프로그램에서 리스타트 매니저가 동작하지 않는 이유와 함께, 실제로 리스타트 매니저를 사용하려고 할 때 알아두면 좋은 몇가지 정보들을 함께 정리해 보도록 하겠습니다.

      

    (중요*) 리스타트 매니저 사용시 고려사항.

    1. 우리가 살펴보고 있는 MFC 10.0의 리스타트 매니저 기능이 실은 첫 번째 포스팅에서 설명 드렸던 것처럼 윈도우 비스타 시스템에서 선보인, OS 차원에서 제공되는 기능입니다. MFC 10.0에서는 이 기능을 좀 더 쉽게 사용할 수 있게끔 추가처리를 지원하는 것입니다. 예를 들자면 win32 GDI의 DC(Device Context)와 MFC의 CDC 클래스 관계 정도가 되겠네요. MFC의 리스타트 매니저도 내부 구현으로 들어가면 비스타 이후 OS에서 지원하는 윈도우 API인 RegisterApplicationRestart, RegisterApplicationRecoveryCallback 등의 함수를 사용해 재시작 및 문서 복구 기능을 제공하고 있습니다. MFC를 사용하지 않은 일반 Win32 윈도우 프로그램이나, 콘솔 프로그램에서 까지도 재시작 및 복구 기능을 사용할 수 있습니다. 하지만 MFC를 이용하면 보다 쉽게 사용할 수 있게 되는 것이지요.
    2. 프로그램 재시작 기능의 핵심이 되는 RegisterApplicationRestart 함수는 사용시 한가지 주의해야 할 사항이 있습니다. 만약 프로그램의 초기화 코드에 오류가 있어서 인스턴스가 실행되자마자 크래시를 내는 상황인데 리스타트 매니저가 동작한다면 어떻게 될까요? 아마 프로그램은 뻗고, 실행되고, 다시 뻗고, 다시 실행되는 무한 재실행을 반복하게 될겁니다. 이런 경우를 피하기 위해서 비스타의 응용 프로그램 재시작 기능은 초기 재시작 후 60초 동안은 크래시로 인한 비정상 종료가 있었다고 해도 동작하지 않습니다. 우리가 실행시킨 샘플 프로그램은 구동한지 60초가 지나지 않았기 때문에, 재실행 기능이 실행되지 않았던 겁니다. 보다 자세한 내용은 MSDN(http://msdn.microsoft.com/en-us/library/aa373347(VS.85).aspx)에서 확인하실 수 있습니다.
    3. 자동 저장되는 문서 파일의 경로는 CDataRecoveryHandler::GetAutosavePath 함수로 알아낼 수 있습니다. 프로그램을 실행하고 해당 경로에 가보면 실제로 자동 저장되는 버전의 문서파일을 확인하실 수 있습니다. 우리의 샘플 프로그램은 10초마다 착실하게 문서를 잘 저장하고 있네요 :) 임시 파일 저장 경로의 변경은 CDataRecoveryHandler::SetAutosavePath 함수로 가능합니다.

     

     이제 예제를 통해 리스타트 매니저 동작을 직접 확인해 봅시다.
    이제는 정말로 리스타트 매니저의 놀라운 동작을 우리 눈으로 직접 확인할 시간입니다. 준비는 이미 다 끝났습니다. 단지 리스타트 매니저가 실행될 수 있도록 예제 프로그램 구동 후 60초의 시간을 기다려 주기만 하면 됩니다.


    (그림 10) 리스타트 매니저의 동작을 한 눈에 파악하기 위해
     모두 저장된 파일, 반만 저장된 파일, 저장 안 된 파일을 준비.

    60초의 시간이 흐르는 동안 저는 위의 (그림 10)과 같이 세 개의 문서 파일을 준비했습니다. 저장한 문서창 하나, 저장한 후 추가로 내용을 추가한 문서창 하나, 그리고 저장하지 않고 내용만 적은 문서창 하나. 곧 리스타트 매니저가 이들을 각각 제대로 복구해 주는지 실제로 확인해 보겠습니다.
    그리고 실제로 우리가 설정한 10초의 간격으로 문서 파일이 저장되는지 확인해 보겠습니다. 저는 윈도우 7을 사용하고 있는데, 임시 문서 기본 저장 경로가 C:\Users\(윈도우계정명)\AppData\Local 으로 잡혀있네요. 윈도우 탐색기로 해당 경로를 열어보면 실제 10초 단위로 갱신되고 있는 임시 저장 파일들이 보입니다.


    (그림 11) 착실히 저장되고 있는 자동 임시 저장 파일들.

    이제 미리 만들어 두었던 리본 UI의 크래시 버튼을 눌러 프로그램을 비정상 종료 시킵니다. 프로그램이 비정상 종료된 후, 자동으로 재시작 되면서 문서를 임시 저장된 버전으로 복구할 것인지를 묻는 대화상자가 출력됩니다.


    (그림 12) 크래시 발생후 리스타트 매니저가 자동으로 응용 프로그램을 재시작.


    (그림 13) 응용 프로그램 재시작 후, 임시 저장된 버전의 문서를 복구할 것인지 묻는 대화상자 출력

    그림 13의 문서 복구 여부 확인창이 떴을 때 ‘자동 저장 문서 복구’ 항목을 선택하면, 크래시가 나기 전에 저장해 두었던 문서의 텍스트들이 그대로 모두 복구되는 것을 확인할 수 있습니다. 임시 버전으로 복구된 파일들은 파일명이 출력되는 탭 부분에 [복구됨] 이라는 표기가 붙어서 출력됩니다.


    (그림 14) 리스타트 매니저의 '자동 복구 기능'으로 복구된 문서파일의 모습.

     

    Outro
    이번 포스팅 에서는 리스타트 매니저를 직접 동작시키고 확인해 볼 수 있는 예제 작성 방법과 몇 가지 주의 사항을 짚어 보았습니다. 첨부된 이미지들이 많아서 괜히 길어 보이긴 하지만, 어려운 내용은 없었으리라 생각합니다.
    이것으로 3회에 걸친 리스타트 매니저 강좌를 마치고, 다음에는 MFC 10.0에서 새롭게 선보이는 CTaskDialog 클래스에 대한 강좌를 가지고 다시 찾아 뵐 예정입니다.
    그럼 다음 강좌에서 뵙도록 할게요.
    감사합니다 ^^*

     

    Reference

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Intro
    안녕하세요. MFC 카테고리의 꽃집총각 입니다.
    지난번 [MFC] 리스타트 매니저(Restart Manager) - (1/3) : 기능 소개 편에 이어서 이번에는 실제로 리스타트 매니저를 사용하려면 어떻게 해야 하는가를 알아보도록 하겠습니다.


    리스타트 매니저 사용하기

    리스타트 매니저를 새로운 응용프로그램 프로젝트에 적용하는 방법과, 기존에 작성된 응용프로그램에 적용하는 방법에는 조금 차이가 있습니다. 하나씩 나누어서 알아보죠.

    1. 새로운 프로젝트에서 리스타트 매니저 사용하기 
    새 프로젝트로 MFC Application을 선택하면, MFC Application Wizard로 넘어가면서 프로젝트 설정을 하게 됩니다. 이 때 AdvancedFeatures 페이지에서 Support Restart Manager (리스타트 매니저 지원) 사항을 체크하면 됩니다.

    (그림 1) 응용프로그램 마법사의 리스타트 매니저 지원 항목.


    (그림 1)에서 보시는 대로 체크버튼이 세 개가 있습니다. 체크버튼의 선택 사항에 따라 지원 범위를 세가지로 선택할 수 있습니다.
    1. ① - 리스타트 매니저 지원 사항만 체크하는 경우
      이 경우 응용 프로그램은 재시작 기능만을 지원하게 됩니다. 응용 프로그램은 업그레이드나 크래시 발생 후 자동으로 재시작하는 기능이 추가되지만, 문서를 자동으로 열어주거나 복구해주는 처리는 하지 않습니다.
    2. ① + ②  - '리스타트 매니저 지원' 항목과 '이전 문서 다시열기' 항목에 체크하는 경우
      재시작 기능과 함께, 이 경우는 이전에 열려있던 문서를 다시 열어주는 기능까지 제공하지만 문서의 자동저장 버전을 복구하는 처리는 하지 않습니다.
    3. ① + ② + ③ - 싸그리 다 체크하는 경우
      자동 재시작 기능, 문서 다시 열어주기 기능, 자동 저장된 버전으로 복구하는 기능까지 모두 제공합니다.

    참고 사항:

    • ② 항목을 선택하기 위해서는 반드시 ① 항목을 선택해야 하며 ( case B ), ③ 항목을 선택하기 위해서는 반드시 ② 항목을 선택해야 합니다. ( case C )
    • 다이얼로그 기반의 응용 프로그램에서는 ②, ③ 항목은 자동으로 비활성화 됩니다.

    2. 기존의 프로젝트에서 리스타트 매니저 사용하기
    기존의 응용프로그램에 적용하기 위해서는 CWinApp 파생 클래스의 생성자에 딱 한 줄의 코드만 추가해주면 끝입니다. (아래 코드는 각각 위에서 설명한 A, B, C 기능과 동일한 기능을 제공합니다.

    1. m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART
    2. m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART_ASPECTS
    3. m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS

    사용된 플래그 상수들은 afxwin.h 파일에 정의되어 있으며, 정의된 위치를 찾아가보면 다른 플래들도 더 보이는 군요. 직접 찾아가 보시면 간단한 주석과 flag 결합 상태등을 좀 더 분명하게 확인하실 수 있습니다.

    (그림 2) 소스코드에서 설정가능한 리스타트 매니저 지원 flag들 (in afxwin.h)

    사실 어플리케이션 위자드에서 체크박스 항목으로 설정한 리스타트 매니저 지원사항들도 처음 기본 소스코드들을 생성해줄 때 위의 소스코드 한 줄을 선택사항에 맞춰 적절하게 적어주는 역할만을 할 뿐입니다.

    (그림 3) 리스타트 매니저의 사용 설정 예시.



    리스타트 매니저 사용에 대한 몇가지 팁들

    팁 하나. 문서의 자동 저장 시간 간격 조절하기
    CdataRecoveryHandler::SetAutosaveInterval() 함수를 사용하면 문서가 자동 저장되는 시간 간격을 조정할 수 있습니다. 기본값은 5분으로 설정되어 있습니다. CDataRecoveryHandler 클래스를 얻으려면 AfxGetApp()->GetDataRecoveryHandler()를 호출하면 됩니다.
    혹은 CDataRecoveryHandler 클래스에 접근하지 않고도 바로 시간간격을 조절할 수도 있습니다. 바로 CWinApp 파생 클래스의 생성자에서 멤버변수 m_nAutosaveInterval에 값을 지정해주면 동일한 처리가 가능합니다.

    (그림 4) 리스타트 매니저가 문서를 자동 저장하는 시간 간격 조절.

    두가지 방법 모두 시간 단위는 밀리초로 입력합니다. 프로그램 실행 시 최초 1회만 시간설정을 하면 된다면 간단하게 CWinApp 생성자에서 설정해주면 되고, 사용자에게 설정 가능한 옵션으로 제공해 런타임에도 시간설정을 변경해야 한다면 SetAutosaveInterval() 함수를 사용하면 되겠지요.

    팁 둘. 리스타트 매니저 직접 만들기(수정하기)
    기본 제공되는 리스타트 매니저 만으로도 꽤나 유용한 기능을 사용할 수 있지만, 개발을 하다 보면 다른 방식의 데이터 저장 기능 등을 구현해야 할 때가 있습니다. 이런 경우에는 CWinApp::GetDataRecoveryHandler()를 오버로딩하고, CDataRecoveryHandler클래스를 상속받아 본인이 직접 구현한 CDataRecoveryHandler 파생 클래스를 붙여주면 됩니다.
    참고로 윈도우 비스타 이전의 운영체제에서 이 함수가 시스템에 의해 호출되면 NULL을 리턴합니다. 앞서 말씀 드렸듯이 리스타트 매니저는 비스타 이후 OS에서만 지원됩니다. DataRecoveryHandler를 얻어내고자 하면 NULL이 반환되기 때문에, 비스타 이전 OS에서는 리스타트 매니저가 설정되지 않은 것처럼 동작하게 되지요. 그래서 리스타트 매니저를 사용하는 응용 프로그램을 비스타 이전 OS에서 구동하게 되어도 재시작 및 복구 기능만 비활성화 될 뿐, 오동작이나 다른 문제가 발생하지는 않습니다.

    팁 셋. 업데이트 상황 재연하기

    리스타트 매니저는 크래시가 발생한 경우 뿐만 아니라, 재실행을 필요로 하는 업데이트 진행시에도 사용할 수 있습니다. 크래시 상황을 재현하는 건 간단히 포인터 조작 오류 등을 넣어서 테스트 할 수 있고요, 인스톨러를 이용한 프로그램 업데이트 상황을 재연하고자 할 때에는 아래의 Rm...으로 시작하는 API 들을 사용해서 재현할 수 있습니다.

    • RmStartSession
    • RmRegisterResources
    • RmShutdown
    • RmRestart

    이 API들은 윈도우 비스타부터 제공된 기능인 Restart Manager 를 설정 및 조작하는 함수들입니다. 응용 프로그램의 패치를 위해 윈도우즈 인스톨러 4.0 이상의 버전을 사용하는 경우는 자동으로 리스타트 매니저를 사용하게 되어 있고, 커스텀 인스톨러를 사용하는 경우도 역시 제공되는 API 함수들을 이용해 리스타트 매니저를 사용할 수 있습니다. 인스톨러에서 응용 프로그램의 종료 및 재시작을 제어하는 부분은 MFC 리스타트 매니저의 사용이라는 주제에서는 다소 벗어난 내용이므로, 본 포스팅에서는 다루지 않습니다. 보다 자세한 정보는 "메인 인스톨러에서 리스타트 매니저 사용하기(Using Restart Manager with a Primary Installer; http://msdn.microsoft.com/en-us/library/aa373681(VS.85).aspx)" 글을 참고하시기 바랍니다.
    참고로 MSDN의 Restart Manager 부분을 한글 번역한 자료가 데브피아에 있습니다(http://www.devpia.com/Maeul/Contents/Detail.aspx?BoardID=63&MAEULNo=24&no=11). 이것도 참고하시면 좋겠네요.


    Outro
    이번 포스팅에서는 MFC 응용 프로그램에서 실제로 리스타트 매니저를 사용하는 방법과 몇가지 팁들을 정리해 보았습니다. 다음 번에는 '활용하기' 편으로, 예제를 위주로 실제 사용시점에 참고할 만한 사항들을 확인해 보는 것으로 리스타트 매니저 편을 마치도록 하겠습니다.
    그럼 다음 포스팅에서 또 만나기로 하고 여기서 줄이겠습니다.
    감사합니다 ^^*


    참고 자료 (reference)

     

    저작자 표시
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    Intro
    안녕하세요. 이번에 팀블로그에 MFC 카테고리를 맡아 합류하게 된 꽃집총각 이라고 합니다.
    앞으로 vs 2010에서 새롭게 선보인 MFC의 기능들을 주제로 포스팅을 하려고 합니다.
    많은 관심과 격려 부탁드립니다 ^^

    리스타트 매니저(Restart Manager) 의 소개
    오늘은 첫 번째 포스팅으로, 응용 프로그램의 예상 못한 종료 상황에 요긴하게 사용될 기능인 리스타트 매니저(Restart Manager; 이하 한글 표기만 사용하겠습니다)에 대해 이야기 하고자 합니다.
    리스타트 매니저는 윈도우 비스타 시스템에서 소개된 새로운 기능입니다. 이 기능은 응용 프로그램이 재시작을 필요로 하는 업데이트를 할 때나 처리하지 못한 exception 등으로 크래시가 발생했을 때, 데이터가 손실되지 않도록 도와줍니다. 비정상적인 종료가 발생했을 때 사용자가 이전에 작업 하면서 저장하지 못했던 데이터를 자동 저장 해주고, 응용 프로그램이 재시작 됐을 때 비정상 종료 전의 상태로 복구하는 것을 가능하게 만들어줍니다. 멋진 기능이죠!
    쉽게 생각하면 이번에 인터넷 익스플로러 8의 새롭게 선보인 '세션 복구' 기능과 비슷하다고 볼 수 있습니다.

    (그림 1) 인터넷 익스플로러 8.0의 세션 복구 기능.


    익스플로러 버전 8 부터는 오류 등으로 인해 예상치 못한 종료가 발생하면 다음 재시작에서 위와 같은 창이 뜹니다. 세션 복구를 선택하면 종료 전에 열려있던 웹 페이지들이 다시 열리게 되죠. 이와 유사한 복구기능을 리스타트 매니저가 지원합니다. 

    vs2010의 MFC를 통해 지원되는 리스타트매니저의 특징들은 간략히 아래와 같이 정리할 수 있습니다.
    1. vs2010으로 새롭게 제작되는 MFC 응용 프로그램들은 MFC 어플리케이션 위자드 (Application Wizard;응용 프로그램 마법사)를 이용해 아주 간단하게 프로그램 재시작 및 복구 기능을 사용할 수 있습니다.
    2. 리스타트 매니저 API중에서 개발자가 설정/변경 가능한 모든 부분들은 오버라이드 할 수 있는 가상멤버의 형식으로 제공됩니다.
    3. 예전에 만들어진 MFC 응용 프로그램에 기본 제공되는 리스타트 매니저의 기능을 붙이고 싶다면, vs2010으로 옮겨온 후 한 줄의 소스코드만 넣으면 됩니다!
    4. 문서파일을 다루는 응용 프로그램의 경우, 문서를 일정 주기마다 임시 파일로 자동 저장하는 기능이 추가됩니다. 자동 저장되는 주기 역시 개발자가 직접 설정할 수 있습니다. 응용 프로그램이 예외상황으로 크래시가 나는 경우, 해당 프로그램은 가장 마지막으로 백업된 임시 데이터를 복구해 새로 시작됩니다.
    5. 복구가 가능한 자동 저장된 버전의 문서가 존재하는 경우, 사용자에게 복구할 것인지를 묻는 UI 창이 기본 제공됩니다.


    리스타트 매니저가 제공하는 기능들

    MFC의 리스타트 메니저가 제공하는 기능은 크게 '재시작 기능'과 '복구 기능' 두 단계로 나누어 볼 수 있습니다.

    1. 재시작 기능 (Restart Support) :
    업그레이드나 크래시가 발생한 후 바로 재시작하는 기능. 이 기능은 모든 MFC 프로그램에서 지원됩니다. 다시말해 다이얼로그 기반이나 SDI, MDI 등의 도큐먼트-뷰 기반 등에 상관없이 MFC 프로젝트로 만든 실행파일은 모두 다 사용 가능하다는 의미입니다.

    (그림 2) 크래시가 나면 해결 방법을 확인 중이라는 창이 뜹니다. 과연 뭘 하는 중일까요...

    (그림 3) 그리고는 곧 '다시 시작하는 중'이라는 창이 뜨고, 프로그램은 자동 재실행 됩니다.

    2. 복구 기능 (Application Recover Support) :
    복구 기능은 또 다시 두 가지 기능으로 나누어 볼 수 있습니다.
      1. 종료되기 전에 열어두고 있었던 문서를 바로 다시 열어주는 기능.
      2. 자동 저장된 버전의 문서를 복구해 주는 기능.
    복구 기능은 문서(Document)에 관련된 기능이니만큼, 도큐먼트-뷰 형식의 MFC 응용 프로그램에서만 사용 가능합니다. 다이얼로그 기반 프로젝트로 만든 실행파일에서는 사용할 수 없어요. 다시 정리하자면, 데이터를 복구한다는 의미는 도큐먼트-뷰 형식의 프로그램에서 Document에 해당하는 문서 데이터 부분을 대상으로 하는 말입니다. 개발자가 임의로 선언해 사용하는 (CDocument 클래스와 크게 상관이 없는) 커스텀한 데이터들은 복구 대상이 아닙니다. 
    하지만 이건 기본 제공되는 리스타트 매니저에 대한 설명입니다. 추가 동작이 필요하다면 기본 기능을 확장할 수 있는 가상함수 형식의 인터페이스들을 통해 직접 기능 확장을 하면 됩니다. 또한 기본 제공 기능만으로도 문서형식의 데이터들은 손하나 대지 않아도 크게 손색이 없는 복구 기능을 붙일 수 있습니다.

    (그림 4) 복구 가능한 문서가 존재할 때 사용자에게 노출되는 UI.

    자동저장된 파일이 존재해 복구가 가능한 경우, 프로그램은 재실행 되자 마자 (그림 4)와 같은 대화상자를 출력합니다.


    Outro
    실제로 리스타트 매니저를 사용하는 방법까지 정리하면 포스팅이 너무 길어질 것 같네요. 일단은 첫 포스팅이기도 하니 가볍게 기능 소개만으로 끝을 맺고, 다음 포스팅에서는 지극히 쉽고 심플한 리스타트 매니저 사용법에 대해서 정리해 보겠습니다. 얼른 적용해 보고 싶으신 분들은 아래에 있는 참고자료 링크들을 찾아가 보세요. 정말 너무너무 쉽습니다!
    그리고 리스타트 매니저는 비스타 이후의 OS에서 지원가능합니다. (비스타, 윈도우7, 윈도우 서버 2008) 그럼 리스타트 매니저를 사용하는 MFC 프로그램이 xp에서 크래시가 나면 어떻게 될까요? 자동 저장 기능은 그냥 disable만 되고 마는 것일까요? 이런 부분들도 차곡차곡 정리해서 포스팅 하도록 하겠습니다.
    글 내용 중 잘못된 부분이나 부족한 부분은 댓글로 의견 주세요. 참고 반영 하겠습니다.
    감사합니다 ^^*

    참고자료 링크 (reference)

    저작자 표시
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License


     

    티스토리 툴바