8. System.Object (2)

CLR 2010. 3. 3. 09:00 Posted by 알 수 없는 사용자
2. GetHashCode

객체 값에 대한 HashCode를 반환해주는 Method입니다. 근데 이 Method가 반환해 주는 Hashcode는 그닥 쓸모가 없다고 합니다. 각 객체마다 유일한 Hashcode를 보장해 주어야 하지만 그렇지 못하기 때문입니다.

암튼 디자이너는 어떠한 객체라도 HashTable에 담길 수 있다면 여러모로 편리할 것이라고 판단해서 모든 Object를 상속하는 객체는 이 Method를 통해 Int32형태의 hashcode를 얻을 수 있도록 설계했다고 합니다.

하지만 결국 이 Method로 유일한 hashcode를 제대로 반환해 줄 수 있도록 사용하기 위해서는 override를 통해 재작성하여 사용해야 합니다. 그래서 CLR via C# 에서는 Object의 Method로 정의되어 있을 것이 아니라 interface로 정의해 놓는게 맞는게 아닌가 라고 말합니다.

그런데 이 GetHashCode를 재정의해서 사용하기 위해서는 기억해야 할 점이 있습니다. 지난 번에 공부했던 Object의 Method인 Equals()를 재정의 한다면 이 GetHashCode()도 재정의를 해 주어야 한다는 점입니다. 만약 Equals()만 재정의 한다면 컴파일시 warning을 발생합니다.

------ Build started: Project: Test04, Configuration: Debug x86 ------
C:\Documents and Settings\XPMUser\my documents\visual studio 2010\Projects\TestSolution\Test04\Program.cs(8,11): warning CS0659: 'Test04.Animal' overrides Object.Equals(object o) but does not override Object.GetHashCode()

Animal이라는 Class에서 Equals()만 재정의 했더니 위와 같은 경고 메시지를 발생하는 것을 볼 수 있습니다. 이유는 두 객체가 같다면 (Equals()의 값이 true라면) 두 객체의 hashcode도 같을 것이라는 가정을 System.Collections.HashTable 타입과 System.Collections.Generic.Dictionary 타입이 하고 있기 때문입니다.
결국 객체의 동질성을 판단하는 Equals()의 알고리즘에 의해 두 객체가 같다고 판명이 난다면 GetHashCode()를 구하는 알고리즘도 Equals()을 판단하는 알고리즘과 연관되어 같은 값을 반환할 수 있도록 구현해 주어야 합니다.



GetHashCode()는 객체가 속한 AppDomain수준에서 유일할 수 있는 ID를 숫자로 반환하게 되어 있으며 이 값은 객체의 일생동안 바뀌지 않는 것이 보장되지만 이 객체가 가비지 수집기에 의해 수집되면 수집된 객체 hashcode를 의미하는 ID가 다른 새로운 객체에 재활용될 수 있다고 하네요. 그래서 유일한 값을 보장 못한다고 말하는데 그렇다면 AppDomain 수준에서 객체의 유일한 값을 보장해주는 HashCode를 반환해주는 Method가 CLR에 없을까요?

아니 있습니다. System.Runtime.ComplierServices 네임 스페이스에 있는 RuntimeHelpers 클래스의 정적 Method인 GetHashCode 메서드를 제공하고 있습니다. 객체가 Object의 GetHashCode 메서드를 재정의했다고 하더라도 해당 Object를 RuntimeHelpers.GetHashCode() 메소드의 인자로 제공하면 유일한 값을 보장받을 수 있는 ID를 제공해 줍니다.

아 그럼 Object의 GetHashCode()를 쓸게 아니라 저 RuntimeHelpers.GetHashCode()를 쓰면 되겠구나.

하면 되겠죠.


그런데 이 GethashCode()가 VS2010에 포함되어 있는 4.0X 버전의 mscorlib.dll 에서 구현하고 있는 것과 이전 버전에서 구현하고 있는 것이 좀 틀립니다.

우선 기존의 CLR의 mscorlib.dll에서 제공하고 있는 GetHashCode()의 내부를 살펴보도록 하겠습니다.


내부에서 Object.InternalGetHashCode()라는 Method를 호출하고 있네요. 저 함수를 들어가 보면


위와 같은 코드를 볼 수 있습니다. 함수 선언을 보면 internalcall이라는게 붙어 있는데 이것은 unmanaged코드가 불리는 것이라고 생각하면 됩니다. CLR의 관리되는 코드가 아니라 nativecode의 영역에서 구현되어 있다는 말이지요.

그래서 실제 GetHashCode는 다음과 같이 구현이 되어 있다고 합니다.

FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
    
    CONTRACTL
    {
        THROWS;
        DISABLED(GC_NOTRIGGER);
        INJECT_FAULT(FCThrow(kOutOfMemoryException););
        MODE_COOPERATIVE;
        SO_TOLERANT;
    }
    CONTRACTL_END;

    VALIDATEOBJECTREF(obj);
    
    DWORD idx = 0;
    
    if (obj == 0)
        return 0;
    
    OBJECTREF objRef(obj);

    HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);

        
    idx = GetHashCodeEx(OBJECTREFToObject(objRef));

    
    HELPER_METHOD_FRAME_END();

    return idx;
}
FCIMPLEND

INT32 ObjectNative::GetHashCodeEx(Object *objRef)
{
    CONTRACTL
    {
        MODE_COOPERATIVE;
        THROWS;
        GC_NOTRIGGER;
        SO_TOLERANT;
    }
    CONTRACTL_END

    VALIDATEOBJECTREF(objRef);

    DWORD iter = 0;
    while (true)
    {
        DWORD bits = objRef->GetHeader()->GetBits();

        if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
        {
            if (bits & BIT_SBLK_IS_HASHCODE)
            {
                return  bits & MASK_HASHCODE;
            }
            else
            {
                SyncBlock *psb = objRef->GetSyncBlock();
                DWORD hashCode = psb->GetHashCode();
                if (hashCode != 0)
                    return  hashCode;

                hashCode = Object::ComputeHashCode();

                return psb->SetHashCode(hashCode);
            }
        }
        else
        {
            if ((bits & (SBLK_MASK_LOCK_THREADID | 
(SBLK_MASK_APPDOMAININDEX << SBLK_APPDOMAIN_SHIFT))) != 0)
            {
                objRef->GetSyncBlock();
            }
            else
            {
                if (bits & BIT_SBLK_SPIN_LOCK)
                {
                    iter++;
                    if ((iter % 1024) != 0 && g_SystemInfo.dwNumberOfProcessors > 1)
                    {
                        YieldProcessor();
                    }
                    else
                    {
                        __SwitchToThread(0);
                    }
                    continue;
                }

                DWORD hashCode = Object::ComputeHashCode();

                DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | 
BIT_SBLK_IS_HASHCODE | hashCode;

                if (objRef->GetHeader()->SetBits(newBits, bits) == bits)
                    return hashCode;
            }
        }
    }
}


Hashcode 소스이지만 이 소스만 읽어서는 어떤 알고리즘으로 hashcode를 생성하는 건지 정확히 파악하기가 어렵군요. :)

암튼 이런식으로 GetHashCode()가 구현되어 있었습니다.

그런데 4.0 버전에서의 구현은 다음과 같습니다.



어라 어디서 많이 보던 함수를 호출 하고 있군요. 아까 앞에서 살펴본 AppDomain상에서 객체마다 유일한 ID를 제공하는 것을 보장해준다고 하는 System.Runtime.CompilerServices.RuntimeHelpers::GetHashCode(object)를 호출하고 있습니다!


System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode()는 역시 unmanaged code를 invoke하게 되어 있는 것 같습니다. 내부 구현 코드를 찾아볼 수 있으면 좋을텐데 찾기가 힘드네요;

결국 내부 코드 구현으로 봤을 때 .NET 4.0기반의 CLR에서는 Object.GethashCode()를 사용하는 것과 System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode()를 사용하는 건 동일합니다.



GetHashCode()를 재정의 할 때 고려해야 할 점을 생각해 볼까요.

1. 무엇보다 잘 분포된 난수를 생성할 수 있는 그러니까 같은 Application 환경에서 유일한 값을 보장할 수 있는 알고리즘을 사용해야 합니다.
2. 정의하는 알고리즘 상에서 상위 클래스의 GetHashCode() 를 호출해서 자신의 Hashcode에 값을 반영하는 경우가 있겠지만 기본적으로 제공하는 Object의 GetHashCode()는 가능하면 사용하지 말아야 합니다. 빠른 성능을 고려해서 만든 알고리즘이 아니기 때문입니다.
3. 알고리즘은 적어도 하나의 인스턴스 필드는 이용해야 합니다. 미리 정의되어 있는 정적 값만을 이용하면 안된다는 말이겠지요.
4. 사용되는 필드는 불변의 속성을 가져야 합니다. 즉 필드값은 한번 생성되면 그 객체가 소멸될 때까지는 절대 값이 바뀌어서는 안됩니다.
5. 객체가 같은 값을 지니고 있다면 HashCode도 같은 값을 반환해야 합니다.

그리고 이 GetHashCode()를 사용할 떄의 주의점도 있습니다. 절대로 이 메소드를 통해 연산한 결과를 저장하여 사용하지 말아야 한다는 점인데 예를 들어 설명하자면 어떤 사이트에서 회원 정보를 받을 때 암호를 GetHashCode()를 이용해서 만들어진 hashcode를 DB에 저장해선 안된다는 것입니다.

지금까지 알아본것 처럼 CLR이 업데이트 되면서 내부 구현이 바뀌게 된다거나, 사용자가 직접 재정의한 GetHashCode()의 알고리즘이 변경된다면 저 DB에 저장된 hashcode는 모두 쓰레기가 되어 버리게 될 테니까요.



[MS@클라우드컨퍼런스] MS 클라우드 기술과 플랫폼

Cloud 2010. 2. 24. 10:48 Posted by 알 수 없는 사용자


안녕하세요? 오랜만에 인사드립니다.
클라우드 컴퓨팅 관련해서 블로깅을 하고 있는 Ted 입니다.

이번 마이크로소프트@클라우드 컨퍼런스에서
"클라우드 애플리케이션 개발을 위해 알아야할 MS 기술과 플랫폼"
이라는 긴 제목의 세션 발표를 하게 됐습니다.

일전에 올린 PT자료와 중복되는 부분도 일부 있습니다.
앞으로 몇 차례에 걸쳐 발표 내용을 상세화한 글을 올리겠습니다.

[MFC] 태스크 대화상자(Task Dialog) - (3/3) : 활용하기

MFC 2010. 2. 22. 09:00 Posted by 알 수 없는 사용자

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