You don't need much OLE theory to build an MFC mini-server. This example is a good place to start, though, because you'll get an idea of how containers and components interact. This component isn't too sophisticated. It simply draws some text and graphics in a window. The text is stored in the document, and there's a dialog for updating it.
Here are the steps for creating the program from scratch:
Header | Implementation | Class | MFC Base Class |
SrvrItem.h | SrvrItem.cpp | CEx28aSrvrItem | COleServerItem |
IpFrame.h | IpFrame.cpp | CInPlaceFrame | COleIPFrameWnd |
CString m_strText;
Set the string's initial value to Initial default text in the document's OnNewDocument member function.
To associate both Modify options with one OnModify function, use ID_MODIFY as the ID for the Modify option of both the IDR_SRVR_EMBEDDED and IDR_SRVR_INPLACE menus. Then use ClassWizard to map both Modify options to the OnModify function in the document class. Code the Modify command handler as shown here:
void CEx28aDoc::OnModify() { CTextDialog dlg; dlg.m_strText = m_strText; if (dlg.DoModal() == IDOK) { m_strText = dlg.m_strText; UpdateAllViews(NULL); // Trigger CEx28aView::OnDraw UpdateAllItems(NULL); // Trigger CEx28aSrvrItem::OnDraw SetModifiedFlag(); } }
pDC->SetMapMode(MM_HIMETRIC);
void CEx28aView::OnDraw(CDC* pDC) { CEx28aDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CFont font; font.CreateFont(-500, 0, 0, 0, 400, FALSE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial"); CFont* pFont = pDC->SelectObject(&font); CRect rectClient; GetClientRect(rectClient); CSize sizeClient = rectClient.Size(); pDC->DPtoHIMETRIC(&sizeClient); CRect rectEllipse(sizeClient.cx / 2 - 1000, -sizeClient.cy / 2 + 1000, sizeClient.cx / 2 + 1000, -sizeClient.cy / 2 - 1000); pDC->Ellipse(rectEllipse); pDC->TextOut(0, 0, pDoc->m_strText); pDC->SelectObject(pFont); }
BOOL CEx28aSrvrItem::OnDraw(CDC* pDC, CSize& rSize) { // Remove this if you use rSize UNREFERENCED_PARAMETER(rSize); CEx28aDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: set mapping mode and extent // (The extent is usually the same as the size returned from // OnGetExtent) pDC->SetMapMode(MM_ANISOTROPIC); pDC->SetWindowOrg(0,0); pDC->SetWindowExt(3000, -3000); CFont font; font.CreateFont(-500, 0, 0, 0, 400, FALSE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial"); CFont* pFont = pDC->SelectObject(&font); CRect rectEllipse(CRect(500, -500, 2500, -2500)); pDC->Ellipse(rectEllipse); pDC->TextOut(0, 0, pDoc->m_strText); pDC->SelectObject(pFont); return TRUE; }
void CEx28aDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << m_strText; } else { ar >> m_strText; } }
There is also a CEx28aSrvrItem::Serialize function that delegates to the document Serialize function.
You debug an embedded component the same way you debug an Automation EXE component. See the sidebar, "Debugging an EXE Component Program", for more information.
When you first insert the EX28A object, you'll see a hatched border, which indicates that the object is in-place active. The bounding rectangle is 3-by-3-cm square, with a 2-cm circle in the center, as illustrated here.
If you click elsewhere in the container's window, the object becomes inactive, and it's shown like this.
In the first case, you saw the output of the view's OnDraw function; in the second case, you saw the output of the server item's OnDraw function. The circles are the same, but the text is formatted differently because the server (component) item code is drawing on a metafile device context.
If you use the resize handles to extend the height of the object (click once on the object to see the resize handles; don't double-click), you'll stretch the circle and the font will get bigger, as shown below in the figure on the left. If you reactivate the object by double-clicking on it, it's reformatted as shown in the figure on the right.
Click elsewhere in the container's window, single-click on the object, and then choose Ex28a Object from the bottom of the Edit menu. Choose Open from the submenu. This starts the component program in embedded mode rather than in in-place mode, as shown here.
Notice that the component's IDR_SRVR_EMBEDDED menu is visible.
An MDI Embedded Component?
The EX28A example is an SDI mini-server. Each time a controller creates an EX28A object, a new EX28A process is started. You might expect an MDI mini-server process to support multiple component objects, each with its own document, but this is not the case. When you ask AppWizard to generate an MDI mini-server, it generates an SDI program, as in EX28A. It's theoretically possible to have a single process support multiple embedded objects in different windows, but you can't easily create such a program with the MFC library.
In-Place Component Sizing Strategy
If you look at the EX28A output, you'll observe that the metafile image does not always match the image in the in-place frame window. We had hoped to create another example in which the two images matched. We were unsuccessful, however, when we tried to use the Microsoft Office 97 applications as containers. Each one did something a little different and unpredictable. A complicating factor is the containers' different zooming abilities.
When AppWizard generates a component program, it gives you an overridden OnGetExtent function in your server item class. This function returns a hard-coded size of (3000, 3000). You can certainly change this value to suit your needs, but be careful if you change it dynamically. We tried maintaining our own document data member for the component's extent, but that messed us up when the container's zoom factor changed. We thought containers would make more use of another component item virtual function, OnSetExtent, but they don't.
You'll be safest if you simply make your component extents fixed and assume that the container will do the right thing. Keep in mind that when the container application prints its document, it prints the component metafiles. The metafiles are more important than the in-place views.
If you control both container and component programs, however, you have more flexibility. You can build up a modular document processing system with its own sizing protocol. You can even use other OLE interfaces.