TN041: migrazione da MFC/OLE1 a MFC/OLE 2
Nota
La seguente nota tecnica non è stata aggiornata da quando è stata inclusa per la prima volta nella documentazione online. Di conseguenza, alcune procedure e argomenti potrebbero essere non aggiornati o errati. Per le informazioni più recenti, è consigliabile cercare l'argomento di interesse nell'indice della documentazione online.
Problemi generali relativi alla migrazione
Uno degli obiettivi di progettazione per le classi OLE 2 in MFC 2.5 (e versioni successive) era quello di mantenere gran parte della stessa architettura messa in atto in MFC 2.0 per il supporto OLE 1.0. Di conseguenza, molte delle stesse classi OLE in MFC 2.0 esistono ancora in questa versione di MFC (COleDocument
, COleServerDoc
, COleClientItem
, COleServerItem
). Inoltre, molte delle API in queste classi sono esattamente le stesse. Tuttavia, OLE 2 è drasticamente diverso da OLE 1.0, quindi è possibile prevedere che alcuni dettagli siano stati modificati. Se hai familiarità con il supporto OLE1 di MFC 2.0, ti sentirai a casa con il supporto MFC 2.0.
Se si accetta un'applicazione MFC/OLE1 esistente e si aggiunge la funzionalità OLE 2, è necessario leggere prima questa nota. Questa nota illustra alcuni problemi generali che possono verificarsi durante la conversione della funzionalità OLE1 in MFC/OLE 2 e quindi illustra i problemi rilevati durante la conversione di due applicazioni incluse in MFC 2.0: gli esempi OLE MFC OCLIENT e HIERSVR.
L'architettura di documento/visualizzazione MFC è importante
Se l'applicazione non usa l'architettura Document/View di MFC e si vuole aggiungere il supporto OLE 2 all'applicazione, è ora possibile passare a Documento/Visualizzazione. Molti dei vantaggi delle classi OLE 2 di MFC vengono realizzati solo quando l'applicazione usa l'architettura e i componenti predefiniti di MFC.
L'implementazione di un server o di un contenitore senza usare l'architettura MFC è possibile, ma non consigliata.
Usare l'implementazione MFC anziché quella personalizzata
Le classi di "implementazione canned" MFC, CToolBar
ad esempio , CStatusBar
e CScrollView
dispongono di codice caso speciale predefinito per il supporto OLE 2. Pertanto, se è possibile usare queste classi nell'applicazione, è possibile trarre vantaggio dal lavoro richiesto per renderle consapevoli di OLE. Anche in questo caso, è possibile "eseguire il roll-your-own" classi per questi scopi, ma non è consigliato. Se è necessario implementare funzionalità simili, il codice sorgente MFC è un riferimento eccellente per gestire alcuni dei punti più sottili di OLE (soprattutto quando si tratta di attivazione sul posto).
Esaminare il codice di esempio MFC
Sono disponibili diversi esempi MFC che includono la funzionalità OLE. Ognuna di queste applicazioni implementa OLE da un angolo diverso:
HIERSVR è destinato principalmente all'uso come applicazione server. È stato incluso in MFC 2.0 come applicazione MFC/OLE1 ed è stato convertito in MFC/OLE 2 e quindi esteso in modo da implementare molte funzionalità OLE disponibili in OLE 2.
OCLIENT Si tratta di un'applicazione contenitore autonoma, destinata a illustrare molte delle funzionalità OLE dal punto di vista del contenitore. È stato convertito anche da MFC 2.0 e quindi esteso per supportare molte delle funzionalità OLE più avanzate, ad esempio formati di Appunti personalizzati e collegamenti a elementi incorporati.
DRAWCLI Questa applicazione implementa il supporto di contenitori OLE molto simile a OCLIENT, ad eccezione del fatto che lo fa all'interno del framework di un programma di disegno orientato agli oggetti esistente. Illustra come implementare il supporto dei contenitori OLE e integrarlo nell'applicazione esistente.
SUPERPAD Questa applicazione, oltre a essere un'applicazione autonoma fine, è anche un server OLE. Il supporto del server implementato è piuttosto minimalista. Di particolare interesse è il modo in cui usa i servizi Appunti OLE per copiare i dati negli Appunti, ma usa la funzionalità incorporata nel controllo "modifica" di Windows per implementare la funzionalità incolla degli Appunti. Questo mostra una combinazione interessante di utilizzo tradizionale delle API di Windows e l'integrazione con le nuove API OLE.
Per altre informazioni sulle applicazioni di esempio, vedere la "Guida di esempio MFC".
Case study: OCLIENT da MFC 2.0
Come illustrato in precedenza, OCLIENT è stato incluso in MFC 2.0 e implementato OLE con MFC/OLE1. I passaggi in base ai quali questa applicazione è stata inizialmente convertita per l'uso delle classi MFC/OLE 2 sono descritte di seguito. Una serie di funzionalità sono state aggiunte dopo il completamento della porta iniziale per illustrare meglio le classi MFC/OLE. Queste funzionalità non verranno trattate qui; fare riferimento all'esempio stesso per altre informazioni su tali funzionalità avanzate.
Nota
Gli errori del compilatore e il processo dettagliato sono stati creati con Visual C++ 2.0. I messaggi di errore e i percorsi specifici possono essere stati modificati con Visual C++ 4.0, ma le informazioni concettuali rimangono valide.
Ottenere l'up e l'esecuzione
L'approccio adottato per convertire l'esempio OCLIENT in MFC/OLE consiste nell'iniziare compilandolo e correggendo gli errori ovvi del compilatore che genereranno il risultato. Se si accetta l'esempio OCLIENT da MFC 2.0 e lo si compila in questa versione di MFC, si noterà che non sono presenti molti errori da risolvere. Gli errori nell'ordine in cui si sono verificati sono descritti di seguito.
Compilare e correggere gli errori
\oclient\mainview.cpp(104) : error C2660: 'Draw' : function does not take 4 parameters
Il primo errore riguarda COleClientItem::Draw
. In MFC/OLE1 sono necessari più parametri rispetto alla versione MFC/OLE. I parametri aggiuntivi spesso non erano necessari e in genere NULL (come in questo esempio). Questa versione di MFC può determinare automaticamente i valori per lpWBounds quando cdc da disegnare è un controller di dominio metafile. Inoltre, il parametro pFormatDC non è più necessario perché il framework ne creerà uno dall'attributo DC del pDC passato. Per risolvere questo problema, è sufficiente rimuovere i due parametri NULL aggiuntivi alla chiamata 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 '
Gli errori precedenti derivano dal fatto che tutte le COleClientItem::CreateXXXX
funzioni in MFC/OLE1 richiedevano che venga passato un nome univoco per rappresentare l'elemento. Si tratta di un requisito dell'API OLE sottostante. Questo non è necessario in MFC/OLE 2 perché OLE 2 non usa DDE come meccanismo di comunicazione sottostante (il nome è stato usato nelle conversazioni DDE). Per risolvere questo problema, è possibile rimuovere la CreateNewName
funzione e tutti i riferimenti. È facile scoprire cosa aspetta ogni funzione MFC/OLE in questa versione semplicemente posizionando il cursore sulla chiamata e premendo F1.
Un'altra area significativamente diversa è la gestione degli Appunti OLE 2. Con OLE1 sono state usate le API degli Appunti di Windows che interagiscono con gli Appunti. Con OLE 2 questa operazione viene eseguita con un meccanismo diverso. Le API MFC/OLE1 presupponevano che gli Appunti siano stati aperti prima di copiare un COleClientItem
oggetto negli Appunti. Questa operazione non è più necessaria e causerà l'esito negativo di tutte le operazioni degli Appunti MFC/OLE. Mentre si modifica il codice per rimuovere le dipendenze da CreateNewName
, è necessario rimuovere anche il codice che si apre e chiude gli Appunti di 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'
Questi errori derivano dal CMainView::OnInsertObject
gestore. La gestione del comando "Inserisci nuovo oggetto" è un'altra area in cui le cose sono cambiate un po'. In questo caso, è più semplice unire semplicemente l'implementazione originale con quella fornita da AppWizard per una nuova applicazione contenitore OLE. Si tratta infatti di una tecnica che è possibile applicare alla conversione di altre applicazioni. In MFC/OLE1 è stata visualizzata la finestra di dialogo "Inserisci oggetto" chiamando AfxOleInsertDialog
la funzione . In questa versione si costruisce un COleInsertObject
oggetto finestra di dialogo e si chiama DoModal
. Inoltre, vengono creati nuovi elementi OLE con un CLSID anziché una stringa classname. Il risultato finale dovrebbe essere simile al seguente
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();
Nota
Inserisci nuovo oggetto può essere diverso per l'applicazione:
È anche necessario includere <afxodlgs.h>, che contiene la dichiarazione per la COleInsertObject
classe dialog e gli altri dialoghi standard forniti da MFC.
\oclient\mainview.cpp(367) : error C2065: 'OLEVERB_PRIMARY' : undeclared identifier
\oclient\mainview.cpp(367) : error C2660: 'DoVerb' : function does not take 1 parameters
Questi errori sono causati dal fatto che alcune costanti OLE1 sono cambiate in OLE 2, anche se nel concetto sono le stesse. In questo caso OLEVERB_PRIMARY
è stato modificato in OLEIVERB_PRIMARY
. In OLE1 e OLE 2 il verbo primario viene in genere eseguito da un contenitore quando l'utente fa doppio clic su un elemento.
Inoltre, DoVerb
accetta ora un parametro aggiuntivo, ovvero un puntatore a una visualizzazione (CView
*). Questo parametro viene usato solo per implementare "Modifica visiva" (o attivazione sul posto). Per il momento si imposta tale parametro su NULL, perché al momento non si implementa questa funzionalità.
Per assicurarsi che il framework non tenti mai di attivare sul posto, è necessario eseguire l'override COleClientItem::CanActivate
come indicato di seguito:
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
In MFC/OLE1 COleClientItem::GetBounds
e SetBounds
sono stati usati per eseguire query e modificare l'extent di un elemento (i left
membri e top
erano sempre zero). In MFC/OLE 2 questo è più direttamente supportato da COleClientItem::GetExtent
e SetExtent
, che gestiscono invece una dimensione o CSize
.
Il codice per le nuove chiamate SetItemRectToServer e UpdateItemRectFromServer è simile al seguente:
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
In MFC/OLE1 le chiamate API sincrone da un contenitore a un server sono state simulate, perché OLE1 era intrinsecamente asincrono in molti casi. Prima di elaborare i comandi dall'utente, è necessario verificare la presenza di una chiamata asincrona in sospeso in corso. MFC/OLE1 ha fornito la COleClientItem::InWaitForRelease
funzione per farlo. In MFC/OLE 2 non è necessario, quindi è possibile rimuovere l'override di OnCommand in CMainFrame tutti insieme.
A questo punto OCLIENT compilerà e collegarà.
Altre modifiche necessarie
Ci sono alcune cose che non vengono eseguite che impediranno l'esecuzione di OCLIENT, tuttavia. È preferibile risolvere questi problemi ora anziché in un secondo momento.
Prima di tutto, è necessario inizializzare le librerie OLE. Questa operazione viene eseguita chiamando AfxOleInit
da InitInstance
:
if (!AfxOleInit())
{
AfxMessageBox("Failed to initialize OLE libraries");
return FALSE;
}
È anche consigliabile verificare la presenza di modifiche all'elenco dei parametri per le funzioni virtuali. Una di queste funzioni è COleClientItem::OnChange
, sottoposta a override in ogni applicazione contenitore MFC/OLE. Esaminando la Guida online, si noterà che è stato aggiunto un 'DWORD dwParam' aggiuntivo. Il nuovo oggetto CRectItem::OnChange ha l'aspetto seguente:
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
}
In MFC/OLE1 le applicazioni contenitore hanno derivato la classe documento da COleClientDoc
. In MFC/OLE 2 questa classe è stata rimossa e sostituita da COleDocument
(questa nuova organizzazione semplifica la compilazione di applicazioni contenitore/server). Esiste un #define che esegue il mapping COleClientDoc
per COleDocument
semplificare la conversione di applicazioni MFC/OLE1 a MFC/OLE 2, ad esempio OCLIENT. Una delle funzionalità non fornite da COleDocument
che è stata fornita da COleClientDoc
è la voce della mappa dei messaggi di comando standard. Questa operazione viene eseguita in modo che le applicazioni server, che usano COleDocument
anche (indirettamente), non comportano il sovraccarico di questi gestori di comandi, a meno che non siano un'applicazione contenitore/server. È necessario aggiungere le voci seguenti alla mappa dei messaggi 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'implementazione di tutti questi comandi è in COleDocument
, ovvero la classe di base per il documento.
A questo punto, OCLIENT è un'applicazione contenitore OLE funzionale. È possibile inserire elementi di qualsiasi tipo (OLE1 o OLE 2). Poiché il codice necessario per abilitare l'attivazione sul posto non è implementato, gli elementi vengono modificati in una finestra separata come con OLE1. La sezione successiva illustra le modifiche necessarie per abilitare la modifica sul posto (talvolta denominata "Modifica visiva").
Aggiunta di "Modifica visiva"
Una delle funzionalità più interessanti di OLE è l'attivazione sul posto (o "Modifica visiva"). Questa funzionalità consente all'applicazione server di assumere parti dell'interfaccia utente del contenitore per fornire un'interfaccia di modifica più semplice per l'utente. Per implementare l'attivazione sul posto in OCLIENT, è necessario aggiungere alcune risorse speciali, nonché codice aggiuntivo. Queste risorse e il codice sono normalmente forniti da AppWizard, in realtà gran parte del codice qui è stato preso in prestito direttamente da una nuova applicazione AppWizard con supporto "Contenitore".
Prima di tutto, è necessario aggiungere una risorsa di menu da usare quando è presente un elemento attivo sul posto. È possibile creare questa risorsa di menu aggiuntiva in Visual C++ copiando la risorsa IDR_OCLITYPE e rimuovendo tutti i popup file e finestra. Tra i popup File e Window vengono inserite due barre separatori per indicare la separazione dei gruppi ( dovrebbe essere simile a : File || Window
). Per altre informazioni sul significato di questi separatori e sul modo in cui i menu del server e del contenitore vengono uniti, vedere Menu e risorse: Unione di menu.
Dopo aver creato questi menu, è necessario informare il framework su di essi. Questa operazione viene eseguita chiamando CDocTemplate::SetContainerInfo
per il modello di documento prima di aggiungerlo all'elenco dei modelli di documento in InitInstance. Il nuovo codice per registrare il modello di documento è simile al seguente:
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 risorsa IDR_OLECLITYPE_INPLACE è la risorsa sul posto speciale creata in Visual C++.
Per abilitare l'attivazione sul posto, è necessario modificare sia la CView
classe derivata (CMainView) sia la COleClientItem
classe derivata (CRectItem). Tutte queste sostituzioni vengono fornite da AppWizard e la maggior parte dell'implementazione proviene direttamente da un'applicazione AppWizard predefinita.
Nel primo passaggio di questa porta, l'attivazione sul posto è stata disabilitata completamente eseguendo l'override di COleClientItem::CanActivate
. Questa sostituzione deve essere rimossa per consentire l'attivazione sul posto. Inoltre, NULL è stato passato a tutte le chiamate a DoVerb
(ne esistono due) perché la visualizzazione era necessaria solo per l'attivazione sul posto. Per implementare completamente l'attivazione sul posto, è necessario passare la visualizzazione corretta nella DoVerb
chiamata. Una di queste chiamate è in CMainView::OnInsertObject
:
pItem->DoVerb(OLEIVERB_SHOW, this);
Un altro è in CMainView::OnLButtonDblClk
:
m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);
È necessario eseguire l'override COleClientItem::OnGetItemPosition
di . Indica al server dove inserire la finestra relativa alla finestra del contenitore quando l'elemento è attivato sul posto. Per OCLIENT, l'implementazione è semplice:
void CRectItem::OnGetItemPosition(CRect& rPosition)
{
rPosition = m_rect;
}
La maggior parte dei server implementa anche ciò che viene definito "ridimensionamento sul posto". In questo modo la finestra del server può essere ridimensionata e spostata mentre l'utente sta modificando l'elemento. Il contenitore deve partecipare a questa azione, poiché lo spostamento o il ridimensionamento della finestra in genere influiscono sulla posizione e le dimensioni all'interno del documento contenitore stesso. L'implementazione per OCLIENT sincronizza il rettangolo interno gestito da m_rect con la nuova posizione e le nuove dimensioni.
BOOL CRectItem::OnChangeItemPosition(const CRect& rectPos)
{
ASSERT_VALID(this);
if (!COleClientItem::OnChangeItemPosition(rectPos))
return FALSE;
Invalidate();
m_rect = rectPos;
Invalidate();
GetDocument()->SetModifiedFlag();
return TRUE;
}
A questo punto, è disponibile codice sufficiente per consentire l'attivazione sul posto di un elemento e per gestire il ridimensionamento e lo spostamento dell'elemento quando è attivo, ma nessun codice consentirà all'utente di uscire dalla sessione di modifica. Anche se alcuni server forniranno questa funzionalità gestendo la chiave di escape, è consigliabile che i contenitori forniscano due modi per disattivare un elemento: (1) facendo clic all'esterno dell'elemento e (2) premendo il tasto ESCAPE.
Per il tasto ESCAPE aggiungere un acceleratore con Visual C++ che esegue il mapping della chiave VK_ESCAPE a un comando, ID_CANCEL_EDIT viene aggiunto alle risorse. Il gestore per questo comando segue:
// 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);
}
Per gestire il caso in cui l'utente fa clic all'esterno dell'elemento, aggiungere il codice seguente all'inizio di CMainView::SetSelection
:
if (pNewSel != m_pSelection || pNewSel == NULL)
{
COleClientItem* pActiveItem =
GetDocument()->GetInPlaceActiveItem(this);
if (pActiveItem != NULL&& pActiveItem != pNewSel)
pActiveItem->Close();
}
Quando un elemento è attivo sul posto, deve avere lo stato attivo. Per assicurarsi che questo sia il caso in cui si gestisce OnSetFocus in modo che lo stato attivo venga sempre trasferito all'elemento attivo quando la visualizzazione riceve lo stato attivo:
// 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 la visualizzazione viene ridimensionata, è necessario notificare all'elemento attivo che il rettangolo di ritaglio è stato modificato. A tale scopo, fornire un gestore per 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();
}
Case study: HIERSVR da MFC 2.0
HIERSVR è stato incluso anche in MFC 2.0 e implementato OLE con MFC/OLE1. Questa nota descrive brevemente i passaggi in base ai quali questa applicazione è stata inizialmente convertita per l'uso delle classi MFC/OLE 2. Dopo il completamento della porta iniziale sono state aggiunte diverse funzionalità per illustrare meglio le classi MFC/OLE 2. Queste funzionalità non verranno trattate qui; fare riferimento all'esempio stesso per altre informazioni su tali funzionalità avanzate.
Nota
Gli errori del compilatore e il processo dettagliato sono stati creati con Visual C++ 2.0. I messaggi di errore e i percorsi specifici possono essere stati modificati con Visual C++ 4.0, ma le informazioni concettuali rimangono valide.
Ottenere l'up e l'esecuzione
L'approccio adottato per convertire l'esempio HIERSVR in MFC/OLE consiste nell'iniziare compilandolo e correggendo gli errori ovvi del compilatore che genereranno il risultato. Se si accetta l'esempio HIERSVR da MFC 2.0 e lo si compila in questa versione di MFC, si noterà che non sono presenti molti errori da risolvere (anche se esistono più di con l'esempio OCLIENT). Gli errori nell'ordine in cui si verificano in genere sono descritti di seguito.
Compilare e correggere gli errori
\hiersvr\hiersvr.cpp(83) : error C2039: 'RunEmbedded' : is not a member of 'COleTemplateServer'
Questo primo errore indica un problema molto più grande con la funzione per i InitInstance
server. L'inizializzazione necessaria per un server OLE è probabilmente una delle modifiche più importanti da apportare all'applicazione MFC/OLE1 per eseguirla. La cosa migliore da fare è esaminare le operazioni create da AppWizard per un server OLE e modificare il codice in base alle esigenze. Ecco alcuni punti da tenere presente:
È necessario inizializzare le librerie OLE chiamando AfxOleInit
Chiamare SetServerInfo nell'oggetto modello di documento per impostare gli handle delle risorse del server e le informazioni sulla classe di runtime che non è possibile impostare con il CDocTemplate
costruttore.
Non visualizzare la finestra principale dell'applicazione se /Embedding è presente nella riga di comando.
È necessario un GUID per il documento. Si tratta di un identificatore univoco per il tipo del documento (128 bit). AppWizard ne creerà uno automaticamente, quindi se si usa la tecnica descritta qui per copiare il nuovo codice da una nuova applicazione server generata da AppWizard, è sufficiente "rubare" il GUID da tale applicazione. In caso contrario, è possibile usare l'utilità GUIDGEN.EXE nella directory BIN.
È necessario "connettere" l'oggetto COleTemplateServer
al modello di documento chiamando COleTemplateServer::ConnectTemplate
.
Aggiornare il Registro di sistema quando l'applicazione viene eseguita in modalità autonoma. In questo modo, se l'utente sposta il file EXE per l'applicazione, eseguirlo dal nuovo percorso aggiornerà il database di registrazione del sistema Windows in modo che punti alla nuova posizione.
Dopo aver applicato tutte queste modifiche in base a ciò che AppWizard crea per InitInstance
, il (e il InitInstance
GUID correlato) per HIERSVR deve essere letto come segue:
// 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;
}
Si noterà che il codice precedente fa riferimento a un nuovo ID risorsa, IDR_HIERSVRTYPE_SRVR_EMB. Si tratta della risorsa di menu da usare quando viene modificato un documento incorporato in un altro contenitore. In MFC/OLE1 le voci di menu specifiche per la modifica di un elemento incorporato sono state modificate in tempo reale. L'uso di una struttura di menu completamente diversa quando si modifica un elemento incorporato anziché modificare un documento basato su file rende molto più semplice fornire interfacce utente diverse per queste due modalità separate. Come si vedrà più avanti, viene usata una risorsa di menu completamente separata durante la modifica di un oggetto incorporato sul posto.
Per creare questa risorsa, caricare lo script della risorsa in Visual C++ e copiare la risorsa di menu IDR_HIERSVRTYPE esistente. Rinominare la nuova risorsa in IDR_HIERSVRTYPE_SRVR_EMB (si tratta della stessa convenzione di denominazione usata da AppWizard). Modificare quindi "File Save" in "File Update"; assegnargli l'ID comando ID_FILE_UPDATE. Modificare anche "File Salva con nome" in "File Salva copia con nome"; assegnargli l'ID comando ID_FILE_SAVE_COPY_AS. Il framework fornisce l'implementazione di entrambi questi comandi.
\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
Esistono diversi errori risultanti dall'override di OnSetData
, poiché fa riferimento al tipo OLESTATUS . OLESTATUS è stato il modo in cui OLE1 ha restituito errori. Questo valore è stato modificato in HRESULT in OLE 2, anche se MFC converte in genere un HRESULT in un oggetto COleException
contenente l'errore. In questo caso specifico, l'override di OnSetData
non è più necessario, quindi la cosa più semplice da fare è rimuoverla.
\hiersvr\svritem.cpp(30) : error C2660: 'COleServerItem::COleServerItem' : function does not take 1 parameters
Il COleServerItem
costruttore accetta un parametro 'BOOL' aggiuntivo. Questo flag determina la modalità di gestione della COleServerItem
memoria sugli oggetti. Impostandolo su TRUE, il framework gestisce la gestione della memoria di questi oggetti, eliminandoli quando non sono più necessari. HIERSVR usa CServerItem
gli oggetti (derivati da COleServerItem
) come parte dei dati nativi, quindi questo flag verrà impostato su FAL edizione Standard. Ciò consente a HIERSVR di determinare quando ogni elemento del server viene eliminato.
\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
Poiché questi errori implicano, esistono alcune funzioni "pure-virtual" che non sono state sottoposte a override in CServerItem. Molto probabilmente questo è causato dal fatto che l'elenco di parametri di OnDraw è cambiato. Per correggere l'errore, modificare CServerItem::OnDraw
come indicato di seguito (nonché la dichiarazione in 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);
}
Il nuovo parametro è 'rSize'. Ciò consente di riempire le dimensioni del disegno, se conveniente. Questa dimensione deve essere in HIMETRIC. In questo caso, non è consigliabile inserire questo valore, quindi il framework chiama OnGetExtent
per recuperare l'extent. Per poter funzionare, è necessario implementare 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 *'
Nella funzione CServerItem::CalcNodeSize le dimensioni dell'elemento vengono convertite in HIMETRIC e archiviate in m_rectBounds. Il membro "m_rectBounds" non documentato di COleServerItem
non esiste (è stato parzialmente sostituito da m_sizeExtent, ma in OLE 2 questo membro ha un utilizzo leggermente diverso rispetto a m_rectBounds fatto in OLE1). Anziché impostare le dimensioni HIMETRIC in questa variabile membro, verrà restituita. Questo valore restituito viene usato in OnGetExtent
, implementato in precedenza.
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 esegue anche l'override COleServerItem::OnGetTextData
di . Questa funzione è obsoleta in MFC/OLE e viene sostituita da un meccanismo diverso. La versione MFC 3.0 dell'esempio OLE MFC HIERSVR implementa questa funzionalità eseguendo l'override di COleServerItem::OnRenderFileData
. Questa funzionalità non è importante per questa porta di base, quindi è possibile rimuovere l'override onGetTextData.
Esistono molti altri errori in svritem.cpp che non sono stati risolti. Non sono errori "reali", ma solo errori causati da errori precedenti.
\hiersvr\svrview.cpp(325) : error C2660: 'CopyToClipboard' : function does not take 2 parameters
COleServerItem::CopyToClipboard
non supporta più il bIncludeNative
flag. I dati nativi (i dati scritti dalla funzione Serialize dell'elemento server) vengono sempre copiati, quindi si rimuove il primo parametro. Inoltre, CopyToClipboard
genererà un'eccezione quando si verifica un errore anziché restituire FAL edizione Standard. Modificare il codice per CServerView::OnEditCopy come indicato di seguito:
void CServerView::OnEditCopy()
{
if (m_pSelectedNode == NULL)
AfxThrowNotSupportedException();
TRY
{
m_pSelectedNode->CopyToClipboard(TRUE);
}
CATCH_ALL(e)
{
AfxMessageBox("Copy to clipboard failed");
}
END_CATCH_ALL
}
Sebbene si siano verificati più errori risultanti dalla compilazione della versione MFC 2.0 di HIERSVR rispetto alla stessa versione di OCLIENT, sono state effettivamente apportate meno modifiche.
A questo punto HIERSVR compilerà e funzionerà come server OLE, ma senza la funzionalità di modifica sul posto, che verrà implementata successivamente.
Aggiunta di "Modifica visiva"
Per aggiungere "Modifica visiva" (o attivazione sul posto) a questa applicazione server, è necessario occuparsi solo di alcuni aspetti:
È necessaria una risorsa di menu speciale da usare quando l'elemento è attivo sul posto.
Questa applicazione dispone di una barra degli strumenti, quindi è necessaria una barra degli strumenti con solo un subset della barra degli strumenti normale in modo che corrisponda ai comandi di menu disponibili dal server (corrisponde alla risorsa di menu menzionata in precedenza).
È necessaria una nuova classe derivata da
COleIPFrameWnd
che fornisce l'interfaccia utente sul posto (molto simile a CMainFrame, derivata daCMDIFrameWnd
, fornisce l'interfaccia utente MDI).È necessario comunicare al framework queste risorse e classi speciali.
La risorsa di menu è facile da creare. Eseguire Visual C++, copiare la risorsa di menu IDR_HIERSVRTYPE in una risorsa di menu denominata IDR_HIERSVRTYPE_SRVR_IP. Modificare il menu in modo che vengano lasciati solo i popup del menu Modifica e Guida. Aggiungere due separatori al menu tra i menu Modifica e Guida (dovrebbe essere simile al seguente: Edit || Help
). Per altre informazioni sul significato di questi separatori e sul modo in cui vengono uniti i menu server e contenitori, vedere Menu e risorse: Unione di menu.
La bitmap per la barra degli strumenti del subset può essere creata facilmente copiando quella da una nuova applicazione generata da AppWizard con un'opzione "Server" selezionata. Questa bitmap può quindi essere importata in Visual C++. Assicurarsi di assegnare alla bitmap un ID di IDR_HIERSVRTYPE_SRVR_IP.
La classe derivata da COleIPFrameWnd
può essere copiata da un'applicazione generata da AppWizard con supporto server. Copiare entrambi i file, IPFRAME. CPP e IPFRAME. H e aggiungerli al progetto. Assicurarsi che la LoadBitmap
chiamata faccia riferimento a IDR_HIERSVRTYPE_SRVR_IP, ovvero la bitmap creata nel passaggio precedente.
Ora che vengono create tutte le nuove risorse e le nuove classi, aggiungere il codice necessario in modo che il framework conosca queste risorse e sappia che questa applicazione supporta ora la modifica sul posto. A tale scopo, aggiungere altri parametri alla SetServerInfo
chiamata nella InitInstance
funzione :
pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
IDR_HIERSVRTYPE_SRVR_IP,
RUNTIME_CLASS(CInPlaceFrame));
È ora pronto per l'esecuzione sul posto in qualsiasi contenitore che supporta anche l'attivazione sul posto. Tuttavia, nel codice è ancora presente un bug secondario. HIERSVR supporta un menu di scelta rapida, visualizzato quando l'utente preme il pulsante destro del mouse. Questo menu funziona quando HIERSVR è completamente aperto, ma non funziona quando si modifica un incorporamento sul posto. Il motivo può essere aggiunto a questa singola riga di codice in CServerView::OnRButtonDown:
pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
point.x,
point.y,
AfxGetApp()->m_pMainWnd);
Si noti il riferimento a AfxGetApp()->m_pMainWnd
. Quando il server è attivato sul posto, ha una finestra principale e m_pMainWnd è impostato, ma in genere è invisibile. Inoltre, questa finestra fa riferimento alla finestra principale dell'applicazione, alla finestra cornice MDI visualizzata quando il server è completamente aperto o eseguito autonomo. Non fa riferimento alla finestra cornice attiva, che quando attivata sul posto è una finestra cornice derivata da COleIPFrameWnd
. Per ottenere la finestra attiva corretta anche quando si modifica sul posto, questa versione di MFC aggiunge una nuova funzione, AfxGetMainWnd
. In genere, è consigliabile usare questa funzione anziché AfxGetApp()->m_pMainWnd
. Questo codice deve cambiare come segue:
pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
point.x,
point.y,
AfxGetMainWnd());
Ora è disponibile un server OLE abilitato al minimo per l'attivazione sul posto funzionale. Tuttavia, esistono ancora molte funzionalità disponibili con MFC/OLE 2 che non erano disponibili in MFC/OLE1. Vedi l'esempio HIERSVR per altre idee sulle funzionalità che potresti voler implementare. Di seguito sono elencate alcune delle funzionalità implementate da HIERSVR:
Zoom, per il comportamento di WYSIWYG reale rispetto al contenitore.
Trascinare/rilasciare e un formato personalizzato degli Appunti.
Scorrere la finestra del contenitore quando viene modificata la selezione.
L'esempio HIERSVR in MFC 3.0 usa anche una progettazione leggermente diversa per gli elementi del server. In questo modo è possibile risparmiare memoria e rendere i collegamenti più flessibili. Con la versione 2.0 di HIERSVR ogni nodo dell'albero è aCOleServerItem
. COleServerItem
comporta un sovraccarico maggiore di quanto sia strettamente necessario per ognuno di questi nodi, ma è COleServerItem
obbligatorio per ogni collegamento attivo. Ma per la maggior parte, ci sono pochissimi collegamenti attivi in qualsiasi momento. Per renderlo più efficiente, HIERSVR in questa versione di MFC separa il nodo da COleServerItem
. Ha sia un CServerNode che una CServerItem
classe . L'oggetto CServerItem
(derivato da COleServerItem
) viene creato solo in base alle esigenze. Quando il contenitore (o i contenitori) smette di usare quel particolare collegamento a quel particolare nodo, l'oggetto CServerItem associato a CServerNode viene eliminato. Questa progettazione è più efficiente e più flessibile. La sua flessibilità è disponibile quando si gestiscono più collegamenti di selezione. Nessuna di queste due versioni di HIERSVR supporta la selezione multipla, ma sarebbe molto più facile aggiungere (e supportare collegamenti a tali selezioni) con la versione MFC 3.0 di HIERSVR, poiché COleServerItem
è separato dai dati nativi.