6.OLE 서버와 컨테이너

본장은 OLE의 마지막 부분입니다. 지금까지는 OLE프로그램을 만들고 OLE프로그램에서 상호 통신에 대한 부분에 대해서 설명하였습니다. 본장에서는 OLE프로그램 에서 데이터를 교환하는 방법에 대해서 설명합니다. 드래깅과 드롭 그리고 클립보드를 이용하여 서로 다른 프로그램이 서로 다른 형태의 데이터를 함께 공유하는 방법을 본장에서 설명합니다. OLE 데이터를 공유하거나 설정하는 방법들을 본장을 통해서 배우시게 될것입니다.

어플리케이션 프로그램에 □객체 삽입□ 항목에 의해서 OLE와 ActiveX프로그램을 자신의 프로그램 내부에 삽입하고 이렇게 삽입하여 만든 데이터를 파일에 저장하는 방법또한 본장에서 설명합니다. 또한 □객체 삽입□ 항목을 선택하였을때 다른 프로그램의 내부에서 구동되는 OLE 서버 프로그램을 제작하는 방법을 설명합니다.

대상: 중급,고급

선택

공부방법: 본장은 OLE 데이터를 로드하거나 또는 교화하고자 할때 , □객체 삽입□ 항목을 자신의 프로그램에 삽입할때 이때 예제를 공부하시기 바랍니다.

1.OLE 데이터

지금까지는 OLE의 프로그램에 대해서 설명하였습니다. 본장은 OLE프로그램에서 발생된 데이터를 저장하고 또한 로드하는 방법에 대해서 설명합니다. COM 과 Automation 그리고 ActiveX 등으로 프로그램을 작성하게 되면 이프로그램에 의해서 데이터가 만들어지게 됩니다. 이데이터를 얻고 파일로 저장하고, 로드하며 데이터를 OLE프로그램에 넘겨주는 내용을 의미합니다.  GetProperty 나 SetProperty함수에 의해서 이모든 것을 해결할수 있으나 복잡한 구조로 설정된 큰 데이터들은 이 이함수를 이용하기 보다 IUnknown구조체를 상속받은 새로운 구조체를 만들고 이구조체를 이용하게 됩니다. 예를 들어서 GIF나 JPG 파일을 만드는 OLE프로그램이 있다고 가정을 합니다. 이두개의 그림 파일들은 그림 크기와 색상정보, 팔레트 테이블,그림의 픽셀 정보등 다양한 데이터정보를 가지게 됩니다. 이렇게 복잡하게 설정된 데이터를 컨트롤 할때는 지금까지 설명한 COM,Automation.ActiveX 에서 설명한 방법이 아닌 새로운 방법을 이용하는 것이 좋을 것입니다. 이렇게 OLE 데이터를 설정하기 위해서 만든 클래스가 있습니다. COleDataObject와 COleDataSource 가 바로 이클래스입니다.

<1>COleDataObject

COleDataObject 든 OLE 프로그램에 의해서 만들어진 OLE 데이터를 컨트롤 하는 클래스 입니다. 클립보드에서 OLE프로그램에 의해서 만들어진 데이터를 얻고자 할때 또는 드래깅에 의해서 다른 프로그램으로 부터 데이터를 넘겨받을경우 이두경우에 넘어오는 데이터를 컨트롤할 클래스가 필요하게 됩니다. COleDataObject 클래스가 바로 이럴경우 사용하는 클래습니다. 예를 들어서 클립보드에서 데이터를 얻고자 할경우에는 COleDataObject의 클래스 맴버함수인 AttachClipboard( ); 함수를 이용합니다. 이함수를 사용하면 클립보드에 접근할수가 있습니다. 다음은 COleDataObject함수를 이용하여 클립보드에 접근하는 예입니다.

COleDataObject dataObject;

dataObject.AttachClipboard();

AttachClipboard() 함수를 실행하였을때 클립보드와 정확하게 연결이 되면 TRUE를 리턴하면 그렇지 않을경우에는 FALSE를 리턴합니다.

또하나의 예를 들어서 드래깅과 드롭에의해서 OLE데이터가 자신의 프로그램에 넘어왔을때 또한 COleDataObject클래스를 이용하여 드래깅 된 데이터를 얻을수 있습니다. CView의 클래스에는 OnDrop 라는 함수가 있습니다. 이함수의 형태는 다음과 같습니다.

BOOL OnDrop( COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point );

다른 프로그램에서 특정 데이터를 설정하고 Draggin하여 현재 프로그램으로 이동되어 드롭 되었을때 수행되는 함수가 OnDrop 함수입니다. 이함수에서도 COleDataObject클래스의 변수 pDataObject 를 이용하여 현재 드래깅되어 들어온 데이터의 정보를 얻을수 있습니다. 드래깅에 의해서 얻을경우에는 OnDrop,그리고 클립보드에서 데이터를 얻을경우에는 AttachCilpboard라는 함수를 이용하여 COleDataObject클래스의 변수및 포인터를 얻게 됩니다.

COleDataObject의 형으로 데이터를 얻게 된상태 즉 지금까지 설명한 OnDrop나 AttachClipboard함수를 이용하여 연결된 상태에서, 우리는 작성된 데이터를 얻어야 합니다. OLE데이터와 연결된 상태에서 실제 데이터를 얻고자 할때는 COleDataObject의 GetData함수를 사용하시면 됩니다. 이함수는 다음과 같은 형태입니다.

BOOL GetData( CLIPFORMAT cfFormat,

LPSTGMEDIUM lpStgMedium, LPFORMATETC lpFormatEtc = NULL );

cfFormat는 클립보드에서 사용하는 일반적인 포맷 형태를 이야기 합니다. 예를 들어 TEXT이면 CF_TEXT,비트맵이면 CF_BITMAP 등등 CF_계열의 플러그를 설정하여 주시면 됩니다.  CF_ 계열의 값들은 매우 많습니다. 비디오 오디오 부터,문자열,그림 등등 윈도우에서 사용하는 모든 데이터 형태를 플러그로 사용하것입니다. 도움말에서 CF_를 입력하시면 CF_ 계열의 플러그를 보실수 있을것입니다. 두번째 인자인 lpStgMedium 에는 실제 데이터의 정보를 가지고 있습니다. 이구조체의 형태는 다음과 같습니다.

typedef struct tagSTGMEDIUM {     DWORD tymed;

[switch_type(DWORD), switch_is((DWORD) tymed)]     union {

[case(TYMED_GDI)]      HBITMAP        hBitmap;

[case(TYMED_MFPICT)]   HMETAFILEPICT  hMetaFilePict;

[case(TYMED_ENHMF)]    HENHMETAFILE   hEnhMetaFile;

[case(TYMED_HGLOBAL)]  HGLOBAL        hGlobal;

[case(TYMED_FILE)]     LPWSTR         lpszFileName;

[case(TYMED_ISTREAM)]  IStream        *pstm;

[case(TYMED_ISTORAGE)] IStorage       *pstg;         [default] ;     };

[unique] IUnknown *pUnkForRelease; }STGMEDIUM;

tymed는 현재 OLE 데이터의 형태 플러그입니다. 이플러그 값들은 표1과 같습니다.

(그림 파일)

(표1) tymed 플러그 리스트

tymed플러그에 의해서 현재 데이터가 어떤 스타일인지 알수 있으며 이렇게 알았을경우 필요한 맴버를 이용하여 데이터를 얻을수 있습니다.  STGMEDIUM 구조체에서 나머지 정보는 union 이라는 것을 생각하시면 됩니다. 만일 현재 스타일이 TYMED_FILE 이라면 STGMEDIUM 구조체에서 lpszFileName의 변수를 이용하여 데이터를 얻을수 있으며 스트림 일경우에는 pstm을 이용하여 얻을수가 있습니다. GetData함수를 이용하면 STGMEDIUM형인 두번째 인자로 부터 실제적인 데이터를 얻을수 있게 됩니다.

GetData의 3번째 인자 LPFORMATETC 는 클립보드에서 데이터를 얻고자 할때 사용하는 구조체 입니다. AttachCilpboard 함수를 이용하여 클립보드와 연결하고, 클립보드에서 데이터를 얻고자 하여 GetData함수를 사용하면 lpFormatEtc 에 현재 설정된 클립보드 데이터 형태를 얻을수 있습니다. 이구조체는 다음과 같은 형태입니다.

typedef struct tagFORMATETC

{

CLIPFORMAT      cfFormat;

DVTARGETDEVICE  *ptd;

DWORD           dwAspect;

LONG            lindex;

DWORD           tymed;

}FORMATETC, *LPFORMATETC;

cfFormat는 클립보드에 설정된 데이터 스타일 플래그입니다. 보통 CF_ 계열의 값이 서정됩니다. ptd는 디바이스 에 대한 정보를 포함하는 구조체 입니다. 만일 Wave와 같은 데이터를 클립보드에서 전송받는다면 Wave를 출력하는 사운드 디바이스 정보들이 ptd에 설정됩니다. dwAspect는 프린터 데이터정보인가 (DVASPECT_DOCPRINT ) 아이콘 형태인가 (DVASPECT_ICON ) 컨텐트 형태인가(DVASPECT_CONTENT ) 등의 값이 설정된 플래그입니다. 이값에 의해서 현재 클립보드에 설정된 데이터를 어떻게 처리할것인가를 결정할수 있습니다. lindex는 데이터 용량이 만을경우 이것을 한개 이상으로 분리하엿을때 사용하는 변수입니다. 그냥 한개의 데이터일경우에는 -1이며 여러개로 분리 되었을때는 번호가 설정됩니다. tymed는  표1에서 설명한 데이터 타입 플래그 입니다.

GetData함수를 이용하면 현재 OLE데이터의 실제 값을 얻을수 있게 됩니다. 클립보드에서 데이터를 받을경우에는 GetData보다 일반적으로 GetGlobalData 함수를 주로 이용합니다. 이함수는 다음과 같습니다.

HGLOBAL GetGlobalData( CLIPFORMAT cfFormat, LPFORMATETC lpFormatEtc = NULL );

cfFormat와 lpFormatEtc는 위의 GetData에서 설명한 변수를 참조하시면 어떤 인자인지 아실수 있을것입니다. 클립보드에서 데이터를 복사하고 할경우 일반적인 프로그램에서는 현재 클립보드에서 어떤 형태의 데이터를 복사하여야 하는 지를 알고 있습니다. 예를 들어서 에디터 박스가 있는 프로그램에서는 클립보드에서 텍스트를 복사해야 한다고 생각할것이며 그림을 출력하는 프로그램에서는 클립보드에서 그림 데이터를 복사하여야 한다고 생각할것입니다. 즉 클립보드에 있는 데이터 형이 어떤 형태인지 알고 이것을 복사한다는 의미입니다. 이럴경우 다음과 같은 루틴을 이용하여 클립보드의 데이터를 복사할수가 있습니다.

COleDataObject dataObject;

dataObject.AttachClipboard();

if(dataObject.IsDataAvailable(CF_TEXT))

{

HGLOBAL hGlo;

LPSTR data;

hGlo=dataObject.GetGlobalData(CF_TEXT);

data=(LPSTR)GlobalLock(hGlo);

}

AttactClipboard함수를 이용하여 클립보드에 접근한다음 IsDataAvilable함수를 이용하여 현재 내가 복사하고자 하는 데이터가 클립보드에 있는 가를 확인하고 클립보드에 있으면 GetGlobalData함수를 이용하여 데이터를 얻는 방법입니다. 이방법을 이용하여 우리가 필요한 데이터를 클립보드에서 얻을수가 있습니다.

<2>COleDataSource

COleDataObject는 OLE데이터를 자신의 프로그램에 삽입하고자 할때 사용하는 클래스라면 COleDataSource는 자신의 데이터를 OLE데이터로 다른 프로그램이나 클립보드에 전송하고자 할때 사용하는 클래스입니다. 결국 COleDataObject와 COleDataSource는 두개의 클래스가 한개의 쌍으로 이루어져 있는 것입니다.

COleDataSource를 이용하여 클립보드에 데이터를 넣고자 할때는 다음과 같은 방법을 이용합니다.

//COleDataSource 형의 데이터변수를 설정

COleDataSource* pSource = new COleDataSource();

//데이터를 설정한다.

pSource->CacheGlobalData(CF_TEXT,hGlo);

//클립보드에 전송

pSource->SetClipboard();

COleDataSource 형의 변수를 설정한다음 CacheClobalData함수를 이용하여 COleDataSource에 OLE데이터를 설정합니다. 이렇게 설정하고 이설정된 데이터를 클립보드에 보내고자 할경우 SetClipboard를 사용하면 됩니다.

만일 드래깅에 의해서 다른 프로그램에 데이터를 전송하고자 할경우에는 DoDrapDrop함수를 이용합니다. 이함수의 형태는 다음과 같습니다.

DROPEFFECT DoDragDrop(

DWORD dwEffects = DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK, LPCRECT lpRectStartDrag = NULL, COleDropSource* pDropSource = NULL );

첫번째 인자인 dwEffects는 현재 데이터의 전송할때 허용하는 내용을 설정한 플러그입니다. DROPFEEECT_COPY 형태는 클립보드에서 복사를 가능하게 하는 의미이며 DROPEFFECT_MOVE는 데이터가 드래깅에 의해서가능한 형태라는 의미이고 DROPEFFECT_LINK는 링크가 가능한 형태로 만들겠다는 의미입니다.  lpRectStartDrag는 드래깅이 시작될때 설정되는 박스영역입니다. 만일 이값이 NULL이면 박스영역은 한개의 픽셀이 설정됩니다. pDropSource는 COleDropSource의 포인터입니다. 일반적으로는 이값을 설정하여 주지 않고 사용합니다. 이럴경우 현재 클래스이 포인터가 OLE데이터 소스가 되는 것입니다.

<3>OLE 데이터 전송 및 받기

COleDataObject와 COleDataSource 두개의 클래스는 쌍으로 설정하여 데이터를 전송하는 측에서는 COleDataSource를 사용하고 받는 측에서는 COleDataObject를 사용합니다. 이두개의 형태를 이용하여 드래깅과 클립보드를 이용하여 데이터를 전송하는 형태는 그림 4-6-1과 같습니다.

(그림 파일)

(그림 4-6-1)OLE 데이터 전송 및 받기 구성

그림 4-6-1의 ⓐ항목은 전송부분이며 ⓑ항목은 전송된 데이터를 받는 부분입니다. ①과 ⑤항목은 각 클래스를 설정시키는 항목입니다.  이렇게 설정된 상태에서 클립보드에 데이터를 전송하고자 할대는 ③과 같은 방법을 이용하며 이방법에 의해서 클립보드에 데이터가 설정되면 이데이터를 얻고자 할때는 ⑥과 같은 방법을 사용합니다.

드래깅과 드롭을 이용할때는 ⓐ항목의 ④항목을 사용합니다. 이렇게 사용하면 CView의 OnDrop함수를 이용하여 현재 드롭된 데이터를 COldDataObject형으로 받을수 있게 됩니다. ④항목을 사용할때는 마우스 버튼을 클릭하고 현재 윈도우 영역을 벗어났을경우에 COleDataSource함수를 이용하여 발생시키면 됩니다.

<4>클립보드에 데이터 넣기와 쓰기 예제 ExOleClip

클립보드에 데이터를 전송하거나 클립보드에서 데이터를 복사할 경우 주로 사용하는 방법이 OpenClipboard, CloseClipboard, GetClipboardData, SetClipboardData 등입니다. OLE는 사실 데이터 형태가 일정하지 않습니다. 어떤 것은 텍스트, 어떤 것은 비트맵, 또 어떤 것은 동영상 이렇습니다. 게다가 텍스트라고 해도 글자체, 글자 크기 등등 여러 복합적인 것이 한꺼번에 묶여서 하나의 데이터가 됩니다. 이런 데이터를 클립보드에 올려놓고 이런 데이터를 클립보드에서 받을 수 있기 위해서는 클립보드를 컨트롤하는 OLE 함수가 필요합니다. 전항목에서 설명한 COleDataSource와 COleDataObject를 이용하여 클립보드에서 문자열을 얻고 또는 쓰는 예제를 만들어 보겠습니다. 본예제는 ExOleClip입니다. 이예제의 전체 구성도는 그림 4-6-2와 같습니다.

(그림 파일)

(그림 4-6-2)ExOleClip 전체 구성도

ExOleClip 프로그램은 SDI형태이며 CView가 CFormView형태로 만들어진 프로그램입니다. 이프로그램은 4-6-2에 그림에서 설정한 내용이 전부입니다. Edit 항목에서 Copy 항목을 선택하면 ①항목이 수행되면서 에디터박스에 입력된 데이터가 클립보드에 전송이 됩니다. Edit 항목의 Paste항목을 선택하였을때 수행되는 함수가 ②항목이며 OnUpdateEditPaste 함수(③항목) 은 UPDATE_COMMAND_UI 항목을 선택하였을때 수행되는 함수입니다. ③항목은 현재 클립보드에서 텍스트 형태의 데이터가 있을경우에는 활성화 되고 그렇지 않을경우에는 비활성화가 되는 형태입니다. 활성화가 되었을때 Paste 항목을 클릭하게 되면 ②항목이 수행되고 클립보드에 있는 데이터가 에디터 박스에 설정됩니다. 본예제는 전항목에서 설명한 COleDataObject와 COleDataSource클래스를 이용하여 OLE데이터를 전송하고 받는 것을 이해하기 위한 예제입니다. 소스 부분은 생략합니다. 제공되는 CD-ROM을 참조하시기 바랍니다.

<5>File 드래깅 ExOleDrag

윈도우 탐색기에서 특정파일을 선택하고 드래깅하여 특정 프로그램에 드롭 하면 해당 파일이 오픈 되는것을 보신적이 있을것이니다. 예를 들어서 DOC 파일을 선택하여 MS-WORD 프로그램위에서 드롭하면 해당 파일이 오픈되는 형태를 말합니다. 이부분은 내부적으로 OLE 데이터를 이용합니다. 그러나 외부적으로는 매우 간단하게 처리됩니다. 그림 4-6-3는 ExOleDrag의 전체 형태입니다.

(그림 파일)

(그림 4-6-3)ExOleDrag의 전체 형태

ExOleDrag 프로그램은 SDI형태로써 View윈도우가 CFormView이며 대화상자폼에 에디터박스가 설정된 형태입니다. 그림 4-6-3처럼 윈도우 탐색기에서 파일을 선택하고 드래깅하여 ExOleDra(①항목) 프로그램에 드롭하게 되면 해당 내용이 에디터박스에 나타나게 됩니다. 이렇게 파일을 드래깅하고자 할때는 App클래스의 InitInstance함수에서 다음과 같이 설정합니다.

ⓐAfxOleInit();

:

①m_pMainWnd->ShowWindow(SW_SHOW);

②m_pMainWnd->UpdateWindow();

③m_pMainWnd->DragAcceptFiles();// 드래깅하여 파일을 받을수 있도록 한다.

④EnableShellOpen();//쉘프로그램이 가능하게 한다.

ⓐ항목과 같이 먼저 AfxOleInit함수를 이용하여 OLE를 초기화 합니다. 이렇게 한후에 ③과④항목을 설정하면 드래깅 파일을 받을수 있습니다.① 항목과 ②항목은 Initinstance함수에 이미 설정되어 있는 부분입니다. 즉 CFrameWnd의 DragAcceptFiles함수를 호출하고 EnableShellOpen 함수를 호출하게 되면 파일 드래깅과 드롭이 가능합니다.

위와 같이 설정한후에 StdAfx.h에 다음과 같이 헤더플 포함해야 합니다.

#include <afxole.h>

위와 같이하면 드롭이 되엇을대 자동적으로 OpenDocumentFile함수가 호출되고 도큐먼트클래스의 OnOpenDocument함수가 실행됩니다. 결국 파일을 오픈한것과 같은 형태가 되는 것입니다. ExOleDra의 OnOpenDocument 함수는 다음과 같습니다.

BOOL CExOleDragDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

if (!CDocument::OnOpenDocument(lpszPathName))

return FALSE;

// TODO: Add your specialized creation code here

CFile m_file;

int len;

//파일을 오픈하고

len=m_file.Open(lpszPathName,CFile::modeRead);

len=m_file.GetLength();

//파일을 읽고

m_file.Read((void *)(LPSTR)(LPCSTR)m_strData,len);

//파일을 닫는다

m_file.Close();

//View 의 첫번째 위치를 얻느다.

POSITION pos=GetFirstViewPosition();

//첫번째 View 윈도우를 얻고

CExOleDragView *pView=(CExOleDragView *)GetNextView(pos);

//View 윈도우의 에디터박스에 데이터 설정

pView->m_strData=m_strData;

pView->UpdateData(FALSE);

return TRUE;

}

OnOpenDocument함수는 현재 도큐먼트와 연결된 에디터박스에 설정된 파일의 내용을 설정하는 것입니다. 이렇게 함으로써 파일을 드래깅하여 설정하면 해당 내용이 에디터박스에 설정되는 것입니다. 본 내용의 소스 또한 생략합니다. 앞에서 설명한 내용만 이해하시면 프로그램 제작에 어려움이 없을것입니다. 나머지 부분은 제공되는 CD-ROM을 참조하시기 바랍니다.

2.객체 삽입

OLE를 대표적으로 많이 사용하는 것이 Object Embedding입니다. 워드프로세서의 편집 메뉴 안에는 거의 필수적으로 객체 삽입이 포함되어 있습니다.

본 장에서 제공하는 ExOleEmbd는 이렇게 객체를 삽입하는 예제 프로그램입니다.

그림 4-6-4은 본 장에서 제공하는 객체 삽입 예제 프로그램 ExOleEmbd 로서 편집 메뉴에서 객체 삽입을 선택하여 그림판을 불러온 형태입니다.

(그림 4-6-4)비트맵 이미지 객체를 삽입시킨 ExOleEmbd 프로그램

<1>객체 삽입 자동화 클래스 COleClientItem

객체 삽입을 아주 쉽게 사용할 수 있게 만들어 놓은 클래스가 COleClientItem클래스입니다. 이 클래스 안에는 OLE에 필요한 부과적인 클래스를 멤버 클래스로 가지고 있어 사용자가 쉽게 객체를 삽입할 수 있도록 해줍니다.

이 클래스와 함께 객체를 삽입할 때 실행되는 OLE Common 대화상자 COleInsertDialog 클래스가 필요합니다. COleInsertDialog 클래스를 실행하면 객체 삽입시에 그림 4-6-5와 같은 화면이 뜨는 대화상자가 실행됩니다. 이 때 선택한 객체를 COleInsertDialog는 COleClientItem에 삽입시켜 줍니다. 예를 든다면 COleInsertDialog형인 변수를 설정한 다음 실행시킵니다.

COleInsertDialog dlg; //객체 삽입 대화상자 설정

dlg.DoModal()      //대화상자 실행

(그림 4-6-5)ColeInsertDialog 함수 실행모습

이 때 실행된 대화상자가 IDOK를 리턴하면 특정 객체를 선택한 것이므로 COleClientItem과 연결시켜 줍니다.

COleClientItem* pItem = NULL;

dlg.CreateItem(pItem); //COleClientItem에 선택된 객체를 넘겨준다.

이제 본 프로그램에서 프로그램을 서버에 넘겨주어 서버가 메인프로그램에서 활동할 수 있게끔 합니다. 예를 든다면 □비트맵 이미지□ 객체를 설정하였을 경우 비트맵 이미지를 수정하거나 새로운 그림을 넣으려고 할 때 이런 모든 동작을 비트맵 이미지 프로그램에게 넘겨주어 실행을 하게 하는 것입니다.

이 함수가 DoVerb입니다.

pItem->DoVerb(OLEIVERB_SHOW, this); //객체 프로그램을 실행시킨다.

DoVerb의 함수 형태는 다음과 같습니다.

virtual BOOL DoVerb( LONG nVerb, CView* pView, LPMSG lpMsg = NULL );

여기에서 nVerb 실행시에 방법을 제시하는 것입니다. OLEIVERB_SHOW란 플래그는 객체가 실행되면서 화면에 보여 주고 수정할 수 있도록 하라는 이야기입니다. 그 외 몇 가지는 다음과 같습니다.

□LEIVERB_OPEN : 수정하는 윈도우와 현재의 프로그램이 분리됩니다.

□OLEIVERB_HIDE : 현재의 아이템을 감춥니다.

이렇게 하여 적당하게 객체가 설정되면 수정하는 모드를 끝내고 모든 프로그램의 동작을 메인 프로그램에 넘겨야 합니다. 이 때 사용하는 함수가  COleClientItem::Close입니다.

pItem->Close();

이렇게 되면 삽입되어 나타났던 객체의 메뉴가 사라지고 객체의 결과만 화면에 도시됩니다.

또 다시 객체를 실행시키려면 pItem->DoVerb를 실행시키면 되겠지요.

<2>COleClientItem 클래스 분석

COleClientItem클래스의 함수를 설명하면 다음과 같습니다.

생성에 관한 부분

□COleClientItem	: 생성자

□CreateFromClipboard	: 클립보드로부터 데이터를 받아서 생성됨.

□CreateFromData	: 메모리에 있는 데이터를 받아서 생성됨.

□CreateFromFile	: 파일로부터 데이터를 받아서 생성됨.

□CreateLinkFromClipboard	: 클립보드에서 데이터를 링크하여 생성됨.

□CreateLinkFromData	: 메모리에 있는 데이터를 링크하여 생성됨.

□CreateLinkFromFile	: 파일로부터 데이터를 링크하여 생성됨.

□CreateNewItem	: 새로운 아이템을 만듦.

상태에 관한 부분

□GetType	: 현재 OLE 아이템의 형태를 받는다.

□GetExtent OLE	: 아이템의 경계 지역을 받는다.

□GetClassID	: 현재 OLE 아이템의 클래스 ID를 얻는다.

□IsModified	: 지금 아이템이 수정되어 있는가를 확인

□IsRunning	: 지금 아이템이 활동중인가를 확인, 예를 든다면 음성 파일이 OLE일 경우 음성 출력이 있는가?

□IsOpen	: 지금 아이템이 열려져 있는가?

□GetDocument	: 현재 아이템과 연결된 도큐먼트를 받는다.

□AttachDataObject	: 클립보드에 접근하듯이 DataObjet 접근한다.

□DoDragDrop	: 드래그 앤 드롭을 실행한다.

□CopyToClipboard	: 클립보드로 데이터를 전송한다.

□GetClipboardData	: 클립보드로부터 데이터를 받는다.

□Delete	: 현재 아이템을 지운다.

□Run	: 현재 아이템을 실행한다.

□SetPrintDevice	: 프린터 디바이스를 설정한다.

□Activate	: 현재 아이템을 활성화한다.

□DoVerb	: 현재 아이템을 관할하는 서버 프로그램이 실행된다.

□SetItemRects	: 아이템의 박스 좌표를 설정한다.

□GetInPlaceWindow : 윈도우 안에 현재 아이템의 좌표를 얻는다.

위의 내용 외에도 많은 함수들이 존재합니다. 지금까지 본 서를 읽어 보았다면 함수만 보아도 아! 하고 어떻게 사용하는가를 알 수 있을 것입니다. 위 함수 중에 클립보드 함수를 자세히 본다면 클립보드에 있는 데이터를 가지고도 아이템을 만들 수 있으면 설정할 수 있습니다. 여기서는 예제를 만들지 않았지만 직접 예제를 만들어 볼 수 있을 것입니다.

<3>COleClientItem over ride

MFC에서 설정된 기본 클래스를 바로 사용해도 무방하지만 그 클래스를 기초 클래스로 지정하고 그 클래스에서 제공하는 몇 개의 함수를 자신의 함수로 코딩하는 것이 많습니다. C++이란 이런 면을 많이 이용하라고 만든 것 같습니다. 기초 클래스 안에 있는 몇 개의 함수를 현재 자신의 상황에 맞게 설정하는 것이 필연적일 때가 많기 때문입니다. CView::OnDraw() 함수는 새로운 프로그램을 만들 때마다 언제나 변하기 때문에 OnDraw 함수는 언제나 갱신을 합니다. 마찬가지로 COleClientItem도 그 클래스를 그대로 사용하는 것보다 자신에게 맞는 클래스로 바꾸는 것이 좋습니다. 아니 AppWizard를 실행하여 Ole Client를 선택했을 경우 COleClientItem을 기초 클래스로 하는 새로운 클래스를 만들어 주고 그곳에 Over ride 함수를 미리 만들어 줍니다.

예를 든다면 ExOleEmbd로 Ole 컨테이너 옵션을 선택하여 프로젝트를 만들면 COleClientItem 클래스를 상속받은 CExOleEmbdCntrItem이 생깁니다. 이 클래스에 COleClientItem에서 사용하는 몇 개의 함수를 Over ride시켜 줍니다.

COleClientItem을 상속받아 만들어진 CExOleEmbdCntrItem은 다음과 같습니다.

(그림 4-6-6) Container 선택화면

// CntrItem.cpp : implementation of the CExOleEmbdCntrItem class

//

#include "stdafx.h"

#include "ExOleEmbd.h"

#include "ExOleEmbdDoc.h"

#include "ExOleEmbdView.h"

#include "CntrItem.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CExOleEmbdCntrItem implementation

IMPLEMENT_SERIAL(CExOleEmbdCntrItem, COleClientItem, 0)

CExOleEmbdCntrItem::CExOleEmbdCntrItem(CExOleEmbdDoc* pContainer)

: COleClientItem(pContainer)

{

// TODO: add one-time construction code here

}

CExOleEmbdCntrItem::~CExOleEmbdCntrItem()

{

// TODO: add cleanup code here

}

//아이템이 변형되었을때 발생하는 함수

void CExOleEmbdCntrItem::OnChange(OLE_NOTIFICATION nCode, DWORD dwParam)

{

ASSERT_VALID(this);

COleClientItem::OnChange(nCode, dwParam);

// When an item is being edited (either in-place or fully open)

//  it sends OnChange notifications for changes in the state of the

//  item or visual appearance of its content.

// TODO: invalidate the item by calling UpdateAllViews

//  (with hints appropriate to your application)

GetDocument()->UpdateAllViews(NULL);

// for now just update ALL views/no hints

}

//아이템의 위치가 변경되었을때

BOOL CExOleEmbdCntrItem::OnChangeItemPosition(const CRect& rectPos)

{

ASSERT_VALID(this);

// During in-place activation CExOleEmbdCntrItem::OnChangeItemPosition

//  is called by the server to change the position of the in-place

//  window.  Usually, this is a result of the data in the server

//  document changing such that the extent has changed or as a result

//  of in-place resizing.

//

// The default here is to call the base class, which will call

//  COleClientItem::SetItemRects to move the item

//  to the new position.

GetDocument()->m_rectTracker.m_rect=rectPos;

if (!COleClientItem::OnChangeItemPosition(rectPos))

return FALSE;

// TODO: update any cache you may have of the item's rectangle/extent

return TRUE;

}

//아이템의 위치를 얻고자 할 때 발생하는 함수

void CExOleEmbdCntrItem::OnGetItemPosition(CRect& rPosition)

{

ASSERT_VALID(this);

// During in-place activation, CExOleEmbdCntrItem::OnGetItemPosition

//  will be called to determine the location of this item.  The default

//  implementation created from AppWizard simply returns a hard-coded

//  rectangle.  Usually, this rectangle would reflect the current

//  position of the item relative to the view used for activation.

//  You can obtain the view by calling CExOleEmbdCntrItem::GetActiveView.

// TODO: return correct rectangle (in pixels) in rPosition

//아이템의 위치는 도큐먼트의 추적박스와 같게 한다.

rPosition.SetRect(GetDocument()->m_rectTracker.m_rect.left,

GetDocument()->m_rectTracker.m_rect.top,

GetDocument()->m_rectTracker.m_rect.right,

GetDocument()->m_rectTracker.m_rect.bottom);

}

void CExOleEmbdCntrItem::OnActivate()

{

// Allow only one inplace activate item per frame

CExOleEmbdView* pView = GetActiveView();

ASSERT_VALID(pView);

COleClientItem* pItem = GetDocument()->GetInPlaceActiveItem(pView);

if (pItem != NULL && pItem != this)

pItem->Close();

COleClientItem::OnActivate();

}

void CExOleEmbdCntrItem::OnDeactivateUI(BOOL bUndoable)

{

COleClientItem::OnDeactivateUI(bUndoable);

// Hide the object if it is not an outside-in object

DWORD dwMisc = 0;

m_lpObject->GetMiscStatus(GetDrawAspect(), &dwMisc);

if (dwMisc & OLEMISC_INSIDEOUT)

DoVerb(OLEIVERB_HIDE, NULL);

}

void CExOleEmbdCntrItem::Serialize(CArchive& ar)

{

ASSERT_VALID(this);

// Call base class first to read in COleClientItem data.

// Since this sets up the m_pDocument pointer returned from

//  CExOleEmbdCntrItem::GetDocument, it is a good idea to call

//  the base class Serialize first.

COleClientItem::Serialize(ar);

// now store/retrieve data specific to CExOleEmbdCntrItem

if (ar.IsStoring())

{

// TODO: add storing code here

}

else

{

// TODO: add loading code here

}

}

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

// CExOleEmbdCntrItem diagnostics

#ifdef _DEBUG

void CExOleEmbdCntrItem::AssertValid() const

{

COleClientItem::AssertValid();

}

void CExOleEmbdCntrItem::Dump(CDumpContext& dc) const

{

COleClientItem::Dump(dc);

}

#endif

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

<4>객체를 파일에 저장하기

위에서 보면 알겠지만 객체 자체에도 Serialize 함수가 통용됩니다. 그렇기 때문에 사용자가 어떻게 데이터가 저장되는가를 걱정하지 않아도 본 클래스의 Serialize만 실행되게 만든다면 자동적으로 파일에 저장이 됩니다. AppWizard로 클래스를 만들게 되면 View와 연결된 Document를 객체 아이템과도 연결시켜 줍니다. 이 객체에서 GetDocument 함수를 호출하면 메인프로그램의 Doc 클래스와 연결됩니다. 이렇게 되면 Doc 클래스의 Serialize 안에 본 아이템의 Seralize만 결합시켜 주면 자동적으로 OLE 데이터가 파일로 저장됩니다.

void CExOleEmbdDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

}

else

{

}

COleDocument::Serialize(ar); //OLE 도큐먼트로 파일에 저장한다.

}

<5>추적 박스 CRectTracker

추적 박스라는 것은 그림판에서 보면 특정 영역을 선택하였을 경우 이 영역이 선택되었다고 알려 주면서  그려지는 사각형입니다. 또한 마우스 버튼을 중앙에 놓고 움직이면 추적 박스 자체가 이동되면서 데이터가 이동되고 각 꼭지점이나 선분 위에서 마우스를 움직이면 크기가 변하거나 새로운 크기로 변하게 됩니다. 이렇게 특정 위치를 설정하고 위치가 변형되는 것을 사용자가 알기 쉽게 만들어 놓은 것이 추적 박스 클래스 CRectTracker입니다. 이 클래스는 보통 OLE가 메인프로그램이 삽입되었을 때 외곽에 박스를 그려 주면서 위치를 변할 수 있도록 하거나 또는 이미지 에디터나 저작 도구에서 새로운 객체를 선택하거나 활성화시킬 때 현 객체가 활성화되었다는 것을 알려 주는 기능을 합니다.

이 추적 박스의 사용법은 매우 간단합니다.

클래스를 사용할 때는 언제나 그 클래스형인 변수로 선언을 합니다. 마찬가지로 CRectTracker형인 변수를 만듭니다.

CRectTracker m_rectTracker;

이 추적 박스는 마우스 버튼을 눌렀을 때 활성화되면 되겠죠. 그렇다면 OnLButtonDown 함수 안에 설정을 해주면 됩니다.

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

{

//현재 포인터가 추적 박스 안에 있으면 추적 박스는 추적을 실행한다.

if(m_rectTracker.m_rect.PtInRect(point))

m_rectTracker.Track(this, point, FALSE, NULL);

//화면을 재도시

Invalidate();

CScrollView::OnLButtonDown(nFlags, point);

}

마우스 버튼이 추적 박스 가장자리에 있으면 마우스 버튼이 변하면서 사이즈를 변경시킬 수 있는 형태로 변한다든지 가운데 있으면 이동할 수 있다는 표시를 해주는 기능이 있습니다. 이 기능은 OnSetCursor 함수에 기록하면 됩니다.

BOOL CExOleEmbdView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)

{

//추적 박스 영역이 있으면 추적 박스 영역의

//커서로 바꾼다.

if(m_rectTracker.SetCursor(pWnd, nHitTest)) {

//만약 추적 박스의 크기가 변하면 이 부분에 추적 박스의 크기를

//바꾸는 내용을 삽입한다.

return TRUE;

}

else {

return CScrollView::OnSetCursor(pWnd, nHitTest, message);

}

}

<6>예제 프로그램 작성

객체 삽입을 할 수 있는 예제 ExOleEmbd를 만들어 보겠습니다. 이 예제는 MDI로 제작하면 AppWizard의 Step 3에서 Container를 설정합니다. 그 외 다른 Ole 옵션은 선택하지 않습니다.

이렇게 하여 프로젝트를 만들면 COleClientItem을 상속받은 CExOleEmbdCntrItem이 만들어지고 View의 객체 삽입 항목이 자동적으로 코딩됩니다. 즉 ID_INSERT_OBJECT 항목이죠. 이 항목이 OnInsertObject() 항목으로 다음과 같이 설정됩니다.

//객체 삽입 메뉴가 설정되면

void CExOleEmbdView::OnInsertObject()

{

// Invoke the standard Insert Object dialog box to obtain information

//  for new CExOleEmbdCntrItem object.

//객체 삽입 대화상자 실행

COleInsertDialog dlg;

if (dlg.DoModal() != IDOK)

return;

BeginWaitCursor();

CExOleEmbdCntrItem* pItem = NULL;

TRY

{

// Create new item connected to this document.

//도큐먼트를 연결하고

CExOleEmbdDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pItem = new CExOleEmbdCntrItem(pDoc);

ASSERT_VALID(pItem);

// Initialize the item from the dialog data.

//대화상자에 설정한 아이템을 pItem에 연결한다.

if (!dlg.CreateItem(pItem))

AfxThrowMemoryException();  // any exception will do

ASSERT_VALID(pItem);

// If item created from class list (not from file) then launch

//  the server to edit the item.

//새로 만든 아이템이면 아이템의 서버 프로그램을 실행하라.

if (dlg.GetSelectionType() == COleInsertDialog::createNewItem)

pItem->DoVerb(OLEIVERB_SHOW, this);

//   pItem->SetItemRects(pDoc->m_rectTracker.m_rect);

ASSERT_VALID(pItem);

// As an arbitrary user interface design, this sets the selection

//  to the last item inserted.

// TODO: reimplement selection as appropriate for your application

m_pSelection = pItem;   // set selection to last inserted item

//모든 뷰를 업 데이트한다.

pDoc->UpdateAllViews(NULL);

}

CATCH(CException, e)//Ole 연결이 실패하였을 경우

{

if (pItem != NULL)

{

ASSERT_VALID(pItem);

pItem->Delete();

}

AfxMessageBox(IDP_FAILED_TO_CREATE);

}

END_CATCH

EndWaitCursor();

}

이렇게 설정되어 있는 상항에서 아무런 코딩 없이 프로그램을 컴파일하고 실행시키면 객체가 삽입되어 객체 프로그램이 실행된 후 삽입된 객체가 닫혀지지 않게 됩니다. 본 프로그램에서는 마우스로 객체의 위치 밖을 클릭하면 현재의 객체가 닫혀지도록 했습니다. 즉 마우스 버튼을 클릭하는 이벤트를 이용하여 객체를 닫게 한 것입니다. 이 때 함수가 OnLButtonDown인데 다음과 같이 설정합니다.

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

{

CExOleEmbdDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//현재 m_pSelection이 열려 있으면 닫는다.

if(m_pSelection)

m_pSelection->Close();

Invalidate();

CScrollView::OnLButtonDown(nFlags, point);

}

이제 닫혀지면 현재의 객체만 화면에 도시되는데 이 도시된 데이터 위에 추적 박스를 겹쳐 넣어야 합니다.

추적 박스의 박스 좌표는 CExOleEmbdCntrItem에서도 사용하고 또한 파일로 저장할 때 위치도 함께 저장하기 위해서 Doc 파일에 설정합니다.

CRectTracker m_rectTracker;

다음 추적 박스도 활성화하기 위해서는 위의 OnLButtonDown을 수정해야 합니다.

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

{

CExOleEmbdDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//현재 m_pSelection이 열려 있으면 닫는다.

if(m_pSelection)

m_pSelection->Close();

//현재 포인터가 추적 박스 안에 있으면 추적 박스는 추적을 실행한다.

if(pDoc->m_rectTracker.m_rect.PtInRect(point))

pDoc->m_rectTracker.Track(this, point, FALSE, NULL);

//화면을 재도시

Invalidate();

CScrollView::OnLButtonDown(nFlags, point);

}

추적 박스 위에서 마우스가 움직이면 커서가 바뀌게 만들어야 합니다. OnSet- Cursor를 다음과 같이 합니다.

BOOL CExOleEmbdView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)

{

CExOleEmbdDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//추적 박스 영역이 있으면 추적 박스 영역의

//커서로 바꾼다.

if(pDoc->m_rectTracker.SetCursor(pWnd, nHitTest)) {

//만약 추적 박스의 크기가 변하면 이 부분에 추적 박스의 크기를

//바꾸는 내용을 삽입한다.

return TRUE;

}

else {

return CScrollView::OnSetCursor(pWnd, nHitTest, message);

}

}

다음은 객체를 그려야 하고 추적 박스를 그려야 합니다. 객체나 추적 박스나 내부 함수에 Draw가 있으므로 호출만 하면 됩니다. 그리는 부분은 OnDraw를 사용합니다.

void CExOleEmbdView::OnDraw(CDC* pDC)

{

CExOleEmbdDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//객체가 설정되지 않았으면

if (m_pSelection == NULL)

{

POSITION pos = pDoc->GetStartPosition();

m_pSelection = (CExOleEmbdCntrItem*)pDoc->GetNextClientItem(pos);

}

//객체가 설정되어 있으면

if (m_pSelection != NULL)

m_pSelection->Draw(pDC,pDoc->m_rectTracker.m_rect);

//추적 박스를 그린다.

pDoc->m_rectTracker.Draw(pDC);

}

추적 박스를 마우스로 이동시키면 마찬가지로 객체 아이템도 이동이 되어야 합니다. 그럼 어떻게 해야 할까요. CExOleEmbdCntrItem 안의 OnGetItemPosition 안에서  rPosition 멤버를 수정합니다.

//아이템의 위치를 얻고자 할 때 발생하는 함수

//본 함수는 CExOleEmbdCntrItem::GetActiveView가 실행될 때

//발생한다.

void CExOleEmbdCntrItem::OnGetItemPosition(CRect& rPosition)

{

ASSERT_VALID(this);

//아이템의 위치는 도큐먼트의 추적 박스와 같게 한다.

rPosition.SetRect(GetDocument()->m_rectTracker.m_rect.left,

GetDocument()->m_rectTracker.m_rect.top,

GetDocument()->m_rectTracker.m_rect.right,

GetDocument()->m_rectTracker.m_rect.bottom);

}

이제 파일에 저장하는 부분만 남았습니다. 파일에 데이터를 저장할 때 현재 선택된 객체를 저장해야 하고 또한 객체가 뷰 화면의 어느 위치에 있는가를 저장해야 합니다.

도큐먼트의 Serialize 함수를 수정하면 되겠지요.

void CExOleEmbdDoc::Serialize(CArchive& ar)

{

int left,right,top,bottom;

//객체가 있는 위치를 먼저 저장한다.

left=m_rectTracker.m_rect.left;

right=m_rectTracker.m_rect.right;

top=m_rectTracker.m_rect.top;

bottom=m_rectTracker.m_rect.bottom;

if (ar.IsStoring())

{

ar <<left;

ar<<right;

ar<<top;

ar<<bottom;

}

else

{

ar >>left;

ar >>right;

ar >>top;

ar >>bottom;

}

m_rectTracker.m_rect.left=left;

m_rectTracker.m_rect.right=right;

m_rectTracker.m_rect.top=top;

m_rectTracker.m_rect.bottom=bottom;

// Calling the base class COleDocument enables serialization

//  of the container document's COleClientItem objects.

//OLE 객체를 저장한다.

COleDocument::Serialize(ar);

}

<7>메인프래임에서 file open

AppWizard로 만든 프로젝트에서는 ID_FILE_OPEN을 자동적으로 설정시켜 줍니다. AppWizard가 제공하는 ID_FILE_OPEN을 제거하면 자동적으로 파일을 개방하는 함수를 모두 사용해야 합니다.

□기존의 FILE OPEN을 그대로 사용하고 그 후에 내 코딩을 하고 싶다□ 할 경우가 있을 것입니다. 그렇다면 App 클래스에서 ID_FILE_OPEN을 설정하세요. 그럼 빈칸에

void CExOleEmbdApp::OnFileOpen()

{

}

함수가 나옵니다. 이 함수에 다음과 같이 하면 예전의 내용과 같은 기능을 하면서 자신의 내용을 코딩할 수 있습니다.

void CExOleEmbdApp::OnFileOpen()

{

CWinApp::OnFileOpen();

//이곳에 자신의 내용을 코딩한다.

}

<8>화면 모드 관계

이 프로그램에서 텍스트를 OLE 객체로 불러오면 글자가 커져서 나타납니다. 이유는 화면 모드 때문입니다. 이 프로그램에서는 화면 모드가 MM_TEXT입니다. 이 모드를 변경시키면 화면에 글자가 깨끗하게 보일 것입니다.

그럼 어떻게 변화시킬까요? 그것을 독자분들에게 숙제로 남겨 두겠습니다. 힌트를 준다면 ISO_TROPIC의 기능을 생각하세요. 매핑 모드는 본서의 PART1의  3장에 있습니다.

3.OLE 서버 프로그램 만들기

클라이언트를 구동하는 클래스가 COleClientItem이면 서버 클래스는 무엇일까요? 당연히 COleSeverItem이죠. 서버 프로그램은 만들기가 매우 간단합니다. AppWizard의 Step 3에서 Olesever 아이템을 선택한 후 프로그램 제작하듯이 프로그래밍하면 됩니다. 단, 화면에 보이는 부분을 두 군데로 나누어 프로그래밍해야 합니다. 서버는 클라이언트 영역에서 실행될 수도 있고 독자적인 윈도우를 가지며 실행될 수도 있습니다. 그렇기 때문에 클라이언트 프로그램의 영역에서 실행될 때 하나, 독자적으로 실행될 때 하나, 이렇게 두 개의 뷰 부분을 가져야 한다는 것입니다.

예를 든다면 CEx2063으로 Ole 서버 프로그램용 프로젝트를 만들었을 때 탄생되는 기본적인 뷰는 CEx2063View입니다. 이것과 함께 또하나의 뷰 기능을 하는 것이 COleSeverItem을 상속받은 CEx2063SrvrItem입니다.

만약 □이 프로그램이 독자적으로 움직일 때는 화면에 이렇게 움직여야 할 것이다.□ 할 경우는 뷰 함수 안의 OnDraw 함수 안에 코딩을 해야 하고 □어느 클라이언트 프로그램 안에서 움직이면 이렇게 되어야 할 것이다.□ 할 경우에는 CEx2063- SrvrItem 안의 OnDraw 함수를 고치면 됩니다.

<1>미니 서버와 풀 서버

AppWizard의 Step 3에는 서버가 두 개로 분리되어 있습니다. Mini-server과 Full-server로 말입니다. 두 개의 긍극적인 차이는 독립적으로 실행이 되는가 안 되는가입니다. Mini-server는 클라이언트 프로그램에서 호출을 해야만 실행됩니다. 그러나 Full-server는 독립적으로도 실행되면서 클라이언트에서도 실행되는 게 차이입니다.

일반 프로그램을 하듯이 서버 프로그램을 제작하라. 단 뷰는 두 개!

서버 프로그램을 제작할 때는 일반 프로그램을 제작하듯이 하면 됩니다.

독립적으로 실행될 때는 View 클래스에 있는 OnDraw 함수가 실행되고 클라이언트 영역에서 실행되면 COleSeverItem의 클래스의 OnDraw 함수가 실행됩니다. 둘 다 한 개의 Doc에 연결되어 있습니다.

<2>미니 서버 Ex2063과 풀 서버 Ex2064

그림 4-6-7는 Ex2063을 실행한 화면입니다. Ex2063과 Ex2064는 그 기능이 같습니다. 다만 미니 서버인가 아니면 풀 서버인가의 차이점입니다.

대화상자 안에 이름과 주소를 삽입하면 화면에 이름과 주소가 나타나는 간단한 프로그램입니다.

우리는 이 서버 프로그램을 통하여 서버 데이터가 어떻게 클라이언트에 설정되는지를 이해할 수 있습니다.

미니 서버 Ex2063과 풀 서버 Ex2064 프로그램 만들기

AppWizard Step 3에서 Ex2063은 Mini-Sever로 Ex2064는 Full-sever로 선택합니다. 프로젝트를 만들고 난 후에 데이터 입력 항목을 설정하고 데이터 입력 항목을 설정하면 새로운 대화상자가 떠서 이름과 주소를 입력하게 만듭니다.

(그림 4-6-7)Ex2063 실행결과

//데이터를 입력할 때

void CEx2063View::OnAdddata()

{

//데이터 입력 대화상자가 뜨고

CAddData pDlg;

CEx2063Doc* pDoc = GetDocument();

if(pDlg.DoModal()==IDOK)

{

//데이터를 도큐먼트의 m_strName와 m_strText에 삽입

pDoc->m_strName=pDlg.m_strName;

pDoc->m_strText=pDlg.m_strText;

}

Invalidate();

}

View 화면의 OnDraw에  m_strName과 m_strText를 출력합니다.

//독자적인 프로그램이 실행될 때 본 Draw가 실행

void CEx2063View::OnDraw(CDC* pDC)

{

CEx2063Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//도큐먼트에 있는 m_strName과 m_strText를 화면에 출력한다.

pDC->TextOut(0,0,pDoc->m_strName);

pDC->TextOut(0,20,pDoc->m_strText);

}

CEx2063SrvrItem의 OnDraw에 도큐먼트의 m_strName과 m_strText를 출력합니다.

//서버 안에서는 본 Draw가 실행

BOOL CEx2063SrvrItem::OnDraw(CDC* pDC, CSize& rSize)

{

CEx2063Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: set mapping mode and extent

//  (The extent is usually the same as the size returned from OnGetExtent)

pDC->SetMapMode(MM_ANISOTROPIC);

pDC->SetWindowOrg(0,0);

pDC->SetWindowExt(3000, 3000);

//도큐먼트에 있는 m_strName와 m_strText를 출력한다.

pDC->TextOut(0,0,pDoc->m_strName);

pDC->TextOut(0,400,pDoc->m_strText);

// TODO: add drawing code here.  Optionally, fill in the HIMETRIC extent.

//  All drawing takes place in the metafile device context (pDC).

return TRUE;

}

이 부분을 보면 클라이언트쪽의 텍스트 출력시 매핑 모드를 설정하는 힌트가 나오는군요. 아니 힌트라기보다는 답이라고 할 수 있겠네요.

<3>프로그램 소스

(프로그램 소스)

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

// Ex2063Vw.h : interface of the CEx2063View class

//

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

class CEx2063View : public CView

{

protected: // create from serialization only

CEx2063View();

DECLARE_DYNCREATE(CEx2063View)

// Attributes

public:

CEx2063Doc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CEx2063View)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CEx2063View();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CEx2063View)

afx_msg void OnCancelEditSrvr();

afx_msg void OnAdddata();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in Ex2063Vw.cpp

inline CEx2063Doc* CEx2063View::GetDocument()

{ return (CEx2063Doc*)m_pDocument; }

#endif

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

// Ex2063Vw.cpp : implementation of the CEx2063View class

//

#include "stdafx.h"

#include "Ex2063.h"

#include "Ex2063Dc.h"

#include "Ex2063Vw.h"

#include "AddData.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CEx2063View

IMPLEMENT_DYNCREATE(CEx2063View, CView)

BEGIN_MESSAGE_MAP(CEx2063View, CView)

//{{AFX_MSG_MAP(CEx2063View)

ON_COMMAND(ID_CANCEL_EDIT_SRVR, OnCancelEditSrvr)

ON_COMMAND(ID_ADDDATA, OnAdddata)

//}}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()

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

// CEx2063View construction/destruction

CEx2063View::CEx2063View()

{

// TODO: add construction code here

}

CEx2063View::~CEx2063View()

{

}

BOOL CEx2063View::PreCreateWindow(CREATESTRUCT& cs)

{

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

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

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

// CEx2063View drawing

//독자적인 프로그램이 실행될때 본Draw가 실행

void CEx2063View::OnDraw(CDC* pDC)

{

CEx2063Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//도큐먼트에 있는 m_strName와 m_strText를 화면에 출력한다.

pDC->TextOut(0,0,pDoc->m_strName);

pDC->TextOut(0,20,pDoc->m_strText);

}

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

// CEx2063View printing

BOOL CEx2063View::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

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

{

// TODO: add extra initialization before printing

}

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

{

// TODO: add cleanup after printing

}

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

// OLE Server support

// The following command handler provides the standard keyboard

//  user interface to cancel an in-place editing session.  Here,

//  the server (not the container) causes the deactivation.

void CEx2063View::OnCancelEditSrvr()

{

GetDocument()->OnDeactivateUI(FALSE);

}

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

// CEx2063View diagnostics

#ifdef _DEBUG

void CEx2063View::AssertValid() const

{

CView::AssertValid();

}

void CEx2063View::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CEx2063Doc* CEx2063View::GetDocument() // non-debug version is inline

{

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

return (CEx2063Doc*)m_pDocument;

}

#endif //_DEBUG

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

// CEx2063View message handlers

//데이타를 입력할때

void CEx2063View::OnAdddata()

{

//데이타 입력 대화상자가 뜨고

CAddData pDlg;

CEx2063Doc* pDoc = GetDocument();

if(pDlg.DoModal()==IDOK)

{

//데이타를 도큐먼트의 m_strName와 m_strText에 삽입

pDoc->m_strName=pDlg.m_strName;

pDoc->m_strText=pDlg.m_strText;

}

Invalidate();

}

// SrvrItem.h : interface of the CEx2063SrvrItem class

//

class CEx2063SrvrItem : public COleServerItem

{

DECLARE_DYNAMIC(CEx2063SrvrItem)

// Constructors

public:

CEx2063SrvrItem(CEx2063Doc* pContainerDoc);

// Attributes

CEx2063Doc* GetDocument() const

{ return (CEx2063Doc*)COleServerItem::GetDocument(); }

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CEx2063SrvrItem)

public:

virtual BOOL OnDraw(CDC* pDC, CSize& rSize);

virtual BOOL OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize);

//}}AFX_VIRTUAL

// Implementation

public:

~CEx2063SrvrItem();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

virtual void Serialize(CArchive& ar);   // overridden for document i/o

};

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

// SrvrItem.cpp : implementation of the CEx2063SrvrItem class

//

#include "stdafx.h"

#include "Ex2063.h"

#include "Ex2063Dc.h"

#include "SrvrItem.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CEx2063SrvrItem implementation

IMPLEMENT_DYNAMIC(CEx2063SrvrItem, COleServerItem)

CEx2063SrvrItem::CEx2063SrvrItem(CEx2063Doc* pContainerDoc)

: COleServerItem(pContainerDoc, TRUE)

{

// TODO: add one-time construction code here

//  (eg, adding additional clipboard formats to the item's data source)

}

CEx2063SrvrItem::~CEx2063SrvrItem()

{

// TODO: add cleanup code here

}

void CEx2063SrvrItem::Serialize(CArchive& ar)

{

// CEx2063SrvrItem::Serialize will be called by the framework if

//  the item is copied to the clipboard.  This can happen automatically

//  through the OLE callback OnGetClipboardData.  A good default for

//  the embedded item is simply to delegate to the document's Serialize

//  function.  If you support links, then you will want to serialize

//  just a portion of the document.

if (!IsLinkedItem())

{

CEx2063Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->Serialize(ar);

}

}

BOOL CEx2063SrvrItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)

{

// Most applications, like this one, only handle drawing the content

//  aspect of the item.  If you wish to support other aspects, such

//  as DVASPECT_THUMBNAIL (by overriding OnDrawEx), then this

//  implementation of OnGetExtent should be modified to handle the

//  additional aspect(s).

if (dwDrawAspect != DVASPECT_CONTENT)

return COleServerItem::OnGetExtent(dwDrawAspect, rSize);

// CEx2063SrvrItem::OnGetExtent is called to get the extent in

//  HIMETRIC units of the entire item.  The default implementation

//  here simply returns a hard-coded number of units.

CEx2063Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: replace this arbitrary size

rSize = CSize(3000, 3000);   // 3000 x 3000 HIMETRIC units

return TRUE;

}

//서버 안에서 는 본 Draw가 실행

BOOL CEx2063SrvrItem::OnDraw(CDC* pDC, CSize& rSize)

{

CEx2063Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: set mapping mode and extent

//  (The extent is usually the same as the size returned from OnGetExtent)

pDC->SetMapMode(MM_ANISOTROPIC);

pDC->SetWindowOrg(0,0);

pDC->SetWindowExt(3000, 3000);

//도큐먼트에 있는 m_strName와 m_strText를 출력한다.

pDC->TextOut(0,0,pDoc->m_strName);

pDC->TextOut(0,400,pDoc->m_strText);

// TODO: add drawing code here.  Optionally, fill in the HIMETRIC extent.

//  All drawing takes place in the metafile device context (pDC).

return TRUE;

}

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

// CEx2063SrvrItem diagnostics

#ifdef _DEBUG

void CEx2063SrvrItem::AssertValid() const

{

COleServerItem::AssertValid();

}

void CEx2063SrvrItem::Dump(CDumpContext& dc) const

{

COleServerItem::Dump(dc);

}

#endif

(프로그램 소스끝)

Ex2064와 Ex2063의 소스내용을 같습니다. 차이는 AppWizard Step3에서 Mini Sever를 선택하였는가 Full Sever를 선택하였는가 입니다.