- Table of Contents
- Copyright
- About the Authors
- About the Contributors
- Acknowledgments
- Tell Us What You Think!
- Introduction
- How to Use This Book
- What You Need to Use This Book
- What's New in Visual C++ 6.0
- Contacting the Main Author
- Part I: Introduction
- Chapter 1. The Visual C++ 6.0 Environment
- Part II: MFC Programming
- Chapter 2. MFC Class Library Overview
- Chapter 3. MFC Message Handling Mechanism
- Chapter 4. The Document View Architecture
- Chapter 5. Creating and Using Dialog Boxes
- Chapter 6. Working with Device Contexts and GDI Objects
- Chapter 7. Creating and Using Property Sheets
- Chapter 8. Working with the File System
- Chapter 9. Using Serialization with File and Archive Objects
- Part III: Internet Programming with MFC
- Chapter 10. MFC and the Internet Server API (ISAPI)
- Chapter 11. The WinInet API
- Chapter 12. MFC HTML Support
- Part IV: Advanced Programming Topics
- Chapter 13. Using the Standard C++ Library
- Chapter 14. Error Detection and Exception Handling Techniques
- Chapter 15. Debugging and Profiling Strategies
- Chapter 16. Multithreading
- Chapter 17. Using Scripting and Other Tools to Automate the Visual C++ IDE
- Part V: Database Programming
- Chapter 18. Creating Custom AppWizards
- Chapter 19. Database Overview
- Chapter 20. ODBC Programming
- Chapter 21. MFC Database Classes
- Chapter 22. Using OLE DB
- Chapter 23. Programming with ADO
- Part VI: MFC Support for COM and ActiveX
- Chapter 24. Overview of COM and Active Technologies
- Chapter 25. Active Documents
- Chapter 26. Active Containers
- Chapter 27. Active Servers
- Chapter 28. ActiveX Controls
- Part VII: Using the Active Template Library
- Chapter 29. ATL Architecture
- Chapter 30. Creating COM Objects Using ATL
- Chapter 31. Creating ActiveX Controls Using ATL
- Chapter 32. Using ATL to Create MTS and COM+ Components
- Part VIII: Finishing Touches
- Chapter 33. Adding Windows Help
- Part IX: Appendix
Designing an MFC Miniserver
To demonstrate the building of an embeddable server, we will be implementing a fairly simple server with a slight twist. Instead of a simple Hello World example, we'll design an OpenGL-embedding server that embeds a three-dimensional cube object into the container.
AppWizard: Step-by-Step
Begin by choosing File | New from the Developer Studio menu and taking a quick peek at the list of projects you can create (see Figure 27.1).
Figure 27.1 A list of new project types.
Of particular interest to us are the two types of projects we're going to build:
- MFC AppWizard (dll)
- MFC AppWizard (exe)
The first project will be an active document miniserver, which we will build as an EXE application. To begin, choose MFC AppWizard (exe). In the Project Name edit box, type GLServer.
Step 1 of 6
Figure 27.2 shows the first step in creating a miniserver. For our example, we'll create a single-document interface (SDI) application.
Figure 27.2 AppWizard: Step 1 of 6.
Step 2 of 6
Here, we have the option of including database support, but we'll simply choose None for our example.
Step 3 of 6
Now things get a little more interesting. Figure 27.3 shows the required parameters. You won't require automation, because you'll tackle that in the MFCAuto example later. Enable the Active Document Server check box.
Figure 27.3 AppWizard: Step 3 of 6.
You don't need support for ActiveX controls. For the GLServer project, this option isn't selected. On the other hand, this could be desirable for a full server to enhance the appearance of the application and provide more functionality to the user.
Step 4 of 6
The only thing to change here is to disable the Printing and Print Preview check box, because a miniserver does not need to support printing itself—this usually is left up to the container application. Figure 27.4 shows MFC AppWizard Step 4 for the GLServer project.
Figure 27.4 AppWizard: Step 4 of 6.
Before moving on to step 5, you must define a file extension for GLServer. If you attempt to move on, MFC AppWizard displays a dialog box as a reminder. Technically, the project doesn't use the file extension, because the server doesn't persist any data to a compound file, but MFC AppWizard will insist. Figure 27.5 shows the Advanced Options dialog box for the GLServer project. The file extension GLS is used for this project.
Figure 27.5 The AppWizard Advanced Options dialog box.
Step 5 of 6
Don't change any options in step 5. It is better to use MFC in a DLL instead of statically linking the libraries, because it conserves disk space.
Step 6 of 6
You've reached the final step in the wizard. Notice that there are a couple of new classes that you may not have seen before: CInPlaceFrame and CGLServerSrvrItem (see Figure 27.6). These new classes are required to provide functionality for the server.
Figure 27.6 AppWizard: Step 6 of 6.
Also, note the base class for CGLServerDoc; it's no longer CDocument but rather COleServerDoc (which is a subclass of CDocument).
A Closer Look at the GLServer Classes
Now, let's open up the GLServer project and take a closer look at the classes AppWizard generated.
CGLServerApp
Notice that in the declaration section for the CGLServerApp class, a new variable has been declared (see Listing 27.1).
Example 27.1. New Data Member for CGLServerApp
// Implementation COleTemplateServer m_server;
This is probably the most important object your project will create. This object is the basis for in-place editing/activation (which is explained in detail later in this chapter in the section "CInPlaceFrame") and provides the functionality used by full servers, mini servers, and automation servers.
Now, let's take a look at the implementation of the CGLServerApp class.
Listing 27.2 looks at the CLSID, or ClassID. When information about the GLServer COM class is added to the Windows Registry, the value is used to uniquely identify this class from other COM classes. If you create the project from scratch, your CLSID will be different from the one shown here.
Example 27.2. CLSID Source for This Application
// { 469849A5-7272-11D3-B515-CC43834E4167}
static const CLSID clsid =
{0x469849a5, 0x7272, 0x11d3,{ 0xb5, 0x15, 0xcc, 0x43, 0x83, 0x4e, 0x41, 0x67} } ;
The InitInstance() method shown in Listing 27.3 is really the only method that is significantly different than in non–OLE server SDI projects. Comments and white space in the listing have been edited for space. The first thing to note is that we call AfxOleInit() to initialize the OLE libraries.
The document template is the same as with non–OLE server projects, except that the CGLServerDoc class now is derived from COleServerDoc instead of CDocument.
Notice the call to SetServerInfo() immediately after the document template is created, which lets the framework know what type of activation is available when the user requests to edit the data. Our server will provide in-place activation.
Lastly, notice that we check to see whether we are in embedding mode; if so, we call RegisterAll(), which actually creates the object the container will use. Notice also that we do not support standalone execution. (Remember, this is a miniserver that must be embedded in a container application.) If the user attempts to start the application as a standalone, a message box explains that the application must be launched by inserting an object into a container.
Example 27.3. Source Code for CGLServerApp::InitInstance
BOOL CGLServerApp::InitInstance()
{
// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#endif
// Change the registry key under which our settings are stored.
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings();
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CGLServerDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CGLServerView));
pDocTemplate->SetServerInfo(
IDR_SRVR_EMBEDDED, IDR_SRVR_INPLACE,
RUNTIME_CLASS(CInPlaceFrame));
AddDocTemplate(pDocTemplate);
// Connect the COleTemplateServer to the document template.
// The COleTemplateServer creates new documents on behalf
// of requesting OLE containers by using information
// specified in the document template.
m_server.ConnectTemplate(clsid, pDocTemplate, TRUE);
// Note: SDI applications register server objects only if
// /Embedding or /Automation
// is present on the command line.
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Check to see if launched as OLE server
if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
{
// Register all OLE server (factories) as running.
COleTemplateServer::RegisterAll();
// Application was run with /Embedding or /Automation.
// Don't show the main window in this case.
return TRUE;
}
// When a server application is launched standalone, it
// is a good idea to update the system registry in case
// it has been damaged.
m_server.UpdateRegistry(OAT_DOC_OBJECT_SERVER);
// When a mini-server is run standalone the registry
// is updated and the user is instructed to use the
// Insert Object dialog in a container to use the server.
// Mini-servers do not have standalone user interfaces.
AfxMessageBox(IDP_USE_INSERT_OBJECT);
return FALSE;
}
CGLServerDoc
The COleServerDoc document class is the heart of the embeddable server and is ready to roll without any modifications. (You'll want to add some customization to make it useful.)
Although not necessary for our particular application, to enable compound files for storage and data display, we simply call the EnableCompoundFile() method during the construction of this document, as shown in Listing 27.4.
Example 27.4. CGLServerDoc Constructor
CGLServerDoc::CGLServerDoc()
{
// Use OLE compound files
EnableCompoundFile();
}
The OnGetEmbeddedItem() notification is called by the class factory to create a new CGLServerSrvrItem item. The CGLServerDoc class also includes the Serialize() method, which can be enhanced to automatically serialize data to the container's persistent storage, as required, to save the state of this server.
For example, if your server has special options that were configured, such as font size and color, you easily could add serialization code so that this information is saved to persistent storage when the user chooses Save As in the container application. The data then is serialized with the container's data, not in a separate file, so that when the container is opened in a future editing session and the server is invoked, it can serialize its data and continue where it left off.
CGLServerSrvrItem
When a GLServer item is embedded in a container (such as WordPad or MS Word), a new instance of CGLServerSrvrItem is created and passed to the container. The OnGetExtent() method is called by the container to determine the size of the object being embedded. The default implementation created by the AppWizard uses a hard-coded 3000x3000 HIMETRIC units extent (see Listing 27.5).
Example 27.5. OnGetExtent() Implementation Code
BOOL CGLServerSrvrItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
if (dwDrawAspect != DVASPECT_CONTENT)
return COleServerItem::OnGetExtent(dwDrawAspect, rSize);
// CGLServerSrvrItem::OnGetExtent is called to get the extent in
// HIMETRIC units of the entire item. The default implementation
// here simply returns a hard-coded number of units.
CGLServerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
rSize = CSize(3000, 3000); // 3000 x 3000 HIMETRIC units
return TRUE;
}
GLServer also paints into a special device context provided by the container called a metafile device context (which can contain any GDI objects, including bitmaps and brushes) when the container calls the OnDraw() member function shown in Listing 27.6.
The container uses this metafile representation to display the item when the server is not active. This way, the container doesn't need to start the GLServer application to see this embedded data. The GLServer application will not be loaded unless the user specifically requests it.
By default, the AppWizard does not provide any painting, so if you build this project as is, the embedded object will not contain any visual representation whatsoever.
Example 27.6. OnDraw() Implementation Code
BOOL CGLServerSrvrItem::OnDraw(CDC* pDC, CSize& rSize)
{
// Remove this if you use rSize
UNREFERENCED_PARAMETER(rSize);
CGLServerDoc* 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);
// TODO: add drawing code here. Optionally, fill in the HIMETRIC extent.
// All drawing takes place in the metafile device context (pDC).
return TRUE;
}
When the container wants to store a document with a GLServer-embedded item, it calls Serialize() to save any modifications. The basic implementation simply calls the CGLServerDoc's default implementation of Serialize() (see Listing 27.7). The data is not serialized to a file but to a persistent storage object provided by the container.
In some cases, calling the default implementation isn't the correct way to handle storage. For example, suppose that you don't want to save the data to the container's storage but would rather store it to a separate file and link that file to the container. Microsoft Word has this option. Instead of saving a picture inside the document, you can link to a separate file, thus saving document storage and retrieval time.
Example 27.7. Serialize() Implementation Code
void CGLServerSrvrItem::Serialize(CArchive& ar)
{
if (!IsLinkedItem())
{
CGLServerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDoc->Serialize(ar);
}
}
CInPlaceFrame
This class requires a bit more explanation than the others did. First, let me explain the term in-place activation. Basically, in-place activation means that when the user invokes an embedded server by double-clicking the object in the container, instead of bringing up a separate window for editing the server data, the server actually uses the container's window. Figures 27.7 and 27.8 provide examples of in-place activation.
Figure 27.7 WordPad with an embedded bitmap.
Figure 27.8 Paintbrush in-place activation inside WordPad.
Figure 27.7 shows a WordPad application with an embedded bitmap in it. Currently, the WordPad application is active, allowing you to change the WordPad text.
By double-clicking the bitmap image (remember that this is an embedded Paintbrush bitmap), the Paintbrush program is activated in-place. This means that it takes over the client area of the WordPad application. Notice that in Figure 27.8, the toolbars on the top have disappeared and have been replaced by the Paintbrush toolbar on the left side. Not visible here is the fact that the menus also have changed.
Remember, though, that in-place acti vation is not the only way to edit embedded objects. You also can use out-of-place activation if you embed an icon representation of the image instead of the image itself. You can do this either when you first insert the object by enabling the Display as Icon check box, or by right-clicking the object inside WordPad, choosing Object Properties, selecting the View tab, and selecting the Display as Icon radio button (see Figure 27.9).
Figure 27.9 Setting the server item to out-of-place activation inside WordPad.
If you look carefully at the in-place activation session, you'll notice a funny rectangle around the object with sizers around the edges. This is called the resizer bar. You can use this bar to resize the embedded object during in-place activation. Listing 27.8 includes the default implementation for this resizer bar.
Drag-and-drop support is registered, although the default implementation does nothing.
Example 27.8. OnCreate() Member Function
int CInPlaceFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (COleIPFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// CResizeBar implements in-place resizing.
if (!m_wndResizeBar.Create(this))
{
TRACE0("Failed to create resize bar\n");
return -1; // fail to create
}
m_dropTarget.Register(this);
return 0;
}
The AppWizard generates a toolbar that appears during in-place activation and tempor arily replaces the container's toolbars. Listing 27.9 shows the code used to register the toolbars. Although this is an embedded toolbar, it still can have all the properties of a regular toolbar, including the dockable characteristic.
Example 27.9. OnCreateControlBars() Member Function
BOOL CInPlaceFrame::OnCreateControlBars(CFrameWnd* pWndFrame, CFrameWnd* pWndDoc)
{
// Remove this if you use pWndDoc
UNREFERENCED_PARAMETER(pWndDoc);
m_wndToolBar.SetOwner(this);
// Create toolbar on client's frame window
if (!m_wndToolBar.Create(pWndFrame) ||
!m_wndToolBar.LoadToolBar(IDR_SRVR_INPLACE))
{
TRACE0("Failed to create toolbar\n");
return FALSE;
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
pWndFrame->EnableDocking(CBRS_ALIGN_ANY);
pWndFrame->DockControlBar(&m_wndToolBar);
return TRUE;
}
CGLServerView
Last but not least is the view class. The only real difference between this view class and view classes in non–OLE server projects is the addition of a special notification to cancel the embedded session, as shown in Listing 27.10. This is required for keyboard processing only (such as the use of the Esc key), because the mouse handler goes to the container instead of the server (by clicking outside the embedded server area).
Example 27.10. OnCancelEditSrvr() Member Function
void CGLServerView::OnCancelEditSrvr()
{
GetDocument()->OnDeactivateUI(FALSE);
}
Combining Container and Server Menus During Activation
As you might have noticed in Figures 27.8 and 27.9, when you start in-place activation, the server's menus are combined with the container's menus.
You won't be modifying this functionality in the example, but I want to give you an understanding of just how these two menus are combined.
Both the container and the server provide partial menus. When the server is activated, the two sets of menus are merged to form the new in-place activation menus. Figure 27.10 shows the partial menu used by GLServer. The container menus can be merged with this menu before Edit, between View and Help (the two separator bars help to determine this) or after Help.
Figure 27.10 The GLServer in-place activation partial menus.
Testing Out the GLServer Skeleton
Before adding customization, try compiling the GLServer example the way it is right now and use WordPad to embed it. It won't do much, but you should be able to embed the server and see an empty window inside the sizer bar (see Figure 27.11). To register this example, you'll have to run the application. You should get a message from GLServer telling you that the application can be run only from a container. Just click OK. Your GLServer now is registered, and you can insert it into WordPad.
Figure 27.11 The GLServer skeleton.
Adding Customization to the GLServer Skeleton
Now, let's start adding some customized methods, data members, and so on, to make this server sing.
Customizing CGLServerDoc
Our document class will hold the methods and data members used for this example. Therefore, most of the code that actually draws the OpenGL shape will be stored in this class, and rightly so. Change the CGLServerDoc declaration, as indicated by the bold text in Listing 27.11.
Example 27.11. Modifications to the CGLServerDoc Class
class CGLServerDoc : public COleServerDoc
{
public:
// private member vars
protected: // create from serialization only
CGLServerDoc();
DECLARE_DYNCREATE(CGLServerDoc)
// Attributes
public:
CGLServerSrvrItem* GetEmbeddedItem()
{ return (CGLServerSrvrItem*)COleServerDoc::GetEmbeddedItem(); }
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CGLServerDoc)
protected:
virtual COleServerItem* OnGetEmbeddedItem();
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
//}}AFX_VIRTUAL
// Implementation
//member vars
private:
float m_fRadius;
HGLRC m_hrc;
LOGPALETTE *m_pPalette;
public:
CDC *m_pMemoryDC;
RECT m_rcViewRect;
CBitmap *m_pOldBitmap, *m_pBitmap;
HPALETTE m_hPalette, m_hOldPalette;
void GLDraw(CDC* pDC, RECT *pRect);
virtual ~CGLServerDoc();
void CreateGLContext(HDC hdc, RECT& rc);
BOOL SetGLPixels(HDC hdc);
unsigned char GetPaletteIndex(int nIndex, UINT nBits, UINT nShift);
void CreateGLPalette(HDC hdc);
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CGLServerDoc)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
Now change the CGLServerDoc implementation as shown in Listing 27.12.
Example 27.12. Modifications to the CGLServerDoc Implementation
// GLServerDoc.cpp : implementation of the CGLServerDoc class
//
#include "stdafx.h"
#include "gl\glaux.h"
#include "GLServer.h"
#include "GLServerDoc.h"
#include "SrvrItem.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
unsigned char cShift1[2] = { 0, 255 } ;
unsigned char cShift3[4] = { 0, 0x55, 0xaa, 0xff } ;
unsigned char cShift7[8] = { 0, 0111 >> 1, 0222 >> 1, 0333 >> 1,
0444 >> 1, 0555 >> 1, 0666 >> 1, 0377 } ;
static int nPalColors[13] = { 0, 3, 24, 27, 64, 67, 88, 173,
181, 236, 247, 164, 91 } ;
static PALETTEENTRY defPalette[20] =
{
{ 0, 0, 0, 0 } , { 0x80,0, 0, 0 } , { 0, 0x80,0, 0 } ,
{ 0x80,0x80,0, 0 } , { 0, 0, 0x80, 0 } , { 0x80,0, 0x80, 0 } ,
{ 0, 0x80,0x80, 0 } , { 0xC0,0xC0,0xC0, 0 } , { 192, 220, 192, 0 } ,
{ 166, 202, 240, 0 } , { 255, 251, 240, 0 } , { 160, 160, 164, 0 } ,
{ 0x80,0x80,0x80, 0 } , { 0xFF,0, 0, 0 } , { 0, 0xFF,0, 0 } ,
{ 0xFF,0xFF,0, 0 } , { 0, 0, 0xFF, 0 } , { 0xFF,0, 0xFF, 0 } ,
{ 0, 0xFF,0xFF, 0 } , { 0xFF,0xFF,0xFF, 0 }
};
/////////////////////////////////////////////////////////////////////////////
// CGLServerDoc
IMPLEMENT_DYNCREATE(CGLServerDoc, COleServerDoc)
BEGIN_MESSAGE_MAP(CGLServerDoc, COleServerDoc)
//{{AFX_MSG_MAP(CGLServerDoc)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CGLServerDoc construction/destruction
CGLServerDoc::CGLServerDoc()
{
// Use OLE compound files
EnableCompoundFile();
// set defaults
m_pPalette = NULL;
m_hPalette, m_hOldPalette = NULL;
m_hrc = NULL;
m_pMemoryDC = NULL;
m_pBitmap = m_pOldBitmap = NULL;
}
CGLServerDoc::~CGLServerDoc()
{
}
BOOL CGLServerDoc::OnNewDocument()
{
if (!COleServerDoc::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// CGLServerDoc server implementation
COleServerItem* CGLServerDoc::OnGetEmbeddedItem()
{
// OnGetEmbeddedItem is called by the framework to get the COleServerItem
// that is associated with the document. It is only called when necessary.
CGLServerSrvrItem* pItem = new CGLServerSrvrItem(this);
ASSERT_VALID(pItem);
return pItem;
}
/////////////////////////////////////////////////////////////////////////////
// CGLServerDoc serialization
void CGLServerDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
/////////////////////////////////////////////////////////////////////////////
// CGLServerDoc diagnostics
#ifdef _DEBUG
void CGLServerDoc::AssertValid() const
{
COleServerDoc::AssertValid();
}
void CGLServerDoc::Dump(CDumpContext& dc) const
{
COleServerDoc::Dump(dc);
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CGLServerDoc commands
void CGLServerDoc::GLDraw(CDC *pDC, RECT *pRect)
{
// setup
HDC hdc = pDC->GetSafeHdc();
CreateGLContext(hdc, *pRect);
// clear buffers and colors
glClearColor(0.0f, 0.0f, 0.0f, 10.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// push matrix
glPushMatrix();
// set up geometric rotation scenario
glTranslatef(0.0f, 0.0f, -m_fRadius);
// FRONTSIDE
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-1.25f, 0.0f, -0.5f);
glColor3f(0.5f, 0.0f, 0.0f); glVertex3f(-1.25f, -1.0f, -0.5f);
glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-0.75f, 0.0f, -0.5f);
glColor3f(0.5f, 0.0f, 0.0f); glVertex3f(-0.75f, -1.0f, -0.5f);
glEnd();
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.0f, 0.5f, 0.0f); glVertex3f(-0.75f, 0.0f, -0.5f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-0.75f, -1.0f, -0.5f);
glColor3f(0.0f, 0.5f, 0.0f); glVertex3f(-0.25f, 0.5f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-0.25f, -0.5f, 0.0f);
glEnd();
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.0f, 0.5f, 1.0f); glVertex3f(-0.25f, 0.5f, 0.0f);
glColor3f(0.0f, 1.0f, 0.5f); glVertex3f(-0.25f, -0.5f, 0.0f);
glColor3f(0.0f, 0.5f, 1.0f); glVertex3f( 0.25f, 0.5f, 0.0f);
glColor3f(0.0f, 1.0f, 0.5f); glVertex3f( 0.25f, -0.5f, 0.0f);
glEnd();
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.0f, 0.5f, 0.0f); glVertex3f( 0.25f, 0.5f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex3f( 0.25f, -0.5f, 0.0f);
glColor3f(0.0f, 0.5f, 0.0f); glVertex3f( 0.75f, 0.0f, -0.5f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex3f( 0.75f, -1.0f, -0.5f);
glEnd();
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.5f, 0.0f, 0.0f); glVertex3f( 0.75f, 0.0f, -0.5f);
glColor3f(1.0f, 0.0f, 0.0f); glVertex3f( 0.75f, -1.0f, -0.5f);
glColor3f(0.5f, 0.0f, 0.0f); glVertex3f( 1.25f, 0.0f, -0.5f);
glColor3f(1.0f, 0.0f, 0.0f); glVertex3f( 1.25f, -1.0f, -0.5f);
glEnd();
// BACKSIDE
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.5f, 1.0f, 0.0f); glVertex3f(-1.25f, 0.0f, -0.5f);
glColor3f(1.0f, 0.5f, 0.0f); glVertex3f(-1.25f, -1.0f, -0.5f);
glColor3f(0.5f, 1.0f, 0.0f); glVertex3f(-0.50f, 1.0f, 0.5f);
glColor3f(1.0f, 0.5f, 0.0f); glVertex3f(-0.50f, 0.0f, 0.5f);
glEnd();
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.5f, 0.0f, 1.0f); glVertex3f(-0.50f, 1.0f, 0.5f);
glColor3f(1.0f, 0.0f, 0.5f); glVertex3f(-0.50f, 0.0f, 0.5f);
glColor3f(0.5f, 0.0f, 1.0f); glVertex3f( 0.50f, 1.0f, 0.5f);
glColor3f(1.0f, 0.0f, 0.5f); glVertex3f( 0.50f, 0.0f, 0.5f);
glEnd();
// draw a side
glBegin(GL_QUAD_STRIP);
glColor3f(0.5f, 1.0f, 0.0f); glVertex3f( 1.25f, 0.0f, -0.5f);
glColor3f(1.0f, 0.5f, 0.0f); glVertex3f( 1.25f, -1.0f, -0.5f);
glColor3f(0.5f, 1.0f, 0.0f); glVertex3f( 0.50f, 1.0f, 0.5f);
glColor3f(1.0f, 0.5f, 0.0f); glVertex3f( 0.50f, 0.0f, 0.5f);
glEnd();
// TOPFACE
glBegin(GL_QUAD_STRIP);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f(-1.25f, 0.0f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(-0.75f, 0.0f, -0.5f);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f(-0.50f, 1.0f, 0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(-0.25f, 0.5f, 0.0f);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f( 0.50f, 1.0f, 0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f( 0.25f, 0.5f, 0.0f);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f( 1.25f, 0.0f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f( 0.75f, 0.0f, -0.5f);
glEnd();
// BOTTOMFACE
glBegin(GL_QUAD_STRIP);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f(-1.25f, -1.0f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(-0.75f, -1.0f, -0.5f);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f(-0.50f, 0.0f, 0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(-0.25f, -0.5f, 0.0f);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f( 0.50f, 0.0f, 0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f( 0.25f, -0.5f, 0.0f);
glColor3f(0.0f, 0.0f, 0.5f); glVertex3f( 1.25f, -1.0f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f( 0.75f, -1.0f, -0.5f);
glEnd();
glPopMatrix();
// all done drawing, finish up and swap buffers
glFinish();
SwapBuffers(wglGetCurrentDC());
SetBkMode(hdc, TRANSPARENT);
// cleanup
::wglMakeCurrent(NULL, NULL);
// delete GL context
if (m_hrc)
{
::wglDeleteContext(m_hrc);
m_hrc = NULL;
}
}
void CGLServerDoc::CreateGLContext(HDC hdc, RECT& rc)
{
PIXELFORMATDESCRIPTOR pfdPixels;
// set up the pixel format
if (SetGLPixels(hdc) == FALSE)
{
return;
}
// create our GL palette
CreateGLPalette(hdc);
// realize palette
::SelectPalette(hdc, m_hPalette, FALSE);
::RealizePalette(hdc);
// set up pixel format
::DescribePixelFormat(hdc, ::GetPixelFormat(hdc), sizeof(pfdPixels),
&pfdPixels);
// create gl context
m_hrc = wglCreateContext(hdc);
wglMakeCurrent(hdc, m_hrc);
glClearDepth(10.0f);
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// check for divide by zero
if (rc.bottom != 0)
gluPerspective(30.0f, (GLfloat)rc.right/rc.bottom, 3.0f, 20.0f);
else
gluPerspective(30.0f, 1.0f, 3.0f, 20.0f);
glMatrixMode(GL_MODELVIEW);
m_fRadius = 3.0f + 3.0f / 2.0f;
}
BOOL CGLServerDoc::SetGLPixels(HDC hdc)
{
int nPixFmt;
static PIXELFORMATDESCRIPTOR pfdPixels =
{
sizeof(PIXELFORMATDESCRIPTOR), 1,
PFD_DOUBLEBUFFER | PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL,
PFD_TYPE_RGBA, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
32, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0
};
if ((nPixFmt = ChoosePixelFormat(hdc, &pfdPixels)) == 0)
{
_ASSERTE(FALSE);
return FALSE;
}
if (SetPixelFormat(hdc, nPixFmt, &pfdPixels) == FALSE)
{
_ASSERTE(FALSE);
return FALSE;
}
return TRUE;
}
unsigned char CGLServerDoc::GetPaletteIndex(int nIndex, UINT nBits, UINT nShift)
{
if (nBits == 1)
return cShift1[(unsigned char) (nIndex >> nShift) & 0x1];
else if (nBits == 2)
return cShift3[(unsigned char) (nIndex >> nShift) & 0x3];
else if (nBits == 3)
return cShift7[(unsigned char) (nIndex >> nShift) & 0x7];
else
return 0;
}
void CGLServerDoc::CreateGLPalette(HDC hdc)
{
PIXELFORMATDESCRIPTOR pfdPixels;
int nPixelFormat, nCounter;
// only do first time in
if (m_pPalette)
return;
// calculate pixel format
nPixelFormat = ::GetPixelFormat(hdc);
::DescribePixelFormat(hdc, nPixelFormat, sizeof(pfdPixels), &pfdPixels);
// change palette if necessary
if (pfdPixels.dwFlags & PFD_NEED_PALETTE)
{
nPixelFormat = 1 << pfdPixels.cColorBits;
m_pPalette = (PLOGPALETTE) new char[sizeof(LOGPALETTE) +
nPixelFormat * sizeof(PALETTEENTRY)];
_ASSERTE(m_pPalette != NULL);
m_pPalette->palVersion = 0x300;
m_pPalette->palNumEntries = nPixelFormat;
// loop through pixel set and set palette colors
for (nCounter=0; nCounter < nPixelFormat; nCounter++)
{
m_pPalette->palPalEntry[nCounter].peRed = GetPaletteIndex(
nCounter, pfdPixels.cRedBits, pfdPixels.cRedShift);
m_pPalette->palPalEntry[nCounter].peGreen = GetPaletteIndex(
nCounter, pfdPixels.cGreenBits, pfdPixels.cGreenShift);
m_pPalette->palPalEntry[nCounter].peBlue = GetPaletteIndex(
nCounter, pfdPixels.cBlueBits, pfdPixels.cBlueShift);
m_pPalette->palPalEntry[nCounter].peFlags = 0;
}
if ((pfdPixels.cColorBits == 8) &&
(pfdPixels.cRedBits == 3) && (pfdPixels.cRedShift == 0) &&
(pfdPixels.cGreenBits == 3) && (pfdPixels.cGreenShift == 3) &&
(pfdPixels.cBlueBits == 2) && (pfdPixels.cBlueShift == 6))
{
for (nCounter = 1 ; nCounter <= 12 ; nCounter++)
m_pPalette->palPalEntry[nPalColors[nCounter]] = defPalette[nCounter];
}
// create GL palette
m_hPalette = ::CreatePalette((LPLOGPALETTE)m_pPalette);
// realize palette
::SelectPalette(hdc, m_hPalette, FALSE);
::RealizePalette(hdc);
}
}
I know a lot of additions are here, but trust me, the result is worth the effort—especially if you decide to add your own enhancements later (such as choice of shapes, colors, lighting, textures, and so on), but I'll leave that up to your imagination.
I'm not going to spend a lot time explaining how OpenGL works, but I will tell you that the only method you will need to call is the GLDraw() method. Pass it a device context to draw in and a rectangle for the size.
Customizing CGLServerView
During in-place activation, you'll want the shape to appear in the view window that the server creates; therefore, you'll need to override the OnDraw() method of the view class. Please add the changes shown in Listing 27.13 to your code.
Because the OpenGL can draw only to an overlapped window, I've fooled the system a little bit here. After calling the GLDraw() method with the view window's HDC, I copied its contents (using the palette that GLDraw() created) into a memory device context (in the CGLServerSrvrItem::OnDraw() method). That way, the server item will be able to copy the image to the metafile device context.
Notice the call to UpdateAllItems(). This tells the document to inform all server items that they need to redraw themselves. (A call to CGLServerSrvrItem::OnDraw() is made.)
To ensure that you always have the correct window size, each time you perform a draw, delete the previous memory DC and bitmap, and re-create the bitmap based on the new window size.
It's really important to remember to realize the palette into the memory DC; otherwise, you'll get an ugly-looking shape.
Example 27.13. Modifications to the CGLServerView Implementation
void CGLServerView::OnDraw(CDC* pDC)
{
CGLServerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// check if memory DC is there, if so, copy contents to it
if (pDoc->m_pMemoryDC != NULL)
{
// restore old palette and bitmap
::SelectPalette(pDoc->m_pMemoryDC->GetSafeHdc(), pDoc->m_hOldPalette, FALSE);
pDoc->m_pMemoryDC->SelectObject(pDoc->m_pOldBitmap);
// clean up previous device context stuff
pDoc->m_pMemoryDC->DeleteDC();
delete pDoc->m_pBitmap;
delete pDoc->m_pMemoryDC;
}
// Get the window size
GetClientRect(&pDoc->m_rcViewRect);
// prepare memorydc and bitmap
pDoc->m_pMemoryDC = new CDC();
pDoc->m_pBitmap = new CBitmap();
// create a compatible dc and bitmap
pDoc->m_pMemoryDC->CreateCompatibleDC(pDC);
pDoc->m_pBitmap->CreateCompatibleBitmap(pDC,
pDoc->m_rcViewRect.right, pDoc->m_rcViewRect.bottom);
pDoc->m_pOldBitmap = pDoc->m_pMemoryDC->SelectObject(pDoc->m_pBitmap);
// draw the shape(s)
pDoc->GLDraw(pDC, &pDoc->m_rcViewRect);
// realize palette into memory dc
pDoc->m_hOldPalette = ::SelectPalette(pDoc->m_pMemoryDC->GetSafeHdc(),
pDoc->m_hPalette, FALSE);
::RealizePalette(pDoc->m_pMemoryDC->GetSafeHdc());
// BitBlt to metafile dc
pDoc->m_pMemoryDC->BitBlt(0, 0, pDoc->m_rcViewRect.right,
pDoc->m_rcViewRect.bottom, pDC, 0, 0, SRCCOPY);
// update the server item as well
pDoc->UpdateAllItems(NULL);
}
Customizing CGLServerSrvrItem
As I stated earlier, the OnDraw() for this class draws to a metafile device context that the container uses to display the server data when the server is not activated.
Use the memory DC created in the CGLServerView class and copy it to the metafile DC. Here, we'll also need to realize the palette (the process of selecting the palette into the device context).
Listing 27.14 contains the required modifications to OnDraw().
Example 27.14. Modifications to the CGLServerSrvrItem Implementation
BOOL CGLServerSrvrItem::OnDraw(CDC* pDC, CSize& rSize)
{
CGLServerDoc* 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);
// calculate drawing rectangle
CRect rect;
rect.TopLeft() = pDC->GetWindowOrg();
rect.BottomRight() = rect.TopLeft() + pDC->GetWindowExt();
// BitBlt memory DC to metafile DC
if (pDoc->m_pMemoryDC != NULL)
{
// realize palette into memory dc
HPALETTE hOldPalette = ::SelectPalette(pDC->GetSafeHdc(),
pDoc->m_hPalette, FALSE);
::RealizePalette(pDC->GetSafeHdc());
// BitBlt to metafile dc
if (pDC->StretchBlt(rect.left, rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
pDoc->m_pMemoryDC, 0, 0,
pDoc->m_rcViewRect.right,
pDoc->m_rcViewRect.bottom, SRCCOPY) == FALSE)
{
return FALSE;
}
// restore old palette
::SelectPalette(pDC->GetSafeHdc(), hOldPalette, FALSE);
}
return TRUE;
}
Testing the GLServer Example
Compile the GLServer example and embed it in WordPad as before. (If you don't already have the OpenGL DLLs GLU32.DLL and OPENGL32.DLL, you'll need to copy them from the CD-ROM included with this book.) You should see a three-dimensional arch-shaped object inside the sizer bar (see Figure 27.12). Now click the container, and the exact same image should be inside the container as a metafile bitmap.
Figure 27.12 The GLServer example in action.
You will get slightly different results if you test GLServer using an active container,such as a default active document container built using MFC AppWizard. For testing purposes, the CD-ROM that accompanies this book includes a project named DefCon that can be used to provide basic active document containment. Figure 27.13 shows an instance of GLServer running inside DefCon. Note that the image provided by GLServer completely fills the client area.
Figure 27.13 The GLServer example running inside an active document container.
Designing an MFC Automation Server | Next Section

Account Sign In
View your cart