[퍼옴]MFC06

WindowsPrograming : 2007.03.11 09:22

6.파일입출력과 스레드

본장은 MFC에서 데이터를 저장하는 Document부분에 대해서 설명합니다. Document부분은 데이터를 파일에서 로드하고 또한 파일에 저장하는 부분이 주된 기능입니다. MFC에서 제공하는 Serialize부분과 직접 파일을 오픈하고 기록하는 부분에 대해서 설명합니다. 모듈이 혼자서 독립적으로 구동하는 방법 스레드에 대해서 설명을 합니다. 루프를 돌면서 연속적으로 데이터를 처리하는 기능은 스레드가 매우 편리합니다. 본장을 통해서 데이터를 처리하는데에 필요한 중요한  두가지를 배우시게 될것입니다.



1.Serialize 를 이용한 파일입출력

Serizlize를 이용한 파일 입출력이라는 의미는 CDocument의 맴버 함수인 Serialize 이용하여 데이터를 읽고 저장하는 방법을 의미합니다. Serialize 함수의 기능은 데이터를 바로 파일에 기록하고 파일에서 데이터를 읽어오는것을 자동적으로 처리하는 함수입니다. 예제를 통해서 이함수의 기능을 배워보겠습니다.


강력한 에디터 CRichEditView

도큐먼트를 파일에 저장하는 부분에 강력한 에디터를 소개하는것이 걸맞지 않다고 생각할지 모릅니다. 그러나 많은 양의 데이터를 저장한다고 가정을 할경우 에디터가 가장 편리하기 때문에 파일입출력 항목에 RichEdit에 대한 설명을 포함시켰습니다. 본장에서 만들 예제는 MExSer입니다. 이 프로젝트를 만들때 이제는 SDI를 이용합니다. Multiple Document Interface형태로 프로젝트를 만든다는 것입니다. 이프로젝트를 만들때 Step6에서 MExSerView를 CRichEditView를 Base class로 설정합니다. 그림 1은 MExSer 프로젝트를 만들때 AppWizard의 Step6의 화면입니다.

               (그림 1)MExSer 프로젝트 제작시 Step6


위와 같이 만든후에 컴파일하고 실행하면 View자체가 하나의 에디터로 구동되는 것을 알수 있을것입니다.

또한 파일저장및 오픈기능이 자동적으로 처리됩니다. 즉 아무런 프로그래밍 코딩없이 간단하게 에디터를 만들게 된것입니다. 또한 OLE 기능까지 완벽하게 제공되기 때문에 객체 삽입을 통해서 원하는 데이터를 완벽하게 이용할수 있습니다. 그림 2는 아무런 코딩없이 간단하게 처리된 MExSer의 실행모습입니다.

               (그림 2) MExSer 실행결과


CRichEditView는 CRichEditCtrl 컨트롤을 포함하고 있는 View입니다. RichEdit에대해서 좀더 자세히 알고 싶은 분은 도움말 샘플프로그램으로 제공하는 WordPad를 참조하시기 바랍니다. 우리가 본예제에서 주목하는 점은 코딩 한줄 없이 어떻게파일에 자동적으로 저장하고 다시 읽어 오는가입니다.  이부분에 대한 해답이 CMExSerDoc의 Serialize 함수에 있습니다. CMExSerDoc의 Serialize함수는 다음과 같습니다.


void CMExSerDoc::Serialize(CArchive& ar)

{

       if (ar.IsStoring())

       {

               // TODO: add storing code here

       }

       else

       {

               // TODO: add loading code here

       }


       // Calling the base class CRichEditDoc enables serialization

       //  of the container document's COleClientItem objects.

       CRichEditDoc::Serialize(ar);

}


CArchive ar이라는 함수는 현재 도큐먼트와 연결되어 있는 파일와 연결된 파이프라고 생각하시면 됩니다. 이파이프가 인자로 넘어오는 Serialize함수는 File메뉴에서 Open이나 Save 또는 Save as 를 기능을 사용할때 가동됩니다. File메뉴에서 Open항목을 선택해서 파일 대화상자를 구동시키고 특정 파일명을 입력한다음 OK버튼을 클릭하면 현재 데이터를 보관하는 Document의 OnOpenDocument함수가 실행됩니다. 이함수는 CDocument의 맴버함수로 되어 있기 때문에 소스에 기록되어 있지 않아도 바로 호출이 되는 것입니다. MExSer에서 File메뉴에서 Open 항목을 선택하면 MExSerDoc의 맴버함수인 OnOpenDocument가 실행됩니다. 이하수가 실행되면서 CDocument의 내부에서는 CFile크래스의 맴버변수를 이용하여 파일을 열고 이파일에 데이터를 넣을수 있는 파이프를 만듭니다. 이파이프가 바로 CArchive 입니다. 이클래스의 변수 ar를 Document의 Serialize의 인자로 넘겨주면서 Serialize가 실행되는 것입니다. 이 CArchive의 맴버함수중에 IsStoring() 함수를 실행시키면 현재 파일에 저장하고자 하는 의미로 CArchive를 만들었다면 TRUE로 그렇지 않다면 FALSE를 리턴합니다. 위의 Serialize 함수에서 ar.IsStoring() 이용하여 두개를 분리한것은 ar.IsStoring() TRUE이면 저장으로 그렇지 않으면 읽기로 구동하기 위해서입니다. 위에서는 Open항목만 설명하였으나 Save,Save as일때도 똑같이 ar를 만들어서 인자로 넘겨주면서 Serialize가 실행되기 때문입니다.

에를 들어어 도큐먼트에 다음과 같은 데이터가 있다고 합시다.

int number=365;

char *data='test:

위의 Serialize에서 위의 두개의 데이터를 파일에 넣고 또는 파일에서 읽어오자고 할경우에는 다음과 같이 할수가 있습니다.


void CMExSerDoc::Serialize(CArchive& ar)

{

       if (ar.IsStoring())

       {

         ar << number;//파일에 number값을 기록

         ar << data;//파일에 data를 기록

       }

       else

       {

         ar >> number;//파일에서 number를 읽는다.

         ar >> data;//파일에서 data를 읽는다.

       }

       :

}


<<와 >>의 기호로 아주 간단하게 파일에 데이터를 기록할수 있는 것입니다. 프로그래밍 할때 가장 어렵거나 귀찮은 부분이라면 파일입출력입니다. 그런데 MFC에서 도큐먼트의 Serialize함수를 이용한다면 이런 부분이 말끔하게 해결이 되는 것입니다.

이제 Serialize와 ar의 기능을 어느정도는 이해를 했습니다. 그런데 저장하고 읽는 부분은 공백으로 되어있습니다. 그럼 에디터에서 입력한 데이터는 도대체 어디에서 저장하는함수가 호출될까요? 위의 Serialize의 마지막줄에 있는 다음과 같은 내용이 해답입니다.


       CRichEditDoc::Serialize(ar);

즉 파일과 연결된 파이프라인 클래스 CArchive형의 변수 ar를 CRichEditDoc클래스의 Serialize로 넘겨주어서 이파이프라인에 CRichEditView에서 입력한 데이터를 저장하게 되는 것입니다. CArchive형을 받아서 파일에 데이터를 기록하는 몇개의 클래스들이 있습니다. 이런 클래스들은 도큐먼트의 Serialize에 넘어온 인자 ar만 바로 넘겨주면 되는 것입니다. CRichEditView는 CRichEditDoc와 쌍을 이룹니다. 이렇게 쌍을 이루기때문에 MExSerDoc는 CDocument를 상속받은것이 아니라 CRichEditDoc를 상속받았습니다. 따라서 base 클래스인 CRichEditDoc의 Serialize를 호출함으로써 에디터에 입력한 데이터를 손쉽게 파일로 저장하는 것입니다.

본예제에서 우리는 한가지는 알고 넘어가야 합니다. 즉 데이터를 저장하고 읽는 것을 담당하는 것이 바로 Serialize 라는 사실과 이 함수에 인자로 넘어온 ar에 <<나 또는 >>을 이용하여 데이터를 기록할수 있다는 사실입니다.



CEdit 클래스를 포함한 프로젝트 만들기

CRichEditView를 이용하면 자동적으로 파일에 기록이 됨으로 더이상 할것은 없습니다. CRichEditView보다는 기능이 강력하지 않지만 에디터를 알수 있는 CEdit를 직접 화면에 도시하고 그리고 이곳에 입력한 데이터를 Serialize를 이용하여 저장해 보겠습니다. 본예제는 MExSerial 로 새로 만들었습니다. MExSerial를 제작할때 프로젝트는 MDI입니다. MExSer를 만들때와 같은 방법으로 만듭니다. CMExSerialDoc의 헤더 즉 MExSerialDoc.h에 다음과 같이 CEdit의 변수를 설정합니다.


CEdit m_Edit;


위와 같이설정한 에디터 클래스를 View에 설정해야 합니다. View에 설정하는 방법은 먼저 WM_CREATE메세지가 왔을때 m_Edit윈도우를 만든다음 WM_SIZE일때 View크기에 삽입되도록 에디터윈도우를 View에 설정합니다. 먼저 WM_CREATE메지에 의해 수행되는 함수 OnCreate 함수를 만들고 다음과 같이 수정합니다.


int CMExSerialView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

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

               return -1;

       

       // TODO: Add your specialized creation code here

       CMExSerialDoc* pDoc = GetDocument();

       CRect rect(0,0,0,0);

       pDoc->m_Edit.Create(ES_AUTOVSCROLL | ES_MULTILINE |

                               ES_WANTRETURN | WS_VISIBLE

                               |WS_CHILD|WS_VSCROLL,rect,this,1);

              

       return 0;

}


Creat함수를 실행할때 크기를 0,0,0,0으로 하였기 때문에 OnCreate함수가 실행되는 당시에는 CEdit윈도우가 화면에 나오지않습니다. 크기가 없기 때문입니다. Create의 첫번째 인자 는 윈도우 스타일을 설정하는 것으로써 ES_계열과 WS_계열을 함께 사용하였습니다. ES_ 계열의 스타일 플러그는 에디터 박스에서 사용할수 있는 플러그입니다. 위의 스타일 플러그 값을 그대로 해석하자면 자동적으로 수직 스크롤이 가능하면서 여러라인을 입력할수 있고 Enter키값을 받을수 있으며 화면에 보일수 있고 수직 스크롤바가 붙어 있는 형태로 설정하자는 의미입니다. Create의 마지막 인자 1은 본 차일드 윈도우의 ID입니다. 이 ID는 여러분이 원하는데로 기입하셔도 됩니다. 보통 WM_COMMAND메시지가 발생될때 wParam의 LOWORD에 차일드 윈도우 ID가 설정됩니다. 예를 들어서 에디터에 설정되어 있는 데이터가 변경될때 EN_CHANGE 메세지가 발생되는데 이메세지는 wParam 의 HIWORD에 설정됩니다. 만일 에디터가 여러개가 있다고 한다면 어느 에디터에서 EN_CHANGE가 발생되었는 지 알수가 없습니다. 그렇기 때문에 LOWORD에 ID를 설정하여서 어느 윈도우에서 이메시지가 발생되었는지를 알수 있는 것입니다. 이 ID를 Create함수를 실행히킬때 마지막 인자에 설정하는 것입니다. 보통 리소스에서 에디터를 만들경우에는 속성대화상자에서 설정해주는 것과 같은 것입니다.

위와 같이하여 윈도우를 만들었다면 이 윈도우를 View와 크기가 같계 설정을 해주어야 합니다. 그러나 View자체는 항상 변할수 있습니다. 사용자가 마우스를 이용하여 윈도우 크기를 변경한다든지 또는 확대버튼을 클릭하여 전체화면으로 표시한다든지 할경우 윈도우 크기가 변하고 View의 크기가 변할경우 이것에 따라서 에디터크기도 변해야 합니다. View의 크기가 변할때 발생되는 메시지가 WM_SIZE입니다. 이 메시지가 발생될때 수행되는 함수를 만든다음에 이함수에서 에디터크기를 변경시키면 됩니다. 클래스위저드로 View클래스에서 크기가 변활때 수행되는 함수를 만들면 OnSize 함수가 만들어 집니다. 이함수에서 에디터크기를 변경하고자 한다면 다음과 같이 할수 있습니다.


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

{

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

       

       // TODO: Add your message handler code here

       CMExSerialDoc* pDoc = GetDocument();

       CRect rect;

       GetClientRect(rect);

       pDoc->m_Edit.SetWindowPos(&wndTop,0,0,rect.right-rect.left,

                               rect.bottom-rect.top,SWP_SHOWWINDOW);

}


먼저 도큐먼트에 설정한 에디터 윈도우 클래스를 받은다음 현재 크기가 변한 View의 영역을 rect로 받습니다. 그리고 SetWindowPos를 이용하여 이크기에 맞겠금 윈도우의 크기를 변경시킨것입니다. 현재윈도우의 최상단에 위치한 윈도우에 설정한다는 의미이며  그다음은 윈도우 출력좌표이고 SWP_SHOWWINDOW는 윈도우를 화면에 출력하자는 의미입니다.

위와 같이하고 컴파일 하고 실행하면 View윈도우 자체가 에디터 윈도우처럼 되어서 출력되고 문자열을 수정할수 있습니다. 본에디터는 수평으로는 스크롤이 되지 않고 수직으로만 스크롤이 되는 형태입니다. 만일 수평으로 스크롤이 되도록 하고자 한다면 윈도우 스타일을 ES_AUTOHSCROLL 를 추가적으로 설정해 주면 됩니다.


                  (그림 3)MExSerial 실행 모습


도큐먼트 데이터 수정 플러그

그림3처럼 MExSerial 프로그램이 실행되고 에디터에 문자를 입력하였다가 저장을 하지 않고 종료할경우 사용자에게 어떤 경고 메시지를 알려주지 않는다면 데이터를 잃어릴수도 있습니다. 보통 에디터나 워드 프로세서에서는 저장하지 않을경우 “현재 수정되었는데 저장되지 않았습니다” 라는 비슷한 내용의 메세지를 출력하고 저장하겠다는 버튼을 클릭하면 저장시켜 주는 기능이 있습니다. AppWizard로 프로젝트를 만들었을경우에는 이런 기능이 단한개의 함수로 가능합니다. 이함수는 CDocument의 맴버함수인 SetModifiedFlag 입니다. 예를 들어서 현재 데이터가 수정되었다면 다음과 같이 할수 있습니다.


CMExSerialDoc* pDoc = GetDocument();

pDoc->SetModifiedFlag(TRUE);


위와 같이 하면 저장을 하지 않고 종료하였을경우 그림 4와 같은 화면이 출려됩니다.

       (그림 4) 저장하지 않고 종료 하였을대 메시지


그림4와 같은 화면이 설정되었을대 “예” 버튼을 클릭하게 되면 바로 OnSaveDocument함수가 실행되고 이함수는 Serialize함수를 호출시켜서 파일에 기록한 데이터를 저장하게 됩니다.

이제 본 예제 MExSerial에서 SetModifiedFlag함수를 설정시키겠습니다. 데이터가 수정되었다는 의미는 바로 에디터 박스에서 글자 한개라도 입력하였다는 의미입니다. 에디터 박스에서 글자 한개라도 입력하거나 수정되었다는 EN_CHANGE라는 이벤트 메시지가 wParam의 HIWORD로 들어 온다고 설명하였습니다. 또한 LOWORD에는 윈도우 ID가 설정된다고 설명하였습니다. 이두개의 값을 이용하여 도큐먼트가 현재 수정된 상태라는 것을 설정하는 함수는 다음과 같습니다.


BOOL CMExSerialView::OnCommand(WPARAM wParam, LPARAM lParam)

{

       // TODO: Add your specialized code here and/or call the base class

       CMExSerialDoc* pDoc = GetDocument();

       switch(LOWORD(wParam))

       {

       case 1:

       switch(HIWORD(wParam))

       {

       case EN_CHANGE:

                       pDoc->SetModifiedFlag(TRUE);

       }

       break;

       }

       return CView::OnCommand(wParam, lParam);

}


클래스위저드를 이용하여 OnCommand항목을 클릭하여 OnCommand함수를 만들면 인자로는 wParam과 lParam이 들어 오게됩니다. 이상태에서 SetModifiedFlag 함수가 설정되어있는 도큐먼트의 포인터를 얻습니다. 첫번째 줄이 바로 현재 View와 연결되어 있는 도큐먼트를 얻고자 하는 것입니다. 여기에서 LOWORD(wParam) 을 이용하여 차일드 윈도우 아이디를 얻습니다. 우리가 에디터박스 아이디를 1로 설정하였으므로 case 1에 해당이 됩니다. 여기에서 HIWORD(wParam)에는 이벤트 메시지가 설정됨으로 이 이벤트 메시지가 EN_CHANGE일때 현재 도큐먼트가 수정된 상태라는 함수를 실행시키는 것입니다.

위와 같은 방법 외에도 전장에서 설명한 ON_EN_ 계열의 메시지를 메시지 맵핑에 추가하여 사용하여도 됩니다. 위와 같은 내용을 OnCommand함수를 사용하지않고  매시지 맵핑으로 이용한다고 하면 다음과 같이 메시지 맵핑을 해주어야 합니다.


ON_EN_CHANGE(1,OnEnChange)


위와 같이 한다음 afx 앞에 쓴 OnEnChange함수를 헤더에 등록하고 OnEnChange함수를 만든다음에 이함수에서 SetModifiedFlag 함수를 사용해도됩니다.

둘중에 어느것이 고급이며 어느것이 하급이라는 원리는 절대 없습니다.  어떻게 보면  ON_EN_CHANGE 함수가 더 깨끗해 보일지는 모르나 내부를 알수 있는 것은 OnCommand가 더 좋을 것입니다.

전장에서는 ON_ 메시지 매핑을 이용하여 이벤트 메시지를 얻었기 때문에 이번장에서는 좀더 다른 방법을 제안한 것입니다.

위와 같이 한후 컴파일하고 에디터 박스에서 데이터를 수정하고 저장없이 종료하면 그림 4와 같은 메시지가 출력될것입니다.

지금까지 설명한 내용을 직접 코딩을 해보시기 바랍니다.



데이터 저장

데이터 저장하고 읽는 부분은 Serialize 함수에 의해서 구동된다고 설명하였습니다.이 Serialize함수를 이용하여 에디터에 있는 데이터를 저장하는 내용을 기록한 소스는 다음과 같습니다.


void CMExSerialDoc::Serialize(CArchive& ar)

{

       char *buff;

       int size;

       if (ar.IsStoring())

       {

               // TODO: add storing code here

               //에디터 박스에 설정된 데이터의 길이를 얻는다.

               size=m_Edit.GetWindowTextLength();

               //데이터를 파일에 기록한다.

               ar << size;

               //데이터의 길이만큼 버퍼를 만든다음

               buff=new char[size+2];

               //버퍼을 0으로 초기화 한다음에

               memset(buff,0,size+2);

               //데이터를 얻어놓고

               m_Edit.GetWindowText(buff,size);

               //파일에 넣는다.

               for(int i=0;i<size+1;i++)

                       ar << buff[i];

               //버퍼를 지운다.

               delete buff;

               //수정상태 해제

               SetModifiedFlag(FALSE);        

       }

       else

       {

               // TODO: add loading code here

               //파일에 서  size값을 얻는다.

               ar >> size;

               //메모리에 데이터를 설정

               buff=new char[size+2];

               memset(buff,0,size+2);

               //파일에서 size크기 만큼 버퍼에 데이터를 읽는다.

               for(int i=0;i<size+1;i++)

                       ar >> buff[i];

               //에디터 박스에 등록

               m_Edit.SetWindowText(buff);

               delete buff;

               //수정상태 해제

               SetModifiedFlag(FALSE);        

       }

}

저장하는 부분에서 먼저 에디터박스의 GetWindowTextLength(); 함수를 이용하여 입력한 데이터의 전체 크기를 얻게 됩니다. 이값을 size에 받고 이것을 파일에 저장하였습니다. 그리고 에디터박스에 있는 데이터를 저장할 buff를 메모리에 할당한다음 이 buff에 에디터박스에 있는 데이터를 GetWindowText 함수를 이용하여 얻은다음 size+1만큼 (끝에 0x00코드가 들어가게 하기 위해서) 반복하면서 한문자씩 데이털를 파일에 넣었습니다. 그리고 SetModifiedFlag(FALSE)함수를 실행하여 수정상태를 해제 하였습니다. 위와 같이 하면 현재 입력된 데이터가 바로 파일에 저장되는 것입니다. 파일에서 읽을경우에는 먼저 size를 읽은 다음 buff에 크기만큼 메모리를 할당한다음 한문자씩 반복하여 파일에서 읽은 형태입니다.

위의 예를 좀더 단순하게 만들수 있습니다. 다음은 CString형을 이용하여 데이터를 저장하고 읽는 방법입니다.

void CMExSerialDoc::Serialize(CArchive& ar)

{

       CString data;

       if (ar.IsStoring())

       {

               m_Edit.GetWindowText(data);

               ar<<data;

               SetModifiedFlag(FALSE);        

       }

       else

       {

               ar>>data;

               m_Edit.SetWindowText(data);

               SetModifiedFlag(FALSE);        

       }

}

위의 방법은 간단하게 data라는 CString클래스를 설정한후에 GetWindowText함수를 이용하여 데이터를 얻은다음 << 를 이용하여 바로 파일에 저장하고 또한 읽을 경우에는 >>함수를 이용해서 바로 data로 로드한다음 이것을 SetWindowText함수를 이용한것입니다. 눈으로 볼때는 두번째 방법이 매우 간결하고 편리합니다. 그러나 필자의 생각은 그렇습니다. 두번째 방법은 ar이라는 파일과 연결된 파이프안에 도대체 데이터가 어떻게 기록되는 가의 기본을 알수가 없습니다. 즉 간단하지만  포장된 클래스를 사용하기 때문에 내부를 알수 없는 형태이며 첫번째 방법은 조금 복잡하지만 내부가 어떻게 돌아간다는 것을 알수 있는 형태입니다.(이방법을 무식한 방법이라고 통상이야기 합니다)

일반적인 Release프로그램 (즉 상업용등으로 완결하는 프로그램) 에서는 두번째 방법이 좋을것입니다. 그러나 배우는 입장에서는 두번째 방법보다는 첫번째 방법이 좀더 실력을 향상시키는데는 좋을 것입니다. 첫번째 방법을 보면서 다소 불만이 것들이 느껴질것입니다. “차라니 buff와size를 함께 구조체로 만들면?” “데이터를 저장하는 또하나의 클래스를 만들어볼까?” 등등으로 말입니다. 여기에서 프로그래밍 실력이 발전할수 있습니다. 두번째 방법을 보고는 어떤 불만이 나타나는지요? 사실 그냥 깨끗하다고만 이야기 할수 있을겁니다.

위와같이 Serialize 함수를 수정한후에 컴파일하고 실행시킨후에 파일에 데이터를 저장하고 읽기를 해보시기 바랍니다.

그림 5는 MExSerial 에서 test.txt를 로드한 형태입니다.

         (그림5)MExSerial 에서 test.txt를 로드한 화면



2.표준 입출력을 이용한 파일 입출력

이번항목에서는 Serialize 이용하지 않고 표준 파일 입출력함수를 이용하여 파일을 입출력해보겠습니다. 표준 파일 입출력함수중에 _open이나 _write또는 _lseek등을 이용하는 방법을 말합니다. 프로그래밍을 할경우 블럭화된 데이터를 저장하거나 이것을 로드할경우 Serialize함수를 이용하는 것은 다소 부적당합니다. 이럴경우에는 가장 하부의 함수 즉 _open이나 _write 함수들을 이용하는 것이 편리합니다. 이함들을 이용하여 간단하게 일기를 기록하는 예제를 작성해 보겠습니다.


일기 기록 프로그램 MExDiary

MExDiary는 MDI용으로 제작한 일기를 쓰는 간단한 프로그램입니다. 본프로그램은 프로젝트를 만들경우 MDI용으로 만들고 Step5에서 MExDiaryView를 FormView로 설정합니다.

MExDiaryView와 연결되는 대화상자 자원 IDD_MEXDIARY_FORM 는 그림 6과 같습니다.

IDD_MEXDIARY_FORM 각 아이디와 연결된 View의 맴버는 표1과 같습니다.


  ID

View의 멤버

  내용

IDC_DATE

CString m_strDate

  날짜 입력

IDC_CONTENT

CString m_strContent

  내용 입력

IDC_SUBJECT

CString m_strSubject

  제목 입력

    (표1) IDD_MEXDIARY_FORM ID와 View의 멤버와 연결된 리스트



       (그림 6)MExDiaryView와 연결된 IDD_MEXDIARY_FORM


날짜와 제목을과 내용을 입력하고 이 입력된 데이터가 바로 파일에 저장하도록 할려고 한다면 현재 View에 있는 데이터를 복사하여서 저장할 도큐먼트의 맴버 변수들이 필요합니다. MExDiaryDoc.h 즉 CMExDiaryDoc는 다음과 같이 View에서 입력된 데이터를 저장할 변수를 만들었습니다.


        char m_strContent[MAXCONTENTSIZE];//내용을 저장하는 변수

        char m_strDate[MAXDATESIZE];//날짜를 저장하는 변수

        char m_strSubject[MAXSUBJECTSIZE];//제목을 저장하는 변수


위의 3개의 변수에 설정된 MAX계열의 값은 MExDiaryDoc.h에 다음과 같이 설정하였습니다.


#define MAXCONTENTSIZE 1024

#define MAXSUBJECTSIZE 256

#define MAXDATESIZE 80


맴버 변수가 있다면 이 변수에 데이터를 설정하는 Set계열 함수와 설정된 데이터를 얻는 Get계열 함수를 만든다는것은 C++의 기본입니다. 따라서 위의 3개의 변수에서 데이터를 읽고 데이터를 쓰는 함수를 MExDiaryDoc.cpp에 ( CMExDiaryDoc클래스의 맴버변수로) 다음과 같이 기록합니다.


//제목 설정하는 함수

void CMExDiaryDoc::SetSubject(char *subject)

{

       strcpy(m_strSubject,subject);

}

//내용 설정하는 함수

void CMExDiaryDoc::SetContent(char *content)

{

       strcpy(m_strContent,content);

}

//날짜 설정하는 함수

void CMExDiaryDoc::SetDate(char *date)

{

       strcpy(m_strDate,date);

}

//날짜를 얻는 함수

char* CMExDiaryDoc::GetDate()

{

       return m_strDate;

}

//제목을 얻는 함수

char * CMExDiaryDoc::GetSubject()

{

       return m_strSubject;

}

//내용을 얻는 함수

char * CMExDiaryDoc::GetContent()

{

       return m_strContent;

}


만일 도큐먼트에 저장된 내용을 얻고 싶다면 GetContent함수를 이용하면 되고 도큐먼트이 m_strContent에 데이터를 넣고 싶다면 SetContent 함수에 넣고자 하는 문자열을 설정해 주면 됩니다. View에서는 항목에 문자열이 입력될때마다 이값을 도큐먼트에 입력하면 됩니다. 제목,날짜,내용 3개의 항목에서 EN_CHANGE메세지가 발생될경우 바로 각항목마다 수정이 되어 있다는 의미입니다. 이때 수정된 데이터를 바로 도큐먼트에 설정해 주면 됩니다. 클래스 위저드를 이용하여 IDC_CONTENT,IDC_SUBJECT,IDC_DATE 3개의 항목에서 EN_CHANGE 메세지가 발생될때 수행되는 함수를 다음과 같이 만든후에 다음과 같이 수정합니다.


//입력된 내용이 변경되었을때

void CMExDiaryView::OnChangeContent()

{

       UpdateData(TRUE);

       CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

       pDoc->SetContent((LPSTR)(LPCSTR)m_strContent);

       

}

//입력된 날짜가 변경되었을때

void CMExDiaryView::OnChangeDate()

{

       UpdateData(TRUE);

       CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

       pDoc->SetDate((LPSTR)(LPCSTR)m_strDate);

       

}

//입력된 제목이 변경되었을때

void CMExDiaryView::OnChangeSubject()

{

       UpdateData(TRUE);

       CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

       pDoc->SetSubject((LPSTR)(LPCSTR)m_strSubject);

       

}


위와 같이하면 View에서 변경된 데이터가 그대로 도큐먼트에 저장됩니다.


이제 도큐먼트에 있는 내용을 View화면으로 옴기는 내용을 만들겠습니다. 도큐먼트에 있는 내용을 View로 옮길때는 새로운 윈도우가 만들어질경우일것입니다. 즉 File Open이나 File New항목일경우 이에 해당하는 데이터를 Document에서 로드함과 동시에 View를 만들기 때문입니다. FormView에서 자원을 초기화할대는 OnInitialUpdate()  라고 하였습니다. 이함수를 만든다음에 다음과 같이 수정을 하면 도큐먼트에 있는 데이터가 바로 View로 전송이 됩니다.


void CMExDiaryView::OnInitialUpdate()

{

       CFormView::OnInitialUpdate();   

       // TODO: Add your specialized code here and/or call the base class

       CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

       m_strSubject=pDoc->GetSubject();

       m_strDate=pDoc->GetDate();

       m_strContent=pDoc->GetContent();

       UpdateData(FALSE);

}

OnInitialUpdate함수는 먼저 도큐먼트를 받은 다음 이 도큐먼트의 각각의 맴버변수 m_strSubject,m_strDate,m_strContent를 Get계열 함수를 이용하여 바로 View의 변수에 저장하였으며 UpdateData라는 함수를 이용하여 자원에 전송하였습니다.

만일 Open항목을 통해서 파일에 저장된 데이터가 도큐먼트에 로드되면 이 로드된 데이터가 View항목의 맴버로 연결되고 이것이 다시 자원으로 연결되기 때문에 파일에 있는 데이터가 화면에 보이는 것입니다.


OnOpenDocument OnSaveDocument

File 항목에서 Open 항목을 통해서 새로운 데이터를 로드하면 OnOpenDocument가 실행되고 이함수는 다시 Serialize함수를 호출한다고 설명하였습니다. 클래스 위저드를 이용하여 MExDiaryDoc에서 OnOpenDocument를 선택하여 함수를 만들면 다음과 같이 설정될것입니다.


BOOL CMExDiaryDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

       if (!CDocument::OnOpenDocument(lpszPathName))

               return FALSE;

}

OnOpenDocument는 다시 상위 클래스인 CDocument의 OnOpenDocument를 호출하고 이호출된 OnOpenDocument는 CFile클래스를 이용하여 파일을 열고 이파일에 데이터를 기록 또는 읽는 전송라인(파이프라인이라고 했습니다) CArchive 를 만든다음 이 클래스의 변수 ar를 넘겨주는 Serialize 를 실행한다고 하였습니다.

위의 OnOpenDocument 함수에서 Serialize함수를 실행하지 않고 자신만의 파일 기록함수를 실행하고자 한다면 위의 if 문의 모든 내용을 삭제하면 됩니다. 이렇게 되면 Serialize함수가 실행되지 않습니다. 다음 내용은 Serialize함수를 실행시키지 않고 _open함수와 _read함수를 이용하여 입력된 데이터를 파일에서 읽는 OnOpenDocument함수입니다.


BOOL CMExDiaryDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

       int fd;

       fd=_open(lpszPathName,_O_RDWR|_O_BINARY);

       _read(fd,m_strDate,sizeof(m_strDate));

       _read(fd,m_strSubject,sizeof(m_strSubject));

       _read(fd,m_strContent,sizeof(m_strContent));

       _close(fd);             

       return TRUE;

}

OnOpenDocument함수는 File항목에서 설정한 파일의 전체 경로이름이 lpszPathName로 넘어옵니다. 이 넘어온 파일명으로 파일을  오픈합니다.그리고

_read함수를 이용하여 m_strDate,m_strSubject,m_strContent를 차례로 읽는 것입니다.


클래스 위저드를 이용하여  OnSaveDocument를 만들계 되면 다음과 같은 함수가 만들어 집니다.


BOOL CMExDiaryDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

       // TODO: Add your specialized code here and/or call the base class

       return CDocument::OnSaveDocument(lpszPathName);

}


OnSaveDocument또한 상위 클래스인 CDocument의 OnSaveDocument를 호출하기 때문에 Serialize 함수가 바로 연결됩니다. 따라서 return 문장을 모두 제거해야만 Serialize 함수가 호출되지 않습니다.

위의 OnOpenDocument와 같은 형식으로 파일에 데이터를 저장하는 OnSaveDocument 는 다음과 같습니다.


BOOL CMExDiaryDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

       // TODO: Add your specialized code here and/or call the base class

       int fd;

       fd=_open(lpszPathName,_O_CREAT|_O_RDWR|_O_BINARY);

       _write(fd,m_strDate,sizeof(m_strDate));

       _write(fd,m_strSubject,sizeof(m_strSubject));

       _write(fd,m_strContent,sizeof(m_strContent));

       _close(fd);     

       return TRUE;

}


이와 같이 한후에 프로그램을 컴파일하고 실행한후에 데이터를 입력하고 저장하는 것을 실행해 보시기 바랍니다. 그림 7은 MExDiary의 실행결과 입니다.

               (그림 7) MExDiary 실행 결과


MExDiary의 버그와 개선점

MExDiary의 프로그램을 컴파일하고 실행하면 처음에 출력될때 날짜와 제목 내용이 이상한 글자로 가득채워져서 출력이 될것입니다. 이것은 각 맴버의 변수들을 초기화 해주지 않았기 때문입니다. 도큐먼트의 생성자에 각맴버의 변수들을 초기화해보세요. 그러면 화면에 깨끗해 질것입니다. 초기화 방법은 매우 간단하빈다. 즉 문자열을 모두 0으로 하거나 또는 “”을 복사하면 됩니다. 이것은 여러분이 직접 해보시기 바랍니다. 또한 문제가 있습니다. 입력하는 내용이 1024 가 최대치 이기 때문에 1024가 넘게 되면 프로그램의 오류가 생깁니다. 즉 데이터의 길이가 입력한 데이터의 크기에 맞게 메모리 할당이 되어야 하고 그크기에 맞는 형태로 파일에 저장되어야 합니다. 이방법은 여러분에게 숙제로 넘기겠습니다. MExDiary를 한번 고쳐 봐주시기 바랍니다.

본장에서 필자가 여러분에게 알려주고 싶은것은 File입출력이 도큐먼트에서 어떻게 이루어지는가 입니다. 자동으로 이루어질때는 Serialize 함수를 수정하고 Serialize 기능을 쓰지 않을려면 OnOpenDocument와 OnSaveDocument를 이용하는 방법을 설명한것입니다. 숙제로 넘긴 부분을 풀지 못하더라도 이 두가지 방법에 대해서는 이해하고 넘어가시기를 바랍니다.


3. 스레드

윈도우 차체가 하나의 스레드입니다. 워드프로세서 프로그램을 윈도우로 띄어 놓고 한쪽에는 Avi출력기를 띄어 놓고 또한쪽에는 채팅 프로그램을 띄어 놓았다고 가정을 합시다. 워드프로세서는 키보드 입력에 따라서 독립적으로 돌아가고 있으며 Avi출력기는 영화를 계속적으로 화면에 출력하고 통신프로그램은 계속적으로 데이터를 받아서 화면에 보여 주고 있습니다. 결국 수행되는 작업이 독립적으로 움직인다는 것입니다. 이렇게 수행되는 작업이 독립적으로 돌아갈때 하나의 독립 모듈을 스레드라고 합니다. 프로그램 제작도중에 굉장히 많은 시간을 소요하면서 작업을 하는 프로그램들이 있습니다. 만일 이프로그램을 그냥 한 함수에 기록하고 그것을 실행한다면 그작업이 종료되기 이전에는 아무런 작업도 하지 못할겁니다. 때로는 사용자로 하여금 매우 지루함을 느끼게 만듭니다. 이런 많은 시간을 소요하는 작업은 따로 독립적으로 구동하는것이 좋습니다. 이렇게 따로 구동할때는 스레드를 이용합니다. 예제를 만들어 보면서 스레드를 이용하는 방법을 알아보겠습니다.


스레드의 구동

MExThread 는 스레드를 위한 예제 프로그램입니다. 이프로그램을 SDI형태로 제작되면 View는 CView입니다. 즉 Step1에서 Single Document Interface를 선택하신후에 Finished 를 하여서 프로젝트를 만들면됩니다.

스레드를 구동하기 위해서는 구동할 스레드 함수를 만듭니다. 본항목에서 설명할 스레드는 윈도우 스레드가 아닌 작업스레드를 이야기 합니다. 단일 작업 스레드를 구동하고자 할경우에는 보통 다음과 같은 형으로 만듭니다.


   UINT threadfunc(LPVOID lparam)


즉 함수명과 인자가 lparam으로 설정된 하나의 프로시저 함수를 만드는것입니다. 인자를 LPVOID로 한것은 어떤 형이든지 넘겨질수 있다는 의미 때문입니다. 예를 들어서 ThreadProc 스레드 함수를 만들고자 한다면 다음과 같이 할수 있습니다.


UINT ThreadProc(LPVOID lParam)

{

    스레드의 내용 코딩

}

위와 같은 스레드를 동작시키고자 한다면 AfxBeginThread 함수를 이용하면 됩니다. AfxBeginThread 첫번째 인자로는 스레드 함수명이고 두번째 인자로는 lParam에 넘겨줄 데이터 포인터 입니다. 예를 들어서 CMExThreadView에서 ThreadProc를 구동하고자 한다면 다음과 같이 호출할수 있습니다.


AfxBeginThread(ThreadProc,this);


위와 같이하여서 스레드를 구동하면 lParam으로 넘겨지는 것은  현재 View의 CMExThreadView가 넘어가게 되는 것입니다.  이값을 스레드에서 받고자 한다면 다음과 같이 할수 잇을것입니다.

UINT ThreadProc(LPVOID lParam)

{

  CMExThreadView *pView=(CMExThreadView *)lParam;

    스레드의 내용 코딩

}


위와 같은 방법으로 lParam으로 넘어온 현재의 View포인터를 다시 pView가 형전환으로 받게 되는 것입니다. 위와 같이하면 CMexThreadView클래스이 맴버함수를 스레드에서 모두 이용할수 있는 것입니다.


보통 스레드는 루프를 돌면서 작업을 하는 경우가 대부분입니다. 루프를 돌지 않는 작업이라면 단순하게 위의 함수에 작업내용을 그대로 기록하면 됩니다. 스레드를 구종한다면 중요한것은 종료 싯점입니다. 즉 작업을 하는 도중에 작업을 멈추고자 할경우 스레드에서는 내부에서 동작을 멈추게 하기는 매우 힘듭니다. 따라서 전역변수를 하나 두고 이변수를 보면서 작업을 하는 방법이 이상적입니다.


int extflag;


UINT ThreadProc(LPVOID lParam)

{

   while(!extflag)

  {

    스레드의 내용 코딩

  }

  스레드 종료후 리턴

}


위와 같이 한다면 while루프를 돌면서 exflag를 계속적으로 체크합니다. 이값이 1이 되면 바로 루프가 종료 되고 스레드의 작업이 종료 되는 형태로 변하게 됩니다.

위와 같이 하였을경우 위의 스레드를 종료 하고자 한다면 간단하게 다음과 같이 함으로써 스레드를 종료 할수가 있습니다.


extflag=TRUE;


위와 같이 함으로써 스레드 자체는 extflag의 값을 계속 검사하기 때문에 1이되면 바로 루프를 종료하게 됩니다.



램덤한 박스 그리기


MExThread에서는 메뉴에서 스레드 시작이라는 항목을 클릭하면 화면에 램덤한 크기에 임의의 색으로 박스를 그리는 스레드가 구동됩니다. 이스레드가 구동되면서도 다른 모든 작업을 마음대로 할수 있습니다. 이렇게 하기 위한 스레드 함수는 다음과 같습니다.


BOOL exitFlag=TRUE;


UINT ThreadProc(LPVOID lParam)

{

     CBrush cBrush ,*oldBrush;

        CDC *pDC;

        RECT rect;

     int    xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;

        CMExThreadView *pView;

pView=(CMExThreadView *)lParam;

pView->GetClientRect(&rect);

pDC = pView->GetDC();

     while (TRUE)

          {

            //램덤한 박스 좌표 만들기

               xLeft   = rand () % rect.right ;

               xRight  = rand () % rect.right ;

               yTop    = rand () % rect.bottom ;

               yBottom = rand () % rect.bottom;

            //램덤한 색만들기

               iRed    = rand () & 255 ;

               iGreen  = rand () & 255 ;

               iBlue   = rand () & 255 ;

               //브러쉬만들기

               cBrush.CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;

               oldBrush=pDC->SelectObject (&cBrush) ;

               //박스 그리기

               pDC->Rectangle ( min (xLeft, xRight), min (yTop, yBottom),

                               max (xLeft, xRight), max (yTop, yBottom)) ;

                          pDC->SelectObject(oldBrush);

                          cBrush.DeleteObject();

                          if(exitFlag)

                                  break;

          }

     pView->ReleaseDC (pDC) ;

       return 0;

}


pView는 CMExThreadView 를 받고 이View의 DC를 pDC로 받은다음 View의 클라이언트 영역을 rect 로 받습니다. 이영역보다 작은 박스 영역을 rand()함수를 이용하여 임의의 영역을 만들고 R,G,B의 색을 램덤하게 만든다음 계속적으로 박스를 만드는 스레드입니다.

위의 스레드는 exitFlag가 1일경우에는 스레드가 종료 됩니다. 본함수위에 설정한 exitFlag는 TRUE 로 설정하였기 때문에 스레드를 구동하기 전에는 exitFlag를 FALSE로 설정해 주어야 합니다.

스레드 메뉴 설정

MExThread에서 스레드를 구동하는 항목과 스레드를 종료하는 항목을 설정합니다. 설정한 메뉴의 형태는 그림 8과 같습니다.

               (그림 8)MExThread 메뉴에 스레드 항목 추가


스레드 시작항목의 ID는 ID_THREADBEGIN 이며 스레드 끝의 ID는 ID_THREADEND 입니다.

이와 같이 만든후에 스레드 시작이라는 항목을 선택했을때 구동하는 함수를 View에 만듭니다.(절대 CMainFrame가 아닙니다) 즉 클래스 위저드를 이용하여 클래스이름이 CMExThreadView라는 의미입니다. 이렇게 하여 만든 함수는 다음과 같습니다.


void CMExThreadView::OnThreadbegin()

{

       // TODO: Add your command handler code here

       exitFlag=FALSE;

       AfxBeginThread(ThreadProc,(LPVOID)this);

}


exitFlag값을 0으로 넣어두고 스레드를 실행하면 while루프가 실행되기 때문에 계속적으로 화면에 박스가 램덤하게 보이게 됩니다. 메뉴에서 스레드종료 항목을 선택하였을대 구동하는 함수를 만들고 종료 루틴을 넣은 것은 다음과 같습니다.

//스레드 종료 항목 선택

void CMExThreadView::OnThreadend()

{

       // TODO: Add your command handler code here

       exitFlag=TRUE; 

}

단지 exitFlag값을 설정함으로써 스레드를 종료 할수 있도록 한것입니다.


현재 스레드가 가동중이라면 메뉴에서 스레드 시작이라는 항목을 비활성화 해야 합니다. 또한 스레드가 멈처 있다면 메뉴에서 스레드 종료라는 항목을 비활성화 해야 합니다. 두개의 항목을 활성 비활성으로 만들기 위한 UPDATE_COMMAND_UI이벤트 함수를 만들고 다음과 같이 수정을 합니다.


//현재 스레드가 실행되면 스레드 시작 항목은 비활성

//그외 스레드 시작 항목 활성

void CMExThreadView::OnUpdateThreadbegin(CCmdUI* pCmdUI)

{

       // TODO: Add your command update UI handler code here

       if(exitFlag)

               pCmdUI->Enable(TRUE);

       else

               pCmdUI->Enable(FALSE);

}

//현재 스레드가 실행되면 스레드 종료 항목은 활성

//그외 스레드 종료 항목 비활성

void CMExThreadView::OnUpdateThreadend(CCmdUI* pCmdUI)

{

       if(!exitFlag)

               pCmdUI->Enable(TRUE);

       else

               pCmdUI->Enable(FALSE);

       

}


exitFlag가 TRUE라는 의미는 현재 스레드가 멈추어져 있다는 의미이므로 이때는 스레드 시작 이라는 메뉴항목이 활성화 되어야 하며 exitFlag 가 FALSE 인것은 현재 스레드가 가동된다는 의미입니다. 따라서 exitFlag를 이용하여 두개의 메뉴를 활성 비활성화 할수 있는 것입니다.

위와 같이 한후에 프로그램을 컴파일하고 실행한후에 “스레드 시작”이라는 항목을 클릭하면 그림 9와 같은 결과가 출력됩니다.

                    (그림 9)MExThread 실행 결과


예제 소스

MExThread는 실제 코딩은 MExThreadView.cpp에서 만 이루어 졌습니다.

본소스를 확인하면서 스레드의 형태를 이해하시기 바랍니다.


(프로그램 소스)

// MExThreadView.cpp : implementation of the CMExThreadView class

//


#include "stdafx.h"

#include "MExThread.h"


#include "MExThreadDoc.h"

#include "MExThreadView.h"


#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif


/////////////////////////////////////////////////////////////////////////////

// CMExThreadView


IMPLEMENT_DYNCREATE(CMExThreadView, CView)


BEGIN_MESSAGE_MAP(CMExThreadView, CView)

        //{{AFX_MSG_MAP(CMExThreadView)

        ON_COMMAND(ID_THREADBEGIN, OnThreadbegin)

        ON_COMMAND(ID_THREADEND, OnThreadend)

        ON_UPDATE_COMMAND_UI(ID_THREADBEGIN, OnUpdateThreadbegin)

        ON_UPDATE_COMMAND_UI(ID_THREADEND, OnUpdateThreadend)

        //}}AFX_MSG_MAP

        // Standard printing commands

        ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

        ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

        ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////

// CMExThreadView construction/destruction

BOOL exitFlag=TRUE;


UINT ThreadProc(LPVOID lParam)

{

     CBrush cBrush ,*oldBrush;

        CDC *pDC;

        RECT rect;

     int    xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;

        CMExThreadView *pView;

        pView=(CMExThreadView *)lParam;

        pView->GetClientRect(&rect);

        pDC = pView->GetDC();

     while (TRUE)

          {

               xLeft   = rand () % rect.right ;

               xRight  = rand () % rect.right ;

               yTop    = rand () % rect.bottom ;

               yBottom = rand () % rect.bottom;

               iRed    = rand () & 255 ;

               iGreen  = rand () & 255 ;

               iBlue   = rand () & 255 ;


               cBrush.CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;

               oldBrush=pDC->SelectObject (&cBrush) ;

               pDC->Rectangle ( min (xLeft, xRight), min (yTop, yBottom),

                               max (xLeft, xRight), max (yTop, yBottom)) ;

                           pDC->SelectObject(oldBrush);

                           cBrush.DeleteObject();

                           if(exitFlag)

                                   break;

          }

     pView->ReleaseDC (pDC) ;

        return 0;

}



CMExThreadView::CMExThreadView()

{

        // TODO: add construction code here


}


CMExThreadView::~CMExThreadView()

{

}


BOOL CMExThreadView::PreCreateWindow(CREATESTRUCT& cs)

{

        // TODO: Modify the Window class or styles here by modifying

        //  the CREATESTRUCT cs


        return CView::PreCreateWindow(cs);

}


/////////////////////////////////////////////////////////////////////////////

// CMExThreadView drawing


void CMExThreadView::OnDraw(CDC* pDC)

{

        CMExThreadDoc* pDoc = GetDocument();

        ASSERT_VALID(pDoc);


        // TODO: add draw code for native data here

}


/////////////////////////////////////////////////////////////////////////////

// CMExThreadView printing


BOOL CMExThreadView::OnPreparePrinting(CPrintInfo* pInfo)

{

        // default preparation

        return DoPreparePrinting(pInfo);

}


void CMExThreadView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

        // TODO: add extra initialization before printing

}


void CMExThreadView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

        // TODO: add cleanup after printing

}


/////////////////////////////////////////////////////////////////////////////

// CMExThreadView diagnostics


#ifdef _DEBUG

void CMExThreadView::AssertValid() const

{

        CView::AssertValid();

}


void CMExThreadView::Dump(CDumpContext& dc) const

{

        CView::Dump(dc);

}


CMExThreadDoc* CMExThreadView::GetDocument() // non-debug version is inline

{

        ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMExThreadDoc)));

        return (CMExThreadDoc*)m_pDocument;

}

#endif //_DEBUG


/////////////////////////////////////////////////////////////////////////////

// CMExThreadView message handlers

//스레드 시작 항목 선택

void CMExThreadView::OnThreadbegin()

{

        // TODO: Add your command handler code here

        exitFlag=FALSE;

        AfxBeginThread(ThreadProc,(LPVOID)this);

}

//스레드 종료 항목 선택

void CMExThreadView::OnThreadend()

{

        // TODO: Add your command handler code here

        exitFlag=TRUE; 

}

//현재 스레드가 실행되면 스레드 시작 항목은 비활성

//그외 스레드 시작 항목 활성

void CMExThreadView::OnUpdateThreadbegin(CCmdUI* pCmdUI)

{

        // TODO: Add your command update UI handler code here

        if(exitFlag)

                pCmdUI->Enable(TRUE);

        else

                pCmdUI->Enable(FALSE);

}

//현재 스레드가 실행되면 스레드 종료 항목은 활성

//그외 스레드 종료 항목 비활성

void CMExThreadView::OnUpdateThreadend(CCmdUI* pCmdUI)

{

        if(!exitFlag)

                pCmdUI->Enable(TRUE);

        else

                pCmdUI->Enable(FALSE);

       

}

(프로그램 소스끝)

'WindowsPrograming' 카테고리의 다른 글

[퍼옴]MFC05  (0) 2007.03.11
[퍼옴]MFC06  (0) 2007.03.11
RichEditBox에서 글자색 관련 질문입니다..  (0) 2007.03.11
리치에디트 컨트롤 사용법  (2) 2007.03.11
Posted by Real_G