Partilhar via


TN041: migração de MFC/OLE1 para MFC/OLE 2

Observação

A nota técnica a seguir não foi atualizada desde que foi incluída pela primeira vez na documentação online. Como resultado, alguns procedimentos e tópicos podem estar desatualizados ou incorretos. Para obter as informações mais recentes, é recomendável que você pesquise o tópico de interesse no índice de documentação online.

Problemas gerais relacionados à migração

Uma das metas de design para as classes OLE 2 no MFC 2.5 (e superior) era manter grande parte da mesma arquitetura implementada no MFC 2.0 para suporte ao OLE 1.0. Assim, muitas das mesmas classes OLE no MFC 2.0 ainda existem nessa versão do MFC (COleDocument, COleServerDoc, COleClientItem, COleServerItem). Além disso, muitas das APIs nessas classes são exatamente iguais. No entanto, o OLE 2 é drasticamente diferente do OLE 1.0, ou seja, pode ter certeza de que alguns dos detalhes foram alterados. Se você estiver familiarizado com o suporte ao OLE1 do MFC 2.0, você se sentirá em casa com o suporte ao MFC 2.0.

Se você estiver usando um aplicativo MFC/OLE1 existente e adicionando a funcionalidade OLE 2 a ele, leia esta nota primeiro. Esta nota aborda alguns problemas gerais que você pode encontrar ao portar sua funcionalidade OLE1 para MFC/OLE 2 e discute os problemas descobertos durante a portabilidade de dois aplicativos incluídos no MFC 2.0: os exemplos de OLE do MFC/OLE OCLIENT e HIERSVR.

A Arquitetura de Documento/Exibição do MFC é importante

Se o aplicativo não usar a arquitetura de Documento/Exibição do MFC e você quiser adicionar suporte ao OLE 2 ao seu aplicativo, agora é a hora de migrar para Documento/Exibição. Muitos dos benefícios das classes OLE 2 do MFC só são concretizados quando seu aplicativo está usando a arquitetura interna e os componentes do MFC.

A implementação de um servidor ou contêiner sem usar a arquitetura MFC é possível, mas não é recomendável.

Usar implementação MFC em vez da sua própria

Classes de "implementação enlatada" do MFC, como CToolBar, CStatusBar e CScrollView, têm código de caso especial interno para suporte ao OLE 2. Portanto, se você puder usar essas classes em seu aplicativo, vai se beneficiar do esforço colocado nelas para fazer com que elas tenham reconhecimento do OLE. Novamente, é possível "distribuir suas próprias" classes aqui para esses fins, mas não é recomendável. Se você precisar implementar uma funcionalidade semelhante, o código-fonte do MFC será uma excelente referência para lidar com alguns dos pontos mais delicados do OLE (especialmente quando se trata de ativação local).

Examinar o código de exemplo do MFC

Há vários exemplos de MFC que incluem a funcionalidade OLE. Cada um desses aplicativos implementa o OLE de um ângulo diferente:

  • HIERSVR Destinado principalmente para uso como um aplicativo de servidor. Ele foi incluído no MFC 2.0 como um aplicativo MFC/OLE1, portado para MFC/OLE 2 e, em seguida, estendido de modo a implementar muitos recursos OLE disponíveis no OLE 2.

  • OCLIENT Esse é um aplicativo de contêiner autônomo, destinado a demonstrar muitos dos recursos OLE do ponto de vista do contêiner. Ele também foi portado do MFC 2.0 e estendido para dar suporte a muitos dos recursos OLE mais avançados, como formatos de área de transferência personalizados e links para itens inseridos.

  • DRAWCLI Esse aplicativo implementa o suporte de contêiner OLE da mesma forma que o OCLIENT; a diferença é que ele faz isso dentro da estrutura de um programa de desenho voltado para objetos existente. Ele mostra como você pode implementar o suporte ao contêiner OLE e integrá-lo ao aplicativo existente.

  • SUPERPAD Esse aplicativo, além de ser um aplicativo autônomo, também é um servidor OLE. O suporte ao servidor que ele implementa é bastante minimalista. É particularmente interessante como ele usa serviços de área de transferência OLE para copiar dados para a área de transferência, mas usa a funcionalidade interna no controle "editar" do Windows para implementar a funcionalidade de colagem da área de transferência. Isso mostra uma combinação interessante do uso tradicional da API do Windows, bem como a integração com as novas APIs OLE.

Para saber mais sobre os aplicativos de exemplo, confira a "Ajuda de exemplo do MFC".

Estudo de caso: OCLIENT do MFC 2.0

Como discutido acima, OCLIENT foi incluído no MFC 2.0 e implementou OLE com MFC/OLE1. As etapas pelas quais esse aplicativo foi inicialmente convertido para usar as classes MFC/OLE 2 estão descritas abaixo. Vários recursos foram adicionados depois que a porta inicial foi concluída para ilustrar melhor as classes MFC/OLE. Esses recursos não serão abordados aqui; confira o próprio exemplo para saber mais sobre esses recursos avançados.

Observação

Os erros do compilador e o processo passo a passo foram criados com o Visual C++ 2.0. As mensagens de erro e os locais específicos podem ter sido alterados com o Visual C++ 4.0, mas as informações conceituais permanecem válidas.

Colocando-o em execução

A abordagem usada para portar o exemplo OCLIENT para MFC/OLE é começar com sua criação e correção de erros óbvios do compilador resultantes. Se você usar o exemplo OCLIENT do MFC 2.0 e compilá-lo nessa versão do MFC, descobrirá que não há muitos erros a serem resolvidos. Os erros na ordem em que ocorreram estão descritos abaixo.

Compilar e corrigir erros

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

O primeiro erro diz respeito a COleClientItem::Draw. No MFC/OLE1, foram necessários mais parâmetros do que os usados pela versão do MFC/OLE. Os parâmetros extras geralmente não eram necessários e geralmente NULL (como neste exemplo). Essa versão do MFC pode determinar automaticamente os valores para o lpWBounds quando o CDC que está sendo desenhado é um DC de metarquivo. Além disso, o parâmetro pFormatDC não é mais necessário, pois a estrutura criará um do "DC de atributo" do pDC transmitido. Portanto, para corrigir esse problema, basta remover os dois parâmetros NULL extras para a chamada 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 '

Os erros acima resultam do fato de que todas as funções COleClientItem::CreateXXXX no MFC/OLE1 exigiram que um nome exclusivo fosse transmitido para representar o item. Esse era um requisito da API OLE subjacente. Isso não é necessário no MFC/OLE 2, pois o OLE 2 não usa o DDE como o mecanismo de comunicação subjacente (o nome foi usado em conversas DDE). Para corrigir esse problema, você pode remover a função CreateNewName, bem como todas as referências a ela. É fácil descobrir o que cada função MFC/OLE está esperando nesta versão simplesmente colocando o cursor na chamada e pressionando F1.

Outra área que é significativamente diferente é a manipulação da área de transferência OLE 2. Com o OLE1, você usava as APIs da área de transferência do Windows para interagir com a área de transferência. Com o OLE 2, isso é feito com um mecanismo diferente. As APIs MFC/OLE1 presumiram que a área de transferência estava aberta antes de copiar um objeto COleClientItem para a área de transferência. Isso não é mais necessário e fará com que todas as operações de área de transferência MFC/OLE falhem. Ao editar o código para remover dependências em CreateNewName, você também deverá remover o código que abre e fecha a área de transferência do 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'

Esses erros resultam do manipulador CMainView::OnInsertObject. A manipulação do comando "Inserir Novo Objeto" é outra área em que as coisas mudaram bastante. Nesse caso, é mais fácil simplesmente mesclar a implementação original com a fornecida pelo AppWizard para um novo aplicativo de contêiner OLE. Na verdade, essa é uma técnica que você pode aplicar à portabilidade de outros aplicativos. No MFC/OLE1, você exibia o diálogo "Inserir Objeto" chamando a função AfxOleInsertDialog. Nesta versão, você constrói um objeto de diálogo COleInsertObject e chama DoModal. Além disso, novos itens OLE são criados com um CLSID em vez de uma cadeia de caracteres de nome de classe. O resultado final deve ser semelhante a

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

Observação

Inserir Novo Objeto pode ser diferente em seu aplicativo):

Também é necessário incluir <afxodlgs.h>, que contém a declaração para a classe de diálogo COleInsertObject, bem como os outros diálogos padrão fornecidos pelo MFC.

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

Esses erros são causados pelo fato de que algumas constantes OLE1 foram alteradas no OLE 2, embora, no conceito, elas sejam as mesmas. Nesse caso, OLEVERB_PRIMARY foi alterado para OLEIVERB_PRIMARY. No OLE1 e no OLE 2, o verbo primário geralmente é executado por um contêiner quando o usuário clica duas vezes em um item.

Além disso, DoVerb agora usa um parâmetro extra, um ponteiro para uma exibição (CView*). Esse parâmetro é usado apenas para implementar "Edição visual" (ou ativação local). Por enquanto, você define esse parâmetro como NULL, pois não está implementando esse recurso no momento.

Para garantir que a estrutura nunca tentará ativar localmente, você deve substituir COleClientItem::CanActivate da seguinte maneira:

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

No MFC/OLE1, COleClientItem::GetBounds e SetBounds foram usados para consultar e manipular a extensão de um item (os membros left e top eram sempre zero). No MFC/OLE 2, isso tem suporte mais direto de COleClientItem::GetExtent e SetExtent, que lidam com um SIZE ou CSize.

O código para suas novas chamadas SetItemRectToServer e UpdateItemRectFromServer tem esta aparência:

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

Em MFC/OLE1, chamadas de API síncronas de um contêiner para um servidor foram simuladas, pois o OLE1 era inerentemente assíncrono em muitos casos. Foi necessário verificar se há uma chamada assíncrona pendente em andamento antes de processar comandos do usuário. O MFC/OLE1 fornecia a função COleClientItem::InWaitForRelease para isso. No MFC/OLE 2, isso não é necessário, ou seja, você pode remover a substituição de OnCommand no CMainFrame.

Neste ponto, o OCLIENT compilará e vinculará.

Outras alterações necessárias

No entanto, há poucas coisas que, se não forem feitas, impedirão o OCLIENT de ser executado. É melhor corrigir esses problemas agora, em vez de mais tarde.

Primeiro, é necessário inicializar as bibliotecas OLE. Isso é feito chamando AfxOleInit de InitInstance:

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

Também é uma boa ideia verificar se há funções virtuais para alterações na lista de parâmetros. Uma dessas funções é COleClientItem::OnChange, substituída em cada aplicativo de contêiner MFC/OLE. Ao olhar a ajuda online, você verá que um 'DWORD dwParam' extra foi adicionado. O novo CRectItem::OnChange tem a seguinte aparência:

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
}

No MFC/OLE1, os aplicativos de contêiner derivavam a classe de documento de COleClientDoc. No MFC/OLE 2, essa classe foi removida e substituída por COleDocument (essa nova organização facilita a criação de aplicativos de contêiner/servidor). Há um #define que mapeia COleClientDoc para COleDocument a fim de simplificar a portabilidade de aplicativos MFC/OLE1 para MFC/OLE 2, como OCLIENT. Um dos recursos não fornecidos pelo COleDocument que foi providenciado por COleClientDoc é entradas de mapa de mensagens de comando padrão. Isso é feito para que os aplicativos de servidor, que também usam COleDocument (indiretamente), não carreguem com eles a sobrecarga desses manipuladores de comando, a menos que sejam um aplicativo de contêiner/servidor. Você precisa adicionar as seguintes entradas ao mapa de mensagens 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)

A implementação de todos esses comandos está dentro de COleDocument, que é a classe base do documento.

Neste ponto, o OCLIENT é um aplicativo de contêiner OLE funcional. É possível inserir itens de qualquer tipo (OLE1 ou OLE 2). Como o código necessário para habilitar a ativação local não é implementado, os itens são editados em uma janela separada, assim como com o OLE1. A próxima seção discute as alterações necessárias para habilitar a edição local (às vezes chamada de "Edição Visual").

Adicionando "Edição Visual"

Um dos recursos mais interessantes do OLE é a ativação local (ou "Edição Visual"). Esse recurso permite que o aplicativo de servidor assuma partes da interface do usuário do contêiner para fornecer uma interface de edição mais perfeita para o usuário. Para implementar a ativação local no OCLIENT, alguns recursos especiais precisam ser adicionados, bem como algum código adicional. Esses recursos e o código são normalmente fornecidos pelo AppWizard; na verdade, muito do código aqui foi tomado emprestado diretamente de um aplicativo AppWizard novo com suporte a “Contêiner”.

Em primeiro lugar, é necessário adicionar um recurso de menu a ser usado quando há um item ativo no local. Você pode criar esse recurso de menu extra no Visual C++ copiando o recurso IDR_OCLITYPE e removendo tudo exceto os pop-ups Arquivo e Janela. Duas barras de separador são inseridas entre os pop-ups Arquivo e Janela para indicar a separação de grupos (deve ser semelhante a: File || Window). Para saber mais sobre o que esses separadores significam e como os menus de servidor e contêiner são mesclados, confira Menus e recursos: mesclagem de menus.

Depois de criar esses menus, você precisará informar a estrutura sobre eles. Isso é feito chamando CDocTemplate::SetContainerInfo para o modelo de documento antes de adicioná-lo à lista de modelos de documento na sua InitInstance. O novo código para registrar o modelo de documento tem esta aparência:

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

O recurso IDR_OLECLITYPE_INPLACE é o recurso local especial criado no Visual C++.

Para habilitar a ativação local, há algumas coisas que precisam ser alteradas na classe derivada CView (CMainView), bem como na classe derivada COleClientItem (CRectItem). Todas essas substituições são fornecidas pelo AppWizard e a maior parte da implementação virá diretamente de um aplicativo AppWizard padrão.

Na primeira etapa dessa porta, a ativação local foi totalmente desabilitada pela substituição de COleClientItem::CanActivate. Essa substituição deve ser removida para permitir a ativação local. Além disso, NULL foi transmitido a todas as chamadas para DoVerb (há duas delas) porque o fornecimento do modo de exibição só era necessário para ativação local. Para implementar totalmente a ativação local, é necessário transmitir a exibição correta na chamada DoVerb. Uma dessas chamadas está em CMainView::OnInsertObject:

pItem->DoVerb(OLEIVERB_SHOW, this);

Outra está em CMainView::OnLButtonDblClk:

m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);

É necessário substituir COleClientItem::OnGetItemPosition. Isso informa ao servidor onde colocar sua janela em relação à janela do contêiner quando o item é ativado no local. Para OCLIENT, a implementação é trivial:

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

A maioria dos servidores também implementa o chamado “redimensionamento local”. Isso permite que a janela do servidor seja dimensionada e movida enquanto o usuário está editando o item. O contêiner precisa participar dessa ação, pois mover ou redimensionar a janela geralmente afeta a posição e o tamanho dentro do próprio documento de contêiner. A implementação do OCLIENT sincroniza o retângulo interno mantido por m_rect com a nova posição e o tamanho.

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

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

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

    return TRUE;
}

Aqui há código suficiente para permitir que um item seja ativado no local e para lidar com o dimensionamento e a movimentação do item quando ele está ativo, mas nenhum código permitirá que o usuário saia da sessão de edição. Embora alguns servidores forneçam essa funcionalidade por conta própria manipulando a tecla de escape, é sugerido que os contêineres forneçam duas maneiras de desativar um item: (1) clicando fora do item e (2) pressionando a tecla ESCAPE.

Para a tecla ESCAPE, adicione um acelerador com Visual C++ que mapeie a chave VK_ESCAPE para um comando; ID_CANCEL_EDIT é adicionado aos recursos. O manipulador para este comando vem em seguida:

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

Para lidar com o caso em que o usuário clica fora do item, adicione o seguinte código ao início de CMainView::SetSelection:

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

Quando um item está ativo no local, ele deve ter o foco. Para garantir que esse seja o caso, você manipula OnSetFocus para que o foco seja sempre transferido para o item ativo quando o modo de exibição receber o foco:

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

Quando o modo de exibição for redimensionado, você precisará notificar o item ativo de que o retângulo de recorte foi alterado. Para isso, você fornece um manipulador a 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();
}

Estudo de Caso: HIERSVR do MFC 2.0

HIERSVR também foi incluído no MFC 2.0 e implementou OLE com MFC/OLE1. Esta nota descreve brevemente as etapas pelas quais esse aplicativo foi inicialmente convertido para usar as classes MFC/OLE 2. Vários recursos foram adicionados depois que a porta inicial foi concluída para ilustrar melhor as classes MFC/OLE 2. Esses recursos não serão abordados aqui; confira o próprio exemplo para saber mais sobre esses recursos avançados.

Observação

Os erros do compilador e o processo passo a passo foram criados com o Visual C++ 2.0. As mensagens de erro e os locais específicos podem ter sido alterados com o Visual C++ 4.0, mas as informações conceituais permanecem válidas.

Colocando-o em execução

A abordagem usada para portar o exemplo HIERSVR para MFC/OLE é começar com sua criação e correção de erros óbvios do compilador resultantes. Se você usar o exemplo HIERSVR do MFC 2.0 e compilá-lo nesta versão do MFC, descobrirá que não há muitos erros a serem resolvidos (embora haja mais do que com o exemplo OCLIENT). Os erros na ordem em que geralmente ocorrem estão descritos abaixo.

Compilar e corrigir erros

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

Esse primeiro erro aponta um problema muito maior com a função InitInstance para servidores. A inicialização necessária para um servidor OLE é provavelmente uma das maiores alterações que você terá que fazer em seu aplicativo MFC/OLE1 para executá-lo. A melhor coisa a fazer é examinar o que o AppWizard cria para um servidor OLE e modificar seu código conforme apropriado. Veja aqui algumas coisas para ter em mente:

É necessário inicializar as bibliotecas OLE chamando AfxOleInit

Chame SetServerInfo no objeto de modelo de documento para definir identificadores de recursos do servidor e informações de classe de runtime que você não pode definir com o construtor CDocTemplate.

Não mostre a janela principal do aplicativo se /Embedding estiver presente na linha de comando.

Você precisará de um GUID para seu documento. Esse é um identificador exclusivo para o tipo do documento (128 bits). O AppWizard criará um para você, ou seja, se usar a técnica descrita aqui de copiar o novo código de um aplicativo de servidor novo gerado pelo AppWizard, poderá simplesmente “roubar” o GUID desse aplicativo. Caso contrário, você pode usar o utilitário GUIDGEN.EXE no diretório BIN.

É necessário "conectar" seu objeto COleTemplateServer ao modelo de documento chamando COleTemplateServer::ConnectTemplate.

Atualize o registro do sistema quando seu aplicativo for executado autonomamente. Dessa forma, se o usuário mover o .EXE para seu aplicativo, a execução no novo local atualizará o banco de dados de registro do sistema Windows para apontar para o novo local.

Depois de aplicar todas essas alterações com base no que o AppWizard cria para InitInstance, a InitInstance (e o GUID relacionado) para HIERSVR deve ser lido da seguinte maneira:

// 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;
}

Você observará que o código acima refere-se a uma nova ID de recurso, IDR_HIERSVRTYPE_SRVR_EMB. Esse é o recurso de menu a ser usado quando um documento inserido em outro contêiner é editado. No MFC/OLE1, os itens de menu específicos para editar um item inserido foram modificados prontamente. O uso de uma estrutura de menu totalmente diferente ao editar um item inserido em vez da edição de um documento baseado em arquivo torna muito mais fácil fornecer interfaces de usuário diferentes para esses dois modos separados. Como você verá posteriormente, um recurso de menu totalmente separado é usado ao editar um objeto inserido no local.

Para criar esse recurso, carregue o script de recurso no Visual C++ e copie o recurso de menu IDR_HIERSVRTYPE existente. Renomeie o novo recurso IDR_HIERSVRTYPE_SRVR_EMB (essa é a mesma convenção de nomenclatura que o AppWizard usa). Em seguida, altere "Salvar Arquivo" para "Atualização de Arquivo"; forneça a ID do comando ID_FILE_UPDATE. Altere também "Salvar Arquivo Como" para "Salvar Cópia do Arquivo Como"; forneça a ID do comando ID_FILE_SAVE_COPY_AS. A estrutura fornece a implementação de ambos os comandos.

\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

Há uma série de erros resultantes da substituição de OnSetData, uma vez que ele está se referindo ao tipo OLESTATUS. OLESTATUS era a maneira como o OLE1 retornava erros. Isso foi alterado para HRESULT no OLE 2, embora o MFC geralmente converta um HRESULT em um COleException que contém o erro. Nesse caso específico, a substituição de OnSetData não é mais necessária, ou seja, a coisa mais fácil a fazer é removê-la.

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

O construtor COleServerItem usa um parâmetro 'BOOL' extra. Esse sinalizador determina como o gerenciamento de memória é feito nos objetos COleServerItem. Ao defini-lo como TRUE, a estrutura lida com o gerenciamento de memória desses objetos, excluindo-os quando não são mais necessários. HIERSVR usa objetos CServerItem (derivados de COleServerItem) como parte de seus dados nativos, ou seja, você definirá esse sinalizador como FALSE. Isso permite que o HIERSVR determine quando cada item de servidor é excluído.

\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

Como esses erros indicam, há algumas funções “pure-virtual” que não foram substituídas no CServerItem. Isso provavelmente é causado pelo fato de que a lista de parâmetros do OnDraw foi alterada. Para corrigir esse erro, altere CServerItem::OnDraw da seguinte forma (bem como a declaração em 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);
}

O novo parâmetro é 'rSize'. Isso permite que você preencha o tamanho do desenho, se for conveniente. Esse tamanho precisa estar em HIMETRIC. Nesse caso, não é conveniente preencher esse valor, ou seja, a estrutura chama OnGetExtent para recuperar a extensão. Para que isso funcione, você terá que implementar 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 *'

Na função CServerItem::CalcNodeSize, o tamanho do item é convertido em HIMETRIC e armazenado em m_rectBounds. O membro "m_rectBounds" de COleServerItem não documentado não existe (foi parcialmente substituído por m_sizeExtent, mas no OLE 2 esse membro tem um uso ligeiramente diferente do que m_rectBounds fazia no OLE1). Em vez de definir o tamanho de HIMETRIC nessa variável de membro, você o retornará. Esse valor retornado é usado em OnGetExtent, implementado anteriormente.

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 também substitui COleServerItem::OnGetTextData. Essa função ficou obsoleta no MFC/OLE e é substituída por um mecanismo diferente. A versão MFC 3.0 do exemplo MFC OLE HIERSVR implementa essa funcionalidade substituindo COleServerItem::OnRenderFileData. Essa funcionalidade não é importante para essa porta básica, ou seja, você pode remover a substituição OnGetTextData.

Há muitos outros erros em svritem.cpp que não foram resolvidos. Eles não são erros “reais”, apenas erros causados por erros anteriores.

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

COleServerItem::CopyToClipboard não dá mais suporte ao sinalizador bIncludeNative. Os dados nativos (os dados gravados pela função Serialize do item de servidor) são sempre copiados, ou seja, você remove o primeiro parâmetro. Além disso, CopyToClipboard gerará uma exceção quando ocorrer um erro em vez de retornar FALSE. Altere o código para CServerView::OnEditCopy da seguinte maneira:

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

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

Embora tenham existido mais erros resultantes da compilação da versão MFC 2.0 do HIERSVR do que na mesma versão do OCLIENT, houve, na verdade, menos alterações.

Neste ponto, o HIERSVR compilará, vinculará e funcionará como um servidor OLE, mas sem o recurso de edição local, que será implementado em seguida.

Adicionando "Edição Visual"

Para adicionar "Edição Visual" (ou ativação local) a esse aplicativo de servidor, há apenas algumas coisas que você precisa ter em mente:

  • Você precisa de um recurso de menu especial para ser usado quando o item estiver ativo localmente.

  • Esse aplicativo tem uma barra de ferramentas, ou seja, você precisará de uma barra de ferramentas com apenas um subconjunto da barra de ferramentas normal para corresponder aos comandos de menu disponíveis no servidor (corresponde ao recurso de menu mencionado acima).

  • Você precisa de uma nova classe derivada de COleIPFrameWnd que forneça a interface do usuário local (assim como o CMainFrame, derivado de CMDIFrameWnd, fornece a interface do usuário MDI).

  • Você precisa informar a estrutura sobre esses recursos e classes especiais.

O recurso de menu é fácil de criar. Execute o Visual C++, copie o recurso de menu IDR_HIERSVRTYPE para um recurso de menu chamado IDR_HIERSVRTYPE_SRVR_IP. Modifique o menu para que restem somente os pop-ups de menu Editar e Ajuda. Adicione dois separadores ao menu entre os menus Editar e Ajuda (ele deve ter a seguinte aparência: Edit || Help). Para saber mais sobre o que esses separadores significam e como os menus de servidor e contêiner são mesclados, confira Menus e recursos: mesclagem de menus.

O bitmap para a barra de ferramentas de subconjunto pode ser criado facilmente copiando-o de um novo aplicativo gerado pelo AppWizard com uma opção "Servidor" marcada. Esse bitmap pode ser importado para o Visual C++. Forneça ao bitmap uma ID de IDR_HIERSVRTYPE_SRVR_IP.

A classe derivada de COleIPFrameWnd também pode ser copiada de um aplicativo gerado pelo AppWizard com suporte de servidor. Copie ambos os arquivos, IPFRAME.CPP e IPFRAME.H e adicione-os ao projeto. Verifique se a chamada LoadBitmap se refere a IDR_HIERSVRTYPE_SRVR_IP, o bitmap criado na etapa anterior.

Agora que todos os novos recursos e classes foram criados, adicione o código necessário para que a estrutura saiba sobre eles (e saiba que esse aplicativo agora dá suporte à edição local). Isso é feito adicionando mais alguns parâmetros à chamada SetServerInfo na função InitInstance:

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

Agora ele está pronto para ser executado localmente em qualquer contêiner que também dê suporte à ativação local. Mas, há um pequeno bug ainda à espreita no código. O HIERSVR dá suporte a um menu de contexto, exibido quando o usuário pressiona o botão direito do mouse. Esse menu funciona quando o HIERSVR está totalmente aberto, mas não funciona ao editar uma inserção local. O motivo pode estar restrito a essa única linha de código em CServerView::OnRButtonDown:

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

Observe a referência a AfxGetApp()->m_pMainWnd. Quando o servidor é ativado localmente, ele tem uma janela principal e m_pMainWnd está definido, mas geralmente é invisível. Além disso, essa janela refere-se à janela principal do aplicativo, a janela de quadro MDI que aparece quando o servidor está totalmente aberto ou executa autonomamente. Ela não se refere à janela do quadro ativo, que, quando ativada localmente é uma janela de quadro derivada de COleIPFrameWnd. Para obter a janela ativa correta mesmo durante a edição local, essa versão do MFC adiciona uma nova função, AfxGetMainWnd. Em geral, você deve usar essa função em vez de AfxGetApp()->m_pMainWnd. Esse código precisa ser alterado da seguinte maneira:

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

Agora você tem um servidor OLE minimamente habilitado para ativação funcional no local. Mas ainda há muitos recursos disponíveis com MFC/OLE 2 que não estavam disponíveis no MFC/OLE1. Confira o exemplo de HIERSVR para obter mais ideias sobre os recursos que talvez você queira implementar. Alguns dos recursos que o HIERSVR implementa estão listados abaixo:

  • Ampliação, para o verdadeiro comportamento WYSIWYG em relação ao contêiner.

  • Arrastar/soltar e um formato de área de transferência personalizado.

  • Rolagem da janela do contêiner conforme a seleção é alterada.

O exemplo HIERSVR no MFC 3.0 também usa um design ligeiramente diferente para seus itens de servidor. Isso ajuda a conservar a memória e torna seus links mais flexíveis. Com a versão 2.0 de HIERSVR, cada nó na árvore é umCOleServerItem. COleServerItem carrega um pouco mais de sobrecarga do que é estritamente necessário para cada um desses nós, só que um COleServerItem é necessário para cada link ativo. Mas, na maioria das vezes, há muito poucos links ativos em algum momento. Para tornar isso mais eficiente, o HIERSVR nesta versão do MFC separa o nó do COleServerItem. Ele tem um CServerNode e uma classe CServerItem. O CServerItem (derivado de COleServerItem) só é criado conforme a necessidade. Depois que o contêiner (ou contêineres) parar de usar esse link específico para o nó específico, o objeto CServerItem associado ao CServerNode será excluído. Esse design é mais eficiente e flexível. Sua flexibilidade aparece ao lidar com vários links de seleção. Nenhuma dessas duas versões do HIERSVR dá suporte a várias seleções, mas seria muito mais fácil adicionar (e dar suporte a links para essas seleções) com a versão MFC 3.0 do HIERSVR, pois o COleServerItem é separado dos dados nativos.

Confira também

Observações técnicas por número
Observações técnicas por categoria