ActiveX Document Server Example EX36A

You could construct the EX36A example in two phases. The first phase is a plain ActiveX document server that loads a file from its container. The view base class is CRichEditView, which means the program loads, edits, and stores text plus embedded objects. In the second phase, the application is enhanced to download a separate text file from the Internet one line at a time, demonstrating that ActiveX document servers can make arbitrary WinInet calls.

EX36A Phase 1—A Simple Server

The EX36A example on the book's CD-ROM is complete with the text download feature from Phase 2. You can exercise its Phase 1 capabilities by building it, or you can create a new application with AppWizard. If you do use AppWizard, you should refer to Figure 36-3 to see the AppWizard EXE project dialog and select the appropriate options. All other options are the default options, except those for selecting SDI (Step 1), setting the project's filename extension to 36a using the Advanced button in Step 4, and changing the view's base class (CRichEditView—on the wizard's last page). You don't have to write any C++ code at all.

Be sure to run the program once in stand-alone mode to register it. While the program is running in stand-alone mode, type some text (and insert some OLE embedded objects) and then save the document as test.36a in your Internet server's home directory (\scripts or \wwwroot directory). Try loading test.36a from Internet Explorer and from Office Binder. Use Binder's Section menu for loading and storing EX36A documents to and from disk files.

You should customize the document icons for your ActiveX document servers because those icons show up on the right side of an Office Binder window.

Debugging an ActiveX Document Server

If you want to debug your program in ActiveX document server mode, click on the Debug tab in the Build Settings dialog. Set Program Arguments to /Embedding, and then start the program. Now start the container program and use it to "start" the server, which has in fact already started in the debugger and is waiting for the container.

EX36A Phase 2—Adding WinInet Calls

The EX36A example on the CD-ROM includes two dialog bar objects, one for the main frame window and another for the in-place frame window. Both are attached to the same resource template, IDD_DIALOGBAR, which contains an edit control that accepts a text file URL plus start and stop buttons that display green and red bitmaps. If you click the green button (handled by the OnStart member function of the CEx36aView class), you'll start a thread that reads the text file one line at a time. The thread code from the file UrlThread.cpp is shown here:

CString g_strURL = "http:// ";
volatile BOOL g_bThreadStarted = FALSE;
CEvent g_eKill;

UINT UrlThreadProc(LPVOID pParam)
{
    g_bThreadStarted = TRUE;
    CString strLine;
    CInternetSession session;
    CStdioFile* pFile1 = NULL;

    try {
        pFile1 = session.OpenURL(g_strURL, 0, INTERNET_FLAG_TRANSFER_BINARY
            | INTERNET_FLAG_KEEP_CONNECTION); // needed for Windows NT  
                                              //  c/r authentication
        // Keep displaying text from the URL until the Kill event is 
        //  received
        while(::WaitForSingleObject(g_eKill.m_hObject, 0) != WAIT_OBJECT_0) {
            // one line at a time
            if(pFile1->ReadString(strLine) == FALSE) break;
            strLine += `\n';
            ::SendMessage((HWND) pParam, EM_SETSEL, (WPARAM) 999999, 
                1000000);
            ::SendMessage((HWND) pParam, EM_REPLACESEL, (WPARAM) 0,
                          (LPARAM) (const char*) strLine);
            Sleep(250); // Deliberately slow down the transfer
        }
    }
    catch(CInternetException* e) {
        LogInternetException(pParam, e);
        e->Delete();
    }
    if(pFile1 != NULL) delete pFile1; // closes the file—prints a warning
    g_bThreadStarted = FALSE;
    // Post any message to update the toolbar buttons
    ::PostMessage((HWND) pParam, EM_SETSEL, (WPARAM) 999999, 1000000);
    TRACE("Post thread exiting normally\n");
    return 0;
}

This code uses the CStdioFile pointer to pFile1 returned from OpenURL. The ReadString member function reads one line at a time, and each line is sent to the rich edit view window. When the main thread sets the "kill" event (the red button), the URL thread exits.

Before you test EX36A, make sure that the server (EX34A or IIS) is running and that you have a text file in the server's home directory. Test the EX36A program first in stand-alone mode by entering the text file URL in the dialog bar. Next try running the program in server mode from Internet Explorer. Enter test.36a (the document you created when you ran EX36A in stand-alone mode) in Internet Explorer's Address field to load the server.

We considered using the CAsyncMonikerFile class (see Asynchronous Moniker Files) instead of the MFC WinInet classes to read the text file. We stuck with WinInet, however, because the program could use the CStdioFile class ReadString member function to "pull" individual text lines from the server when it wanted them. The CAsyncMonikerFile class would have "pushed" arbitrary blocks of characters into the program (by calling the overridden OnDataAvailable function) as soon as the characters had been received.

Displaying Bitmaps on Buttons

Chapter 11 describes the CBitmapButton class for associating a group of bitmaps with a pushbutton. Microsoft Windows 95, Microsoft Windows 98, and Microsoft Windows NT 4.0 support an alternative technique that associates a single bitmap with a button. First you apply the Bitmap style (on the button's property sheet) to the button, and then you declare a variable of class CBitmap that will last at least as long as the button is enabled. Then you make sure that the CButton::SetBitmap function is called just after the button is created.

Here is the code for associating a bitmap with a button, from the EX36A MainFrm.cpp and IpFrame.cpp files:

m_bitmapGreen.LoadBitmap(IDB_GREEN);
HBITMAP hBitmap = (HBITMAP) m_bitmapGreen.GetSafeHandle();
((CButton*) m_wndDialogBar.GetDlgItem(IDC_START))
->SetBitmap(hBitmap);

If your button was in a dialog, you could put similar code in the OnInitDialog member function and declare a CBitmap member in the class derived from CDialog.