TN041: MFC/OLE 2로 MFC/OLE1 마이그레이션

참고 항목

다음 기술 노트는 온라인 설명서에 먼저 포함되어 있었으므로 업데이트되지 않았습니다. 따라서 일부 절차 및 항목은 만료되거나 올바르지 않을 수 있습니다. 최신 정보를 보려면 온라인 설명서 색인에서 관심 있는 항목을 검색하는 것이 좋습니다.

마이그레이션과 관련된 일반적인 문제

MFC 2.5 이상에서 OLE 2 클래스의 디자인 목표 중 하나는 OLE 1.0 지원을 위해 MFC 2.0에 배치된 동일한 아키텍처를 대부분 유지하는 것이었습니다. 따라서 MFC 2.0의 동일한 OLE 클래스는 이 버전의 MFC(COleDocument, , COleServerDocCOleClientItemCOleServerItem)에 여전히 존재합니다. 또한 이러한 클래스의 많은 API는 정확히 동일합니다. 그러나 OLE 2는 OLE 1.0과 크게 다르므로 일부 세부 정보가 변경될 것으로 예상할 수 있습니다. MFC 2.0의 OLE1 지원에 익숙한 경우 MFC의 2.0 지원을 통해 집에서 느낄 수 있습니다.

기존 MFC/OLE1 애플리케이션을 사용하고 OLE 2 기능을 추가하는 경우 먼저 이 메모를 읽어야 합니다. 이 참고에서는 OLE1 기능을 MFC/OLE 2로 포팅하는 동안 발생할 수 있는 몇 가지 일반적인 문제에 대해 설명한 다음 MFC 2.0에 포함된 두 애플리케이션인 MFC OLE 샘플 OCLIENTHIERSVR을 포팅하는 동안 발견된 문제에 대해 설명합니다.

MFC 문서/뷰 아키텍처가 중요합니다.

애플리케이션이 MFC의 문서/보기 아키텍처를 사용하지 않고 애플리케이션에 OLE 2 지원을 추가하려는 경우 이제 문서/보기로 이동할 때입니다. MFC의 OLE 2 클래스의 많은 이점은 애플리케이션이 MFC의 기본 제공 아키텍처 및 구성 요소를 사용하는 경우에만 실현됩니다.

MFC 아키텍처를 사용하지 않고 서버 또는 컨테이너를 구현할 수 있지만 권장되지는 않습니다.

사용자 고유의 구현 대신 MFC 구현 사용

MFC "통조림 구현" 클래스(예: CToolBar) CStatusBarCScrollView OLE 2 지원을 위한 특수 사례 코드가 기본 제공됩니다. 따라서 애플리케이션에서 이러한 클래스를 사용할 수 있는 경우 OLE를 인식하도록 하기 위해 이러한 클래스에 투입된 노력의 이점을 활용할 수 있습니다. 다시 말하지만, 이러한 목적을 위해 여기에 클래스를 "직접 롤"할 수 있지만 제안되지는 않습니다. 유사한 기능을 구현해야 하는 경우 MFC 소스 코드는 OLE의 몇 가지 세부 사항을 처리하기 위한 훌륭한 참조입니다(특히 현재 위치 활성화의 경우).

MFC 샘플 코드 검사

OLE 기능을 포함하는 여러 MFC 샘플이 있습니다. 이러한 각 애플리케이션은 다른 각도에서 OLE를 구현합니다.

  • HIERSVR 은 주로 서버 애플리케이션으로 사용하기 위한 것입니다. MFC 2.0에 MFC/OLE1 애플리케이션으로 포함되었으며 MFC/OLE 2로 이식된 다음 OLE 2에서 사용할 수 있는 많은 OLE 기능을 구현하도록 확장되었습니다.

  • OCLIENT 이 애플리케이션은 독립 실행형 컨테이너 애플리케이션으로, 컨테이너 관점에서 많은 OLE 기능을 보여 줍니다. 또한 MFC 2.0에서 이식된 다음 사용자 지정 클립보드 형식 및 포함된 항목에 대한 링크와 같은 고급 OLE 기능을 지원하도록 확장되었습니다.

  • DRAWCLI 이 애플리케이션은 기존 개체 지향 그리기 프로그램의 프레임워크 내에서 수행된다는 점을 제외하고 OCLIENT와 마찬가지로 OLE 컨테이너 지원을 구현합니다. OLE 컨테이너 지원을 구현하고 기존 애플리케이션에 통합하는 방법을 보여 줍니다.

  • SUPERPAD 이 애플리케이션은 훌륭한 독립 실행형 애플리케이션일 뿐만 아니라 OLE 서버이기도 합니다. 구현하는 서버 지원은 매우 미니멀합니다. 특히 OLE 클립보드 서비스를 사용하여 데이터를 클립보드에 복사하지만 Windows "편집" 컨트롤에 기본 제공되는 기능을 사용하여 클립보드 붙여넣기 기능을 구현하는 방법이 특히 중요합니다. 새로운 OLE API와의 통합뿐만 아니라 기존 Windows API 사용의 흥미로운 조합을 보여 줍니다.

샘플 애플리케이션에 대한 자세한 내용은 "MFC 샘플 도움말"을 참조하세요.

사례 연구: MFC 2.0의 OCLIENT

위에서 설명한 대로 OCLIENT 는 MFC 2.0에 포함되었으며 MFC/OLE1을 사용하여 OLE를 구현했습니다. 이 애플리케이션이 MFC/OLE 2 클래스를 사용하도록 처음 변환된 단계는 아래에 설명되어 있습니다. MFC/OLE 클래스를 보다 잘 설명하기 위해 초기 포트가 완료된 후 많은 기능이 추가되었습니다. 이러한 기능은 여기서 다루지 않습니다. 이러한 고급 기능에 대한 자세한 내용은 샘플 자체를 참조하세요.

참고 항목

컴파일러 오류 및 단계별 프로세스는 Visual C++ 2.0을 사용하여 만들어졌습니다. Visual C++ 4.0에서 특정 오류 메시지 및 위치가 변경되었을 수 있지만 개념 정보는 유효한 기본.

시작 및 실행

OCLIENT 샘플을 MFC/OLE로 이식하는 방법은 먼저 OCLIENT 샘플을 빌드하고 발생할 명백한 컴파일러 오류를 수정하는 것입니다. MFC 2.0에서 OCLIENT 샘플을 가져와 이 버전의 MFC에서 컴파일하는 경우 해결할 오류가 많지 않다는 것을 알 수 있습니다. 발생한 순서의 오류는 아래에 설명되어 있습니다.

오류 컴파일 및 수정

\oclient\mainview.cpp(104) : error C2660: 'Draw' : function does not take 4 parameters

첫 번째 오류는 다음과 같습니다.COleClientItem::Draw MFC/OLE1에서는 MFC/OLE 버전보다 더 많은 매개 변수가 필요했습니다. 추가 매개 변수는 종종 필요하지 않으며 일반적으로 NULL입니다(이 예제와 같이). 이 버전의 MFC는 그려지는 CDC가 메타파일 DC인 경우 lpWBounds의 값을 자동으로 확인할 수 있습니다. 또한 프레임워크가 전달된 pDC의 "특성 DC"에서 pFormatDC 매개 변수를 빌드하므로 더 이상 필요하지 않습니다. 따라서 이 문제를 해결하려면 그리기 호출에 대한 두 개의 추가 NULL 매개 변수를 제거하면 됩니다.

\oclient\mainview.cpp(273) : error C2065: 'OLE_MAXNAMESIZE' : undeclared identifier
\oclient\mainview.cpp(273) : error C2057: expected constant expression
\oclient\mainview.cpp(280) : error C2664: 'CreateLinkFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(286) : error C2664: 'CreateFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(288) : error C2664: 'CreateStaticFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '

위의 오류는 MFC/OLE1의 COleClientItem::CreateXXXX 모든 함수에서 항목을 나타내기 위해 고유한 이름을 전달해야 하므로 발생합니다. 이는 기본 OLE API의 요구 사항이었습니다. OLE 2는 기본 통신 메커니즘으로 DDE를 사용하지 않으므로 MFC/OLE 2에서는 필요하지 않습니다(이름은 DDE 대화에서 사용됨). 이 문제를 해결하려면 함수와 함수에 CreateNewName 대한 모든 참조를 제거할 수 있습니다. 호출에 커서를 놓고 F1 키를 누르면 이 버전에서 각 MFC/OLE 함수가 무엇을 기대하는지 쉽게 알 수 있습니다.

상당히 다른 다른 영역은 OLE 2 클립보드 처리입니다. OLE1에서는 Windows 클립보드 API가 클립보드와 상호 작용하는 데 사용했습니다. OLE 2를 사용하면 다른 메커니즘으로 수행됩니다. MFC/OLE1 API는 개체를 클립보드에 복사 COleClientItem 하기 전에 클립보드가 열려 있다고 가정했습니다. 더 이상 필요하지 않으며 모든 MFC/OLE 클립보드 작업이 실패합니다. 코드를 편집하여 종속성을 CreateNewName제거하는 동안 Windows 클립보드를 열고 닫는 코드도 제거해야 합니다.

\oclient\mainview.cpp(332) : error C2065: 'AfxOleInsertDialog' : undeclared identifier
\oclient\mainview.cpp(332) : error C2064: term does not evaluate to a function
\oclient\mainview.cpp(344) : error C2057: expected constant expression
\oclient\mainview.cpp(347) : error C2039: 'CreateNewObject' : is not a member of 'CRectItem'

이러한 오류는 처리기에서 발생합니다 CMainView::OnInsertObject . "새 개체 삽입" 명령을 처리하는 것은 상황이 상당히 변경된 또 다른 영역입니다. 이 경우 새 OLE 컨테이너 애플리케이션에 대해 AppWizard에서 제공한 원래 구현을 병합하는 것이 가장 쉽습니다. 실제로 이는 다른 애플리케이션 포팅에 적용할 수 있는 기술입니다. MFC/OLE1에서는 함수를 호출 AfxOleInsertDialog 하여 "개체 삽입" 대화 상자를 표시했습니다. 이 버전에서는 대화 상자 개체를 COleInsertObject 생성하고 DoModal호출합니다. 또한 클래스 이름 문자열 대신 CLSID사용하여 새 OLE 항목을 만듭니다. 최종 결과는 다음과 같이 표시됩니다.

COleInsertDialog dlg;
if (dlg.DoModal() != IDOK)
    return;

BeginWaitCursor();

CRectItem* pItem = NULL;
TRY
{
    // First create the C++ object
    pItem = GetDocument()->CreateItem();
    ASSERT_VALID(pItem);

    // Initialize the item from the dialog data.
    if (!dlg.CreateItem(pItem))
        AfxThrowMemoryException();
            // any exception will do
    ASSERT_VALID(pItem);

    // run the object if appropriate
    if (dlg.GetSelectionType() == COleInsertDialog::createNewItem)
        pItem->DoVerb(OLEIVERB_SHOW, this);

    // update right away
    pItem->UpdateLink();
    pItem->UpdateItemRectFromServer();

    // set selection to newly inserted item
    SetSelection(pItem);
    pItem->Invalidate();
}
CATCH (CException, e)
{
    // clean up item
    if (pItem != NULL)
        GetDocument()->DeleteItem(pItem);

    AfxMessageBox(IDP_FAILED_TO_CREATE);
}
END_CATCH

EndWaitCursor();

참고 항목

새 개체 삽입은 애플리케이션에 따라 다를 수 있습니다.

또한 대화 클래스에 대한 COleInsertObject 선언과 MFC에서 제공하는 다른 표준 대화 상자가 포함된 afxodlgs.h>를 포함<해야 합니다.

\oclient\mainview.cpp(367) : error C2065: 'OLEVERB_PRIMARY' : undeclared identifier
\oclient\mainview.cpp(367) : error C2660: 'DoVerb' : function does not take 1 parameters

이러한 오류는 개념에서 동일하더라도 일부 OLE1 상수가 OLE 2에서 변경되었다는 사실 때문에 발생합니다. 이 경우 OLEVERB_PRIMARY .로 변경되었습니다 OLEIVERB_PRIMARY. OLE1 및 OLE 2에서 기본 동사는 일반적으로 사용자가 항목을 두 번 클릭할 때 컨테이너에 의해 실행됩니다.

또한 DoVerb 이제 뷰(CView*)에 대한 포인터인 추가 매개 변수를 사용합니다. 이 매개 변수는 "시각적 편집"(또는 현재 위치 활성화)을 구현하는 데만 사용됩니다. 지금은 이 기능을 구현하지 않으므로 해당 매개 변수를 NULL로 설정합니다.

프레임워크가 정품 인증을 시도하지 않도록 하려면 다음과 같이 재정 COleClientItem::CanActivate 의해야 합니다.

BOOL CRectItem::CanActivate()
{
    return FALSE;
}
\oclient\rectitem.cpp(53) : error C2065: 'GetBounds' : undeclared identifier
\oclient\rectitem.cpp(53) : error C2064: term does not evaluate to a function
\oclient\rectitem.cpp(84) : error C2065: 'SetBounds' : undeclared identifier
\oclient\rectitem.cpp(84) : error C2064: term does not evaluate to a function

MFC/OLE1 COleClientItem::GetBoundsSetBounds 에서는 항목의 익스텐트를 쿼리하고 조작하는 데 사용되었습니다(및 top 멤버는 left 항상 0임). MFC/OLE 2에서는 SIZE를 처리하거나 CSize 대신 처리하는 보다 직접적으로 지원 COleClientItem::GetExtent 됩니다SetExtent.

새 SetItemRectToServer 및 UpdateItemRectFromServer 호출에 대한 코드는 다음과 같습니다.

BOOL CRectItem::UpdateItemRectFromServer()
{
    ASSERT(m_bTrackServerSize);
    CSize size;
    if (!GetExtent(&size))
        return FALSE;    // blank

    // map from HIMETRIC to screen coordinates
    {
        CClientDC screenDC(NULL);
        screenDC.SetMapMode(MM_HIMETRIC);
        screenDC.LPtoDP(&size);
    }
    // just set the item size
    if (m_rect.Size() != size)
    {
        // invalidate the old size/position
        Invalidate();
        m_rect.right = m_rect.left + size.cx;
        m_rect.bottom = m_rect.top + size.cy;
        // as well as the new size/position
        Invalidate();
    }
    return TRUE;
}

BOOL CRectItem::SetItemRectToServer()
{
    // set the official bounds for the embedded item
    CSize size = m_rect.Size();
    {
        CClientDC screenDC(NULL);
        screenDC.SetMapMode(MM_HIMETRIC);
        screenDC.DPtoLP(&size);
    }
    TRY
    {
        SetExtent(size);    // may do a wait
    }
    CATCH(CException, e)
    {
        return FALSE;  // links will not allow SetBounds
    }
    END_CATCH
    return TRUE;
}
\oclient\frame.cpp(50) : error C2039: 'InWaitForRelease' : is not a member of 'COleClientItem'
\oclient\frame.cpp(50) : error C2065: 'InWaitForRelease' : undeclared identifier
\oclient\frame.cpp(50) : error C2064: term does not evaluate to a function

MFC/OLE1에서는 컨테이너에서 서버로의 동기 API 호출이 시뮬레이션되었습니다. OLE1은 기본적으로 비동기적이었기 때문입니다. 사용자의 명령을 처리하기 전에 진행 중인 미해결 비동기 호출을 검사 것이 필요했습니다. MFC/OLE1은 COleClientItem::InWaitForRelease 이를 위한 함수를 제공했습니다. MFC/OLE 2에서는 필요하지 않으므로 CMainFrame에서 OnCommand의 재정의를 모두 함께 제거할 수 있습니다.

이때 OCLIENT가 컴파일되고 연결됩니다.

기타 필요한 변경 내용

그러나 OCLIENT를 실행하지 못하게 하는 작업은 거의 없습니다. 나중에 문제가 아닌 이러한 문제를 해결하는 것이 좋습니다.

먼저 OLE 라이브러리를 초기화해야 합니다. 이 작업은 다음에서 호출 AfxOleInit 하여 수행됩니다.InitInstance

if (!AfxOleInit())
{
    AfxMessageBox("Failed to initialize OLE libraries");
    return FALSE;
}

또한 매개 변수 목록 변경에 대한 가상 함수를 검사 것이 좋습니다. 이러한 함수 중 하나는 모든 MFC/OLE 컨테이너 애플리케이션에서 재정의되는 함수입니다 COleClientItem::OnChange. 온라인 도움말을 보면 추가 'DWORD dwParam'이 추가된 것을 볼 수 있습니다. 새 CRectItem::OnChange는 다음과 같습니다.

void
CRectItem::OnChange(OLE_NOTIFICATION wNotification, DWORD dwParam)
{
    if (m_bTrackServerSize && !UpdateItemRectFromServer())
    {
        // Blank object
        if (wNotification == OLE_CLOSED)
        {
            // no data received for the object - destroy it
            ASSERT(!IsVisible());
            GetDocument()->DeleteItem(this);
            return; // no update (item is gone now)
        }
    }
    if (wNotification != OLE_CLOSED)
        Dirty();
    Invalidate();
    // any change will cause a redraw
}

MFC/OLE1에서 컨테이너 애플리케이션은 에서 문서 클래스를 파생시켰습니다 COleClientDoc. MFC/OLE 2에서 이 클래스는 제거되고 대체 COleDocument 되었습니다(이 새 조직에서는 컨테이너/서버 애플리케이션을 더 쉽게 빌드할 수 있습니다). MFC/OLE1 애플리케이션을 MFC/OLE 2로의 이식을 간소화하기 위해 매핑 COleClientDocCOleDocument 되는 #define 있습니다(예: OCLIENT). 제공된 COleDocumentCOleClientDoc 기능 중 하나는 표준 명령 메시지 맵 항목입니다. 이는 간접적으로도 사용하는 COleDocument 서버 애플리케이션이 컨테이너/서버 애플리케이션이 아닌 한 이러한 명령 처리기의 오버헤드를 수행하지 않도록 하기 위해 수행됩니다. CMainDoc 메시지 맵에 다음 항목을 추가해야 합니다.

ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdatePasteMenu)
ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE_LINK, OnUpdatePasteLinkMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_LINKS, OnUpdateEditLinksMenu)
ON_COMMAND(ID_OLE_EDIT_LINKS, COleDocument::OnEditLinks)
ON_UPDATE_COMMAND_UI(ID_OLE_VERB_FIRST, OnUpdateObjectVerbMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_CONVERT, OnUpdateObjectVerbMenu)
ON_COMMAND(ID_OLE_EDIT_CONVERT, OnEditConvert)

이러한 모든 명령의 구현은 COleDocument문서의 기본 클래스인 에 있습니다.

이 시점에서 OCLIENT는 기능적인 OLE 컨테이너 애플리케이션입니다. 모든 형식의 항목(OLE1 또는 OLE 2)을 삽입할 수 있습니다. 현재 위치 활성화를 사용하도록 설정하는 데 필요한 코드가 구현되지 않으므로 항목은 OLE1과 마찬가지로 별도의 창에서 편집됩니다. 다음 섹션에서는 현재 위치 편집을 사용하도록 설정하는 데 필요한 변경 내용("시각적 편집"이라고도 함)에 대해 설명합니다.

"시각적 편집" 추가

OLE의 가장 흥미로운 기능 중 하나는 바로 활성화(또는 "시각적 편집")입니다. 이 기능을 사용하면 서버 애플리케이션이 컨테이너 사용자 인터페이스의 일부를 인수하여 사용자에게 보다 원활한 편집 인터페이스를 제공할 수 있습니다. OCLIENT에 대한 현재 위치 활성화를 구현하려면 일부 특수 리소스와 몇 가지 추가 코드를 추가해야 합니다. 이러한 리소스와 코드는 일반적으로 AppWizard에서 제공됩니다. 실제로 여기에 있는 대부분의 코드는 "컨테이너" 지원을 통해 새로운 AppWizard 애플리케이션에서 직접 빌렸습니다.

우선 현재 위치 활성 항목이 있을 때 사용할 메뉴 리소스를 추가해야 합니다. IDR_OCLITYPE 리소스를 복사하고 파일 및 창 팝업을 제외한 모든 항목을 제거하여 Visual C++에서 이 추가 메뉴 리소스를 만들 수 있습니다. 파일과 창 팝업 사이에 두 개의 구분선이 삽입되어 그룹의 분리를 나타냅니다(다음과 File || Window같아야 함). 이러한 구분 기호의 의미와 서버 및 컨테이너 메뉴 병합 방법에 대한 자세한 내용은 메뉴 및 리소스: 메뉴 병합을 참조하세요.

이러한 메뉴를 만든 후에는 프레임워크에 해당 메뉴에 대해 알려야 합니다. 이 작업은 InitInstance의 문서 서식 파일 목록에 추가하기 전에 문서 서식 파일을 호출 CDocTemplate::SetContainerInfo 하여 수행됩니다. 문서 서식 파일을 등록하는 새 코드는 다음과 같습니다.

CDocTemplate* pTemplate = new CMultiDocTemplate(
    IDR_OLECLITYPE,
    RUNTIME_CLASS(CMainDoc),
    RUNTIME_CLASS(CMDIChildWnd), // standard MDI child frame
    RUNTIME_CLASS(CMainView));

pTemplate->SetContainerInfo(IDR_OLECLITYPE_INPLACE);

AddDocTemplate(pTemplate);

IDR_OLECLITYPE_INPLACE 리소스는 Visual C++에서 만든 특수한 현재 위치 리소스입니다.

현재 위치 활성화를 사용하도록 설정하려면 (CMainView) 파생 클래스와 COleClientItem 파생 클래스(CRectItem) 모두에서 CView 변경해야 하는 몇 가지 사항이 있습니다. 이러한 모든 재정의는 AppWizard에서 제공되며 대부분의 구현은 기본 AppWizard 애플리케이션에서 직접 제공됩니다.

이 포트의 첫 번째 단계에서 현재 위치 활성화는 재정의하여 COleClientItem::CanActivate완전히 비활성화되었습니다. 현재 위치 활성화를 허용하려면 이 재정의를 제거해야 합니다. 또한 뷰를 제공하는 것은 현재 위치 활성화에 DoVerb 만 필요했기 때문에 NULL이 모든 호출에 전달되었습니다(두 가지가 있음). 현재 위치 활성화를 완전히 구현하려면 호출에서 DoVerb 올바른 보기를 전달해야 합니다. 다음 호출 중 하나는 다음과 같습니다.CMainView::OnInsertObject

pItem->DoVerb(OLEIVERB_SHOW, this);

다른 하나는 다음 위치에 있습니다.CMainView::OnLButtonDblClk

m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);

재정의해야 COleClientItem::OnGetItemPosition합니다. 그러면 항목이 현재 위치로 활성화될 때 컨테이너 창을 기준으로 창을 배치할 위치를 서버에 알립니다. OCLIENT의 경우 구현은 간단합니다.

void CRectItem::OnGetItemPosition(CRect& rPosition)
{
    rPosition = m_rect;
}

대부분의 서버는 "현재 위치 크기 조정"을 구현합니다. 이렇게 하면 사용자가 항목을 편집하는 동안 서버 창의 크기를 조정하고 이동할 수 있습니다. 창 이동 또는 크기 조정은 일반적으로 컨테이너 문서 자체 내의 위치와 크기에 영향을 주므로 컨테이너는 이 작업에 참여해야 합니다. OCLIENT 구현은 m_rect 기본 내부 사각형을 새 위치 및 크기와 동기화합니다.

BOOL CRectItem::OnChangeItemPosition(const CRect& rectPos)
{
    ASSERT_VALID(this);

    if (!COleClientItem::OnChangeItemPosition(rectPos))
        return FALSE;

    Invalidate();
    m_rect = rectPos;
    Invalidate();
    GetDocument()->SetModifiedFlag();

    return TRUE;
}

이 시점에서 항목이 활성화되고 활성화될 때 항목의 크기 조정 및 이동을 처리할 수 있는 충분한 코드가 있지만 사용자가 편집 세션을 종료할 수 있는 코드는 없습니다. 일부 서버는 이스케이프 키를 처리하여 이 기능을 직접 제공하지만 컨테이너는 항목을 비활성화하는 두 가지 방법을 제공하는 것이 좋습니다. (1) 항목 외부를 클릭하여, (2) ESCAPE 키를 눌러서.

ESCAPE 키의 경우 VK_ESCAPE 키를 명령에 매핑하는 Visual C++를 사용하여 가속기를 추가합니다. ID_CANCEL_EDIT 리소스에 추가됩니다. 이 명령의 처리기는 다음과 같습니다.

// The following command handler provides the standard
// keyboard user interface to cancel an in-place
// editing session.void CMainView::OnCancelEdit()
{
    // Close any in-place active item on this view.
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->Close();
    ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);
}

사용자가 항목 외부에서 클릭하는 경우를 처리하려면 다음 코드를 시작 CMainView::SetSelection부분에 추가합니다.

if (pNewSel != m_pSelection || pNewSel == NULL)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL&& pActiveItem != pNewSel)
        pActiveItem->Close();
}

항목이 현재 위치 활성 상태이면 포커스가 있어야 합니다. 보기에서 포커스를 받을 때 포커스가 항상 활성 항목으로 전송되도록 OnSetFocus를 처리하는 경우를 확인하려면 다음을 수행합니다.

// Special handling of OnSetFocus and OnSize are required
// when an object is being edited in-place.
void CMainView::OnSetFocus(CWnd* pOldWnd)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);

    if (pActiveItem != NULL &&
        pActiveItem->GetItemState() == COleClientItem::activeUIState)
    {
        // need to set focus to this item if it is same view
        CWnd* pWnd = pActiveItem->GetInPlaceWindow();
        if (pWnd != NULL)
        {
            pWnd->SetFocus();   // don't call the base class
            return;
        }
    }

    CView::OnSetFocus(pOldWnd);
}

보기 크기가 조정되면 활성 항목에 클리핑 사각형이 변경되었음을 알려야 합니다. 이렇게 하려면 다음을 위한 OnSize처리기를 제공합니다.

void CMainView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->SetItemRects();
}

사례 연구: MFC 2.0의 HIERSVR

HIERSVR 은 MFC 2.0에도 포함되었으며 MFC/OLE1을 사용하여 OLE를 구현했습니다. 이 참고에서는 이 애플리케이션이 MFC/OLE 2 클래스를 사용하도록 처음 변환된 단계를 간략하게 설명합니다. MFC/OLE 2 클래스를 보다 잘 설명하기 위해 초기 포트가 완료된 후 많은 기능이 추가되었습니다. 이러한 기능은 여기서 다루지 않습니다. 이러한 고급 기능에 대한 자세한 내용은 샘플 자체를 참조하세요.

참고 항목

컴파일러 오류 및 단계별 프로세스는 Visual C++ 2.0을 사용하여 만들어졌습니다. Visual C++ 4.0에서 특정 오류 메시지 및 위치가 변경되었을 수 있지만 개념 정보는 유효한 기본.

시작 및 실행

HIERSVR 샘플을 MFC/OLE로 이식하는 방법은 먼저 이를 빌드하고 발생할 명백한 컴파일러 오류를 수정하는 것입니다. MFC 2.0에서 HIERSVR 샘플을 가져와 이 버전의 MFC에서 컴파일하는 경우 OCLIENT 샘플보다 많은 오류가 있지만 해결해야 할 오류는 많지 않습니다. 일반적으로 발생하는 순서의 오류는 아래에 설명되어 있습니다.

오류 컴파일 및 수정

\hiersvr\hiersvr.cpp(83) : error C2039: 'RunEmbedded' : is not a member of 'COleTemplateServer'

이 첫 번째 오류는 서버의 함수에 InitInstance 대한 훨씬 더 큰 문제를 지적합니다. OLE 서버에 필요한 초기화는 MFC/OLE1 애플리케이션을 실행하기 위해 수행해야 하는 가장 큰 변경 사항 중 하나일 것입니다. 가장 좋은 방법은 AppWizard가 OLE 서버에 대해 만드는 작업을 살펴보고 코드를 적절하게 수정하는 것입니다. 다음은 유의해야 할 몇 가지 사항입니다.

호출하여 OLE 라이브러리를 초기화해야 합니다. AfxOleInit

문서 템플릿 개체에서 SetServerInfo를 호출하여 생성자로 설정할 수 없는 서버 리소스 핸들 및 런타임 클래스 정보를 설정합니다 CDocTemplate .

명령줄에 /Embedding이 있는 경우 애플리케이션의 기본 창을 표시하지 마세요.

문서에 대한 GUID필요합니다. 문서 형식(128비트)에 대한 고유 식별자입니다. AppWizard는 사용자를 위해 만듭니다. 따라서 여기에 설명된 기술을 사용하여 새 AppWizard 생성 서버 애플리케이션에서 새 코드를 복사하는 경우 해당 애플리케이션에서 GUID를 "도용"할 수 있습니다. 그렇지 않은 경우 BIN 디렉터리에서 GUIDGEN.EXE 유틸리티를 사용할 수 있습니다.

를 호출COleTemplateServer::ConnectTemplate하여 개체를 문서 서식 파일에 "연결"해야 합니다COleTemplateServer.

애플리케이션이 독립 실행형으로 실행되면 시스템 레지스트리를 업데이트합니다. 이렇게 하면 사용자가 애플리케이션에 대한 .EXE를 이동하는 경우 새 위치에서 실행하면 새 위치를 가리키도록 Windows 시스템 등록 데이터베이스가 업데이트됩니다.

AppWizard에서 만드는 항목에 따라 이러한 모든 변경 내용을 적용한 후 HIERSVR에 대한 InitInstanceInitInstance (및 관련 GUID)는 다음과 같이 읽어야 합니다.

// this is the GUID for HIERSVR documents
static const GUID BASED_CODE clsid =
{ 0xA0A16360L, 0xC19B, 0x101A, { 0x8C, 0xE5, 0x00, 0xDD, 0x01, 0x11, 0x3F, 0x12 } };

/////////////////////////////////////////////////////////////////////////////
// COLEServerApp initialization

BOOL COLEServerApp::InitInstance()
{
    // OLE 2 initialization
    if (!AfxOleInit())
    {
        AfxMessageBox("Initialization of the OLE failed!");
        return FALSE;
    }

    // Standard initialization
    LoadStdProfileSettings();   // Load standard INI file options

    // Register document templates
    CDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(IDR_HIERSVRTYPE,
        RUNTIME_CLASS(CServerDoc),
        RUNTIME_CLASS(CMDIChildWnd),
        RUNTIME_CLASS(CServerView));
    pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB);
    AddDocTemplate(pDocTemplate);

    // create main MDI Frame window
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
        return FALSE;
    m_pMainWnd = pMainFrame;

    SetDialogBkColor(); // gray look

    // enable file manager drag/drop and DDE Execute open
    m_pMainWnd->DragAcceptFiles();
    EnableShellOpen();

    m_server.ConnectTemplate(clsid, pDocTemplate, FALSE);
    COleTemplateServer::RegisterAll();

    // try to launch as an OLE server
    if (RunEmbedded())
    {
        // "short-circuit" initialization -- run as server!
        return TRUE;
    }
    m_server.UpdateRegistry();
    RegisterShellFileTypes();

    // not run as OLE server, so show the main window
    if (m_lpCmdLine[0] == '\0')
    {
        // create a new (empty) document
        OnFileNew();
    }
    else
    {
        // open an existing document
        OpenDocumentFile(m_lpCmdLine);
    }

    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();

    return TRUE;
}

위의 코드는 IDR_HIERSVRTYPE_SRVR_EMB 새 리소스 ID를 참조하는 것을 알 수 있습니다. 다른 컨테이너에 포함된 문서를 편집할 때 사용할 메뉴 리소스입니다. MFC/OLE1에서는 포함된 항목을 편집하는 데 관련된 메뉴 항목이 즉석에서 수정되었습니다. 파일 기반 문서를 편집하는 대신 포함된 항목을 편집할 때 완전히 다른 메뉴 구조를 사용하면 이러한 두 가지 개별 모드에 대해 다른 사용자 인터페이스를 훨씬 쉽게 제공할 수 있습니다. 나중에 볼 수 있듯이 포함된 개체를 현재 위치에서 편집할 때 완전히 별도의 메뉴 리소스가 사용됩니다.

이 리소스를 만들려면 리소스 스크립트를 Visual C++에 로드하고 기존 IDR_HIERSVRTYPE 메뉴 리소스를 복사합니다. 새 리소스의 이름을 IDR_HIERSVRTYPE_SRVR_EMB 바꿉니다(AppWizard에서 사용하는 것과 동일한 명명 규칙). 다음으로 "파일 저장"을 "파일 업데이트"로 변경합니다. 명령 ID ID_FILE_UPDATE 제공합니다. 또한 "다른 이름으로 파일 저장"을 "다른 이름으로 파일 저장"으로 변경합니다. 명령 ID ID_FILE_SAVE_COPY_AS 제공합니다. 프레임워크는 이러한 두 명령의 구현을 제공합니다.

\hiersvr\svritem.h(60) : error C2433: 'OLESTATUS' : 'virtual' not permitted on data declarations
\hiersvr\svritem.h(60) : error C2501: 'OLESTATUS' : missing decl-specifiers
\hiersvr\svritem.h(60) : error C2146: syntax error : missing ';' before identifier 'OnSetData'
\hiersvr\svritem.h(60) : error C2061: syntax error : identifier 'OLECLIPFORMAT'
\hiersvr\svritem.h(60) : error C2501: 'OnSetData' : missing decl-specifiers

OLESTATUS 형식을 참조하므로 재정의OnSetData로 인해 발생하는 많은 오류가 있습니다. OLESTATUS 는 OLE1에서 오류를 반환하는 방식이었습니다. MFC는 일반적으로 HRESULT를 포함하는 오류로 변환하지만 OLE 2의 HRESULTCOleException 변경되었습니다. 이 특정 경우 재정의 OnSetData 는 더 이상 필요하지 않으므로 가장 쉬운 작업은 제거하는 것입니다.

\hiersvr\svritem.cpp(30) : error C2660: 'COleServerItem::COleServerItem' : function does not take 1 parameters

COleServerItem 생성자는 추가 'BOOL' 매개 변수를 사용합니다. 이 플래그는 개체에서 COleServerItem 메모리 관리가 수행되는 방법을 결정합니다. 이 개체를 TRUE로 설정하면 프레임워크는 이러한 개체의 메모리 관리를 처리하여 더 이상 필요하지 않을 때 삭제합니다. HIERSVR은 네이티브 데이터의 일부로 개체를 사용 CServerItem 하므로 COleServerItem이 플래그를 FALSE로 설정합니다. 이를 통해 HIERSVR은 각 서버 항목이 삭제되는 시기를 결정할 수 있습니다.

\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class
\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class

이러한 오류에서 알 수 있듯이 CServerItem에서 재정의되지 않은 일부 '순수 가상' 함수가 있습니다. 대부분의 경우 이는 OnDraw의 매개 변수 목록이 변경되어 발생합니다. 이 오류를 해결하려면 다음과 같이 변경 CServerItem::OnDraw 하고 svritem.h의 선언도 변경합니다.

BOOL CServerItem::OnDraw(CDC* pDC, CSize& rSize)
{
    // request from OLE to draw node
    pDC->SetMapMode(MM_TEXT); // always in pixels
    return DoDraw(pDC, CPoint(0, 0), FALSE);
}

새 매개 변수는 'rSize'입니다. 이렇게 하면 편리한 경우 드로잉의 크기를 채울 수 있습니다. 이 크기는 HIMETRIC있어야 합니다. 이 경우 이 값을 채우는 것이 편리하지 않으므로 프레임워크는 익스텐트 검색을 호출 OnGetExtent 합니다. 이렇게 하려면 다음을 구현 OnGetExtent해야 합니다.

BOOL CServerItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
    if (dwDrawAspect != DVASPECT_CONTENT)
        return COleServerItem::OnGetExtent(dwDrawAspect, rSize);

    rSize = CalcNodeSize();
    return TRUE;
}
\hiersvr\svritem.cpp(104) : error C2065: 'm_rectBounds' : undeclared identifier
\hiersvr\svritem.cpp(104) : error C2228: left of '.SetRect' must have class/struct/union type
\hiersvr\svritem.cpp(106) : error C2664: 'void __pascal __far DPtoLP(struct ::tagPOINT __far *,
    int)__far const ' : cannot convert parameter 1 from 'int __far *' to 'struct ::tagPOINT __far *'

CServerItem::CalcNodeSize 함수에서 항목 크기는 HIMETRIC으로 변환되고 m_rectBounds 저장됩니다. 문서화되지 않은 'm_rectBounds' 멤버 COleServerItem 가 없습니다(부분적으로 m_sizeExtent 대체되었지만 OLE 2에서는 이 멤버가 OLE1에서 m_rectBounds 것과 약간 다른 사용법이 있음). HIMETRIC 크기를 이 멤버 변수로 설정하는 대신 반환합니다. 이 반환 값은 이전에 구현된 에서 OnGetExtent사용됩니다.

CSize CServerItem::CalcNodeSize()
{
    CClientDC dcScreen(NULL);

    m_sizeNode = dcScreen.GetTextExtent(m_strDescription,
        m_strDescription.GetLength());
    m_sizeNode += CSize(CX_INSET * 2, CY_INSET * 2);

    // set suggested HIMETRIC size
    CSize size(m_sizeNode.cx, m_sizeNode.cy);
    dcScreen.SetMapMode(MM_HIMETRIC);
    dcScreen.DPtoLP(&size);
    return size;
}

CServerItem도 재정의합니다 COleServerItem::OnGetTextData. 이 함수는 MFC/OLE에서 사용되지 않으며 다른 메커니즘으로 대체됩니다. MFC OLE 샘플 HIERSVR 의 MFC 3.0 버전은 재정의하여 이 기능을 구현합니다 COleServerItem::OnRenderFileData. 이 기능은 이 기본 포트에 중요하지 않으므로 OnGetTextData 재정의를 제거할 수 있습니다.

svritem.cpp에는 해결되지 않은 더 많은 오류가 있습니다. 이러한 오류는 "실제" 오류가 아니며 이전 오류로 인한 오류일 뿐입니다.

\hiersvr\svrview.cpp(325) : error C2660: 'CopyToClipboard' : function does not take 2 parameters

COleServerItem::CopyToClipboard 더 이상 플래그를 bIncludeNative 지원하지 않습니다. 네이티브 데이터(서버 항목의 Serialize 함수에서 작성한 데이터)는 항상 복사되므로 첫 번째 매개 변수를 제거합니다. 또한 CopyToClipboard FALSE를 반환하는 대신 오류가 발생할 때 예외를 throw합니다. 다음과 같이 CServerView::OnEditCopy에 대한 코드를 변경합니다.

void CServerView::OnEditCopy()
{
    if (m_pSelectedNode == NULL)
        AfxThrowNotSupportedException();

    TRY
    {
        m_pSelectedNode->CopyToClipboard(TRUE);
    }
    CATCH_ALL(e)
    {
        AfxMessageBox("Copy to clipboard failed");
    }
    END_CATCH_ALL
}

동일한 버전의 OCLIENT보다 MFC 2.0 버전의 HIERSVR 컴파일로 인한 오류가 더 많았지만 실제로는 변경 내용이 적습니다.

이 시점에서 HIERSVR은 컴파일하고 연결하고 OLE 서버로 작동하지만, 다음에 구현될 현재 위치 편집 기능이 없습니다.

"시각적 편집" 추가

이 서버 애플리케이션에 "시각적 편집"(또는 현재 위치 정품 인증)을 추가하려면 몇 가지 사항만 처리해야 합니다.

  • 항목이 현재 위치 활성 상태일 때 사용할 특별한 메뉴 리소스가 필요합니다.

  • 이 애플리케이션에는 도구 모음이 있으므로 서버에서 사용할 수 있는 메뉴 명령과 일치하도록 일반 도구 모음의 하위 집합만 있는 도구 모음이 필요합니다(위에 멘션 메뉴 리소스와 일치).

  • 현재 위치 사용자 인터페이스를 제공하는 파생된 COleIPFrameWnd 새 클래스가 필요합니다(CMainFrame과 마찬가지로 파생된 CMDIFrameWndCMainFrame은 MDI 사용자 인터페이스를 제공합니다).

  • 이러한 특수 리소스 및 클래스에 대해 프레임워크에 알려야 합니다.

메뉴 리소스를 쉽게 만들 수 있습니다. Visual C++를 실행하고 메뉴 리소스 IDR_HIERSVRTYPE IDR_HIERSVRTYPE_SRVR_IP 메뉴 리소스에 복사합니다. 편집 및 도움말 메뉴 팝업만 남도록 메뉴를 수정합니다. 편집 및 도움말 메뉴 사이에 메뉴에 두 개의 구분 기호를 추가합니다(다음과 같 Edit || Help아야 합니다.). 이러한 구분 기호의 의미와 서버 및 컨테이너 메뉴가 병합되는 방법에 대한 자세한 내용은 메뉴 및 리소스: 메뉴 병합을 참조하세요.

하위 집합 도구 모음의 비트맵은 "서버" 옵션이 검사 새 AppWizard 생성 애플리케이션에서 복사하여 쉽게 만들 수 있습니다. 그런 다음, 이 비트맵을 Visual C++로 가져올 수 있습니다. 비트맵에 IDR_HIERSVRTYPE_SRVR_IP ID를 지정해야 합니다.

파생된 COleIPFrameWnd 클래스는 서버 지원을 통해 AppWizard에서 생성된 애플리케이션에서도 복사할 수 있습니다. 두 파일( IPFRAME)을 복사합니다. CPP 및 IPFRAME. H를 사용하여 프로젝트에 추가합니다. 호출이 LoadBitmap 이전 단계에서 만든 비트맵인 IDR_HIERSVRTYPE_SRVR_IP 참조하는지 확인합니다.

이제 모든 새 리소스와 클래스가 생성되었으므로 프레임워크에서 이를 알 수 있도록 필요한 코드를 추가합니다(이제 이 애플리케이션이 현재 위치 편집을 지원한다는 것을 알고 있습니다). 이 작업은 함수의 호출에 몇 가지 매개 변수를 SetServerInfo 더 추가하여 수행됩니다 InitInstance .

pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
    IDR_HIERSVRTYPE_SRVR_IP,
    RUNTIME_CLASS(CInPlaceFrame));

이제 현재 위치 활성화를 지원하는 모든 컨테이너에서 바로 실행할 준비가 되었습니다. 그러나 코드에 아직 숨어 있는 사소한 버그가 하나 있습니다. HIERSVR은 사용자가 마우스 오른쪽 단추를 누를 때 표시되는 상황에 맞는 메뉴를 지원합니다. 이 메뉴는 HIERSVR이 완전히 열려 있을 때 작동하지만 포함을 현재 위치에서 편집할 때는 작동하지 않습니다. CServerView::OnRButtonDown에서 이 단일 코드 줄로 이유를 고정할 수 있습니다.

pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
    point.x,
    point.y,
    AfxGetApp()->m_pMainWnd);

에 대한 참조를 확인합니다 AfxGetApp()->m_pMainWnd. 서버가 현재 위치로 활성화되면 기본 창이 있고 m_pMainWnd 설정되지만 일반적으로 보이지 않습니다. 또한 이 창은 서버가 완전히 열려 있거나 독립 실행형으로 실행되면 나타나는 MDI 프레임 창인 애플리케이션의 기본 창을 나타냅니다. 활성 프레임 창을 참조하지 않습니다. 즉, 현재 위치에서 활성화되는 경우에서 파생된 COleIPFrameWnd프레임 창입니다. 현재 위치 편집 시에도 올바른 활성 창을 가져오기 위해 이 버전의 MFC는 새 함수 AfxGetMainWnd를 추가합니다. 일반적으로 이 함수는 AfxGetApp()->m_pMainWnd대신 사용해야 합니다. 이 코드는 다음과 같이 변경해야 합니다.

pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
    point.x,
    point.y,
    AfxGetMainWnd());

이제 현재 위치 정품 인증을 위해 OLE 서버를 최소한으로 사용하도록 설정했습니다. 그러나 MFC/OLE1에서는 사용할 수 없는 MFC/OLE 2에서 사용할 수 있는 기능이 여전히 많이 있습니다. 구현할 수 있는 기능에 대한 자세한 내용은 HIERSVR 샘플을 참조하세요. HIERSVR에서 구현하는 기능 중 일부는 다음과 같습니다.

  • 컨테이너와 관련된 진정한 WYSIWYG 동작에 대한 확대/축소입니다.

  • 끌어서 놓기 및 사용자 지정 클립보드 형식

  • 선택 영역이 변경될 때 컨테이너 창을 스크롤합니다.

MFC 3.0의 HIERSVR 샘플은 서버 항목에 대해 약간 다른 디자인을 사용합니다. 이렇게 하면 메모리를 절약하고 링크를 보다 유연하게 만들 수 있습니다. 2.0 버전의 HIERSVR을 사용하면 트리 의 각 노드는 -aCOleServerItem입니다. COleServerItem 는 이러한 각 노드에 대해 엄격하게 필요한 것보다 약간 더 많은 오버헤드를 수행하지만 COleServerItem 각 활성 링크에 필요합니다. 그러나 대부분의 경우 지정된 시간에 활성 링크가 거의 없습니다. 이를 보다 효율적으로 만들기 위해 이 MFC 버전의 HIERSVR은 노드를 .에서 COleServerItem분리합니다. CServerNode와 클래스가 모두 있습니다 CServerItem . 파생된 COleServerItem항목은 CServerItem 필요에 따라 생성됩니다. 컨테이너(또는 컨테이너)가 특정 노드에 대한 특정 링크 사용을 중지하면 CServerNode와 연결된 CServerItem 개체가 삭제됩니다. 이 디자인은 더 효율적이고 유연합니다. 여러 선택 링크를 처리할 때 유연성이 제공됩니다. 이러한 두 버전의 HIERSVR 중 어느 것도 여러 선택을 지원하지 않지만 네이티브 데이터와 분리되므로 MFC 3.0 버전의 HIERSVR COleServerItem 을 사용하여 추가(및 이러한 선택에 대한 링크를 지원)하는 것이 훨씬 쉽습니다.

참고 항목

번호별 기술 참고 사항
범주별 기술 참고 사항