Partager via


TN041 : migration de MFC/OLE1 vers MFC/OLE 2

Remarque

La note technique suivante n'a pas été mise à jour depuis son inclusion initiale dans la documentation en ligne. Par conséquent, certaines procédures et rubriques peuvent être obsolètes ou incorrectes. Pour obtenir les informations les plus récentes, il est recommandé de rechercher l'objet qui vous intéresse dans l'index de la documentation en ligne.

Problèmes généraux liés à la migration

L’un des objectifs de conception pour les classes OLE 2 dans MFC 2.5 (et versions ultérieures) était de conserver une grande partie de la même architecture mise en place dans MFC 2.0 pour la prise en charge d’OLE 1.0. Par conséquent, la plupart des mêmes classes OLE dans MFC 2.0 existent toujours dans cette version de MFC (COleDocument, COleServerDoc, COleClientItem, COleServerItem). En outre, la plupart des API de ces classes sont exactement les mêmes. Toutefois, OLE 2 est considérablement différent de OLE 1.0, ce qui vous permet de vous attendre à ce que certains détails aient changé. Si vous êtes familiarisé avec le support OLE1 de MFC 2.0, vous vous sentirez à la maison avec le support 2.0 de MFC.

Si vous prenez une application MFC/OLE1 existante et que vous y ajoutez des fonctionnalités OLE 2, vous devez d’abord lire cette note. Cette note aborde certains problèmes généraux que vous pouvez rencontrer lors du portage de vos fonctionnalités OLE1 vers MFC/OLE 2, puis décrit les problèmes découverts lors du portage de deux applications incluses dans MFC 2.0 : les exemples OLE MFC OCLIENT et HIERSVR.

L’architecture de document/vue MFC est importante

Si votre application n’utilise pas l’architecture document/vue de MFC et que vous souhaitez ajouter la prise en charge OLE 2 à votre application, il est maintenant temps de passer à Document/Affichage. La plupart des avantages des classes OLE 2 de MFC ne sont réalisés qu’une fois que votre application utilise l’architecture intégrée et les composants de MFC.

L’implémentation d’un serveur ou d’un conteneur sans utiliser l’architecture MFC est possible, mais pas recommandée.

Utiliser l’implémentation MFC au lieu de vos propres

Les classes « implémentation canned » MFC telles que CToolBar, CStatusBaret CScrollView ont du code de cas spécial intégré pour la prise en charge d’OLE 2. Par conséquent, si vous pouvez utiliser ces classes dans votre application, vous bénéficiez de l’effort qu’ils déploient pour les rendre conscients d’OLE. Là encore, il est possible de « roll-your-own » classes ici à ces fins, mais il n’est pas suggéré. Si vous devez implémenter des fonctionnalités similaires, le code source MFC est une excellente référence pour traiter certains des points plus fins d’OLE (en particulier quand il s’agit d’une activation sur place).

Examiner l’exemple de code MFC

Il existe un certain nombre d’exemples MFC qui incluent des fonctionnalités OLE. Chacune de ces applications implémente OLE à partir d’un angle différent :

  • HIERSVR Destiné principalement à être utilisé en tant qu’application serveur. Il a été inclus dans MFC 2.0 en tant qu’application MFC/OLE1 et a été porté vers MFC/OLE 2, puis étendu de telle sorte qu’il implémente de nombreuses fonctionnalités OLE disponibles dans OLE 2.

  • OCLIENT Il s’agit d’une application conteneur autonome, destinée à illustrer la plupart des fonctionnalités OLE du point de vue du conteneur. Il a également été porté à partir de MFC 2.0, puis étendu pour prendre en charge de nombreuses fonctionnalités OLE plus avancées, telles que les formats de Presse-papiers personnalisés et les liens vers les éléments incorporés.

  • DRAWCLI Cette application implémente la prise en charge du conteneur OLE comme OCLIENT, sauf qu’elle le fait dans le cadre d’un programme de dessin orienté objet existant. Il vous montre comment implémenter la prise en charge du conteneur OLE et l’intégrer à votre application existante.

  • SUPERPAD Cette application, ainsi qu’une application autonome fine, est également un serveur OLE. La prise en charge du serveur qu’il implémente est assez minimaliste. En particulier, il utilise les services de Presse-papiers OLE pour copier des données dans le Presse-papiers, mais utilise les fonctionnalités intégrées au contrôle Windows « edit » pour implémenter la fonctionnalité de collage du Presse-papiers. Cela montre une combinaison intéressante de l’utilisation traditionnelle de l’API Windows, ainsi que de l’intégration avec les nouvelles API OLE.

Pour plus d’informations sur les exemples d’applications, consultez l’article « Exemple d’aide MFC ».

Étude de cas : OCLIENT de MFC 2.0

Comme indiqué ci-dessus, OCLIENT a été inclus dans MFC 2.0 et implémenté OLE avec MFC/OLE1. Les étapes par lesquelles cette application a été initialement convertie pour utiliser les classes MFC/OLE 2 sont décrites ci-dessous. Plusieurs fonctionnalités ont été ajoutées une fois le port initial terminé pour mieux illustrer les classes MFC/OLE. Ces fonctionnalités ne seront pas couvertes ici ; reportez-vous à l’exemple lui-même pour plus d’informations sur ces fonctionnalités avancées.

Remarque

Les erreurs du compilateur et le processus pas à pas ont été créés avec Visual C++ 2.0. Des messages d’erreur et des emplacements spécifiques peuvent avoir changé avec Visual C++ 4.0, mais les informations conceptuelles restent valides.

Mise en route

L’approche adoptée pour porter l’exemple OCLIENT vers MFC/OLE consiste à commencer par la générer et à corriger les erreurs évidentes du compilateur qui aboutiront. Si vous prenez l’exemple OCLIENT de MFC 2.0 et compilez-le sous cette version de MFC, vous constaterez qu’il n’existe pas autant d’erreurs à résoudre. Les erreurs dans l’ordre dans lequel elles se sont produites sont décrites ci-dessous.

Compiler et corriger les erreurs

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

La première erreur concerne COleClientItem::Draw. Dans MFC/OLE1, il a fallu plus de paramètres que la version MFC/OLE. Les paramètres supplémentaires n’étaient souvent pas nécessaires et généralement NULL (comme dans cet exemple). Cette version de MFC peut déterminer automatiquement les valeurs des lpWBounds lorsque la capture de données modifiées qui est dessinée est un contrôleur de domaine de métafichier. En outre, le paramètre pFormatDC n’est plus nécessaire, car l’infrastructure en génère une à partir du contrôleur de domaine d’attribut du contrôleur de domaine passé. Pour résoudre ce problème, vous supprimez simplement les deux paramètres NULL supplémentaires à l’appel 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 '

Les erreurs ci-dessus résultent du fait que toutes les COleClientItem::CreateXXXX fonctions dans MFC/OLE1 ont requis qu’un nom unique soit passé pour représenter l’élément. Il s’agissait d’une exigence de l’API OLE sous-jacente. Cela n’est pas nécessaire dans MFC/OLE 2, car OLE 2 n’utilise pas DDE comme mécanisme de communication sous-jacent (le nom a été utilisé dans les conversations DDE). Pour résoudre ce problème, vous pouvez supprimer la CreateNewName fonction ainsi que toutes les références à celle-ci. Il est facile de déterminer ce que chaque fonction MFC/OLE attend dans cette version simplement en plaçant votre curseur sur l’appel et en appuyant sur F1.

Une autre zone sensiblement différente est la gestion du Presse-papiers OLE 2. Avec OLE1, vous avez utilisé les API du Presse-papiers Windows interagissent avec le Presse-papiers. Avec OLE 2, il s’agit d’un autre mécanisme. Les API MFC/OLE1 supposaient que le Presse-papiers était ouvert avant de copier un COleClientItem objet dans le Presse-papiers. Cela n’est plus nécessaire et entraîne l’échec de toutes les opérations de presse-papiers MFC/OLE. Lorsque vous modifiez le code pour supprimer les dépendances sur CreateNewName, vous devez également supprimer le code qui s’ouvre et ferme le Presse-papiers 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'

Ces erreurs proviennent du CMainView::OnInsertObject gestionnaire. La gestion de la commande « Insérer un nouvel objet » est une autre zone où les choses ont changé un peu. Dans ce cas, il est plus facile de fusionner simplement l’implémentation d’origine avec celle fournie par AppWizard pour une nouvelle application ole Container. En fait, il s’agit d’une technique que vous pouvez appliquer au portage d’autres applications. Dans MFC/OLE1, vous avez affiché la boîte de dialogue « Insérer un objet » en appelant AfxOleInsertDialog la fonction. Dans cette version, vous construisez un COleInsertObject objet de dialogue et appelez DoModal. De plus, de nouveaux éléments OLE sont créés avec un CLSID au lieu d’une chaîne classname. Le résultat final doit ressembler à ceci

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

Remarque

L’insertion d’un nouvel objet peut être différente pour votre application :

Il est également nécessaire d’inclure <afxodlgs.h>, qui contient la déclaration de la COleInsertObject classe de dialogue ainsi que les autres dialogues standard fournis par MFC.

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

Ces erreurs sont causées par le fait que certaines constantes OLE1 ont changé dans OLE 2, même si dans le concept elles sont identiques. Dans ce cas OLEVERB_PRIMARY , la valeur est passée à OLEIVERB_PRIMARY. Dans OLE1 et OLE 2, le verbe principal est généralement exécuté par un conteneur lorsque l’utilisateur double-clique sur un élément.

De plus, DoVerb prend maintenant un paramètre supplémentaire : un pointeur vers une vue (CView*). Ce paramètre est utilisé uniquement pour implémenter « Modification visuelle » (ou activation sur place). Pour le moment, vous définissez ce paramètre sur NULL, car vous n’implémentez pas cette fonctionnalité pour l’instant.

Pour vous assurer que l’infrastructure n’essaie jamais d’activer sur place, vous devez remplacer COleClientItem::CanActivate comme suit :

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

Dans MFC/OLE1, COleClientItem::GetBounds et SetBounds ont été utilisés pour interroger et manipuler l’étendue d’un élément (les membres et top les left membres étaient toujours zéro). Dans MFC/OLE 2, cela est plus directement pris en charge par COleClientItem::GetExtent et SetExtent, qui traitent d’une taille ou CSize à la place.

Le code de vos nouveaux appels SetItemRectToServer et UpdateItemRectFromServer ressemble à ceci :

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

Dans les appels d’API synchrones MFC/OLE1 d’un conteneur vers un serveur ont été simulés, car OLE1 était intrinsèquement asynchrone dans de nombreux cas. Il était nécessaire de case activée pour un appel asynchrone en cours avant de traiter les commandes de l’utilisateur. MFC/OLE1 a fourni la COleClientItem::InWaitForRelease fonction pour ce faire. Dans MFC/OLE 2, cela n’est pas nécessaire. Vous pouvez donc supprimer le remplacement de OnCommand dans CMainFrame tous ensemble.

À ce stade, OCLIENT compile et lie.

Autres modifications nécessaires

Toutefois, il existe quelques opérations qui ne sont pas effectuées pour empêcher l’exécution d’OCLIENT. Il est préférable de résoudre ces problèmes maintenant plutôt que plus tard.

Tout d’abord, il est nécessaire d’initialiser les bibliothèques OLE. Pour ce faire, appelez AfxOleInit à partir de InitInstance:

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

Il est également judicieux de case activée pour les fonctions virtuelles pour les modifications de liste de paramètres. Une de ces fonctions est COleClientItem::OnChange, substituée dans chaque application conteneur MFC/OLE. En examinant l’aide en ligne, vous verrez qu’un « DWORD dwParam » supplémentaire a été ajouté. Le nouveau CRectItem ::OnChange se présente comme suit :

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
}

Dans MFC/OLE1, les applications conteneur dérivées de la classe de document à partir de COleClientDoc. Dans MFC/OLE 2, cette classe a été supprimée et remplacée par COleDocument (cette nouvelle organisation facilite la génération d’applications conteneur/serveur). Il existe une #define qui est mappée COleClientDoc pour COleDocument simplifier le portage des applications MFC/OLE1 vers MFC/OLE 2, comme OCLIENT. L’une des fonctionnalités non fournies par COleDocumentCOleClientDoc celle-ci est les entrées de mappage de messages de commande standard. Pour ce faire, les applications serveur, qui utilisent COleDocument également (indirectement), ne comportent pas la surcharge de ces gestionnaires de commandes, sauf s’il s’agit d’une application conteneur/serveur. Vous devez ajouter les entrées suivantes à la carte de messages 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)

L’implémentation de toutes ces commandes se trouve dans COleDocumentla classe de base de votre document.

À ce stade, OCLIENT est une application conteneur OLE fonctionnelle. Il est possible d’insérer des éléments de n’importe quel type (OLE1 ou OLE 2). Étant donné que le code nécessaire pour activer l’activation sur place n’est pas implémenté, les éléments sont modifiés dans une fenêtre distincte comme avec OLE1. La section suivante décrit les modifications nécessaires pour activer la modification sur place (parfois appelée « Édition visuelle »).

Ajout de « Modification visuelle »

L’une des fonctionnalités les plus intéressantes d’OLE est l’activation sur place (ou « Modification visuelle »). Cette fonctionnalité permet à l’application serveur de prendre en charge les parties de l’interface utilisateur du conteneur afin de fournir une interface d’édition plus transparente pour l’utilisateur. Pour implémenter l’activation sur place sur OCLIENT, certaines ressources spéciales doivent être ajoutées, ainsi que du code supplémentaire. Ces ressources et le code sont normalement fournis par AppWizard . En fait, une grande partie du code ici a été empruntée directement à partir d’une nouvelle application AppWizard avec la prise en charge de « Conteneur ».

Tout d’abord, il est nécessaire d’ajouter une ressource de menu à utiliser lorsqu’un élément est actif sur place. Vous pouvez créer cette ressource de menu supplémentaire dans Visual C++ en copiant la ressource IDR_OCLITYPE et en supprimant toutes les fenêtres contextuelles fichier et fenêtre. Deux barres de séparation sont insérées entre les fenêtres contextuelles Fichier et Fenêtre pour indiquer la séparation des groupes (elle doit ressembler à : File || Window). Pour plus d’informations sur ce que signifient ces séparateurs et sur la façon dont les menus serveur et conteneur sont fusionnés, consultez Menus et Ressources : Fusion de menus.

Une fois ces menus créés, vous devez informer l’infrastructure de ces menus. Pour ce faire, appelez CDocTemplate::SetContainerInfo le modèle de document avant de l’ajouter à la liste des modèles de document dans votre InitInstance. Le nouveau code pour inscrire le modèle de document ressemble à ceci :

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

La ressource IDR_OLECLITYPE_INPLACE est la ressource spéciale sur place créée dans Visual C++.

Pour activer l’activation sur place, il existe des éléments qui doivent changer à la fois dans la CView classe dérivée (CMainView) ainsi que dans la COleClientItem classe dérivée (CRectItem). Toutes ces substitutions sont fournies par AppWizard et la plupart de l’implémentation provient directement d’une application AppWizard par défaut.

Lors de la première étape de ce port, l’activation sur place a été entièrement désactivée en remplaçant COleClientItem::CanActivate. Cette substitution doit être supprimée pour autoriser l’activation sur place. De plus, NULL a été passé à tous les appels à DoVerb (il y a deux d’entre eux) car la fourniture de la vue n’était nécessaire que pour l’activation sur place. Pour implémenter entièrement l’activation sur place, il est nécessaire de passer la vue correcte dans l’appel DoVerb . L’un de ces appels est dans CMainView::OnInsertObject:

pItem->DoVerb(OLEIVERB_SHOW, this);

Un autre est dans CMainView::OnLButtonDblClk:

m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);

Il est nécessaire de remplacer COleClientItem::OnGetItemPosition. Cela indique au serveur où placer sa fenêtre par rapport à la fenêtre du conteneur lorsque l’élément est activé. Pour OCLIENT, l’implémentation est triviale :

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

La plupart des serveurs implémentent également ce qui est appelé « redimensionnement sur place ». Cela permet à la fenêtre du serveur d’être dimensionnée et déplacée pendant que l’utilisateur modifie l’élément. Le conteneur doit participer à cette action, car le déplacement ou le redimensionnement de la fenêtre affecte généralement la position et la taille dans le document conteneur lui-même. L’implémentation pour OCLIENT synchronise le rectangle interne géré par m_rect avec la nouvelle position et la nouvelle taille.

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

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

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

    return TRUE;
}

À ce stade, il existe suffisamment de code pour permettre à un élément d’être activé sur place et de gérer le dimensionnement et le déplacement de l’élément lorsqu’il est actif, mais aucun code ne permet à l’utilisateur de quitter la session d’édition. Bien que certains serveurs fournissent cette fonctionnalité eux-mêmes en gérant la clé d’échappement, il est recommandé que les conteneurs fournissent deux façons de désactiver un élément : (1) en cliquant en dehors de l’élément, et (2) en appuyant sur la touche ESCAPE.

Pour la touche ESCAPE, ajoutez un accélérateur avec Visual C++ qui mappe la clé VK_ESCAPE à une commande, ID_CANCEL_EDIT est ajouté aux ressources. Le gestionnaire de cette commande suit :

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

Pour gérer le cas où l’utilisateur clique en dehors de l’élément, vous ajoutez le code suivant au début de CMainView::SetSelection:

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

Lorsqu’un élément est actif sur place, il doit avoir le focus. Pour vous assurer que c’est le cas, vous gérez OnSetFocus afin que le focus soit toujours transféré vers l’élément actif lorsque votre vue reçoit le focus :

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

Lorsque la vue est redimensionnée, vous devez informer l’élément actif que le rectangle de découpage a changé. Pour ce faire, vous fournissez un gestionnaire pour 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();
}

Étude de cas : HIERSVR de MFC 2.0

HIERSVR a également été inclus dans MFC 2.0 et implémenté OLE avec MFC/OLE1. Cette note décrit brièvement les étapes par lesquelles cette application a été initialement convertie pour utiliser les classes MFC/OLE 2. Un certain nombre de fonctionnalités ont été ajoutées une fois le port initial terminé pour mieux illustrer les classes MFC/OLE 2. Ces fonctionnalités ne seront pas couvertes ici ; reportez-vous à l’exemple lui-même pour plus d’informations sur ces fonctionnalités avancées.

Remarque

Les erreurs du compilateur et le processus pas à pas ont été créés avec Visual C++ 2.0. Des messages d’erreur et des emplacements spécifiques peuvent avoir changé avec Visual C++ 4.0, mais les informations conceptuelles restent valides.

Mise en route

L’approche adoptée pour porter l’exemple HIERSVR vers MFC/OLE consiste à commencer par la générer et à corriger les erreurs évidentes du compilateur qui aboutiront. Si vous prenez l’exemple HIERSVR à partir de MFC 2.0 et compilez-le sous cette version de MFC, vous constaterez qu’il n’y a pas beaucoup d’erreurs à résoudre (bien qu’il y ait plus que l’exemple OCLIENT). Les erreurs dans l’ordre dans lequel elles se produisent généralement sont décrites ci-dessous.

Compiler et corriger les erreurs

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

Cette première erreur signale un problème beaucoup plus important avec la InitInstance fonction pour les serveurs. L’initialisation requise pour un serveur OLE est probablement l’une des modifications les plus importantes que vous devrez apporter à votre application MFC/OLE1 pour l’exécuter. La meilleure chose à faire est d’examiner ce qu’AppWizard crée pour un serveur OLE et de modifier votre code selon les besoins. Voici quelques points à retenir :

Il est nécessaire d’initialiser les bibliothèques OLE en appelant AfxOleInit

Appelez SetServerInfo sur l’objet de modèle de document pour définir les handles de ressources du serveur et les informations de classe runtime que vous ne pouvez pas définir avec le CDocTemplate constructeur.

N’affichez pas la fenêtre principale de votre application si /Embedding est présent sur la ligne de commande.

Vous aurez besoin d’un GUID pour votre document. Il s’agit d’un identificateur unique pour le type de votre document (128 bits). AppWizard en créera un pour vous. Par conséquent, si vous utilisez la technique décrite ici pour copier le nouveau code à partir d’une nouvelle application serveur générée par AppWizard, vous pouvez simplement « voler » le GUID de cette application. Si ce n’est pas le cas, vous pouvez utiliser l’utilitaire GUIDGEN.EXE dans le répertoire BIN.

Il est nécessaire de « connecter » votre COleTemplateServer objet au modèle de document en appelant COleTemplateServer::ConnectTemplate.

Mettez à jour le Registre système lorsque votre application est exécutée autonome. De cette façon, si l’utilisateur déplace le fichier .EXE pour votre application, son exécution à partir de son nouvel emplacement met à jour la base de données d’inscription du système Windows pour qu’elle pointe vers le nouvel emplacement.

Après avoir appliqué toutes ces modifications en fonction de ce que Crée AppWizard pour InitInstance, le InitInstance GUID (et le GUID associé) pour HIERSVR doit lire comme suit :

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

Vous remarquerez que le code ci-dessus fait référence à un nouvel ID de ressource, IDR_HIERSVRTYPE_SRVR_EMo. Il s’agit de la ressource de menu à utiliser lorsqu’un document incorporé dans un autre conteneur est modifié. Dans MFC/OLE1, les éléments de menu spécifiques à la modification d’un élément incorporé ont été modifiés à la volée. L’utilisation d’une structure de menu entièrement différente lors de la modification d’un élément incorporé au lieu de modifier un document basé sur un fichier facilite grandement la fourniture d’interfaces utilisateur différentes pour ces deux modes distincts. Comme vous le verrez plus tard, une ressource de menu entièrement distincte est utilisée lors de la modification d’un objet incorporé sur place.

Pour créer cette ressource, chargez le script de ressource dans Visual C++ et copiez la ressource de menu IDR_HIERSVRTYPE existante. Renommez la nouvelle ressource en IDR_HIERSVRTYPE_SRVR_EMo (il s’agit de la même convention d’affectation de noms que celle utilisée par AppWizard). Remplacez ensuite « Enregistrement de fichier » par « Mise à jour de fichier » ; donnez-lui l’ID de commande ID_FILE_UPDATE. Remplacez également « Fichier Enregistrer sous » par « Fichier Enregistrer en tant que » ; donnez-lui l’ID de commande ID_FILE_SAVE_COPY_AS. L’infrastructure fournit l’implémentation de ces deux commandes.

\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

Il existe un certain nombre d’erreurs résultant du remplacement de OnSetData, car il fait référence au type OLESTATUS . OLESTATUS était la façon dont OLE1 a retourné des erreurs. Cela a changé en HRESULT dans OLE 2, bien que MFC convertit généralement un HRESULT en une COleException erreur contenant l’erreur. Dans ce cas particulier, le remplacement de OnSetData n’est plus nécessaire, donc la chose la plus simple à faire est de le supprimer.

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

Le COleServerItem constructeur accepte un paramètre « BOOL » supplémentaire. Cet indicateur détermine la façon dont la gestion de la mémoire est effectuée sur les COleServerItem objets. En le définissant sur TRUE, l’infrastructure gère la gestion de la mémoire de ces objets , en les supprimant lorsqu’elles ne sont plus nécessaires. HIERSVR utilise CServerItem des objets (dérivés de COleServerItem) dans le cadre de ses données natives. Vous devez donc définir cet indicateur sur FALSE. Cela permet à HIERSVR de déterminer quand chaque élément de serveur est supprimé.

\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

Comme ces erreurs impliquent, certaines fonctions « pure-virtuelles » n’ont pas été remplacées dans CServerItem. Cela est probablement dû au fait que la liste des paramètres d’OnDraw a changé. Pour corriger cette erreur, changez CServerItem::OnDraw comme suit (ainsi que la déclaration dans 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);
}

Le nouveau paramètre est « rSize ». Cela vous permet de remplir la taille du dessin, le cas échéant. Cette taille doit être dans HIMETRIC. Dans ce cas, il n’est pas pratique de remplir cette valeur, de sorte que les appels OnGetExtent d’infrastructure récupèrent l’étendue. Pour que cela fonctionne, vous devez implémenter 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 *'

Dans la fonction CServerItem ::CalcNodeSize, la taille de l’élément est convertie en HIMETRIC et stockée dans m_rectBounds. Le membre « m_rectBounds » non documenté n’existe COleServerItem pas (il a été partiellement remplacé par m_sizeExtent, mais dans OLE 2, ce membre a une utilisation légèrement différente de m_rectBounds dans OLE1). Au lieu de définir la taille HIMETRIC dans cette variable membre, vous le retournerez. Cette valeur de retour est utilisée dans OnGetExtent, implémentée précédemment.

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 remplace COleServerItem::OnGetTextDataégalement . Cette fonction est obsolète dans MFC/OLE et est remplacée par un autre mécanisme. La version MFC 3.0 de l’exemple OLE MFC HIERSVR implémente cette fonctionnalité en substituant COleServerItem::OnRenderFileData. Cette fonctionnalité n’est pas importante pour ce port de base. Vous pouvez donc supprimer le remplacement OnGetTextData.

Il existe de nombreuses autres erreurs dans svritem.cpp qui n’ont pas été traitées. Il ne s’agit pas d’erreurs « réelles » : il s’agit simplement d’erreurs provoquées par des erreurs précédentes.

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

COleServerItem::CopyToClipboard ne prend plus en charge l’indicateur bIncludeNative . Les données natives (les données écrites par la fonction Sérialiser de l’élément de serveur) sont toujours copiées. Vous supprimez donc le premier paramètre. En outre, CopyToClipboard lève une exception lorsqu’une erreur se produit au lieu de retourner FALSE. Modifiez le code de CServerView ::OnEditCopy comme suit :

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

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

Bien qu’il y ait eu plus d’erreurs résultant de la compilation de la version MFC 2.0 de HIERSVR que pour la même version d’OCLIENT, il y avait en fait moins de modifications.

À ce stade, HIERSVR compile et lie et fonctionne en tant que serveur OLE, mais sans la fonctionnalité d’édition sur place, qui sera implémentée ensuite.

Ajout de « Modification visuelle »

Pour ajouter « Modification visuelle » (ou activation sur place) à cette application serveur, il n’existe que quelques éléments dont vous devez vous occuper :

  • Vous avez besoin d’une ressource de menu spéciale à utiliser lorsque l’élément est actif sur place.

  • Cette application a une barre d’outils. Vous avez donc besoin d’une barre d’outils avec uniquement un sous-ensemble de la barre d’outils normale pour faire correspondre les commandes de menu disponibles à partir du serveur (correspond à la ressource de menu mentionnée ci-dessus).

  • Vous avez besoin d’une nouvelle classe dérivée de COleIPFrameWnd qui fournit l’interface utilisateur sur place (comme CMainFrame, dérivée de CMDIFrameWnd, fournit l’interface utilisateur MDI).

  • Vous devez indiquer à l’infrastructure ces ressources et classes spéciales.

La ressource de menu est facile à créer. Exécutez Visual C++, copiez la ressource de menu IDR_HIERSVRTYPE dans une ressource de menu appelée IDR_HIERSVRTYPE_SRVR_IP. Modifiez le menu afin que seules les fenêtres contextuelles Modifier et Aide soient laissées. Ajoutez deux séparateurs au menu entre les menus Édition et Aide (il doit ressembler à : Edit || Help). Pour plus d’informations sur ce que signifient ces séparateurs et sur la façon dont les menus serveur et conteneur sont fusionnés, consultez Menus et Ressources : Fusion de menus.

La bitmap de la barre d’outils de sous-ensemble peut être facilement créée en copiant celle à partir d’une nouvelle application générée par AppWizard avec une option « Server » case activée ed. Cette bitmap peut ensuite être importée dans Visual C++. Veillez à donner à la bitmap un ID de IDR_HIERSVRTYPE_SRVR_IP.

La classe dérivée de COleIPFrameWnd peut également être copiée à partir d’une application générée par AppWizard avec prise en charge du serveur. Copiez les deux fichiers, IPFRAME. CPP et IPFRAME. H et les ajouter au projet. Assurez-vous que l’appel LoadBitmap fait référence à IDR_HIERSVRTYPE_SRVR_IP, la bitmap créée à l’étape précédente.

Maintenant que toutes les nouvelles ressources et classes sont créées, ajoutez le code nécessaire pour que l’infrastructure sache ces informations (et sait que cette application prend désormais en charge la modification sur place). Pour ce faire, ajoutez d’autres paramètres à l’appel SetServerInfo dans la InitInstance fonction :

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

Il est maintenant prêt à s’exécuter sur place dans n’importe quel conteneur qui prend également en charge l’activation sur place. Mais il existe un bogue mineur qui se cache toujours dans le code. HIERSVR prend en charge un menu contextuel, affiché lorsque l’utilisateur appuie sur le bouton droit de la souris. Ce menu fonctionne lorsque HIERSVR est entièrement ouvert, mais ne fonctionne pas lors de la modification d’une incorporation sur place. La raison peut être épinglée à cette seule ligne de code dans CServerView ::OnRButtonDown :

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

Notez la référence à AfxGetApp()->m_pMainWnd. Lorsque le serveur est activé sur place, il a une fenêtre principale et m_pMainWnd est défini, mais il est généralement invisible. En outre, cette fenêtre fait référence à la fenêtre principale de l’application, la fenêtre frame MDI qui s’affiche lorsque le serveur est entièrement ouvert ou exécuté autonome. Elle ne fait pas référence à la fenêtre d’image active , qui lorsqu’elle est activée sur place est une fenêtre frame dérivée de COleIPFrameWnd. Pour obtenir la fenêtre active correcte même lors de la modification sur place, cette version de MFC ajoute une nouvelle fonction. AfxGetMainWnd En règle générale, vous devez utiliser cette fonction au lieu de AfxGetApp()->m_pMainWnd. Ce code doit changer comme suit :

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

Vous disposez maintenant d’un serveur OLE minimalement activé pour l’activation sur place fonctionnelle. Mais il existe toujours de nombreuses fonctionnalités disponibles avec MFC/OLE 2 qui n’étaient pas disponibles dans MFC/OLE1. Pour plus d’idées sur les fonctionnalités que vous souhaitez implémenter, consultez l’exemple HIERSVR. Voici quelques-unes des fonctionnalités que HIERSVR implémente :

  • Zoom avant, pour un comportement WYSIWYG vrai en ce qui concerne le conteneur.

  • Faire glisser/déplacer et un format de Presse-papiers personnalisé.

  • Défilement de la fenêtre conteneur lorsque la sélection est modifiée.

L’exemple HIERSVR dans MFC 3.0 utilise également une conception légèrement différente pour ses éléments serveur. Cela permet de conserver la mémoire et de rendre vos liens plus flexibles. Avec la version 2.0 de HIERSVR chaque nœud de l’arborescence est aCOleServerItem. COleServerItem transporte un peu plus de surcharge que ce qui est strictement nécessaire pour chacun de ces nœuds, mais il COleServerItem est nécessaire pour chaque lien actif. Mais pour la plupart, il y a très peu de liens actifs à un moment donné. Pour rendre cette opération plus efficace, HIERSVR dans cette version de MFC sépare le nœud du COleServerItem. Il a à la fois un CServerNode et une CServerItem classe. Le CServerItem (dérivé de COleServerItem) est créé uniquement si nécessaire. Une fois que le conteneur (ou les conteneurs) s’arrête à l’aide de ce lien particulier vers ce nœud particulier, l’objet CServerItem associé au CServerNode est supprimé. Cette conception est plus efficace et plus flexible. Sa flexibilité est prise en compte lors de la gestion de plusieurs liens de sélection. Aucune de ces deux versions de HIERSVR ne prend en charge plusieurs sélections, mais il serait beaucoup plus facile d’ajouter (et de prendre en charge les liens vers ces sélections) avec la version MFC 3.0 de HIERSVR, car elle COleServerItem est séparée des données natives.

Voir aussi

Notes techniques par numéro
Notes techniques par catégorie