Introduction
This article explains the advantages of abstracting the various layers during the time of Rapid Application Development (RAD). Everybody would have got a chance to work on ERP applications. A client who needs ERP products prefers UI of type Windows application. This is mainly because of the fact that Windows applications are quick in response and also have many sophisticated controls. ERP applications expect uniformity in UI across forms as one its key requirements. The uniformity in UI improves the productivity of the end users who use the ERP application. The major problem in an ERP application source is that the code written will become unmanageable after some time and it also becomes hard to extend. In this article, we will discuss how OOPS helps us to achieve some basic UI layering, which helps us to develop rapid ERP applications which are uniform in their look.
Layering of Windows Controls
First and foremost step in the product development life cycle is abstracting the Windows controls which we are going to program around. As a thumb rule, we should always create a wrapper class (class inherits from the base class) of all the controls which we re going to use in the project and publish the assembly as a UI control library to all the developers in the project. This is applicable to only those controls which are inheritable. Most of the controls available are inheritable except a few like FileOpenDialog
etc. are not inheritable. This helps us to have one centralised place of control over thousands of forms using n number of controls. Initially, we might not understand the advantages of doing this. But this layering helps only in the long run. All the WinForm controls are well defined and the signatures are well published and coded in OOPS. So they are by default extensible through inheritance and overriding.
As a Proof Of Concept (POC), I would like to explain one of the key features of extending a control. In all our forms which we develop, we need to perform some basic non-business validations like email validations, mathematical calculation validations etc. Normally these types of validations will be done in the textbox validating event. In this POC, I would like to highlight the powerfulness of Regular Expressions and the layering of Windows controls. Let us re-define our problem statement. Regular Expressions are a very powerful library which helps us to perform pattern matching validations. In simple terms, we can define Regular Expressions as a formula which evaluates for a value which returns true or false. True means that the value evaluated adheres to the formula pattern. False means the value evaluated fails in the pattern match. TextBox
is one of the most widely used controls which takes user inputs. Now we will extend the text box to support regular expression validations.
Imports System.Text
Public Class TextBoxBase
Inherits System.Windows.Forms.TextBox
Private _RegularExpression As String = ""
Private _ErrorMessage As String = ""
Private ErrControl As New ErrorProvider
Public Property RegExToValidate() As String
Get
Return _RegularExpression
End Get
Set(ByVal Value As String)
_RegularExpression = Value
End Set
End Property
Public Property ErrorMessage() As String
Get
Return _ErrorMessage
End Get
Set(ByVal Value As String)
_ErrorMessage = Value
End Set
End Property
Private Sub TextBoxBase_Validating(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs) _
Handles MyBase.Validating
Try
If _RegularExpression.Length > 0 Then
If RegularExpressions.Regex.IsMatch(Me.Text, _
_RegularExpression) = False Then
ErrControl.SetError(Me, _ErrorMessage) e.Cancel = True
Else
ErrControl.SetError(Me, Nothing)
End If
End If
Catch ex As Exception
End Try
End Sub
End Class
In the above example, the extension is very simple to make the article easy to understand. Ideally speaking, we will give much more configurations like, validating the textbox data based on multiple regular expressions, and also we should be able to configure the validations as AND/OR conditions. This will help us to have a very strong validation layer with various options. I don't want to cover the entire configuration, because in this, we are not trying to explain the complexity of the functionality, instead we are trying to explain the advantages of controls abstraction.
Abstracting UI Layer
Abstracting the Windows controls is the starting point in UI abstraction. Abstracting the controls helps us to extend the control's functionality in one place and it will be available to all the controls inheriting in the same hierarchy tree. Abstracting the UI layer is nothing but a flexible design on which the entire ERP form development happens. The main point which should be there in our mind during the design is that, the forms should be pluggable and also have a uniform look. There are various designs available to achieve this. Before getting to the design part, I would like to give some basic thumb rule which we need to keep in our mind.
- Never have a static menu system which is hard wired in code. Have a dynamic menu system based on some data store. Data store might be an XML file or a database. This helps us add/remove menus without touching the code.
- Never allow the forms to inherit from the base .NET Windows class (
System.Windows.Forms.Form
). Instead always have one level of inherited wrapper class.
- Never commit a signature on a class implementation, instead commit on an interface.
- Let form instantiation happen in a single place, and always float a static context object across the forms. This gives us a way to have some communication between the loaded forms. This is applicable in MDI architecture where the loaded child forms can communicate. The context object can hold all the necessary data which needs to be shared by all the forms in the system. The context object acts as a holder of meta data of the system.
In the design which we made for this article, we have used XML file as the data store to store the menu information. Each and every menu item will carry AssemblyFilePath
and TypeName
to locate the form. All the forms which we are going to develop will not be of type Form
, instead it will be of type UserControl
. In runtime, the UI layer will render the output of the control in a form. Whenever the form gets loaded, an ApplicationContext
class implementation of IApplicationContext
is getting passed to all the forms which are getting loaded. Source code of the UI layer is available for download in the article. So I am not planning to explain the internal classes which are used in the UI layer, instead I am going to explain steps to be followed to develop a form using this architecture.
Form Development Steps
- Install the UI layer package to a specified location.
- Create a WinForm project and make the project as class library. Name the project as MyERPApplication.
- Point the project start application to the path of MainUI.exe. MainUI.exe will be available in the installation path of the package.
- Add reference to WinformUIAbstraction.dll.
WinformUIAbstraction
will be available in the installation folder.
- Create a new WinForm and name the form as
Form1
.
- Go to the code view of the form
Form1
and the change the inheritance from System.Windows.Forms.Form
to WinformUIAbstraction.FormBase
.
- Now if you switch to design mode, you can see the container as a user control.
- Now you can place as many controls as you want depending upon your requirement.
- Change the Project Output to the Package installation directory.
Registering Menus
- Go to the installation folder and open Menus.XML.
="1.0"="utf-8"
<configuration>
<Menu>
<SubMenu Name="Main Menu1">
<Component ID ="9E8E613F-37C7-4bc9-9A84-4D9298A44D87"
Name="Menu1-Form1" AssemblyPath="MainUI.exe"
TypeName="MainUI.MenuConfigEditor"></Component>
<Component ID ="9E8E613F-37C7-4bc9-9A84-4D9298A44D86"
Name="Menu1-Form2" AssemblyPath="MainUI.exe"
TypeName="MainUI.Test"></Component>
</SubMenu>
<SubMenu Name="Main Menu2">
<Component ID ="" Name="Form1" AssemblyPath=""
TypeName=""></Component>
</SubMenu>
</Menu>
</configuration>
- We can add a new menu by adding a
SubMenu
node and we can add the actual menu using the Component
node.
="1.0"="utf-8"
<SubMenu Name="<Name of the main menu>">
<Component ID =""<GUID>" Name=""<Sub Meny Name >"
AssemblyPath=""<Path of the assembly>"
TypeName=""<Fully Qualified TypeName>"></Component>
<Component ID ="9E8E613F-37C7-4bc9-9A84-4D9298A44D86"
Name="My First Form" AssemblyPath="MyERPApplication.dll"
TypeName="MyERPApplication.Form1"></Component>
</SubMenu>
- Now Run the project. The mainUI.exe will get loaded with the configured menus. If we click on the "My First Form" menu, the user control will get loaded in a form. In runtime, we will not feel that the loading container is a user control, it will look like a form only.
Application Context Object
When ever the form gets loaded, the first method which will get called is OnInit
. This method carries an object of type IApplicationContext
. This object carries all the meta data of the system like connection string, instances of the forms which are currently loaded in the system etc. We can extend this to store as many information as it can carry. Following interface code tells us about the structure of IApplicationContext
interface.
Public Interface IApplicationContext
ReadOnly Property ConnectionString() As String
ReadOnly Property SQLConnection() As SqlClient.SqlConnection
Function BeginTransaction() As SqlClient.SqlTransaction
ReadOnly Property FormsCollection() As FormCollection
Property MainUI() As Object
End Interface
Forms Collection
Application Context Object has a readonly property called FormsCollection
. This class contains all the instances of the forms which are loaded in the UI, by making FormID
as the key which is specified in menus.xml. Following is the structure of the FormCollection
class.
Public Class FormCollection
Public Shared Function FindForm(ByVal FormID As String) As Form
Public Function AddForm(ByVal frm As Form, ByVal FormID As String) As Boolean
Public Function RemoveForm(ByVal FormID As String) As Boolean
End Class
Application Context Object adheres to Singleton pattern
Application context object is one of the good examples of implementation of Singleton pattern. This object should be created only once and the same instance should get passed across forms. Following code snippet explains the minimal implementation of the Application Context class:
Public Class WinApplicationContext
Implements IApplicationContext
Shared _MyInstance As IApplicationContext
Private Sub New()
End Sub Public
Shared Function ApplicationContext() As IApplicationContext
If _MyInstance Is Nothing Then
_MyInstance = New WinApplicationContext
End
Return _MyInstance
End Function
End Class
Enhancements
- Currently the menu items which we configure always expects a form. It's not necessary that the menu click should result in opening up a form. It can be any thing. Interfaces can be extended to accommodate this.
- Menus section can be easily moved to DB for scalability.
- Good level of extensions can be done in all the wrapper controls.
- Currently the base class is an empty container. Since we are planning for ERP forms which will always have some standard UI templates like "New", "Edit" etc., those controls can get added in the base class and appropriate hooks can be provided to the developers to handle the events. Enabling and disabling of controls can be handled in the base class so that developers need not write code to enable all the buttons during the "New" button click.
- Design can also be improved.
History
Has only the layering for UI, which is not configurable. Only menus can get re-directed to a new component. In part II of this article, we will discuss about a design in which all the code which we write is pluggable.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.