TN041: Migración de MFC/OLE1 a MFC/OLE 2
Nota:
La nota técnica siguiente no se ha actualizado desde que se incluyó por primera vez en la documentación en línea. Como resultado, algunos procedimientos y temas podrían estar obsoletos o ser incorrectos. Para obtener información más reciente, se recomienda buscar el tema de interés en el índice de la documentación en línea.
Problemas generales relacionados con la migración
Uno de los objetivos de diseño de las clases OLE 2 en MFC 2.5 (y versiones posteriores) era conservar gran parte de la misma arquitectura implementada en MFC 2.0 para la compatibilidad con OLE 1.0. Como resultado, muchas de las mismas clases OLE de MFC 2.0 siguen existiendo en esta versión de MFC (COleDocument
, COleServerDoc
, COleClientItem
y COleServerItem
). Además, muchas de las API de estas clases son exactamente las mismas. Sin embargo, OLE 2 es drásticamente diferente de OLE 1.0, por lo que puede esperar que algunos detalles hayan cambiado. Si está familiarizado con la compatibilidad de MFC 2.0 con OLE1, se sentirá como en casa con la compatibilidad de MFC 2.0.
Si va a tomar una aplicación MFC/OLE1 existente y a agregarle la funcionalidad OLE 2, primero debe leer esta nota. En esta nota se tratan algunos problemas generales que pueden surgir al migrar la funcionalidad OLE1 a MFC/OLE 2 y, a continuación, se describen los problemas sin tratar al migrar dos aplicaciones incluidas en MFC 2.0: las muestras OLE de MFC OCLIENT y HIERSVR.
La arquitectura documento/vista de MFC es importante
Si la aplicación no usa la arquitectura documento/vista de MFC y quiere agregar compatibilidad con OLE 2 a la aplicación, ahora es el momento de pasar a documento/vista. Muchas de las ventajas de las clases OLE 2 de MFC solo se ven una vez que la aplicación usa la arquitectura integrada y los componentes de MFC.
Es posible implementar un servidor o contenedor sin usar la arquitectura de MFC, pero no se recomienda.
Use la implementación de MFC en lugar de la propia
Las clases de "implementación definida" de MFC, como CToolBar
, CStatusBar
y CScrollView
tienen código de caso especial integrado para la compatibilidad con OLE 2. Por lo tanto, si puede usar estas clases en la aplicación, se beneficiará del esfuerzo realizado para que reconozcan OLE. De nuevo, es posible "implementar sus propias" clases aquí para estos fines, pero no se recomienda. Si necesita implementar una funcionalidad similar, el código fuente de MFC es una referencia excelente para tratar algunos de los puntos más finos de OLE (especialmente cuando se trata de la activación en contexto).
Examine el código de ejemplo de MFC
Hay varios ejemplos de MFC que incluyen la funcionalidad OLE. Cada una de estas aplicaciones implementa OLE desde un ángulo diferente:
HIERSVR: diseñado principalmente para su uso como aplicación de servidor. Se incluyó en MFC 2.0 como una aplicación MFC/OLE1 y se ha portado a MFC/OLE 2 y, posteriormente, se ha ampliado para que implemente muchas características de OLE disponibles en OLE 2.
OCLIENT: se trata de una aplicación contenedora independiente, diseñada para demostrar muchas de las características de OLE desde el punto de vista del contenedor. También se ha portado desde MFC 2.0 y, posteriormente, se ha ampliado para admitir muchas de las características de OLE más avanzadas, como los formatos de Portapapeles personalizados y los vínculos a elementos incrustados.
DRAWCLI: esta aplicación implementa compatibilidad con contenedores OLE de forma muy similar a OCLIENT, salvo que lo hace dentro del marco de un programa de dibujo orientado a objetos existente. Muestra cómo puede implementar la compatibilidad con contenedores OLE e integrarla en la aplicación existente.
SUPERPAD: esta aplicación, además de ser una buena aplicación independiente, también es un servidor OLE. La compatibilidad con el servidor que implementa es bastante minimalista. Es de especial interés la forma en que usa los servicios del Portapapeles OLE para copiar datos en el Portapapeles, pero usa la funcionalidad integrada en el control "editar" de Windows para implementar la funcionalidad de pegado del Portapapeles. Esto muestra una combinación interesante de la utilización de la API de Windows tradicional y la integración con las nuevas API de OLE.
Para obtener más información sobre las aplicaciones de ejemplo, consulte la "Ayuda sobre ejemplos de MFC".
Caso práctico: OCLIENT desde MFC 2.0
Como se ha explicado anteriormente, OCLIENT se incluyó en MFC 2.0 e implementaba OLE con MFC/OLE1. A continuación se describen los pasos por los que esta aplicación se convirtió inicialmente para usar las clases MFC/OLE 2. Tras completar la migración inicial, se agregaron varias características para ilustrar mejor las clases MFC/OLE. Estas características no se tratarán aquí; consulte el propio ejemplo para obtener más información sobre esas características avanzadas.
Nota:
Los errores del compilador y el proceso paso a paso se crearon con Visual C++ 2.0. Es posible que los mensajes de error y las ubicaciones específicos hayan cambiado con Visual C++ 4.0, pero la información conceptual sigue siendo válida.
Puesta en marcha
El enfoque adoptado para portar el ejemplo de OCLIENT a MFC/OLE es empezar por compilarlo y corregir los errores obvios del compilador que se producirán. Si toma el ejemplo de OCLIENT de MFC 2.0 y lo compila en esta versión de MFC, verá que no hay tantos errores que resolver. A continuación se describen los errores en el orden en que se produjeron.
Compilación y corrección de errores
\oclient\mainview.cpp(104) : error C2660: 'Draw' : function does not take 4 parameters
El primer error hace referencia a COleClientItem::Draw
. En MFC/OLE1 se tomaban más parámetros que en la versión MFC/OLE. Los parámetros adicionales a menudo no eran necesarios y normalmente eran null (como en este ejemplo). Esta versión de MFC puede determinar automáticamente los valores de lpWBounds cuando el CDC en el que se dibuja es un DC de metarchivo. Además, el parámetro pFormatDC ya no es necesario, ya que el marco compilará uno a partir del "DC de atributo" del pDC que se ha pasado. Por lo tanto, para corregir este problema, basta con quitar los dos parámetros null adicionales a la llamada a 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 '
Los errores anteriores resultan del hecho de que todas las funciones COleClientItem::CreateXXXX
de MFC/OLE1 requerían que se pasara un nombre único para representar el elemento. Se trataba de un requisito de la API de OLE subyacente. Esto no es necesario en MFC/OLE 2, ya que OLE 2 no usa DDE como mecanismo de comunicaciones subyacente (el nombre se usaba en las conversaciones DDE). Para corregir este problema, puede quitar la función CreateNewName
, así como todas las referencias a ella. Puede averiguar fácilmente qué espera cada función de MFC/OLE en esta versión simplemente colocando el cursor en la llamada y presionando F1.
Otra área que es notablemente diferente es el control del Portapapeles de OLE 2. Con OLE1, se usaban las API del Portapapeles de Windows para interactuar con el Portapapeles. Con OLE 2, esto se realiza mediante un mecanismo diferente. Las API de MFC/OLE1 presuponían que el Portapapeles estaba abierto antes de copiar un objeto COleClientItem
en el Portapapeles. Esto ya no es necesario y hará que se produzcan errores en todas las operaciones del Portapapeles de MFC/OLE. Mientras edita el código para quitar las dependencias de CreateNewName
, también debería quitar el código que abre y cierra el Portapapeles de 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'
Estos errores son resultado del controlador CMainView::OnInsertObject
. Controlar el comando "Insertar nuevo objeto" es otra área en la que las cosas han cambiado bastante. En este caso, lo más fácil es simplemente combinar la implementación original con la que proporciona AppWizard para una nueva aplicación contenedora OLE. De hecho, se trata de una técnica que se puede aplicar a la portabilidad de otras aplicaciones. En MFC/OLE1, se mostraba el cuadro de diálogo "Insertar objeto" mediante una llamada a la función AfxOleInsertDialog
. En esta versión, se crea un objeto de diálogo COleInsertObject
y se llama a DoModal
. Además, se crean nuevos elementos OLE con un objeto CLSID en lugar de una cadena de nombre de clase. El resultado final debe ser similar al siguiente:
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:
El comando "Insertar nuevo objeto" puede ser diferente para su aplicación):
También es necesario incluir <afxodlgs.h>, que contiene la declaración de la clase de diálogo COleInsertObject
, así como los demás diálogos estándar que proporciona MFC.
\oclient\mainview.cpp(367) : error C2065: 'OLEVERB_PRIMARY' : undeclared identifier
\oclient\mainview.cpp(367) : error C2660: 'DoVerb' : function does not take 1 parameters
Estos errores se deben al hecho de que algunas constantes de OLE1 han cambiado en OLE 2, aunque conceptualmente son las mismas. En este caso, OLEVERB_PRIMARY
ha cambiado a OLEIVERB_PRIMARY
. Tanto en OLE1 como en OLE 2, un contenedor suele ejecutar el verbo principal cuando el usuario hace doble clic en un elemento.
Además, DoVerb
ahora toma un parámetro adicional: un puntero a una vista (CView
*). Este parámetro solo se usa para implementar la "Edición visual" (o activación en contexto). Por ahora, ha establecido ese parámetro en null, ya que no está implementando esta característica en este momento.
Para asegurarse de que el marco nunca intente activarse en contexto, debe invalidar COleClientItem::CanActivate
como se indica a continuación:
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
En MFC/OLE1, COleClientItem::GetBounds
y SetBounds
se usaban para consultar y manipular la extensión de un elemento (los miembros left
y top
siempre eran cero). En MFC/OLE 2, esto es compatible más directamente con COleClientItem::GetExtent
y SetExtent
, que tratan con un objeto SIZE o CSize
en su lugar.
El código de las nuevas llamadas SetItemRectToServer y UpdateItemRectFromServer tiene este aspecto:
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
En MFC/OLE1, las llamadas API sincrónicas desde un contenedor a un servidor se simulaban, ya que OLE1 era intrínsecamente asincrónico en muchos casos. Era necesario comprobar si había una llamada asincrónica pendiente en curso antes de procesar los comandos del usuario. Para ello, MFC/OLE1 proporcionaba la función COleClientItem::InWaitForRelease
. En MFC/OLE 2, esto no es necesario, por lo que puede quitar la invalidación de OnCommand en CMainFrame.
Llegados a este punto, OCLIENT compilará y vinculará.
Otros cambios necesarios
Sin embargo, hay algunas cosas que no se han hecho y que impedirán que OCLIENT se ejecute. Es mejor corregir estos problemas ahora que hacerlo más adelante.
En primer lugar, es necesario inicializar las bibliotecas de OLE. Esto se hace llamando a AfxOleInit
desde InitInstance
:
if (!AfxOleInit())
{
AfxMessageBox("Failed to initialize OLE libraries");
return FALSE;
}
También es una buena idea comprobar si hay funciones virtuales para buscar cambios en la lista de parámetros. Una de estas funciones es COleClientItem::OnChange
, invalidada en cada aplicación contenedora de MFC/OLE. Al examinar la ayuda en línea, verá que se ha agregado un "DWORD dwParam" adicional. El nuevo CRectItem::OnChange tiene el siguiente aspecto:
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
}
En MFC/OLE1, las aplicaciones contenedoras derivaban la clase de documento de COleClientDoc
. En MFC/OLE 2, esta clase se ha quitado y se ha reemplazado por COleDocument
(esta nueva organización facilita la compilación de aplicaciones contenedoras o de servidor). Hay un #define que asigna COleClientDoc
a COleDocument
para simplificar la migración de aplicaciones MFC/OLE1 a MFC/OLE 2, como OCLIENT. Una de las características que COleDocument
no proporciona y que proporciona COleClientDoc
son las entradas de mapa de mensajes del comando estándar. Esto se hace para que las aplicaciones de servidor, que también usan COleDocument
(indirectamente), no lleven consigo la sobrecarga de estos controladores de comandos a menos que sean una aplicación contenedora o de servidor. Debe agregar las entradas siguientes al mapa de mensajes de 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)
La implementación de todos estos comandos está en COleDocument
, que es la clase base del documento.
Llegados a este punto, OCLIENT es una aplicación contenedora OLE funcional. Es posible insertar elementos de cualquier tipo (OLE1 o OLE 2). Dado que no se ha implementado el código necesario para habilitar la activación en contexto, los elementos se editan en una ventana independiente de forma muy similar a OLE1. En la sección siguiente se describen los cambios necesarios para habilitar la edición en contexto (a veces denominada "Edición visual").
Adición de la "Edición visual"
Una de las características más interesantes de OLE es la activación en contexto (o "Edición visual"). Esta característica permite a la aplicación de servidor asumir partes de la interfaz de usuario del contenedor para proporcionar una interfaz de edición más fluida para el usuario. Para implementar la activación en contexto en OCLIENT, es necesario agregar algunos recursos especiales, así como código adicional. Estos recursos y código se proporcionan normalmente mediante AppWizard; de hecho, gran parte del código de aquí se tomó prestado directamente de una nueva aplicación AppWizard con compatibilidad con "Contenedores".
En primer lugar, es necesario agregar un recurso de menú que se usará cuando haya un elemento que esté activo en contexto. Puede crear este recurso de menú adicional en Visual C++; para ello, copie el recurso IDR_OCLITYPE y quite todos los elementos emergentes excepto Archivo y Ventana. Se insertan dos barras de separación entre los elementos emergentes Archivo y Ventana para indicar la separación de grupos (debería tener el siguiente aspecto: File || Window
). Para obtener más información sobre el significado de estos separadores y cómo se combinan los menús de servidor y de contenedor, consulte Menús y recursos: combinación de menús.
Una vez creados estos menús, debe informar al marco sobre ellos. Esto se hace mediante una llamada a CDocTemplate::SetContainerInfo
para la plantilla de documento antes de agregarla a la lista de plantillas de documento en InitInstance. El nuevo código para registrar la plantilla de documento tiene el siguiente aspecto:
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);
El recurso IDR_OLECLITYPE_INPLACE es el recurso en contexto especial creado en Visual C++.
Para habilitar la activación en contexto, hay algunos aspectos que deben cambiar tanto en la clase derivada CView
(CMainView) como en la clase derivada COleClientItem
(CRectItem). AppWizard proporciona todas estas invalidaciones y la mayoría de la implementación procederá directamente de una aplicación de AppWizard predeterminada.
En el primer paso de esta migración, la activación en contexto se deshabilitó completamente invalidando COleClientItem::CanActivate
. Esta invalidación debe quitarse para permitir la activación en contexto. Además, se pasó null a todas las llamadas a DoVerb
(hay dos) porque proporcionar la vista solo era necesario para la activación en contexto. Para implementar completamente la activación en contexto, es necesario pasar la vista correcta en la llamada DoVerb
. Una de estas llamadas está en CMainView::OnInsertObject
:
pItem->DoVerb(OLEIVERB_SHOW, this);
Otra está en CMainView::OnLButtonDblClk
:
m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);
Es necesario invalidar COleClientItem::OnGetItemPosition
. Esto indica al servidor dónde debe colocar su ventana en relación con la ventana del contenedor cuando el elemento está activado en contexto. Para OCLIENT, la implementación es trivial:
void CRectItem::OnGetItemPosition(CRect& rPosition)
{
rPosition = m_rect;
}
La mayoría de los servidores también implementan lo que se denomina "cambio de tamaño en contexto". Esto permite cambiar el tamaño de la ventana del servidor y moverla mientras el usuario edita el elemento. El contenedor debe participar en esta acción, ya que mover o cambiar el tamaño de la ventana normalmente afecta a la posición y el tamaño dentro del propio documento contenedor. La implementación de OCLIENT sincroniza el rectángulo interno mantenido por m_rect con la nueva posición y el nuevo tamaño.
BOOL CRectItem::OnChangeItemPosition(const CRect& rectPos)
{
ASSERT_VALID(this);
if (!COleClientItem::OnChangeItemPosition(rectPos))
return FALSE;
Invalidate();
m_rect = rectPos;
Invalidate();
GetDocument()->SetModifiedFlag();
return TRUE;
}
En este momento, hay suficiente código para permitir que un elemento esté activado en contexto y para tratar con el ajuste de tamaño y el desplazamiento del elemento cuando está activo, pero ningún código permitirá al usuario salir de la sesión de edición. Aunque algunos servidores proporcionarán esta funcionalidad controlando la clave de escape, se recomienda que los contenedores proporcionen dos maneras de desactivar un elemento: (1) haciendo clic fuera del elemento y (2) presionando la tecla ESCAPE.
Para la tecla ESCAPE, agregue un acelerador con Visual C++ que asigne la clave VK_ESCAPE a un comando, ID_CANCEL_EDIT se agrega a los recursos. El controlador de este comando se muestra a continuación:
// 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 controlar el caso en el que el usuario hace clic fuera del elemento, agregue el código siguiente al inicio de CMainView::SetSelection
:
if (pNewSel != m_pSelection || pNewSel == NULL)
{
COleClientItem* pActiveItem =
GetDocument()->GetInPlaceActiveItem(this);
if (pActiveItem != NULL&& pActiveItem != pNewSel)
pActiveItem->Close();
}
Cuando un elemento está activo en contexto, debe tener el foco. Para asegurarse de que este es el caso, controle OnSetFocus para que el foco siempre se transfiera al elemento activo cuando la vista reciba el 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);
}
Cuando se cambia el tamaño de la vista, debe notificar al elemento activo que el rectángulo de recorte ha cambiado. Para ello, proporcione un controlador para 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();
}
Caso práctico: HIERSVR desde MFC 2.0
HIERSVR también se incluyó en MFC 2.0 e implementó OLE con MFC/OLE1. En esta nota se describen brevemente los pasos por los que esta aplicación se convirtió inicialmente para usar las clases MFC/OLE 2. Tras completar la migración inicial, se agregaron varias características para ilustrar mejor las clases MFC/OLE 2. Estas características no se tratarán aquí; consulte el propio ejemplo para obtener más información sobre esas características avanzadas.
Nota:
Los errores del compilador y el proceso paso a paso se crearon con Visual C++ 2.0. Es posible que los mensajes de error y las ubicaciones específicos hayan cambiado con Visual C++ 4.0, pero la información conceptual sigue siendo válida.
Puesta en marcha
El enfoque adoptado para portar el ejemplo de HIERSVR a MFC/OLE es empezar por compilarlo y corregir los errores obvios del compilador que se producirán. Si toma el ejemplo de HIERSVR de MFC 2.0 y lo compila en esta versión de MFC, verá que no hay muchos errores que resolver (aunque hay más que en el ejemplo de OCLIENT). A continuación se describen los errores en el orden en que se suelen producir.
Compilación y corrección de errores
\hiersvr\hiersvr.cpp(83) : error C2039: 'RunEmbedded' : is not a member of 'COleTemplateServer'
Este primer error señala un problema mucho mayor con la función InitInstance
para servidores. La inicialización necesaria para un servidor OLE es probablemente uno de los cambios más importantes que tendrá que realizar en la aplicación MFC/OLE1 para que se ejecute. Lo mejor es ver lo que AppWizard crea para un servidor OLE y modificar el código según corresponda. Estos son algunos aspectos que debe tener en cuenta:
Es necesario inicializar las bibliotecas de OLE mediante una llamada a AfxOleInit
.
Llame a SetServerInfo en el objeto de plantilla de documento para establecer los controladores de recursos de servidor y la información de clase de runtime que no se puede establecer con el constructor CDocTemplate
.
No muestre la ventana principal de la aplicación si /Embedding está presente en la línea de comandos.
Necesitará un GUID para el documento. Se trata de un identificador único para el tipo del documento (128 bits). AppWizard creará uno, por lo que si usa la técnica que se describe aquí de copiar el nuevo código desde una nueva aplicación de servidor generada por AppWizard, puede simplemente "robar" el GUID de esa aplicación. Si no es así, puede usar la utilidad GUIDGEN.EXE del directorio BIN.
Es necesario "conectar" el objeto COleTemplateServer
a la plantilla de documento mediante una llamada a COleTemplateServer::ConnectTemplate
.
Actualice el registro del sistema cuando la aplicación se ejecute de forma independiente. De este modo, si el usuario mueve el .EXE de la aplicación, con la ejecución desde la nueva ubicación se actualizará la base de datos de registro del sistema de Windows para que apunte a la nueva ubicación.
Después de aplicar todos estos cambios basados en lo que AppWizard crea para InitInstance
, el InitInstance
(y el GUID relacionado) para HIERSVR debe decir lo siguiente:
// 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;
}
Observará que el código anterior hace referencia a un nuevo identificador de recursos, IDR_HIERSVRTYPE_SRVR_EMB. Este es el recurso de menú que se usará cuando se edita un documento incrustado en otro contenedor. En MFC/OLE1, los elementos de menú específicos para editar un elemento incrustado se modificaron sobre la marcha. El uso de una estructura de menús completamente diferente al editar un elemento incrustado en lugar de editar un documento basado en archivos facilita mucho la prestación de diferentes interfaces de usuario para estos dos modos independientes. Como verá más adelante, se usa un recurso de menú completamente independiente al editar un objeto incrustado en contexto.
Para crear este recurso, cargue el script de recursos en Visual C++ y copie el recurso de menú IDR_HIERSVRTYPE existente. Cambie el nombre del nuevo recurso a IDR_HIERSVRTYPE_SRVR_EMB (se trata de la misma convención de nomenclatura que usa AppWizard). A continuación, cambie "File Save" a "File Update" y asígnele el identificador de comando ID_FILE_UPDATE. Cambie también "File Save As" a "File Save Copy As" y asígnele el identificador de comando ID_FILE_SAVE_COPY_AS. El marco proporciona la implementación de ambos 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
Hay varios errores resultantes de la invalidación de OnSetData
, ya que hace referencia al tipo OLESTATUS. OLESTATUS era la forma en que OLE1 devolvía los errores. Esto ha cambiado a HRESULT en OLE 2, aunque MFC normalmente convierte un HRESULT en un objeto COleException
que contiene el error. En este caso concreto, la invalidación de OnSetData
ya no es necesaria, por lo que lo más sencillo es quitarlo.
\hiersvr\svritem.cpp(30) : error C2660: 'COleServerItem::COleServerItem' : function does not take 1 parameters
El constructor COleServerItem
toma un parámetro "BOOL" adicional. Esta marca determina cómo se realiza la administración de memoria en los objetos COleServerItem
. Al establecerlo en TRUE, el marco controla la administración de memoria de estos objetos y los elimina cuando ya no son necesarios. HIERSVR usa objetos CServerItem
(derivados de COleServerItem
) como parte de sus datos nativos, por lo que establecerá esta marca en FALSE. Esto permite a HIERSVR determinar cuándo se elimina cada elemento de servidor.
\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 estos errores implican, hay algunas funciones "pure-virtual" que no se han invalidado en CServerItem. Lo más probable es que esto se deba al hecho de que la lista de parámetros de OnDraw ha cambiado. Para corregir este error, cambie CServerItem::OnDraw
como se indica a continuación (así como la declaración en 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);
}
El nuevo parámetro es "rSize". Esto le permite rellenar el tamaño del dibujo, si hace falta. Este tamaño debe estar en HIMETRIC. En este caso, no es conveniente rellenar este valor, por lo que el marco llama a OnGetExtent
para recuperar la extensión. Para que funcione, tendrá 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 *'
En la función CServerItem::CalcNodeSize, el tamaño del elemento se convierte en HIMETRIC y se almacena en m_rectBounds. El miembro "m_rectBounds" no documentado de COleServerItem
no existe (se ha reemplazado parcialmente por m_sizeExtent, pero en OLE 2 este miembro tiene un uso ligeramente diferente al que tenía m_rectBounds en OLE1). En lugar de establecer el tamaño de HIMETRIC en esta variable de miembro, lo devolverá. Este valor devuelto se usa en 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 también invalida COleServerItem::OnGetTextData
. Esta función está obsoleta en MFC/OLE y se ha reemplazado por un mecanismo diferente. La versión MFC 3.0 del ejemplo OLE de MFC HIERSVR implementa esta funcionalidad mediante la invalidación de COleServerItem::OnRenderFileData
. Esta funcionalidad no es importante para esta migración básica, por lo que puede quitar la invalidación OnGetTextData.
Hay muchos más errores en svritem.cpp que no se han abordado. No son errores "reales": solo errores causados por errores anteriores.
\hiersvr\svrview.cpp(325) : error C2660: 'CopyToClipboard' : function does not take 2 parameters
COleServerItem::CopyToClipboard
ya no admite la marca bIncludeNative
. Los datos nativos (los datos escritos por la función Serialize del elemento de servidor) siempre se copian, por lo que debe quitar el primer parámetro. Además, CopyToClipboard
producirá una excepción cuando se produzca un error en lugar de devolver FALSE. Cambie el código de CServerView::OnEditCopy de la siguiente manera:
void CServerView::OnEditCopy()
{
if (m_pSelectedNode == NULL)
AfxThrowNotSupportedException();
TRY
{
m_pSelectedNode->CopyToClipboard(TRUE);
}
CATCH_ALL(e)
{
AfxMessageBox("Copy to clipboard failed");
}
END_CATCH_ALL
}
Aunque se producían más errores como resultado de la compilación de la versión MFC 2.0 de HIERSVR de los que se producían para la misma versión de OCLIENT, realmente hubo menos cambios.
En este momento, HIERSVR compilará y vinculará y funcionará como un servidor OLE, pero sin la característica de edición en contexto, que se implementará a continuación.
Adición de la "Edición visual"
Para agregar la "Edición visual" (o activación en contexto) a esta aplicación de servidor, solo debe tener en cuenta algunas cosas:
Necesita que se use un recurso de menú especial cuando el elemento esté activo en contexto.
Esta aplicación tiene una barra de herramientas, por lo que necesitará una barra de herramientas con solo un subconjunto de la barra de herramientas normal para que coincida con los comandos de menú disponibles en el servidor (coincide con el recurso de menú mencionado anteriormente).
Necesita una nueva clase derivada de
COleIPFrameWnd
que proporcione la interfaz de usuario en contexto (al igual que CMainFrame, derivada deCMDIFrameWnd
, proporciona la interfaz de usuario MDI).Debe indicar al marco estos recursos y clases especiales.
El recurso de menú es fácil de crear. Ejecute Visual C++ y copie el recurso de menú IDR_HIERSVRTYPE en un recurso de menú denominado IDR_HIERSVRTYPE_SRVR_IP. Modifique el menú para que solo queden los elementos emergentes de menú Editar y Ayuda. Agregue dos separadores al menú entre los menús Editar y Ayuda (debería tener el siguiente aspecto: Edit || Help
). Para obtener más información sobre el significado de estos separadores y cómo se combinan los menús de servidor y de contenedor, consulte Menús y recursos: combinación de menús.
El mapa de bits de la barra de herramientas del subconjunto se puede crear fácilmente copiando el de una nueva aplicación generada por AppWizard con una opción "Servidor" activada. A continuación, este mapa de bits se puede importar en Visual C++. Asegúrese de proporcionar al mapa de bits un identificador de IDR_HIERSVRTYPE_SRVR_IP.
La clase derivada de COleIPFrameWnd
también se puede copiar de una aplicación generada por AppWizard con compatibilidad con servidor. Copie ambos archivos, IPFRAME.CPP e IPFRAME.H y agréguelos al proyecto. Asegúrese de que la llamada LoadBitmap
hace referencia a IDR_HIERSVRTYPE_SRVR_IP, el mapa de bits creado en el paso anterior.
Una vez creados todos los nuevos recursos y clases, agregue el código necesario para que el marco los reconozca (y sepa que esta aplicación ahora admite la edición en contexto). Para ello, agregue algunos parámetros más a la llamada SetServerInfo
en la función InitInstance
:
pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
IDR_HIERSVRTYPE_SRVR_IP,
RUNTIME_CLASS(CInPlaceFrame));
Ahora está listo para ejecutarse en contexto en cualquier contenedor que también admita la activación en contexto. Sin embargo, todavía hay un pequeño error escondido en el código. HIERSVR admite un menú contextual, que se muestra cuando el usuario presiona el botón derecho del mouse. Este menú funciona cuando HIERSVR está totalmente abierto, pero no funciona al editar una inserción en contexto. El motivo se puede anclar a esta línea de código individual en CServerView::OnRButtonDown:
pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
point.x,
point.y,
AfxGetApp()->m_pMainWnd);
Observe la referencia a AfxGetApp()->m_pMainWnd
. Cuando el servidor está activado en contexto, tiene una ventana principal y se establece m_pMainWnd, pero normalmente es invisible. Además, esta ventana hace referencia a la ventana principal de la aplicación, la ventana de marco MDI que aparece cuando el servidor está totalmente abierto o se ejecuta de forma independiente. No hace referencia a la ventana de marco activa, que cuando está activada en contexto es una ventana de marco derivada de COleIPFrameWnd
. Para obtener la ventana activa correcta incluso durante la edición en contexto, esta versión de MFC agrega una nueva función, AfxGetMainWnd
. Por lo general, debe usar esta función en lugar de AfxGetApp()->m_pMainWnd
. Este código debe cambiar de la siguiente manera:
pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
point.x,
point.y,
AfxGetMainWnd());
Ahora tiene un servidor OLE habilitado mínimamente para la activación en contexto funcional. Pero todavía hay muchas características disponibles con MFC/OLE 2 que no estaban disponibles en MFC/OLE1. Consulte el ejemplo HIERSVR para obtener más ideas sobre las características que podría querer implementar. A continuación se enumeran algunas de las características que implementa HIERSVR:
Zoom, para un comportamiento WYSIWYG true con respecto al contenedor.
Arrastrar o colocar y un formato de Portapapeles personalizado.
Desplazarse por la ventana del contenedor a medida que se cambia la selección.
El ejemplo HIERSVR de MFC 3.0 también usa un diseño ligeramente diferente para sus elementos de servidor. Esto ayuda a ahorrar memoria y hace que los vínculos sean más flexibles. Con la versión 2.0 de HIERSVR, cada nodo del árbol is-aCOleServerItem
. COleServerItem
conlleva un poco más de sobrecarga de la que es estrictamente necesaria para cada uno de estos nodos, pero se requiere un objeto COleServerItem
para cada vínculo activo. Pero en su mayor parte, hay muy pocos vínculos activos en un momento dado. Para que esto sea más eficaz, HIERSVR en esta versión de MFC separa el nodo de COleServerItem
. Tiene un CServerNode y una clase CServerItem
. El CServerItem
(derivado de COleServerItem
) solo se crea según sea necesario. Una vez que el contenedor (o contenedores) deje de usar ese vínculo concreto a ese nodo determinado, se elimina el objeto CServerItem asociado al CServerNode. Este diseño es más eficaz y flexible. Su flexibilidad se hace patente al tratar con los vínculos de selección múltiple. Ninguna de estas dos versiones de HIERSVR admite la selección múltiple, pero sería mucho más fácil agregar (y admitir vínculos a dichas selecciones) con la versión MFC 3.0 de HIERSVR, ya que COleServerItem
está separado de los datos nativos.