More on our Kiosk Application. Dynamically loading a form based on your config file.
As in a previous post, I discussed a Kiosk framework that myself and Derek Walters ( another dotnet geek ( and a very smart one at that ) ) licensed to Cox Communications for the rollout of their kiosk bill paying solution in several cities around the country. Since the Kiosk application is a touch screen application, its flow between screens is very structured; however, during the development of a working application we needed an approach that enabled us to dynamically change how the screens flowed.
This was absolutely necessary for us. Without a plug-in style architecture, we would have created a static ( and very fragile ) application where any change would have forced us to go through another round of regression testing. This in turn would push out the delivery date and negate the need for the framework in the firstplace.
In this blog, I want to show a nice approach to wrapping a class around a section of a config file. The use of this class then enabled us to dynamically add screens and/or change the flow of the kiosk application without recompiling.
First, ( a portion of ) the config file.
<?xml version="1.0" encoding="Windows-1252" ?>
<configuration>
<configSections>
<sectionGroup name="plugins">
<sectionGroup name="forms">
<sectionGroup name="scanbill">
<section name="plugin" type="System.Configuration.SingleTagSectionHandler" allowLocation="false" />
<section name="navigation" type="System.Configuration.SingleTagSectionHandler" allowLocation="false" />
<section name="form" type="System.Configuration.SingleTagSectionHandler" allowLocation="false" />
</sectionGroup>
<sectionGroup name="language">
<section name="plugin" type="System.Configuration.SingleTagSectionHandler" allowLocation="false" />
<section name="navigation" type="System.Configuration.SingleTagSectionHandler" allowLocation="false" />
<section name="form" type="System.Configuration.SingleTagSectionHandler" allowLocation="false" />
</sectionGroup>
</sectionGroup>
</sectionGroup>
</configSections>
<plugins>
<forms>
<scanbill>
<plugin assembly="CoxPlugins" class="Cox.Kiosk.Forms.CoxScanBillForm" />
<navigation back="language" next="accountinquirywaitform" cancel="start" help="" error="" timeout="start" timeoutvalue="30" touchtocontinueenabled="true"/>
<form nextbutton="false" backbutton="true" cancelbutton="true" helpbutton="false" />
</scanbill>
<language>
<plugin assembly="CoxPlugins" class="Cox.Kiosk.Forms.CoxSelectLanguageForm" />
<navigation back="start" next="scanbill" cancel="start" help="" error="" timeout="start" timeoutvalue="30" touchtocontinueenabled="true"/>
<form nextbutton="false" backbutton="true" cancelbutton="true" helpbutton="false" />
</scanbill>
</forms>
</plugins>
</configuration>
What we are doing here is setting up the app.config file so that we can use the .net classes to interact with it. The first section, configuration/configSections/sectionGroup[name=plugins]/sectionGroup[name=forms]/sectionGroup[name=scanbill], is setting up the configuration section for the plugins section below. I will not go into detail on it because you can find out more about setting up a config file via MSDN.
The items I want you to take note of are the attributes of the navigation sections. The back, next, cancel, help, error and timeout attribute values denote another plug in to move to when the user selects one of the buttons on the touch-screen window. For example configuration/plugins/scanbill/navigation[back=language] points to the language node under plugins. In other words, when the user is using the kiosk and has navigated to the 'Scan Your Bill Form' and decides to select the Back button the framework will look in the config file and navigate to the language form by interrogating the config file.
Now for the code. The following is a constructor for a class that loads information for a form's config section. Once loaded other classes interact with the information. The code below was actually written by Derek, but it is a pattern we both followed throughout the framework.
namespace Cybral.Kiosk.Configuration{
................................................ ................................................
................................................ ................................................
................................................ ................................................
................................................ ................................................
public NavConfigReader( string strName )
{
string strConfigPath = string.Format( "plugins/{0}/navigation", strName );
string strCurrentNavName=null;
strCurrentNavName = strName;
if( null != strCurrentNavName )
{
strCurrentNavName = strCurrentNavName.Substring(
strCurrentNavName.LastIndexOf( "/" ) + 1 );
}
IDictionary idct = (IDictionary)
ConfigurationSettings.GetConfig( strConfigPath );
m_strCurrentNav = strCurrentNavName;
m_strNextNav = (string) idct["next"];
m_strBackNav = (string) idct["back"];
m_strCancelNav = (string) idct["cancel"];
m_strHelpNav = (string) idct["help"];
m_strErrorNav = (string) idct["error"];
m_strTimeoutNav = (string) idct["timeout"];
m_strActionPlugin = (string) idct["actionplugin"];
try
{
m_intTimeoutValue =
Int32.Parse( (string) idct["timeoutvalue"] );
// The values in the configuration file are in seconds...
// timers typically measure in milliseconds.
m_intTimeoutValue *= 1000;
}
catch
{
m_intTimeoutValue = 0;
}
try
{
string strTrue = Boolean.TrueString.ToLower();
m_blnTouchToContinueEnabled =
( strTrue.CompareTo( idct["touchtocontinueenabled"] ) == 0 );
}
catch
{
m_blnTouchToContinueEnabled = true;
}
}
}
Well, I am getting tired of writing. Essentially, what we do next is load the control specified in the config file, destroy what is currently there and display the control to the user. There is actually quite a bit involved in this, but the premise is simple enough.