TN041. Миграция MFC/OLE1 на MFC/OLE 2

Примечание.

Следующее техническое примечание не было обновлено, поскольку сначала оно было включено в электронную документацию. В результате некоторые процедуры и разделы могут быть устаревшими или неверными. Для получения последних сведений рекомендуется выполнить поиск интересующей темы в алфавитном указателе документации в Интернете.

Общие проблемы, связанные с миграцией

Одна из целей проектирования для классов OLE 2 в MFC 2.5 (и выше) заключалась в сохранении большей части той же архитектуры, что и поддержка OLE 1.0 в MFC 2.0. В результате многие из таких же классов OLE в MFC 2.0 по-прежнему существуют в этой версии MFC (COleDocument, COleServerDoc, COleClientItem). COleServerItem Кроме того, многие API в этих классах точно одинаковы. Однако OLE 2 резко отличается от OLE 1.0, поэтому можно ожидать, что некоторые из деталей изменились. Если вы знакомы с поддержкой OLE1 mFC 2.0, вы будете чувствовать себя дома с поддержкой MFC 2.0.

Если вы принимаете существующее приложение MFC/OLE1 и добавляете в него функции OLE 2, сначала следует прочитать эту заметку. В этой заметке рассматриваются некоторые общие проблемы, которые могут возникнуть при переносе функций OLE1 в MFC/OLE 2, а затем рассматриваются проблемы, обнаруженные при переносе двух приложений, включенных в MFC 2.0: примеры OCLIENT и HIERSVR MFC.

Архитектура документа и представления MFC важна

Если приложение не использует архитектуру документа и представления документов и представлений MFC, и вы хотите добавить поддержку OLE 2 в приложение, теперь пришло время перейти к документу или представлению. Многие преимущества классов OLE 2 MFC реализуются только после использования встроенной архитектуры и компонентов MFC.

Реализация сервера или контейнера без использования архитектуры MFC возможна, но не рекомендуется.

Использование реализации MFC вместо собственного

Классы MFC "canned implementation", такие как CToolBar, CStatusBarи CScrollView имеют встроенный код специального регистра для поддержки OLE 2. Таким образом, если вы можете использовать эти классы в приложении, вы получите выгоду от усилий, которые поставляются в них, чтобы сделать их с учетом OLE. Опять же, можно "свернуть собственные" классы здесь для этих целей, но это не рекомендуется. Если необходимо реализовать аналогичную функциональность, исходный код MFC является отличной ссылкой для работы с некоторыми из более подробных точек OLE (особенно когда речь идет о активации на месте).

Изучение примера кода MFC

Существует ряд примеров MFC, включающих функции OLE. Каждое из этих приложений реализует OLE с разного угла:

  • HIERSVR предназначен в основном для использования в качестве серверного приложения. Он был включен в MFC 2.0 в качестве приложения MFC/OLE1 и был перенесен в MFC/OLE 2, а затем расширен таким образом, чтобы он реализовал множество функций OLE, доступных в OLE 2.

  • OCLIENT Это автономное приложение контейнера, предназначенное для демонстрации многих функций OLE с точки зрения контейнера. Она также была перенесена из MFC 2.0, а затем расширена для поддержки многих более сложных функций OLE, таких как настраиваемые форматы буфера обмена и ссылки на внедренные элементы.

  • DRAWCLI Это приложение реализует поддержку контейнера OLE, как и OCLIENT, за исключением того, что это делает в рамках существующей объектно-ориентированной программы рисования. Здесь показано, как реализовать поддержку контейнеров OLE и интегрировать его в существующее приложение.

  • SUPERPAD Это приложение, а также отличное автономное приложение также является сервером OLE. Поддержка сервера, реализуемая им, является довольно минималистикой. Особенно интересно использовать службы буфера обмена OLE для копирования данных в буфер обмена, но используют функциональные возможности, встроенные в элемент управления "Изменить" Windows для реализации функций вставки буфера обмена. В этом примере показано интересное сочетание традиционных использования API Windows, а также интеграции с новыми API OLE.

Дополнительные сведения о примерах приложений см. в разделе "Справка по образцу MFC".

Пример: OCLIENT из MFC 2.0

Как упоминалось выше, OCLIENT был включен в MFC 2.0 и реализован OLE с помощью MFC/OLE1. Ниже описаны шаги, с помощью которых изначально преобразовано это приложение для использования классов MFC/OLE 2. После завершения начального порта были добавлены ряд функций, чтобы лучше проиллюстрировать классы MFC/OLE. Эти функции не будут рассмотрены здесь; Ознакомьтесь с примером для получения дополнительных сведений об этих расширенных функциях.

Примечание.

Ошибки компилятора и пошаговый процесс были созданы с помощью Visual C++ 2.0. Некоторые сообщения об ошибках и расположения могут быть изменены с помощью Visual C++ 4.0, но концептуальная информация остается допустимой.

Получение и запуск

Подход, используемый для переноса примера OCLIENT в MFC/OLE, заключается в том, чтобы начать с создания и устранения очевидных ошибок компилятора, которые будут привести. Если вы принимаете пример OCLIENT из MFC 2.0 и компилируете его в этой версии MFC, вы обнаружите, что для устранения ошибок не существует. Ошибки в порядке, в котором они произошли, описаны ниже.

Компиляция и исправление ошибок

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

Первая ошибка.COleClientItem::Draw В MFC/OLE1 потребовалось больше параметров, чем принимает версия MFC/OLE. Дополнительные параметры часто не требуются и обычно null (как в этом примере). Эта версия MFC может автоматически определять значения для lpWBounds, когда cdC, нарисованный в виде контроллера домена метафайла. Кроме того, параметр pFormatDC больше не требуется, так как платформа будет создавать одну из "атрибутов контроллера домена" переданного pDC. Чтобы устранить эту проблему, необходимо просто удалить два дополнительных параметра NULL для вызова Draw.

\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 '

Приведенные выше ошибки приводят к тому, что все COleClientItem::CreateXXXX функции в MFC/OLE1 требуют, чтобы уникальное имя было передано для представления элемента. Это было требованием базового API OLE. Это необязательно в MFC/OLE 2, так как OLE 2 не использует DDE в качестве базового механизма коммуникации (имя использовалось в беседах DDE). Чтобы устранить эту проблему, можно удалить функцию, а также все ссылки на нее CreateNewName . Легко узнать, какую функцию MFC/OLE ожидается в этой версии, просто поместив курсор на вызов и нажав клавишу F1.

Другая область, которая значительно отличается от обработки буфера обмена OLE 2. С OLE1 вы использовали API буфера обмена Windows, взаимодействующие с буфером обмена. С OLE 2 это делается с другим механизмом. API MFC/OLE1 предполагают, что буфер обмена открыт перед копированием 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 . Обработка команды "Вставка нового объекта" — это другая область, в которой все изменилось довольно немного. В этом случае проще всего объединить исходную реализацию с приложением AppWizard для нового приложения контейнера OLE. На самом деле, это метод, который можно применить к переносу других приложений. В MFC/OLE1 вы отображали диалоговое окно "Вставка объекта" путем вызова AfxOleInsertDialog функции. В этой версии создается объект диалогового COleInsertObject окна и вызывается DoModal. Кроме того, новые элементы OLE создаются с помощью CLSID вместо строки имени класса. Конечный результат должен выглядеть примерно так:

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();

Примечание.

Вставка нового объекта может отличаться для приложения):

Также необходимо включить <afxodlgs.h>, содержащий объявление для COleInsertObject класса диалогов, а также другие стандартные диалоги, предоставляемые MFC.

\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::GetBounds и SetBounds использовались для запроса и управления экстентом элемента (и lefttop члены всегда были нулевыми). В MFC/OLE 2 это более напрямую поддерживается COleClientItem::GetExtent и SetExtentкоторые имеют дело с SIZE или CSize вместо этого.

Код для новых вызовов 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 это не обязательно, поэтому можно удалить переопределение OnCommand в CMainFrame все вместе.

На этом этапе OCLIENT компилирует и связывается.

Другие необходимые изменения

Существует несколько действий, которые не выполняются, что позволит OCLIENT работать, однако. Лучше устранить эти проблемы теперь, а не позже.

Во-первых, необходимо инициализировать библиотеки OLE. Это делается путем вызова AfxOleInit из InitInstance:

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

Также рекомендуется проверка для виртуальных функций для изменений списка параметров. Одна из таких функций — COleClientItem::OnChangeпереопределение в каждом приложении контейнера MFC/OLE. Просмотрев справку по Интернету, вы увидите, что добавлен дополнительный 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 (эта новая организация упрощает сборку приложений контейнера или сервера). Существует #define , которая сопоставляется COleClientDoc с COleDocument упрощением переноса приложений MFC/OLE1 в MFC/OLE 2, таких как 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 с поддержкой Container.

Во-первых, необходимо добавить ресурс меню, который будет использоваться при наличии элемента, активного на месте. Вы можете создать этот дополнительный ресурс меню в Visual C++ путем копирования ресурса IDR_OCLITYPE и удаления всех, кроме всплывающих окон и файлов. Две полосы разделителя вставляются между всплывающим окном "Файл" и "Окно", чтобы указать разделение групп (оно должно выглядеть следующим образом: File || Window). Дополнительные сведения о том, что означает эти разделители и как объединяются меню сервера и контейнера, см. в разделе "Меню и ресурсы: слияние меню".

После создания этих меню необходимо сообщить платформе о них. Это делается путем вызова CDocTemplate::SetContainerInfo шаблона документа перед добавлением его в список шаблонов документов в initInstance. Новый код для регистрации шаблона документа выглядит следующим образом:

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), так и в CView производном COleClientItem классе (CRectItem). Все эти переопределения предоставляются AppWizard, и большая часть реализации будет поступать непосредственно из приложения AppWizard по умолчанию.

На первом шаге этого порта активация на месте была полностью отключена путем переопределения COleClientItem::CanActivate. Это переопределение должно быть удалено, чтобы разрешить активацию на месте. Кроме того, значение NULL было передано всем вызовам DoVerb (есть два из них), так как предоставление представления было необходимо только для активации на месте. Чтобы полностью реализовать активацию на месте, необходимо передать правильное представление в вызове 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 добавьте акселератор с Visual C++, который сопоставляет ключ VK_ESCAPE с командой, 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();
}

Пример: HIERSVR из MFC 2.0

HIERSVR также был включен в MFC 2.0 и реализован OLE с помощью MFC/OLE1. В этом примечании кратко описаны шаги, с помощью которых приложение было первоначально преобразовано для использования классов MFC/OLE 2. После завершения начального порта были добавлены ряд функций, чтобы лучше проиллюстрировать классы MFC/OLE 2. Эти функции не будут рассмотрены здесь; Ознакомьтесь с примером для получения дополнительных сведений об этих расширенных функциях.

Примечание.

Ошибки компилятора и пошаговый процесс были созданы с помощью Visual C++ 2.0. Некоторые сообщения об ошибках и расположения могут быть изменены с помощью Visual C++ 4.0, но концептуальная информация остается допустимой.

Получение и запуск

Подход, используемый для переноса примера HIERSVR в MFC/OLE, заключается в том, чтобы начать с создания и исправления очевидных ошибок компилятора, которые будут привести. Если вы берете пример HIERSVR из MFC 2.0 и скомпилируете его в этой версии 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 из этого приложения. В противном случае можно использовать служебную программу GUIDGEN.EXE в каталоге BIN.

Необходимо подключить COleTemplateServer объект к шаблону документа путем вызова COleTemplateServer::ConnectTemplate.

Обновите системный реестр при запуске автономного приложения. Таким образом, если пользователь перемещает EXE-файл для приложения, его запуск из нового расположения обновит базу данных регистрации системы Windows, чтобы указать новое расположение.

После применения всех этих изменений в зависимости от того, для чего создается InitInstanceAppWizard , InitInstance (и связанный GUID) для HIERSVR следует прочитать следующим образом:

// 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_EМБ. Это ресурс меню, используемый при редактировании документа, внедренного в другой контейнер. В MFC/OLE1 элементы меню, относящиеся к редактированию внедренного элемента, были изменены на лету. Использование совершенно другой структуры меню при редактировании внедренного элемента вместо редактирования документа на основе файлов упрощает предоставление различных пользовательских интерфейсов для этих двух отдельных режимов. Как вы увидите позже, при редактировании внедренного объекта на месте используется полностью отдельный ресурс меню.

Чтобы создать этот ресурс, загрузите скрипт ресурсов в Visual C++ и скопируйте существующий ресурс меню IDR_HIERSVRTYPE. Переименуйте новый ресурс в IDR_HIERSVRTYPE_SRVR_EМБ (это то же соглашение об именовании, которое использует AppWizard). Далее измените значение "Сохранить файл" на "Обновление файлов"; присвойте ему идентификатор команды ID_FILE_UPDATE. Также измените значение "Сохранить файл как" на "Сохранить файл как"; присвойте ему идентификатор команды 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

Существует ряд ошибок, вызванных переопределением OnSetData, так как он ссылается на тип OLESTATUS . OLESTATUS — это способ возврата OLE1 ошибок. Это изменилось на HRESULT в OLE 2, хотя MFC обычно преобразует HRESULT в COleException ошибку, содержащую ошибку. В этом случае переопределение 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

Так как эти ошибки подразумевают, существуют некоторые функции pure-virtual, которые не были переопределены в 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" (он частично заменен m_sizeExtent, но в OLE 2 этот элемент имеет немного другое использование, чем m_rectBoundsCOleServerItem в OLE1). Вместо настройки размера 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 3.0 примера OLE MFC HIERSVR реализует эту функцию путем переопределения COleServerItem::OnRenderFileData. Эта функция не важна для этого базового порта, поэтому можно удалить переопределение OnGetTextData.

Существует много других ошибок в svritem.cpp, которые не были устранены. Они не являются "реальными" ошибками — просто ошибки, вызванные предыдущими ошибками.

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

COleServerItem::CopyToClipboard больше не поддерживает bIncludeNative флаг. Собственные данные (данные, записанные функцией сериализации элемента сервера), всегда копируются, поэтому удалите первый параметр. Кроме того, CopyToClipboard при возникновении ошибки возникает исключение, а не возвращает значение FALSE. Измените код для 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
}

Хотя произошло больше ошибок, вызванных компиляцией версии MFC 2.0 HIERSVR, чем для той же версии OCLIENT, на самом деле было меньше изменений.

На этом этапе HIERSVR компилирует и связывает и работает как сервер OLE, но без функции редактирования на месте, которая будет реализована далее.

Добавление элемента "Визуальное редактирование"

Чтобы добавить "Визуальное редактирование" (или активация на месте) в это серверное приложение, необходимо учитывать только несколько действий.

  • Вам нужен специальный ресурс меню, который будет использоваться при активном элементе.

  • У этого приложения есть панель инструментов, поэтому вам потребуется панель инструментов с подмножеством обычной панели инструментов, чтобы соответствовать командам меню, доступным на сервере (соответствует ресурсу меню упоминание выше).

  • Вам нужен новый класс, производный от COleIPFrameWnd этого, предоставляющий пользовательский интерфейс на месте (например, CMainFrame, производный от CMDIFrameWnd, предоставляет пользовательский интерфейс MDI).

  • Вам нужно рассказать об этих специальных ресурсах и классах.

Ресурс меню легко создать. Запустите Visual C++, скопируйте ресурс меню IDR_HIERSVRTYPE в ресурс меню с именем IDR_HIERSVRTYPE_SRVR_IP. Измените меню, чтобы осталось только всплывающее меню "Изменить" и "Справка". Добавьте два разделителя в меню между меню "Изменить" и "Справка" (оно должно выглядеть следующим образом: Edit || Help). Дополнительные сведения о том, что означает эти разделители и как объединяются меню сервера и контейнера, см. в разделе "Меню и ресурсы: слияние меню".

Растровое изображение панели инструментов подмножества можно легко создать, скопировав его из нового созданного приложения AppWizard с параметром "Сервер" проверка. Затем этот растровый рисунок можно импортировать в Visual C++. Обязательно укажите точечный рисунок идентификатором IDR_HIERSVRTYPE_SRVR_IP.

Класс, производный от 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/OLE 2, которые недоступны в MFC/OLE1. См. пример HIERSVR для получения дополнительных идей о функциях, которые может потребоваться реализовать. Ниже перечислены некоторые функции, реализующие HIERSVR:

  • Масштабирование для истинного поведения WYSIWYG относительно контейнера.

  • Перетаскивание и пользовательский формат буфера обмена.

  • Прокрутите окно контейнера по мере изменения выделения.

Пример HIERSVR в MFC 3.0 также использует немного другой дизайн для элементов сервера. Это помогает сохранять память и делает ссылки более гибкими. С версией 2.0 HIERSVR каждый узел в дереве является-aCOleServerItem. COleServerItem имеет немного больше накладных расходов, чем строго необходимо для каждого из этих узлов, но COleServerItem требуется для каждой активной ссылки. Но в большинстве случаев есть очень мало активных ссылок в любое время. Чтобы сделать это более эффективным, HIERSVR в этой версии MFC отделяет узел от COleServerItemузла. Он имеет как CServerNode, так и CServerItem класс. ( CServerItem производный от COleServerItem) создается только при необходимости. Когда контейнер (или контейнеры) перестанет использовать определенную ссылку на этот конкретный узел, удаляется объект CServerItem, связанный с CServerNode. Эта конструкция является более эффективной и более гибкой. Ее гибкость возникает при работе с несколькими ссылками выбора. Ни одно из этих двух версий HIERSVR не поддерживает несколько вариантов выбора, но было бы гораздо проще добавить (и поддерживать ссылки на такие выборы) с версией HIERSVR MFC 3.0, так как они COleServerItem отделены от собственных данных.

См. также

Технические примечания по номеру
Технические примечания по категории