| Tenouk C & C++ | MFC Home | ActiveX Doc Servers & Internet 4 | Visual C++ & DHTML | Download | Site Index |


 

 

 

 

 

 

ActiveX Document Servers and the Internet Part 5

 

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2 and some Figure screen snapshots have been taken on Windows 2000 server. The Internet Information Services version is IIS 4.x/5.x/6.x on Windows 2000 Server SP 4 and Windows XP Pro SP 2. The Internet Explorer is 6.x. Topics and sub topics for this tutorial are listed below. A complete information about IIS installation, configuration and testing a Web site is dumped HERE and how to setup FTP server also included HERE. Both Web and FTP servers were done on Windows 2000 Server SP4. Don’t forget to read Tenouk’s small disclaimer.

  1. CMyex35bView Class...Continue

  2. The Story

  3. Field Validation in an MFC Form View

  4. Generating POST Requests Under Program Control

  5. The MYEX35B View Class

  6. Building and Testing MYEX35B

  7. ActiveX Document Servers vs. VB Script

  8. Going Further with ActiveX Document Servers

 

Complete the implementation part for the others as shown in the following Listing.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// myex35bView.cpp : implementation of the CMyex35bView class

//

 

#include "stdafx.h"

#include "myex35b.h"

 

#include "myex35bDoc.h"

#include "CntrItem.h"

#include "myex35bView.h"

#include "AddrDialog.h"

#include "PostThread.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[ ] = __FILE__;

#endif

 

/////////////////////////////////////////////////////////////////////////////

// CMyex35bView

 

IMPLEMENT_DYNCREATE(CMyex35bView, CValidForm)

 

BEGIN_MESSAGE_MAP(CMyex35bView, CValidForm)

       //{{AFX_MSG_MAP(CMyex35bView)

       ON_WM_SETFOCUS()

       ON_WM_SIZE()

       ON_COMMAND(ID_OLE_INSERT_NEW, OnInsertObject)

       ON_COMMAND(ID_CANCEL_EDIT_CNTR, OnCancelEditCntr)

       ON_COMMAND(ID_CANCEL_EDIT_SRVR, OnCancelEditSrvr)

       ON_BN_CLICKED(IDC_SUBMIT, OnSubmit)

       ON_BN_CLICKED(IDCANCEL, OnCancel)

       ON_COMMAND(ID_SERVER_ADDRESS, OnServerAddress)

       //}}AFX_MSG_MAP

       // Standard printing commands

       ON_COMMAND(ID_FILE_PRINT, CValidForm::OnFilePrint)

       ON_COMMAND(ID_FILE_PRINT_DIRECT, CValidForm::OnFilePrint)

       ON_COMMAND(ID_FILE_PRINT_PREVIEW, CValidForm::OnFilePrintPreview)

       ON_MESSAGE(WM_POSTCOMPLETE, OnPostComplete)

END_MESSAGE_MAP()

 

/////////////////////////////////////////////////////////////////////////////

// CMyex35bView construction/destruction

 

CMyex35bView::CMyex35bView() : CValidForm(CMyex35bView::IDD)

{

       //{{AFX_DATA_INIT(CMyex35bView)

       m_dHours = 0.0;

       m_strEmployee = _T("");

       m_nJob = 0;

       m_nPeriod = 0;

       //}}AFX_DATA_INIT

       m_pSelection = NULL;

       // TODO: add construction code here

}

 

CMyex35bView::~CMyex35bView()

{      }

 

void CMyex35bView::DoDataExchange(CDataExchange* pDX)

{

       CValidForm::DoDataExchange(pDX);

       DDX_CBString(pDX, IDC_EMPLOYEE, m_strEmployee);

       DDV_MaxChars(pDX, m_strEmployee, 10);

       if(pDX->m_bSaveAndValidate && m_strEmployee.IsEmpty()) {

              AfxMessageBox("Must supply an employee name");

              pDX->Fail();

       }

       //{{AFX_DATA_MAP(CMyex35bView)

       DDX_Text(pDX, IDC_HOURS, m_dHours);

       DDV_MinMaxDouble(pDX, m_dHours, 0.1, 100.);

       DDX_Text(pDX, IDC_JOB, m_nJob);

       DDV_MinMaxInt(pDX, m_nJob, 1, 20);

       DDX_Text(pDX, IDC_PERIOD, m_nPeriod);

       //}}AFX_DATA_MAP

}

 

BOOL CMyex35bView::PreCreateWindow(CREATESTRUCT& cs)

{

       // TODO: Modify the Window class or styles here by modifying the CREATESTRUCT cs

 

       return CValidForm::PreCreateWindow(cs);

}

 

void CMyex35bView::OnInitialUpdate()

{

       CMyex35bDoc* pDoc = GetDocument();

       m_nPeriod = pDoc->m_nPeriod;

       UpdateData(FALSE);

 

       // TODO: remove this code when final selection model code is written

       m_pSelection = NULL;    // initialize selection

}

 

/////////////////////////////////////////////////////////////////////////////

// CMyex35bView printing

 

BOOL CMyex35bView::OnPreparePrinting(CPrintInfo* pInfo)

{

       // default preparation

       return DoPreparePrinting(pInfo);

}

 

void CMyex35bView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

       // TODO: add extra initialization before printing

}

 

void CMyex35bView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

       // TODO: add cleanup after printing

}

 

void CMyex35bView::OnPrint(CDC* pDC, CPrintInfo*)

{

       // TODO: add code to print the controls

}

 

/////////////////////////////////////////////////////////////////////////////

// OLE Client support and commands

 

BOOL CMyex35bView::IsSelected(const CObject* pDocItem) const

{

       // The implementation below is adequate if your selection consists of

       //  only CMyex35bCntrItem objects.  To handle different selection

       //  mechanisms, the implementation here should be replaced.

 

       // TODO: implement this function that tests for a selected OLE client item

 

       return pDocItem == m_pSelection;

}

 

void CMyex35bView::OnInsertObject()

{

       // Invoke the standard Insert Object dialog box to obtain information

       //  for new CMyex35bCntrItem object.

       COleInsertDialog dlg;

       if (dlg.DoModal() != IDOK)

              return;

 

       BeginWaitCursor();

 

       CMyex35bCntrItem* pItem = NULL;

       TRY

       {

              // Create new item connected to this document.

              CMyex35bDoc* pDoc = GetDocument();

              ASSERT_VALID(pDoc);

              pItem = new CMyex35bCntrItem(pDoc);

              ASSERT_VALID(pItem);

 

              // Initialize the item from the dialog data.

              if (!dlg.CreateItem(pItem))

                     AfxThrowMemoryException();  // any exception will do

              ASSERT_VALID(pItem);

 

              // If item created from class list (not from file) then launch the server to edit the item.

              if (dlg.GetSelectionType() == COleInsertDialog::createNewItem)

                     pItem->DoVerb(OLEIVERB_SHOW, this);

 

              ASSERT_VALID(pItem);

              // As an arbitrary user interface design, this sets the selection to the last item inserted.

              // TODO: re-implement selection as appropriate for your application

              m_pSelection = pItem;   // set selection to last inserted item

              pDoc->UpdateAllViews(NULL);

       }

       CATCH(CException, e)

       {

              if (pItem != NULL)

              {

                     ASSERT_VALID(pItem);

                     pItem->Delete();

              }

              AfxMessageBox(IDP_FAILED_TO_CREATE);

       }

       END_CATCH

       EndWaitCursor();

}

 

// The following command handler provides the standard keyboard

//  user interface to cancel an in-place editing session.  Here,

//  the container (not the server) causes the deactivation.

void CMyex35bView::OnCancelEditCntr()

{

       // Close any in-place active item on this view.

       COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this);

       if (pActiveItem != NULL)

       {

              pActiveItem->Close();

       }

       ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);

}

 

// Special handling of OnSetFocus and OnSize are required for a container

//  when an object is being edited in-place.

void CMyex35bView::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 in the same view

              CWnd* pWnd = pActiveItem->GetInPlaceWindow();

              if (pWnd != NULL)

              {

                     pWnd->SetFocus();   // don't call the base class

                     return;

              }

       }

       CValidForm::OnSetFocus(pOldWnd);

}

 

void CMyex35bView::OnSize(UINT nType, int cx, int cy)

{

       CValidForm::OnSize(nType, cx, cy);

       COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this);

       if (pActiveItem != NULL)

              pActiveItem->SetItemRects();

}

 

/////////////////////////////////////////////////////////////////////////////

// OLE Server support

 

// The following command handler provides the standard keyboard

//  user interface to cancel an in-place editing session.  Here,

//  the server (not the container) causes the deactivation.

void CMyex35bView::OnCancelEditSrvr()

{

       GetDocument()->OnDeactivateUI(FALSE);

}

 

/////////////////////////////////////////////////////////////////////////////

// CMyex35bView diagnostics

 

#ifdef _DEBUG

void CMyex35bView::AssertValid() const

{

       CValidForm::AssertValid();

}

 

void CMyex35bView::Dump(CDumpContext& dc) const

{

       CValidForm::Dump(dc);

}

 

CMyex35bDoc* CMyex35bView::GetDocument() // non-debug version is inline

{

       ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyex35bDoc)));

       return (CMyex35bDoc*)m_pDocument;

}

#endif //_DEBUG

 

/////////////////////////////////////////////////////////////////////////////

// CMyex35bView message handlers

 

void CMyex35bView::OnCancel()

{

       CMyex35bDoc* pDoc = GetDocument();

       m_dHours = 0;

       m_strEmployee = "";

       m_nJob = 0;

       m_nPeriod = pDoc->m_nPeriod;

       UpdateData(FALSE);

}

 

void CMyex35bView::OnSubmit()

{

       if(UpdateData(TRUE) == TRUE) {

              GetDlgItem(IDC_SUBMIT)->EnableWindow(FALSE);

              CString strHours, strJob, strPeriod;

              strPeriod.Format("%d", m_nPeriod);

              strHours.Format("%3.2f", m_dHours);

              strJob.Format("%d", m_nJob);

              g_strParameters = "Period=" + strPeriod + "&Employee=" +

                          m_strEmployee + "&Hours=" + strHours +

                          "&Job=" + strJob + "\r\n";

              TRACE("parameter string = %s", g_strParameters);

              AfxBeginThread(PostThreadProc, GetSafeHwnd(), THREAD_PRIORITY_NORMAL);

       }

}

 

// overridden CValidForm function

void CMyex35bView::ValidateDlgItem(CDataExchange* pDX, UINT uID)

{

       ASSERT(this);

       TRACE("CMyex35bView::ValidateDlgItem\n");

       switch (uID) {

       case IDC_EMPLOYEE:

              DDX_CBString(pDX, IDC_EMPLOYEE, m_strEmployee);

              // need custom DDV for empty string

              DDV_MaxChars(pDX, m_strEmployee, 10);

              if(m_strEmployee.IsEmpty()) {

                     AfxMessageBox("Must supply an employee name");

                     pDX->Fail();

              }

              break;

       case IDC_HOURS:

              DDX_Text(pDX, IDC_HOURS, m_dHours);

              DDV_MinMaxDouble(pDX, m_dHours, 0.1, 100.);

              break;

       case IDC_JOB:

              DDX_Text(pDX, IDC_JOB, m_nJob);

              DDV_MinMaxInt(pDX, m_nJob, 1, 20);

              break;

       default:

              break;

       }

       return;

}

 

void CMyex35bView::OnServerAddress()

{

       CAddrDialog dlg;

       dlg.m_strServerName = g_strServerName;

       dlg.m_strFile = g_strFile;

       if(dlg.DoModal() == IDOK) {

              g_strFile = dlg.m_strFile;

              g_strServerName = dlg.m_strServerName;

       }

}

 

// thread sends this message when it's done wParam = 1 for success

LONG CMyex35bView::OnPostComplete(UINT wParam, LONG lParam)

{

       TRACE("CMyex35bView::OnPostComplete - %d\n", wParam);

       if(wParam == 0) {

              AfxMessageBox("Server did not accept the transaction");

       }

       else OnCancel();

       GetDlgItem(IDC_SUBMIT)->EnableWindow(TRUE);

       return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Listing 30.

 

 

Build and run MYEX35B. The following is the output. Activate the Server Address menu.

 

Figure 87: MYEX35B program output - ActiveX document and Internet.

 

Figure 87: MYEX35B program output.

 

Figure 88: The Address sub menu displaying the server address and DLL file location settings. You can change this - ActiveX document and Internet.

 

Figure 88: The Address sub menu displaying the server address and DLL file location settings. You can change this.

 

Figure 89: The validation message box for the Name field - ActiveX document and Internet.

 

Figure 89: The validation message box for the Name field.

 

Figure 90: The validation message box for the Hours field - ActiveX document and Internet.

 

Figure 90: The validation message box for the Hours field.

 

Enter some data a shown below and click the Submit button.

 

Figure 91: Entering some data to myex35b form - ActiveX document and Internet.

 

Figure 91: Entering some data to myex35b form.

 

Figure 92: Saving the form - ActiveX document and Internet.

 

Figure 92: Saving the form.

 

Figure 93: Saving the form, test.35b under wwwroot sub directory - ActiveX document and Internet.

 

Figure 93: Saving the form, test.35b under wwwroot sub directory.

 

Figure 94: Opening test.35b in internet Explorer browser - ActiveX document and Internet.

 

Figure 94: Opening test.35b in internet Explorer browser.

 

Figure 95: Testing the validation in the browser - ActiveX document and Internet.

 

Figure 95: Testing the validation in the browser.

 

 

The Story: Field Validation in an MFC Form View

 

Problem: MFC's standard validation scheme validates data only when CWnd::UpdateData(TRUE) is called, usually when the user exits the dialog. Applications often need to validate data the moment the user leaves a field (edit control, list box, and so on). The problem is complex because Windows permits the user to freely jump between fields in any sequence by using the mouse. Ideally, standard MFC DDX/DDV (data exchange/validation) code should be used for field validation and the standard DoDataExchange() function should be called when the user finishes the transaction.

Solution: Derive your field validation form view classes from the class CValidForm, derived from CFormView, with this header:

 

// valform.h

#ifndef _VALIDFORM

#define _VALIDFORM

 

#define WM_VALIDATE WM_USER + 5

 

class CValidForm : public CFormView

{

DECLARE_DYNAMIC(CValidForm)

private:

    BOOL m_bValidationOn;

public:

    CValidForm(UINT ID);

    // override in derived dlg to perform validation

    virtual void ValidateDlgItem(CDataExchange* pDX, UINT ID);

    //{{AFX_VIRTUAL(CValidForm)

    protected:

    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

    //}}AFX_VIRTUAL

 

    //{{AFX_MSG(CValidForm)

    afx_msg LONG OnValidate(UINT wParam, LONG lParam);

    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()

};

#endif // _VALIDFORM

 

This class has one virtual function, ValidateDlgItem(), which accepts the control ID as the second parameter. The derived form view class implements this function to call the DDX/DDV functions for the appropriate field. Here is a sample ValidateDlgItem() implementation for a form view that has two numeric edit controls:

 

void CMyForm::ValidateDlgItem(CDataExchange* pDX, UINT uID)

{

    switch (uID) {

    case IDC_EDIT1:

        DDX_Text(pDX, IDC_EDIT1, m_nEdit1);

        DDV_MinMaxInt(pDX, m_nEdit1, 0, 10);

        break;

    case IDC_EDIT2:

        DDX_Text(pDX, IDC_EDIT2, m_nEdit2);

        DDV_MinMaxInt(pDX, m_nEdit2, 20, 30);

        break;

    default:

        break;

    }

}

 

Notice the similarity to the wizard-generated DoDataExchange() function:

 

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

    //{{AFX_DATA_MAP(CMyForm)

    DDX_Text(pDX, IDC_EDIT1, m_nEdit1);

    DDV_MinMaxInt(pDX, m_nEdit1, 0, 10);

    DDX_Text(pDX, IDC_EDIT2, m_nEdit2);

    DDV_MinMaxInt(pDX, m_nEdit2, 20, 30);

    //}}AFX_DATA_MAP

}

How does it work? The CValidForm class traps the user's attempt to move away from a control. When the user presses the Tab key or clicks on another control, the original control sends a killfocus command message (a control notification message) to the parent window, the exact format depending on the kind of control. An edit control, for example, sends an EN_KILLFOCUS command. When the form window receives this killfocus message, it invokes the DDX/DDV code that is necessary for that field, and if there's an error, the focus is set back to that field.

There are some complications, however. First, we want to allow the user to freely switch the focus to another application - we're not interested in trapping the killfocus message in that case. Next, we must be careful how we set the focus back to the control that produced the error. We can't just call SetFocus() in direct response to the killfocus message; instead we must allow the killfocus process to complete. We can achieve this by posting a user-defined WM_VALIDATE message back to the form window. The WM_VALIDATE handler calls our ValidateDlgItem() virtual function after the focus has been moved to the next field. Also, we must ignore the killfocus message that results when we switch back from the control that the user tried to select, and we must allow the IDCANCEL button to abort the transaction without validation. Most of the work here is done in the view's virtual OnCommand() handler, which is called for all control notification messages. We could, of course, individually map each control's killfocus message in our derived form view class, but that would be too much work. Here is the OnCommand() handler:

 

BOOL CValidForm::OnCommand(WPARAM wParam, LPARAM lParam)

{

    // specific for WIN32 — wParam/lParam processing different for WIN16

    TRACE("CValidForm::OnCommand, wParam = %x, lParam = %x\n", wParam, lParam);

    TRACE("m_bValidationOn = %d\n", m_bValidationOn);

    if(m_bValidationOn) {   // might be a killfocus

        UINT notificationCode = (UINT) HIWORD(wParam);

        if((notificationCode == EN_KILLFOCUS)  || (notificationCode == LBN_KILLFOCUS) ||

           (notificationCode == CBN_KILLFOCUS)) {

            CWnd* pFocus = CWnd::GetFocus(); // static function call

            // if we're changing focus to another control in the same form

            if(pFocus && (pFocus->GetParent() == this)) {

                if(pFocus->GetDlgCtrlID() != IDCANCEL) {

                    // and focus not in Cancel button validate AFTER drawing finished

                    BOOL rtn = PostMessage(WM_VALIDATE, wParam);

                    TRACE("posted message, rtn = %d\n", rtn);

                }

            }

        }

    }

    return CFormView::OnCommand(wParam, lParam); // pass it on

}

 

Note that m_bValidationOn is a Boolean data member in CValidForm. Finally, here is the OnValidate() message handler, mapped to the user-defined WM_VALIDATE message:

 

LONG CValidForm::OnValidate(UINT wParam, LONG lParam)

{

    TRACE("Entering CValidForm::OnValidate\n");

    CDataExchange dx(this, TRUE);

    m_bValidationOn = FALSE;   // temporarily off

    UINT controlID = (UINT) LOWORD(wParam);

    try {

        ValidateDlgItem(&dx, controlID);

    }

    catch(CUserException* pUE)

    {

        pUE->Delete();

        TRACE("CValidForm caught the exception\n");

        // fall through — user already alerted via message box

    }

    m_bValidationOn = TRUE;

    return 0;     // goes no further

}

 

Instructions for use:

 

  1. Add valform.h and valform.cpp to your project.

  2. Insert the following statement in your view class header file:

#include "valform.h"

  1. Change your view class base class from CFormView to CValidForm.

  2. Override ValidateDlgItem() for your form's controls as shown above.

 

That's all. For dialogs, follow the same steps, but use valid.h and valid.cpp. Derive your dialog class from CValidDialog instead of from CDialog.

 

Generating POST Requests Under Program Control

 

The heart of the MYEX35B program is a worker thread that generates a POST request and sends it to a remote server. The server doesn't care whether the POST request came from an HTML form or from your program. It could process the POST request with an ISAPI DLL, with a PERL script, or with a Common Gateway Interface (CGI) executable program. Here's what the server receives when the user clicks the MYEX35B Submit button:

 

POST scripts/myex34a.dll?ProcessTimesheet HTTP/1.0

(request headers)

(blank line)

Period=12&Name=Dunkel&Hours=6.5&Job=5

 

And here's the thread code from PostThread.cpp:

 

// PostThread.cpp (uses MFC WinInet calls)

#include <stdafx.h>

#include "PostThread.h"

 

#define MAXBUF 50000

 

CString g_strFile = "/scripts/ex35a.dll";

CString g_strServerName = "localhost";

CString g_strParameters;

 

UINT PostThreadProc(LPVOID pParam)

{

    CInternetSession session;

    CHttpConnection* pConnection = NULL;

    CHttpFile* pFile1 = NULL;

    char* buffer = new char[MAXBUF];

    UINT nBytesRead = 0;

    DWORD dwStatusCode;

    BOOL bOkStatus = FALSE;

    try {

        pConnection = session.GetHttpConnection(g_strServerName, (INTERNET_PORT) 80);

        pFile1 = pConnection->OpenRequest(0, g_strFile + "?ProcessTimesheet", // POST request

            NULL, 1, NULL, NULL, INTERNET_FLAG_KEEP_CONNECTION |

            INTERNET_FLAG_RELOAD); // no cache

        pFile1->SendRequest(NULL, 0, (LPVOID) (const char*) g_strParameters,

            g_strParameters.GetLength());

        pFile1->QueryInfoStatusCode(dwStatusCode);

        if(dwStatusCode == 200) { // OK status

            // doesn't matter what came back from server — we're looking for OK status

            bOkStatus = TRUE;

            nBytesRead = pFile1->Read(buffer, MAXBUF - 1);

            buffer[nBytesRead] = '\0'; // necessary for TRACE

            TRACE(buffer);

            TRACE("\n");

        }

    }

    catch(CInternetException* pe) {

        char text[100];

        pe->GetErrorMessage(text, 99);

        TRACE("WinInet exception %s\n", text);

        pe->Delete();

    }

    if(pFile1) delete pFile1; // does the close — prints a warning

    if(pConnection) delete pConnection; // Why does it print a warning?

    delete [] buffer;

    ::PostMessage((HWND) pParam, WM_POSTCOMPLETE, (WPARAM) bOkStatus, 0);

    return 0;

}

 

The main thread assembles the g_strParameters string based on what the user typed, and the worker thread sends the POST request using the CHttpFile::SendRequest call. The tQueryInfoStatusCode to find out if the server sent back an OK response. Before exiting, the thread posts a message to the main thread, using the bOkStatus value in wParam to indicate success or failure.

 

The MYEX35B View Class

 

The CMYEX35BView class is derived from CValidForm, as described in "Field Validation in an MFC Form View". CMYEX35BView collects user data and starts the post thread when the user clicks the Submit button after all fields have been successfully validated. Field validation is independent of the internet application. You could use CValidForm in any MFC form view application. Here is the code for the overridden or the overridden ValidateDlgItem() member function, which is called whenever the user moves from one control to another:

 

void CMyex35bView::ValidateDlgItem(CDataExchange* pDX, UINT uID)

{

    ASSERT(this);

    TRACE("CMyex35bView::ValidateDlgItem\n");

     switch (uID) {

    case IDC_EMPLOYEE:

        DDX_CBString(pDX, IDC_EMPLOYEE, m_strEmployee);

        // need custom DDV for empty string

        DDV_MaxChars(pDX, m_strEmployee, 10);

        if(m_strEmployee.IsEmpty()) {

            AfxMessageBox("Must supply an employee name");

            pDX->Fail();

        }

        break;

    case IDC_HOURS:

        DDX_Text(pDX, IDC_HOURS, m_dHours);

        DDV_MinMaxDouble(pDX, m_dHours, 0.1, 100.);

        break;

    case IDC_JOB:

        DDX_Text(pDX, IDC_JOB, m_nJob);

        DDV_MinMaxInt(pDX, m_nJob, 1, 20);

        break;

    default:

        break;

    }

    return;

}

 

The OnSubmit() member function is called when the user clicks the Submit button. CWnd::UpdateData returns TRUE only when all the fields have been successfully validated. At that point, the function disables the Submit button, formats g_strParameters, and starts the post thread.

 

void CMyex35bView::OnSubmit()

{

    if(UpdateData(TRUE) == TRUE) {

        GetDlgItem(IDC_SUBMIT)->EnableWindow(FALSE);

        CString strHours, strJob, strPeriod;

        strPeriod.Format("%d", m_nPeriod);

        strHours.Format("%3.2f", m_dHours);

        strJob.Format("%d", m_nJob);

        g_strParameters = "Period=" + strPeriod + "&Employee=" +

            m_strEmployee + "&Hours=" +strHours + "&Job=" + strJob + "\r\n";

        TRACE("parameter string = %s", g_strParameters);

        AfxBeginThread(PostThreadProc, GetSafeHwnd(), THREAD_PRIORITY_NORMAL);

    }

}

 

The OnCancel() member function is called when the user clicks the Reset button. The CValidForm logic requires that the button's control ID be IDCANCEL.

 

void CMyex35bView::OnCancel()

{

    CMyex35bDoc* pDoc = GetDocument();

    m_dHours = 0;

    m_strEmployee = "";

    m_nJob = 0;

    m_nPeriod = pDoc->m_nPeriod;

    UpdateData(FALSE);

}

 

The OnPostComplete() handler is called in response to the user-defined WM_POSTCOMPLETE message sent by the post thread:

 

LONG CMyex35bView::OnPostComplete(UINT wParam, LONG lParam)

{

    TRACE("CMyex35bView::OnPostComplete - %d\n", wParam);

    if(wParam == 0) {

        AfxMessageBox("Server did not accept the transaction");

    }

    else

        OnCancel();

    GetDlgItem(IDC_SUBMIT)->EnableWindow(TRUE);

    return 0;

}

 

This function displays a message box if the server didn't send an OK response. It then enables the Submit button, allowing the user to post another time-sheet entry.

 

Building and Testing MYEX35B

 

Build the MYEX35B project, and then run it once in stand-alone mode to register it and to save a document file sample called test.35b in your WWW root directory. Make sure the MYEX34A DLL (myex34a.dll) is available in the scripts directory (with execute permission) because that DLL contains an ISAPI function, ProcessTimesheet(), which handles the server end of the POST request. Be sure that you have IIS or some other ISAPI-capable server running. Now run Internet Explorer and load test.35b from your server. The MYEX35B program should be running in the Browser window, and you should be able to enter time-sheet transactions.

 

ActiveX Document Servers vs. VB Script

 

It's possible to insert VB Script (or JavaScript) code into an HTML file. We're not experts on VB Script, but we've seen some sample code. You could probably duplicate the MYEX35B time-sheet application with VB Script, but you would be limited to the standard HTML input elements. It would be interesting to see how a VB Script programmer would solve the problem. In any case, you're a C++ programmer, not a Visual Basic programmer, so you might as well stick to what you know.

 

Going Further with ActiveX Document Servers

 

MYEX35A used a worker thread to read a text file from an Internet server. It used the MFC WinInet classes, and it assumed that a standard HTTP server was available. An ActiveX document server could just as easily make Winsock calls using the CBlockingSocket class from Module 32. That would imply that you were going beyond the HTTP and FTP protocols. You could, for example, write a custom internet server program that listened on port 81. That server could run concurrently with IIS if necessary. Your ActiveX document server could use a custom TCP/IP protocol to get binary data from an open socket. The server could use this data to update its window in real-time, or it could send the data to another device, such as a sound card.

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | ActiveX Doc Servers & Internet 4 | Visual C++ & DHTML | Download | Site Index |