Export (0) Print
Expand All
C++ Q&A: ATL Virtual Functions and vtables
C++ Q&A: Browser Detection in the Registry, Changing Cursors in Windows, Avoiding Resource ID Collision
C++ Q&A: Browser Detection Revisited, Fixing CPopupText, COM and the IServiceProvider Interface
C++ Q&A: Browser Detection Revisited, Toolbar Info, IUnknown with COM and MFC
C++ Q&A: Commas, Pseudocode, Operator =, and More
C++ Q&A: CPopupText for Home-Grown Tooltips, Controlling Application Instantiation
C++ Q&A: Create a Dialog while Keeping it off that Pesky Taskbar
C++ Q&A: Disabling Context Menus, Sending Commands to Doc Objects
C++ Q&A: Displaying a JPG in your MFC Application
C++ Q&A: Enabling Menus in MFC Apps, Changing the Behavior of Enter with DLGKEYS Sample App
C++ Q&A: Find Icons, Launch an App from List Control, and More
C++ Q&A: Get the Main Window, Get EXE Name
C++ Q&A: Getting the Text of a Window in Another Application; Making Backspace Work in the Explorer Bar
C++ Q&A: Initializing C++ Class Members and Incorporating the Places Bar in your MFC Apps
C++ Q&A: Initializing Class Objects, The TestAD App and Active Desktop, #include Problems
C++ Q&A: Inline Virtual Functions, AVI Files in EXEs, and the DynPrompt Sample App
C++ Q&A: Prevent Users from Performing Normal GUI Operations
C++ Q&A: Progress Indicator in the Status Bar, International UI Terms
C++ Q&A: Sizing Windows for Text Strings, Creating Nonrectangular Windows, Activating an Open Document
C++ Q&A: Stopping Screen Savers, Detecting Screen Resolution, Adding Status Bar Buttons
C++ Q&A: Typename, Disabling Keys in Windows XP with TrapKeys
C++ Q&A: Understanding Categories with CatView, Getting Toolbars in 256 Colors
C++ Q&A: Why = Returns a Reference, Accessing the Recycle Bin, When to Use STL
C++ Q&A: Windows 2000 File Dialog Revisited; Autocompletion and the ACTest Demo App
Improved Error Reporting with DBGHELP 5.1 APIs
Visual Programmer: Adding Your Own AppWizard to Visual C++
Win32 Q&A: Handy Features in Windows, and Interlocked Functions
Win32 Q&A: New C++ Classes for Better Resource Management in Windows
Expand Minimize

Building Web-based Clients Using Dynamic HTML and Microsoft Visual C++

Visual Studio 6.0
 

Christian Gross
euSOFT

May 1998

Christian Gross is a partner at euSOFT, a firm that offers Internet consulting and training to enterprise customers, specializing in architectural issues. He speaks frequently at Microsoft conferences such as the Visual C++ DevCon, Developer Days, Tech Ed, and PDC. He has written numerous articles for MIND, Basic Pro, and other programmer publications, as well as white papers for Microsoft.


Contents

Problem
Design Patterns
The Design Pattern
Implementing the Pattern
Conclusion
For More Information

Summary: Discusses using a new design pattern for moving legacy applications to the Web. (22 printed pages) Covers:

  • Decoupled clients
  • A design pattern that separates format from logic
  • Building a component, based on the pattern, using DHTML and ATL

Problem

Before the popularity of the Web, each company developed legacy applications using its own proprietary solutions. Today, the challenge lies in moving these applications—all of which use different development solutions—to a Web-based framework. Moving legacy applications to the Web is not easy—it requires either a partial or total rewrite, emphasizing the need for methods to develop multitier applications efficiently.

Design Patterns

Why use design patterns? Design patterns provide an efficient means of defining logic without getting into specifics. Assume two mechanical engineers get together and talk about heat turbines. Their conversation is facilitated by common, specialized language for discussing high-level concepts and ideas. Design patterns serve the same purpose by providing an efficient means to define more complex architectures.

While considering the challenge of moving legacy applications to the Web, several design patterns were evaluated. One such pattern, the Model-View-Controller (MVC) approach (see http://atddoc.cern.ch/Atlas/Notes/004/Note004-7.html) initially seemed an excellent choice. Because it decoupled the UI from the logic used to create it, MVC appeared to solve the problem addressed by this article. However, closer inspection shows the MVC pattern uses a central controller and model to manipulate various views. In our design pattern there are multiple data objects and a single view (the Web page). Although the MVC pattern is ideal for multiple document applications, it is not applicable to a Web-based framework.

The Strategy Pattern (Design Patterns, Gamma, et al, p. 315.) also decouples the UI from logic; however, it does not go far enough to allow rich interaction with the UI. Another problem with the Strategy Pattern is that the context object does not define an external interface used in global operations. For an external interface, the Bridge Pattern is ideal because it defines a specific interface class that connects dynamically to a specific implementation. The design pattern outlined in this article builds on the desirable features of these two patterns: decoupled UI of the Strategy Pattern and dynamic interface class of the Bridge Pattern.

Decoupling a Design

Up to this point the term decoupling has been used as a desirable trait. But what do we mean by "decoupling"? Decoupling is the process of defining an interface and then implementing that interface using a specific language. Consider the following example interface definitions:

class baseOperations {
public:
   virtual long operation1( long param1, long param2) = 0;
   virtual long operation2( long param1) = 0;
};

This example uses an interface definition for two numerical operations. How they function and what they do is independent of the interface. This is the point of decoupling. The implementation—rather than the interface—defines the functionality. A sample implementation would be as follows:

class Implemented : public baseOperations 
{
public:
   virtual long operation1( long param1, long param2) {
      return param1 * param2;
   }
   virtual long operation2( long param1) {
      return param1 + param2;
   }
};
This implementation could be used as follows:
void Method1( baseOperations *op, int param1, int param2) {
   printf( "Operation1 is %ld, %ld = %ld\n", param1, param2, op->operation1( param1, param2));
   printf( "Operation2 is %ld = %ld\n", param1, op->operation2( param1));
}

int main() {
   Implemented imp;

   Method1( &imp, 1, 2);
   return 0;
}

The implemented object is passed to a method (Method1) that expects to see the baseOperations interface. This means that any implementation can be used by the Method1 function. In this instance of decoupling, Method1 does not know what the implementation is, it simply uses the implementation.

It is noted that inheritance could be used in this example as well. In fact, inheritance is used to make everything work. The problem with using inheritance is that the main function requires reference to a specific class implementation. If another implementation is used, another class must be referenced. Reference in this manner introduces dependencies and makes it difficult to extend, modify, or reuse the interfaces independently.

The Design Pattern

Using the traits of a decoupled interface design and dynamic interface class, a new design pattern is proposed. An overview of this design pattern is provided below.

Name

Given the characteristics of this design pattern, it is called "Separating Format from Logic."

Problem

How does the Separating Format from Logic design pattern address the issue of moving legacy applications to a Web-based framework? First, consider the legacy application TimeClock, a time tracking application. The original code was written by Zafir Anjum and subsequently modified by My Blenkers. The original TimeClock interface is shown in Figure 1.

Figure 1. The original TimeClock interface

The purpose of the application was to monitor how much time was spent on a specific project. In its first iteration, the application defined things like vacation time and sick time. However, the only possible operation was to define a steady stream of punch-in and punch-out times on a specific timecard. This specific timecard could be a project, but there was no direct correlation. In its second iteration, the program defined a project, but this time sick time and vacation time were not defined.

Both of these iterations demonstrate the inherent problem of coupling a UI to logic. It is very difficult to separate the two and create a fine, granular UI. With a coupled UI it takes much longer than anticipated to accomplish a simple task. As a result, there has been a renaissance of thin-client computing in which the interfaces do nothing but move data from the client to the server. Compared to traditional clients, thin clients do two things:

  1. Move logic from the client to the server, resulting in longer server development times.
  2. Create a simpler interface without rich client-side functionality.

Rapid application development (RAD) tools are partially to blame for this trend because they make it simple to add logic code within an event. With current RAD tools there is either a canvas or a form and elements are placed on it. With this approach it is impossible to incrementally assemble or disassemble the UI.

To offset extra developmental costs, many vendors have been producing tools that allow components to be dragged from a toolbox to a canvas. Although these drag-and-drop tools make it possible to quickly produce user interfaces, the resulting logic is so tightly bound to the UI that it is difficult to extend it or move it to another canvas.

One strategy to facilitate UI development is to buy or build full controls that perform 80 percent of the desired UI logic. These controls work but are hard to override or alter. For example, with a graphing control it is impossible to override the control and dynamically make it Virtual Reality Modeling Language (VRML)-compatible. A new control must be developed or purchased. Either way, it is not a simple operation. It means rewriting the control. Therefore, thin clients, RAD tools, drag-and-drop tools, and full controls do not adequately solve the issue of developing user interfaces that are decoupled from logic. Clearly, something else is needed.

Solution

The proposed solution is based on a UI dynamically coupled to the objects that perform a specific task. In this architecture, a controller manages part of the business logic, but at a very high level. Thus, controller tasks are limited to summarization, event casting, and organization. These high-level tasks are specific to the organization of data and the calling of general business processes (such as punching in and out of the time clock). The controller does not implement the business logic; it only directs the action to the appropriate implementation.

The implementation receiving the general business logic performs the task being requested, but it does not store the results of the operation. Storing the results is left to the controller, which in turn exposes a generic interface used to store and retrieve information. The logic and controller exchange interfaces when the logic component registers itself with the controller. In this process, registration is activated by the logic component, and the controller takes a passive role. Once the logic component makes a connection to the controller through the registration process, the controller executes the specific operations as required. Decoupling in this manner has the advantage of assigning user interface and logic in a dynamic fashion.

The logic component does not have any graphical user interface (GUI) aspects. Instead, it is associated with the UI when the component is instantiated. This association makes it possible to create other user interfaces that look different but perform the same logic. The UI functionality does not influence the operation of the logic component or controller. It is also possible to use a different UI medium for different solutions based on the same logic. This feature of separating the interface from business logic gives the design pattern its name—Separating Format from Logic.

As shown in Figure 2, the Separating Format from Logic architectural layout consists of three elements: 1) the controller, 2) the interface, and 3) the component.

Figure 2. The Separating Format from Logic architectural layout

Note   For the purpose of this article, component, logic, business logic, and logic component are used interchangeably.

Consequences

The Separating Format from Logic design pattern has the following consequences:

  • Language-neutral: The controller, interface, and component are tied together using a distributed object technology, making it simpler to add a new implementation or UI. A critical factor to making subsequent changes is to use a language appropriate to the architectural element. For example, the UI needs to be dynamic and, therefore, could use Dynamic HTML, while the logic component should use a programming language such as Microsoft® Visual C++® development system.
  • Controller-dependent: A poorly designed controller that does not reflect static business practice causes the components implementing the controller's interface to become obsolete automatically. Such a situation requires a rewrite of the business logic.
  • Hidden implementation details: It is not necessary for the controller to know where the UI details originate. In fact, the controller can communicate to the component on another machine in another UI, if desired. It is entirely flexible, since the registration process is instantiated by the logic component.
  • Simple: The design pattern offers simplicity since the design cleanly separates the format of the UI from the logic of what the UI is to achieve.
  • Extensibility: Communication between the UI and business logic components is a purely dynamic process, which each component completes independently. The Component Object Model (COM) makes this dynamic communication possible.

Implementing the Pattern

Up to this point we have identified the need for a new design strategy, and defined the architectural layout and features of the Separating Format from Logic design pattern. Now let's take the next step by demonstrating how this pattern can be used to modify an existing legacy application. Continuing with our previous example, TimeClock, the remainder of this article considers the three-step process of implementing the design pattern:

  1. Define the interface
  2. Build the component
  3. Build the controller

Technologies Used

Before discussing implementation of the design pattern, the technologies used in the implementation must be considered. The proposed design pattern is biased toward technology because it requires a technological solution using dynamic association and decoupling. Two technological layers are required:

  • Component Object Model (COM): A component object technology allowing the decoupling of an interface and implementation.
  • Dynamic HTML: The UI that performs dynamic association and builds a UI in a piecemeal fashion.

JavaScript and Visual C++ are the languages used in this design pattern. Since this model results in client code, one might think Java is more useful. However, C++ offers the advantage of its rich features and ability to build tight code quickly. Based on the strengths of each tool, Dynamic HTML is great for dynamic scripting and association, while C++ is great for implementing logic.

Step 1: Defining the Interfaces

The first step in implementing the solution is to define interfaces to be used by the logic component and controller. The first interface is the operations that each component must implement. Because our legacy application only supports two operations (punching in and punching out), the interface in our example must implement these operations. Two additional functions are necessary for housekeeping purposes. Using Interface Definition Language (IDL), the resulting interface is as follows:

[
        object,
        uuid(EA55BFDF-BC3E-11d1-9484-00A0247D759A),
        pointer_default(unique),
        local,
        version(1.0)
]
interface ITimeCard : IUnknown
{
   HRESULT PunchIn( BSTR time, [out,retval]long *retVal);
   HRESULT PunchOut( BSTR time, [out,retval]long *retVal);
   HRESULT Descriptor([out, retval]BSTR *description);
   HRESULT SetService(IUnknown *serviceProvider);
}

When the operation is to punch in or punch out, time is the first parameter. Because all timecards should be synchronized, the controller generates the time parameter. The parameter retval is a return code performed by the operation.

Note   Returning error codes is a task that could be handled by the COM layer. It does not need an additional parameter. But implementation of COM errors is an extra step and beyond the scope of this article.

The two housekeeping methods mentioned above are the descriptor and SetService. The descriptor is a simple description of the object used by the controller to display which implementations have registered themselves. SetService is a method used to associate the controller interface by exposing generic operations that resemble services.

The controller in our example application exposes a service resembling a data recordset. When the component performs its operations, the resulting data is stored in the controller through the service interface. Therefore, the second interface is defined as follows:

[
        object,
        uuid(EA55BFDD-BC3E-11d1-9484-00A0247D759A),
        pointer_default(unique),
        local,
        version(1.0)
]
interface IControllerService : IUnknown
{
   HRESULT Reset();
   HRESULT Rewind();
   HRESULT MoveNext();
   HRESULT GetColumn( BSTR colName, [out,retval]BSTR *value);
   HRESULT SetColumn( BSTR colName, BSTR value);
   HRESULT RecordReference( [out,retval]long *retval);
   HRESULT Add();
   HRESULT Update();
}

For those familiar with recordsets, there are many parallels to the above service interface. The basic operations are:

  • Reset: Empties the recordset in the controller.
  • Rewind: Moves the pointer to the first recordset in the controller.
  • MoveNext: Moves the component recordset pointer to the next position.
  • GetColumn, SetColumn: Gets and sets the data on the controller.
  • RecordReference: Retrieves a bookmark for future reference.
  • Add: Adds a record to the recordset.
  • Update: Updates the data in the recordset, and allows any operations that the controller needs to access the UI or remote data source.

Using both of the above interfaces as a basis, it is possible for a controller to act as a component or vice versa. It is also possible for components or controllers to be written in any language. Thus, the Separating Format from Logic design pattern provides an open architecture solution.

The two interfaces are part of the project CommonInterface. It is a project that only includes the TimeCard.idl file. The IDL file contains a series of "prototypes" that define the interface of objects. The file is compiled using Midl.exe, subsequently producing several types of files. The following command line represents typical IDL usage:

midl /h TimeCard.h /iid TimeCard_i.c TimeCard.idl

The first option, /h, creates the header file used to define the interface in C++. The header file (TimeCard_i.c) declares all IIDs (uuid) as external, and therefore requires a file in which the IIDs are implemented. The second option, /iid, creates the IID definition file. The last file created (not specified on the command line) is the type library (.tlb). This file is used only if the component was written in a language other than C++. For example, a language that would use a type library is Microsoft Visual Basic® development system.

Step 2: Building the Logic Component

When building the business logic, the question is whether to generate full controls or components. The answer is that it does not matter. Continuing with the timecard legacy application as our example, the only important aspect is whether the control or the component implements the ITimeCard interface. To fulfill the requirements of the design pattern, there is no UI; therefore, a component will be used. Developed using the Active Template Library (ATL) Simple Object wizard, the component must be capable of dual interface in order to support scripting association.

Defining the ATL object

The following example demonstrates the necessary changes to the timecard application's ATL object definition:

class ATL_NO_VTABLE CVacation : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CVacation, &CLSID_Vacation>,
   public IDispatchImpl<IVacation, &IID_IVacation, &LIBID_TIMEVACATIONLib>,

   public ITimeCard

{
public:
   CVacation();

   virtual ~CVacation();

DECLARE_REGISTRY_RESOURCEID(IDR_VACATION)

BEGIN_COM_MAP(CVacation)
   COM_INTERFACE_ENTRY( IVacation)
   COM_INTERFACE_ENTRY( IDispatch)

   COM_INTERFACE_ENTRY( ITimeCard)
END_COM_MAP()

// IVacation
public:
   STDMETHOD(SetCommentElement)(IUnknown *component);

// ITimeCard

public:

    STDMETHOD( PunchIn)( BSTR time, long *retVal);

   STDMETHOD( PunchOut)( BSTR time, long *retVal);

   STDMETHOD( Descriptor)( BSTR *description);

   STDMETHOD( SetService)( IUnknown *serviceProvider);

private:
   bool m_punchedIn;
   IControllerService *m_service;
   MSHTML::IHTMLInputTextElement *m_comment;
};

Highlighted are the items specific to the ITimeCard interface. Also, there are three additions to the original code:

  1. Add the public ITimeCard to the inheritance chain. ITimeCard defines a series of virtual functions that resemble the interface defined in the IDL file.
  2. Add the COM_INTERFACE_ENTRY macro to the COM map. The macro adds the interface as part of the component. Whenever a QueryInterface (QI) and the IID_ITimeCard interface are referenced, the ATL implementation of QI looks at the COM map to see if the interface is actually available.
  3. Add the required methods expected by ITimeCard. Even if this step were ignored, the component would not compile because virtual functions need to be implemented.

Setting the service interface

When the component is instantiating the script on the Web page, it registers the component with the controller. The controller then calls the SetService method to register its interface with the component. However, the parameter that is passed in is an IUnknown. While it would have been possible to pass the custom interface directly, it is simpler to pass an IUnknown. The advantage of using the IUnknown is that it promotes a further element of decoupling, permitting the interface pointer to be anything. By decoupling in this manner, the component may select the most appropriate interface. Setting the service interface is accomplished as follows:

HRESULT CVacation::SetService(IUnknown *serviceProvider) {
   try {
      _com_util::CheckError( serviceProvider->QueryInterface( IID_IControllerService, (void **)&m_service));
   } catch ( _com_error err) {
      ::MessageBox( NULL, err.ErrorMessage(), "registerInterface Error is", MB_OK);
   };
   return S_OK;
}

In the class declaration there was a pointer to IControllerService. This pointer is retrieved using a QI for IID_IControllerService. The entire QI process is wrapped within an exception block. It has been commented that exception blocks add an additional 20 KB to the control. While it adds a bit of size, exception-based code provides a simpler approach to deal with the unexpected. The serviceProvider->QueryInterface is embedded within the function _com_util::CheckError. Although undocumented, this function is useful because it checks to make sure that the HRESULT is valid. Otherwise, a COM exception results. The _com_error object is a rich error-handling object that can be used to analyze what went wrong. MessageBox in the catch block displays the COM error string.

Performing the operations

Once the service interface has been retrieved, it is possible to perform the operations and store the resulting data within the controller. Using our example of the timecard application, the operations are punch-in and punch-out. The PunchIn method is implemented as follows:

HRESULT CVacation::PunchIn( BSTR time, long *retVal) {
   *retVal = 0;
   try {
      if( m_punchedIn == true) {
         *retVal = 4;
         return S_OK;
      }
      if( m_comment == NULL) {
         *retVal = 3;
         return S_OK;
      }
      _bstr_t strComment = m_comment->Getvalue();

      if( strComment.length() == 0) {
         *retVal = 2;
         return S_OK;
      }
      
      m_service->Add();

      // I put this into brackets so it will create and
      // destroy the class.  Note this is does not create
      // extra memory because the VC++ catches this and
      // optimizes.  Neat trick, eh?
      {
      _bstr_t col( "TimeIn");
      m_service->SetColumn( col, time);
      }
      {
      _bstr_t col( "TimeOut");
      _bstr_t value("");
      m_service->SetColumn( col, value);
      }
      {
      _bstr_t col( "Empty column");
      _bstr_t value("");
      m_service->SetColumn( col, value);
      }
      {
      _bstr_t col( "Comment");
      m_service->SetColumn( col, strComment);
      }
      m_service->Update();
      m_punchedIn = true;
   } catch ( _com_error err) {
      ::MessageBox( NULL, err.ErrorMessage(), "registerInterface Error is", MB_OK);
      *retVal = 1;
   };
   return S_OK;
}

When reviewing the functionality of this method, notice that it performs an operation that implements logic. The first step of the method is to check if the individual has already punched in. If so, the method returns without doing anything because this violates a rule. Next, it checks if the UI has an associated UI element (m_comment). If this is NULL, there is no association and the method cannot continue. If this element is valid, the current value is retrieved (m_comment->Getvalue()). Finally, the value is checked to make sure it is not empty.

Once each operation is complete, the next step is to save the resulting data to the controller. Saving the data to the controller is as simple as adding a record and then saving the information in a specific column. Once all of the data has been added, the record is updated, ending the functionality of the component.

Working with the Dynamic HTML model

It was mentioned previously that the script binds the UI to the component. This functionality is demonstrated in the following Dynamic HTML code:

<OBJECT ID="TimeVacation" WIDTH=1 HEIGHT=1

   CLASSID="CLSID:29F14E14-C10A-11D1-9486-00A0247D759A">

    <PARAM NAME="_Version" VALUE="65536">

    <PARAM NAME="_ExtentX" VALUE="2646">

    <PARAM NAME="_ExtentY" VALUE="1323">

    <PARAM NAME="_StockProps" VALUE="0">

</OBJECT>

<div class="components" id=secVacation style="visibility:hidden">
<b>Vaction Options</b>
<table>
   <tr>
      <td>Comment:</td>

      <td><input id="txtVacComment" type="text" name="txtComment" size="20"></td>

</tr>

</table>
…
</div>

<script language=javascript>

function WindowOnLoad() {
   Controller.RegisterComponent( TimeWorking);
   Controller.RegisterComponent( TimeVacation)
   TimeWorking.SetCommentElement( txtWorkComment);
   TimeWorking.SetProjectElement( optProjects);

   TimeVacation.SetCommentElement( txtVacComment);

   Controller.activeInterface( TimeWorking);
}   

In the preceding example, the three highlighted fields demonstrate Dynamic HTML. The first highlighted field defines the COM component discussed thus far. The second field defines a Dynamic HTML input element. This UI element will be associated with the component using the scripting defined in the third field. Notice that the component is passed as a simple object reference.

Now let's consider implementation of the SetCommentElement:

STDMETHODIMP CVacation::SetCommentElement(IUnknown * inpElement)
{
   try {
      if( m_comment != NULL) {
         m_comment->Release();
      }
      _com_util::CheckError( inpElement->QueryInterface( __uuidof(MSHTML::IHTMLInputTextElement), (void **)&m_comment));
   } catch ( _com_error err) {
      ::MessageBox( NULL, err.ErrorMessage(), "registerInterface Error is", MB_OK);
   };
   return S_OK;
}

The SetComment element is passed as an IUnknown (inpElement). With Microsoft Internet Explorer, all Dynamic HTML elements are exposed on a Web page as COM components. When an Internet Explorer component is passed to another COM component, it is as a COM component that can be referenced and manipulated. The full reference model is stored within the file Mshtml.dll. In the newest editions of the Platform SDK, the header is included. A simpler way to get access to the reference model is to use COM compiler support as follows:

#import "mshtml.dll" 

Going back to the method implementation, it is necessary to convert the IUnknown into an IHTMLInputTextElement. Doing a QI and asking for the __uuidof (MSHTML::IHTMLInputTextElement) does this. Once the interface has been retrieved, the element's methods can be accessed. Although Dynamic HTML method names are different in a C++ environment, they access the same methods. For example, in scripting the method name is the value for setting and getting data. In C++ these same names are converted to Getvalue and Putvalue. However, in both environments these names access the same methods.

Step 3: Building the Controller

The last step in the design solution is to build the controller. Building the controller is a more complicated process than the previous two steps, involving the definition of some generic code. Note that the example controller is not considered to be thread-safe or robust. It is only provided as an example of how to possibly build the controller. Therefore, the only steps that will be discussed are new concepts directly related to implementing the design pattern.

The controller is programmed as an ATL-based Active Control. While it could have been a component, there are some advantages to using a control. The first advantage is user feedback. While it is possible to link the user feedback to some UI element, it adds an unnecessary programming burden. The user feedback could also be linked to the server for counting registered components, and so on. Connecting user feedback to the server should be avoided when building a full UI with a control, because it prohibits future alternations.

The controller exposes the operation functionality that will be used and is defined as follows:

   [
      object,
      uuid(29F14DFC-C10A-11D1-9486-00A0247D759A),
      dual,
      helpstring("IController2 Interface"),
      pointer_default(unique)
   ]
   interface IController2 : IDispatch
   {
   [id(1), helpstring("method ")] HRESULT RegisterComponent(IUnknown *component);
   [id(2), helpstring("method PunchIn")] HRESULT PunchIn();
   [id(3), helpstring("method PunchOut")] HRESULT PunchOut();
   [id(4), helpstring("method ActiveInterface")] HRESULT ActiveInterface(IUnknown *currInterface);
   [id(5), helpstring("method ResetActiveInterface")] HRESULT ResetActiveInterface();
   };

These methods are exposed as dual-capable, a requirement because scripting will use the controller. The two operations are punch-in and punch-out and the other methods are again housekeeping methods. The operations do not need further explanation because they relay the calls to the components. What do need discussion are the housekeeping methods.

In the previously shown Dynamic HTML code excerpt, the first piece of script registered the component with the controller using the RegisterComponent:

STDMETHODIMP CController2::RegisterComponent(IUnknown * component)
{
   IOleClientSite *site;

   try {
      // Set the client site.
      // The site is set every time an interface is registered.
      // Sure it's not the most efficient, but it's simple and effective.
      GetClientSite( &site);
      m_objectDHTML.setSite( site);
      m_dataManager->setDHTMLReference( &m_objectDHTML);
      m_dataManager->addComponent( component);
   } catch ( _com_error err) {
      ::MessageBox( NULL, err.ErrorMessage(), "registerInterface Error is", MB_OK);
   };

   return S_OK;
}

There are two housekeeping operations: getting the Dynamic HTML pointer and setting up the environment. Each operation is discussed below.

Working with the Dynamic HTML pointer

The controller, unlike the component, finds its UI pieces. There is less flexibility on what kind of UI is to be associated with the controller because the controller changes the UI dynamically. This is not a requirement, but is added to highlight some additional features of Dynamic HTML.

It is possible for the controller to determine its environment because of the way COM instantiates controls. When a control is created and executed, it executes within a container. The container and control are tightly related and exchange information about each other. Internet Explorer works in the same fashion. When Internet Explorer instantiates the control, the container is the Web page. Using this relationship, it is possible for the control to access other components or elements on the Web page. This access is made possible through the following code:

STDMETHODIMP CController2::RegisterComponent(IUnknown * component)
{
   IOleClientSite *site;

   try {
      // Set the client site.
      // The site is set every time an interface is registered.
      // Sure it's not the most efficient, but it's simple and effective.

      GetClientSite( &site);

      m_objectDHTML.setSite( site);

      m_dataManager->setDHTMLReference( &m_objectDHTML);
      m_dataManager->addComponent( component);
   } catch ( _com_error err) {
      ::MessageBox( NULL, err.ErrorMessage(), "registerInterface Error is", MB_OK);
   };

   return S_OK;
}

bool CDHtmlObjectModel::setSite( 
   LPOLECLIENTSITE clientsite) {

   // The next step is a bit touchy because it attempts to retrieve the
   // IWebBrowser2 interface from the container. We want to do this
   // because once we have this interface we can do almost anything.
   try
   {
      IServiceProviderPtr spSP((LPOLECLIENTSITE)clientsite);

      if( NULL == spSP) {
         return false;
      }

      spSP->QueryService(__uuidof(SHDocVw::IWebBrowserApp), 
            __uuidof(SHDocVw::IWebBrowser2), (void**)&m_spWebBrowser);
      if( m_spWebBrowser == NULL) {
         return false;
      }

      m_spDocument2 = m_spWebBrowser->GetDocument();
   }
   catch(...)
   {
      return false;
   }
   return true;
}

When the component is registered with the controller, a call is made to retrieve the IoleClientSite interface. This site is the container that holds the control. GetClientSite is a method provided by the ATL Control classes. The CDHtmlObjectModel::setSite method takes the site interface and asks for the WebBrowser interface. It is very important to note that the test is contained within a try-and-catch block. But this catch block has a triple ellipse, which means it catches any exception. Visual C++ exception handling can catch any type of exception, including General Protection Fault (GPF), math overflow, and so on. This block is designed to catch GPFs, a potential exception caused when the site interface queries the WebBrowser interface and it does not exist. Further, the site may not support the QueryService method, another potential cause of a GPF. The last step is to retrieve the root Dynamic HTML pointer using the m_spWebBrowser->GetDocument() call. Now it is possible to navigate the Dynamic HTML tree representing the current Web page.

Setting up the environment

The second housekeeping operation is to set up the environment for the service interface. When the controller instantiates, it creates a CDataManager instance. This object is responsible for managing the service interfaces and the resulting data components. The class CControllerServiceImpl, which in turn is managed by CConnector, implements the IControllerService. CConnector is a simple class whose only purpose is to keep the IControllerServiceImpl interface and ITimeCard interface. Throughout all of these classes there are a series of arrays requiring storage. This is provided by the Standard Template Library (STL) vector class and is declared as follows:

std::vector< CRecord *> m_records;

Using the standard template library makes it easy to manage and contain the array of elements. It is recommended to use STL as much as possible. ATL does not have any problem with using STL since it is stored in a different namespace. A discussion of STL is beyond the scope of this article.

Looking at the record and UI

The last item of interest in the controller is how it updates the UI. When the component calls the IControllerService::Add method, a CRecord object is added to the m_records vector. The definition of a CRecord class is as follows:

 struct _tagColumn {
   char name[ 255];
   char value[ 255];
};

class CRecord  
{
public:
   void addColumn( char *name, char *value);
   void setColumn( char *name, char *value);
   CRecord();
   virtual ~CRecord();

   MSHTML::IHTMLTableRowPtr m_row;
   std::vector< struct _tagColumn *> m_columns;

private:

};

Each record object contains a vector of m_columns representing individual columns and their values. This arrangement is not the most efficient, but allows for custom records. The other variable, m_row, is a reference to a row on the Web page. Whenever a row is created using IControllerService::Add, a Dynamic HTML row is created as follows:

STDMETHODIMP CControllerServiceImpl::Add() {

   // Add this row and then add an empty record set.
   m_currRecord = new CRecord;

   m_currRecord->m_row = m_parent->m_objectDHTML->addTableRow( "tableTimeCard", "Work", "", "", "", "");

   m_currRecord->setColumn("Type", "Work");
   m_parent->m_records.push_back( m_currRecord);
   m_iterator = m_parent->m_records.end();
   return S_OK;
}

MSHTML::IHTMLTableRowPtr CDHtmlObjectModel::addTableRow(
   char *table, 
   char *type,
   char *inTime,
   char *outTime,
   char *project,
   char *comment) {
   // Retrieve all of the page elements.
   MSHTML::IHTMLTablePtr spTable;
   MSHTML::IHTMLElementCollectionPtr spAllElements = m_spDocument2->Getall();

   _variant_t vaTag( table);

   if((spTable = spAllElements->item( vaTag)) != NULL) {
      // We have found the table, so now add a row.
      MSHTML::IHTMLTableRowPtr spRow( spTable->insertRow( 1));

      MSHTML::IHTMLTableCellPtr spType( spRow->insertCell( 0));
      MSHTML::IHTMLTableCellPtr spTimeIn( spRow->insertCell( 1));
      MSHTML::IHTMLTableCellPtr spTimeOut( spRow->insertCell( 2));
      MSHTML::IHTMLTableCellPtr spProject( spRow->insertCell( 3));
      MSHTML::IHTMLTableCellPtr spComment( spRow->insertCell( 4));

      // Here is the compiler trick again.
      // If a series of variables are created
      // that are identical in size, the memory will be
      // reused and it will not cost an extra allocation.
      // Neat trick, eh!
      {
      MSHTML::IHTMLElementPtr spAnElement = spType;
      _bstr_t bstrStr( type);
      spAnElement->PutinnerText( bstrStr);
      }

      {
      MSHTML::IHTMLElementPtr spAnElement = spTimeIn;
      _bstr_t bstrStr( inTime);
      spAnElement->PutinnerText( bstrStr);
      }

      {
      MSHTML::IHTMLElementPtr spAnElement = spTimeOut;
      _bstr_t bstrStr( outTime);
      spAnElement->PutinnerText( bstrStr);
      }

      {
      MSHTML::IHTMLElementPtr spAnElement = spProject;
      _bstr_t bstrStr( project);
      spAnElement->PutinnerText( bstrStr);
      }

      {
      MSHTML::IHTMLElementPtr spAnElement = spComment;
      _bstr_t bstrStr( comment);
      spAnElement->PutinnerText( bstrStr);
      }

      return spRow;
   } else {
      MSHTML::IHTMLTableRowPtr spRow;
      return spRow;
   }
}

The addTableRow method is based on a parameter (table) found on the Web page. The table is located by referencing the root Dynamic HTML pointer and asking for the named element from the collection spAllElements->All(). If the table is found, a row can be inserted (spTable->insertRow()). Next, each cell is inserted (spRow->insertCell()) for each element that is to be displayed. The reason a record keeps a reference to the row on the Web page is because it makes it unnecessary to find each row and cell when updating the row with new information.

The update is performed when a component calls IControllerService::Update as follows:

STDMETHODIMP CControllerServiceImpl::Update() {
   // This is to update the current record on the DHTML page.
   std::vector< CRecord *>::iterator i;

   for( i = m_parent->m_records.begin(); i != m_parent->m_records.end(); i ++) {
      m_parent->m_objectDHTML->updateTableRow( (*i)->m_row, 
         ((*i)->m_columns)[ 0]->value, ((*i)->m_columns)[ 1]->value, 
         ((*i)->m_columns)[ 2]->value, ((*i)->m_columns)[ 3]->value, 
         ((*i)->m_columns)[ 4]->value);
   }
   return S_OK;
}

Implementation of updateTableRow is akin to the addTableRow in the last set of operations. The point is to simplify the updating process.

Taking a Step Back

Through the three implementation steps—define interfaces, build logic component, and build controller—the Separating Format from Logic design pattern is complete. But, how do we know the design pattern was solved? Did the exercise meet the design objectives of a decoupled interface with dynamic association? These objectives are considered below.

Dynamic association

Dynamic association between the interface and logic is accomplished by Dynamic HTML scripting. With Dynamic HTML, the interface is built in a piecemeal approach and, therefore, a UI can change and still perform the same logic. This means any language can be coupled to the logic.

Decoupled

The controller needs to expose a specific service interface, IcontrollerService, and the component needs to expose the interface ITimeCard. As long as the old interfaces are kept intact, the component or controller can be updated to work with a different controller or component. This means that if any changes need to be introduced, they can be done in a piece-by-piece approach.

Conclusion

Now our Separating Format from Logic design pattern requirements are met, resulting in a simpler UI with finer granularity. Additionally, the interface and logic component are independent of each other, providing an excellent means for future proofing of an application.

To build and test the project, complete the following:

  1. Load the project timetracker/timetracker.dsw.
  2. Build the following (order is important):

    a.  CommonInterface

    b.  Controller

    c.  TimeVacation

    d.  TimeWorking

  3. Load the Web page timetracker/testpage.htm to load the components and controller.

For More Information

For the latest information on Microsoft Visual C++ development system, go to http://msdn.microsoft.com/visualc/default.asp.

Show:
© 2014 Microsoft