Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An N-tier Application using MSHTML in the front end

0.00/5 (No votes)
13 Jan 2004 1  
A description of an n-tier application

Introduction

This article briefly describes an n-tier application that I have written using VB.NET. The application has a somewhat obscure purpose which is to provide electronic signatures in a SCADA system called RSView32.

However of interest to readers might be how I configure each tier of the application and also the way in which the presentation tier hosts MSHTML. There has been a lot written about hosting MSHTML but most of it seems to rely on windows.external calls to provide the interface between elements in the MSHTML pane and the application. My application takes a different approach which is to use a one to one relationship whereby my .NET presentation objects host individual DHTML objects within the MSHTML object model.

I must apologize for the cursory nature of this article but I do hope to extend it considerably if there is much interest in what I have done. I haven't included screendumps but they can be viewed in the software description on my website.

Please bear in mind that these screendumps are not web pages. Different elements in the MSHTML pane are in fact connected to different business objects in different processes with remoting links. When a business object fires an event individual portions of the MSHTML pane get refreshed rather than the whole pane.

Background

The ideas for the presentation layer developed out of a reporting system which I prototyped using Internet Explorer whereby when html elements were expanded they fired off web service calls, transformed the results with xslt and then inserted them into the HTML document. I found it hard to handle the complexity with JavaScript and started to think about how I might embed .NET code into the Internet Explorer web page to make it 'live'. Once I discovered I could access the entire object model of a MSHTML page programmatically I realized that the solution was to embed the DHTML objects from this model into a .NET application.

Description

dkTagSet is a suite of programs that provide embedded Electronic Signatures and displayed documentation in an RSView32 SCADA System.

dkTagSet is used to enable the user to set tags from a SCADA system. When a user elects to set or edit tags it brings up a screen which displays documentation to the user and requires the user to enter a number of Electronic Signatures. Once these signatures have been added the required tags are set.

dkTagSet displays detailed information to the user about the tags being set, including PLC address and tag descriptions.

Once configured in an RSView32 project the signatures can be reconfigured by the user from the runtime environment without having to re edit screens. This re configuration consists of configuring both the documentation displayed by the user and also the confirmations required for the action. All re configuration is protected by electronic signatures and all re configuration actions can have documentation associated with them.

dkTagSet maintains a complete audit trail of all actions performed with it. This audit trail includes both tag setting actions and also system reconfiguration. The audit trail is viewable from the front end.

Architecture

dkTagSet has an n-tier architecture. An n-tier application is divided into a number of separate tiers each of which carries out a different class of function. The tiers that dkTagSet is separated into are as follows:

Data Tier

The Data Tier consists of an SQL Server database. All actions performed by the system are logged into a set of tables in this database. It has simple stored procedures called by the data access tier to insert and update data. The architecture allows for different databases to be used.

Data Access Tier

The Data Access Tier is a set of classes written with the .NET framework.

It uses the proprietary driver to communicate with the SQL Server DataBase although in fact this driver is configured so other databases would be possible.

The Data Tier is configured by an XML configuration file which defines a number of different data access objects. When a data access object is requested from the data tier with a particular name it uses this XML file to look up and configure the required object. The XML configuration file configures the following:

  • The .NET type and assembly of the object
  • An XML Schema that defines the structure of a dataset that is used to communicate to and from the object
  • The driver to be used to connect to the database
  • The rules used to read data from and write data to the database

A standard interface is used to talk to all data access objects. This interface uses DataSets.

When data is required from the data access object a dataset defines the data that is required. The Data Access Object uses its configured rules to generate queries to obtain data from the database and then returns the data in a dataset.

When the data object is required to write data to the database the data to be written is passed to it in a dataset. The data object uses the rules configured for it to write this data back to the database.

Data Tier configuration example

This XML Element configures a data access object (in this case the data for a set of confirmations):

    <confirmations>
     <dalObject 
type='dkcs.dal.dalConfirmations.dkConfirmationsDataObject' 
   assemblyFile='dal.dll'>
      <schema path='schemas/schemaConfirmations.xsd' />
      <getDataSet>
       <table name="confirmations">
        <table name="confirmation" 
queryString="select * from confirmation where (1=0) " 
   order='  order by ord asc'>
         <linkFields parentTableName="confirmations">
          <linkField name="parentGuid" 
  parentFieldName="guid" type='string' />
         </linkFields>
        </table>
       </table>
       <dataSet queryString=
   "select * from confirmations where (1 = 9) ">
        <or>
         <table tableName='confirmations' operator='or'>
          <selector leftField='parentGuid' conJoin='=' 
    tableField='parentGuid' type='string' />
          <selector operator='or' leftField='guid' conJoin='=' 
   tableField='guid' type='string' />
         </table>
        </or>
       </dataSet>
      </getDataSet>
      <storeDataset>
       <update tableName='confirmations'>
        <insertCommand commandText='insertConfirmations'>
         <parameter dbType='16' 
  parameterName="@parentGuid" sourceColumn='parentGuid' />
         <parameter dbType='16' 
  parameterName="@guid" sourceColumn='guid' />
         <parameter dbType='16' 
  parameterName="@confirmSet" sourceColumn='confirmSet' />
         <parameter dbType='16' 
  parameterName="@zone" sourceColumn='zone' />
         <parameter dbType='16' 
  parameterName="@dalObject" sourceColumn='dalObject' />
         <parameter dbType='6' 
  parameterName="@dateCreated" sourceColumn='dateCreated' />
        </insertCommand>
        <updateCommand commandText='updateConfirmations'>
         <parameter dbType='16' 
  parameterName="@parentGuid" sourceColumn='parentGuid' />
         <parameter dbType='16' 
  parameterName="@guid" sourceColumn='guid' />
         <parameter dbType='16' 
  parameterName="@confirmSet" sourceColumn='confirmSet' />
         <parameter dbType='16' 
  parameterName="@zone" sourceColumn='zone' />
         <parameter dbType='16' 
  parameterName="@dalObject" sourceColumn='dalObject' />
         <parameter dbType='16' 
  parameterName="@confirmResult" 
    sourceColumn='confirmResult' />
         <parameter dbType='6' 
  parameterName="@dateConfirmed" 
    sourceColumn='dateConfirmed' />
        </updateCommand>
       </update>
       <update tableName='confirmation'>
        <insertCommand commandText='insertConfirmation'>
         <parameter dbType='16' 
  parameterName="@parentGuid" sourceColumn='parentGuid' />
         <parameter dbType='16' 
  parameterName="@guid" sourceColumn='guid' />
         <parameter dbType='10' 
  parameterName="@ord" sourceColumn='ord' />
         <parameter dbType='16' 
  parameterName="@confirmType" sourceColumn='confirmType' />
         <parameter dbType='16' 
  parameterName="@attemptGuid" sourceColumn='attemptGuid' />
        </insertCommand>
        <updateCommand commandText='updateConfirmation'>
         <parameter dbType='16' 
  parameterName="@guid" sourceColumn='guid' />
         <parameter dbType='16' 
  parameterName="@attemptGuid" sourceColumn='attemptGuid' />
         <parameter dbType='16' 
  parameterName="@failGuid" sourceColumn='failGuid' />
        </updateCommand>
       </update>
      </storeDataset>
      <configuration>
       <confirmSets 
  xmlns="http://www.dkcs.ws/dk400/namespaces/confirmations">
        <confirmations confirmSet="defaultConfirm">
         <confirmation ord='1' confirmType="operatorConfirm" />
        </confirmations>
        <confirmations confirmSet="operatorConfirm">
         <confirmation ord='1' confirmType="operatorConfirm" />
        </confirmations>
        <confirmations confirmSet="controllerConfirm">
         <confirmation ord='1' confirmType="controllerConfirm" />
        </confirmations>
        <confirmations confirmSet="no id required">
         <confirmation ord='1' confirmType="no id required" />
        </confirmations>
        <confirmations confirmSet="doubleOperatorConfirm">
         <confirmation ord='1' confirmType="operatorConfirm" />
         <confirmation ord='2' confirmType="operatorConfirm" />
        </confirmations>
        <confirmations confirmSet="administratorConfirm">
         <confirmation ord='1' confirmType="administratorConfirm" />
        </confirmations>
       </confirmSets>
      </configuration>
     </dalObject>
    </confirmations>

data Object Interface

All data objects conform to the following interface. The business layer has no idea and no way of telling what data object it is actuially talking to.


Public Interface dkIDataObject

Inherits general.general.dkIObject

'this interface is shared by all data access objects


'it defines methods to retrieve and store data


' get a dataset sppoecified by the querydataset


Overloads Function getDataSet(ByVal queryDataset _
  As DataSet) As DataSet

' get a dataset as specified by the query string


'Overloads Function getDataset(ByVal queryString _

  As String) As DataSet

'store the dataset in the datasource


Sub storeDataSet(ByVal storeDataset As DataSet)

'return a new blank dataset


Function blankDataSet() As DataSet

'initialise an initial dataset


Function initDataSet() As DataSet

'update a dataset by adding the tables and relations 

'for thew data access object


Function updateDataset(ByVal ds As DataSet) As DataSet



Overloads Function create(ByVal createDataSet As _
  DataSet) As DataSet

End Interface

Data Factory

This is the factory for data objects it creates one, configures it and then calls a function on it for a client object:


Imports System.EnterpriseServices

Imports System.Xml.Serialization

Imports dkcs.dal.dalConfigurable

Imports dkcs.dal.dalGeneral

Imports dkcs.general.general

Namespace dal.dalFactory

<Serializable()> _

Public Class dkDataFactoryConfigurable

'Inherits dkcs.dal.base.dkDataObject


Inherits servicedConfigurator

Implements dkIDataObjectFactory

'this class is a factory class that makes data 

'access layer objects



Public Sub New()

MyBase.new()


End Sub



Private Function getDataObject(ByVal zone As String, _
  ByVal name As String) As dkIDataObject

'return a data object configured with the configuration




Dim c As New dalbase.dkDataObjectConfigurable()

c.configuration = dkZoneConfiguration.zoneConfiguration( _
  zone).SelectSingleNode("dalObjects").SelectSingleNode(name)

Return c

End Function

Business Tier

The Business Tier is a set of classes written with the .NET Framework. All classes have a common interface that allows communication with the objects using .NET DataSets. The classes are also capable of holding other business objects and returning these business objects on request.

The Business Tier is configured by an XML configuration file which defines a number of different business objects. When a business object is requested from the business tier with a particular name it uses this XML file to look up and configure the required object. The XML configuration file configures the following for each business object:

  • The .NET type and assembly of the object
  • The names and network locations of various 'sub objects' these being other business objects and also the data access object
  • Other configuration information specific to the object

Business Tier Configuration Example

This XML Element configures a Business Tier object (in this case for a set of confirmations) the url of the dal object could make it on a different machine:

  <confirmations>  
     <businessObject 
  type='dkcs.business.confirmations.confirmationsBusinessObject' 
  assemblyFile='business.dll' zone="xBrewery">
     
      <objectUrls>
      
       <objectUrl purpose='dal'  
  url='tcp://localhost:123/dalFactory.soap' 
  name='confirmations' />      
       <objectUrl purpose='businessSecurityProviderFactory' 
      url='' name='securityProvider' />
     </objectUrls>  
     </businessObject>
    </confirmations>

Business Tier Interface

This interface is implemented by all business objects objects using a business tier object don't actually know the type of the object that they are using:

Public Interface dkIBusinessObject

Inherits dkcs.general.general.dkIObject

Inherits dkcs.general.general.dkIConfigurable

Inherits IDisposable

Property initDataset() As DataSet 'sets the initial 

'dataset for the object


Property initDataSetString() As String

Function getDataSet() As DataSet 'gets the dataset 

'contained by the object


Function getDataSet(ByVal parentDataSet As DataSet, _
  ByVal tableName As String) As DataSet

Function setDataSet(ByVal ds As DataSet) _
  As DataSet 'used to command the object


Function setDataSet(ByVal ds As DataSet, _
   ByVal objectName As String) As DataSet

Function blankdataset() As DataSet 'returns a blank 

'dataset with required structure


Sub storedataset() 'requests the object to store its dataset


Event changeDataset(ByVal ds As DataSet) 'event raised 

'when dataset changes (would not work for remote objects)


Function getBusinessObject(ByVal name As String, _
  ByVal parentDataSet As DataSet) As dkIBusinessObject 
  'gets a contained business object


Function setBusinessObject(ByVal name As String, _
  ByVal businessObject As dkIBusinessObject) As dkIBusinessObject 
  'sets a business object - used to pass other business 

  'objects into thos one


Property businessFactory() As dkIBusinessFactory 
  'the business factory that created me


ReadOnly Property key()

Function command(ByVal ParamArray commandstring() _
  As String) As String 'command


ReadOnly Property businessObject() As dkIBusinessObject 
  'returns me or my nbusiness object if I am a factory


End Interface

Business Factory

This is the namespace that deals with creating business objects. The businessfactory masquerades as the business object that it has created:

' ## This namespace deals with creating business objects


Public Class myBusinessFactory

' Creates a business object as of the indicated name 

' as defined in the configuration


Shared Function getBusinessObject(ByVal configuration _
  As Xml.XmlElement, ByVal objectName As String, _
  ByVal parentDataSet As DataSet) As dkIBusinessObject

Dim o As Object = getBusinessFactory(configuration, _
  objectName).getBusinessObject(configuration.SelectSingleNode( _
  String.Format("objectUrls/objectUrl[@purpose='{0}']/@name", _
  objectName)).Value, parentDataSet)

Return o

End Function

Overloads Shared Function getBusinessFactory(ByVal configuration _
  As Xml.XmlElement, ByVal objectName As String) As _
  general.dkIBusinessFactory



Dim n As Xml.XmlNode = configuration.SelectSingleNode( _
  String.Format("objectUrls/objectUrl[@purpose='{0}']/@url", _
  objectName))

If Not n Is Nothing Then

Dim b As dkIBusinessFactory = getBusinessFactory( _
  configuration.Attributes("zone").Value, n.Value)



b.name = configuration.SelectSingleNode(String.Format( _
  "objectUrls/objectUrl[@purpose='{0}']/@name", _
  objectName)).Value

Return b

End If


End Function

Overloads Shared Function getBusinessFactory( _
  ByVal zone As String, ByVal url As String) _
  As general.dkIBusinessFactory

'gets a business factory




Dim fact1 As dkcs.business.general.dkIBusinessFactory

If url = "" Then

fact1 = New dkBusinessFactoryConfigurable()

Else



Dim args() As Object

' Uses the UrlAttribute to create a remote object.


Dim activationAttributes() = {New UrlAttribute(url)}

' Activates an object for this client.


Dim hdlSample As ObjectHandle

'hdlSample = Activator.CreateInstance("businessFactory", _

  "dkcs.business.factory.dkBusinessFactoryConfigurable", _
  True, BindingFlags.Instance Or BindingFlags.Public, Nothing, _
  args, Nothing, activationAttributes, Nothing)

hdlSample = Activator.CreateInstance("businessFactory", _
  "dkcs.business.factory.dkBusinessFactoryConfigurable", _
  True, BindingFlags.Instance Or BindingFlags.Public, Nothing, _
   args, Nothing, activationAttributes, Nothing)

fact1 = CType(hdlSample.Unwrap(), dkBusinessFactoryConfigurable)

fact1.zone = zone

End If

fact1.zone = zone


Return fact1

End Function

End Class

Public Class dkBusinessFactoryHost

Inherits dkBusinessFactoryConfigurable

Dim WithEvents hostedObject As dkIBusinessObject

Public Sub New(ByVal myobject As dkIBusinessObject)

Me.myobject = myobject

End Sub

Public Sub doit(ByVal ds As DataSet)

domessagearrival(ds)

End Sub

Private Sub hostedObject_changeDataset(ByVal ds As _
  System.Data.DataSet) Handles hostedObject.changeDataset

domessagearrival(ds)

End Sub

End Class

'this class builds and allows interface to a business object


<Serializable()> _

Public Class dkBusinessFactoryConfigurable

Inherits dkcs.general.general.nonServicedconfigurator

Implements dkIBusinessFactory

Dim mvarname As String

Dim mvarZone As String

'this is the object I have built


Protected WithEvents myobject As _
  dkcs.business.general.dkIBusinessObject

Public Sub New()

MyBase.new()

End Sub

Public Property zone() As String _
  Implements dkIBusinessFactory.zone

Get

Return mvarZone

End Get

Set(ByVal Value As String)



mvarZone = Value

'the configuration of business objects for this zone


Me.configuration = dkZoneConfiguration.zoneConfiguration( _
  Value).SelectSingleNode("businessObjects")



End Set

End Property

'when I am given a name I create a business object as 

'defined in configuration and listen to it


Public Property name() As String Implements _
  dkIBusinessFactory.name

Get

Return (mvarname)

End Get

Set(ByVal Value As String)

mvarname = Value

If Value <> "" Then

myobject = getBusinessObject(name, New DataSet())

myobject.businessFactory = Me

Me.listenToBroadcaster(myobject)

End If

End Set

End Property



'return my business object


Public ReadOnly Property businessobject() As _
  dkcs.business.general.dkIBusinessObject Implements _
  dkIBusinessFactory.businessObject

Get

Return myobject

End Get

End Property



'pass call to contained business object


Public Function blankdataset() As System.Data.DataSet _
  Implements dkcs.business.general.dkIBusinessFactory.blankdataset

Return myobject.blankdataset

End Function



'pass call to contained business object


Public Overloads Function getDataSet() As System.Data.DataSet _
  Implements dkcs.business.general.dkIBusinessFactory.getDataSet

'


Return myobject.getDataSet

End Function



'pass call to contained business object


Public Overloads Function setDataSet(ByVal ds As _
  System.Data.DataSet) As System.Data.DataSet Implements _
  dkcs.business.general.dkIBusinessFactory.setDataSet

Return myobject.setDataSet(ds)

End Function

Public ReadOnly Property key() As Object Implements _
  dkcs.business.general.dkIBusinessFactory.key

Get

Return name

End Get

End Property

'pass call to contained business object


Public Property initDataset() As System.Data.DataSet _
  Implements dkcs.business.general.dkIBusinessFactory.initDataset

Get

'


Return myobject.initDataset

End Get

Set(ByVal Value As System.Data.DataSet)

myobject.initDataset = Value

End Set

End Property

Public Event changeDataset(ByVal ds As System.Data.DataSet) _
  Implements dkcs.business.general.dkIBusinessFactory.changeDataset



'pass call to contained business object


Public Property businessFactory() As _
  dkcs.business.general.dkIBusinessFactory Implements _
  dkcs.business.general.dkIBusinessFactory.businessFactory

Get

Return Me

End Get

Set(ByVal Value As dkcs.business.general.dkIBusinessFactory)

Throw New Exception( _
  "cannot set a business factory's businessfactory")

End Set

End Property

'pass call to contained business object


Public Overloads Function getDataSet(ByVal parentDataSet As _
  System.Data.DataSet, ByVal tableName As String) As _
  System.Data.DataSet Implements _
  dkcs.business.general.dkIBusinessFactory.getDataSet

Dim ds As DataSet = myobject.getDataSet(parentDataSet, tableName)



Return ds

End Function

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

If disposing Then myobject.Dispose()

MyBase.Dispose(disposing)

End Sub

'pass call to contained business object


Public Property initDataSetString() As String Implements _
  dkcs.business.general.dkIBusinessFactory.initDataSetString

Get

Return myobject.initDataSetString

End Get

Set(ByVal Value As String)

myobject.initDataSetString = Value

End Set

End Property

'pass call to contained business object


Public Sub storedataset() Implements _
  dkcs.business.general.dkIBusinessFactory.storedataset

myobject.storedataset()

End Sub



 

'returns a business object specified by name - 

'nb does nothing with parent dataset


Public Function getBusinessObject(ByVal name As String, _
  ByVal parentDataSet As System.Data.DataSet) As _
  dkcs.business.general.dkIBusinessObject Implements _
  dkcs.business.general.dkIBusinessFactory.getBusinessObject

If Not configuration.SelectSingleNode(name) Is Nothing Then

'merge n with any external files


Dim n As Xml.XmlElement = _
  dkcs.general.general.xmlMerge.externalCheck( _
  configuration.SelectSingleNode(name))

'create and configure object


Dim bo As dkcs.business.general.dkIBusinessObject = _
  dkcs.general.general.objectFactory.getConfiguredObject( _
  configuration.SelectSingleNode(name).SelectSingleNode( _
  "businessObject"))



Return bo

End If

End Function

'pass call to contained business object


Public Overloads Function setDataSet(ByVal ds As _
  System.Data.DataSet, ByVal objectName As String) As _ 
  System.Data.DataSet Implements _
  dkcs.business.general.dkIBusinessFactory.setDataSet

Return myobject.setDataSet(ds, objectName)

End Function

'pass call to contained business object


Public Overloads Function command(ByVal ParamArray commandstring() _
  As String) As String Implements _
  dkcs.business.general.dkIBusinessFactory.command

Return Me.businessobject.command(commandstring)

End Function

'pass call to contained business object


Public Function setBusinessObject(ByVal name As String, _
  ByVal businessObject As dkcs.business.general.dkIBusinessObject) _
  As dkIBusinessObject Implements _
  dkcs.business.general.dkIBusinessFactory.setBusinessObject

Return Me.businessobject.setBusinessObject(name, businessObject)

End Function



End Class

Presentation Tier

The Presentation Tier is a set of classes written with the .NET Framework. The objects within the presentation tier form a complex hierarchy depending on the presentation tier's configuration and the structure of the business tier. Presentation objects are capable of holding references to business objects in the business tier and of requesting 'sub objects' from those business tier objects and generating nested objects that hold those business tier objects.

When events are raised by the business tier objects they are received by the presentation tier objects which then update their content in response to the new data received.

The presentation tier objects are capable of requesting functions from the business tier objects by sending them datasets.

Presentation Tier objects are capable of having a number of 'behaviours'. A behaviour controls how the presentation object actually renders itself to the user. This makes the presentation object itself independent from the presentation technology.

Currently the 'behaviour' used holds a reference to a DHTML object within the object model of MSHTML. MSHTML is the COM control that Internet Explorer uses to render HTML content to the user. Each DHTML control is an element within the HTML rendered by MSHTML. The presentation control directly manipulates the properties of its hosted DHTML control and responds to the events that it raises. This makes for a 'live' page with different parts responding to different business objects at different network and process locations. When the presentation control itself contains other presentation controls the DHTML controls that these contained presentation controls host are themselves contained in the parent presentation object's hosted DHTML control. This architecture differs from a web page which, generally connects to a single server, requires the entire page to be refreshed when it is updated and does not respond to events raised by the server.

The presentation tier is configured in a set of XML files. Each presentation object is configured in these files. The files configure the name and location of the root object for the display and then a hierarchy of controls. Providing it is connecting to a business tier that implements the standard business tier interfaces the same presentation can be configured to connect to entirely different business tiers with different functions.

The XML file for the presentation tier configures the type of a number of different presentation tier controls and also the type of its behaviours. Then within the hierarchy of presentation tier controls it configures for each presentation control:

  • How to obtain its business object (if it obtains a new one)
  • An XPath to the data from the business object to display
  • Attributes for its hosted object (for DHTML objects this would be HTML attributes (class etc)
  • Presentation controls that are contained within the control
  • Presentation controls that are generated when the control is expanded.
  • Menu options available by right clicking on an element

Elements within the presentation configuration can refer to external files whose content is merged with theirs.

Presentation Tier configuration example

This XML (and the external files it references) configures how confirmations are displayed:

<listedControl function='expander'>

 <menuControls />
 
 <containedControls> 
 
  <dkXmlAttributeControl function='label' 
dkKey='authorisationLabel' attributeName="@confirmSet">
   
   <configurator>
    
    <oneOffs>
     
     <htmlAttributes>
      <class>authorisation</class>
   
      <style>width:20%</style>    
    
     </htmlAttributes>   
   
    </oneOffs>  
  
   </configurator> 
 
  </dkXmlAttributeControl>
 
 </containedControls>

 <subControls>
  <listingControl function='list' 
  xpath="confirmations:confirmation" 
  keyAttribute="guid" childIndex='99'>
   <listedControl function='expander'>    
   
    <configurator>
     
    
     <oneOffs>
      
     
      <expanded>true</expanded>
    
     </oneOffs>   
   
    </configurator>    
   
    <containedControls>     
    
     <dkXmlAttributeControl function='label' 
  attributeName="@confirmType" dkKey='confirmType'>
      
     
      <configurator>
       
      
       <oneOffs>
        
       
        <htmlAttributes>
         
        
         <width>10%</width>
        
         <class>confirmation</class>
       
        </htmlAttributes>
       
      
       </oneOffs>
      
     
      </configurator>
     
    
     </dkXmlAttributeControl>
    
   
    </containedControls>
    
   
    <subControls>
     
    
     <listingControl function='list' 
  xpath="confirmations:confirmationAttempt[@confirmationResult]/.."
      keyAttribute="" childIndex='1' dkKey='failedAttempts'>
      
     
      <listedControl function='expander'>
       
      
       <configurator>
        
       
        <oneOffs></oneOffs>
       
      
       </configurator>
       
      
       <containedControls>
        
       
        <dkXmlAttributeControl function='label' dkKey='error'>         
        
         <configurator>
          
         
          <oneOffs>
           
          
           <prefix>failed Attempts</prefix>          
           <htmlAttributes>
            
           
            <class>error</class>
          
           </htmlAttributes>
          
         
          </oneOffs>
         
        
         </configurator>
        
       
        </dkXmlAttributeControl>
       
      
       </containedControls>
       
      
       <subControls>
        
       
        <listingControl function='list' 
  xpath="confirmations:confirmationAttempt[@confirmationResult]" 
  keyAttribute="guid">
         
        
         <listedControl function='expander' 
  externalFilePath='confirmationAttempt.xml' />
        
       
        </listingControl>
       
      
       </subControls>
      
     
      </listedControl>
     
    
     </listingControl>
     
    
     <listingControl function='list' 
  xpath="confirmations:confirmationAttempt[not (@confirmationResult)]"
      keyAttribute="guid" 
  externalFilePath='confirmationAttempt.xml' dkKey='confirmationAttempt'>
      
     
      <listedControl function='expander' 
externalFilePath='confirmationAttempt.xml' />
     
    
     </listingControl>
    
   
    </subControls>
   
  
   </listedControl>
  
 
  </listingControl>
 </subControls>
</listedControl>

Points of Interest

I struggled a bit with the MSHTML object model and still have not resolved everything. For example I never managed to receive keypress events from individual objects. I'm looking forward to the new webbrowser control and wonder I Microsoft have gone so far as to sort out access to all the DHTML objects in the model.

History

  • No changes so far

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here