Using .Net, Paul will demonstrate how to generate an object driven interface. A rich source of support material is included with this article so you can start building interfaces today.
An Object Driven Interface with .Net - Part 3: The Interface Generator (Page 3 of 4 )
Our Interface Generator is going to generate an interface based on our business object, I decided to create an interface based on the Visual Studio .NET Property Grid since this control does something very similar to what we are doing in this exercise, however to make it interesting we are going to create a Server Side ASP.Net Web Control.
An example of what our interface will look like.
One of the first things we need to do especially based on the fact that we are creating a web control is take into account two things, 1. We want to be able to retrieve the values the user has entered into the interface, and 2. The controls we are interested in are probably going to be deeply nested within other controls on our web page making it extremely difficult to find them again. For Example based on our Business Class and the interface you can see above, The Control Containing the value for City will be nested as follows
Base Control |----Table1 |----Table2 |----Div1 | |----Table3 |----Table4 |----Div2 | |----Table5 |----Row1 |----Row2 |----Row3 |----Cell1 |----Cell2 |----Cell3 |----City
Therefore to get at this control first you need to know exactly where it is and second you need to type something like this Control = Base.Controls(5).Controls(1).Rows(3).Cells(3).Controls(1) just to get to it, to make this a little easier we are going to create a structure that we can use to store a pointer to our control, and some information about the property this control is attached to.
Public Structure fieldContainer Public name As String Public field As System.Web.UI.Control Public fieldType As Type Public propertyType As Type End Structure
The above structure lets us store a pointer to our controls, the name of the property this control is attached to, the type of field control it’s using such as System.Web.UI.Webcontrols.Textbox and the type of the property such as System.String.
<ToolboxData("<{0}:InterfaceGenerator runat=server></{0}:InterfaceGenerator>")> _ Public Class InterfaceGenerator Inherits System.Web.UI.WebControls.WebControl ... End Class
the code declaration above creates a class that inherits from System.Web.UI.WebControls.WebControl to create our custom control, and uses the ToolboxData attribute to specify the way the control is rendered in the Designer at design time, the attributes above result in Runtime HTML Designer Code similar to the code below where cc1 is the tagprefix declared in the reference code.
once we have created the class for our interface designer we now need to start creating our interface, firstly our control can do nothing if an instance of a business object is not passed to it, so we start by creating a New Constructor that has a required parameter WizardObject as type Object.
<ToolboxData("<{0}:InterfaceGenerator runat=server></{0}:InterfaceGenerator>")> _ Public Class InterfaceGenerator Inherits System.Web.UI.WebControls.WebControl ... Public Sub New(ByVal WizardObject As Object) ... End Sub ... End Class
We are going to be using a resource file in which we will be storing some style information and also a few lines of javascript which we want to add to our interface when it’s generated, this style info and javascript will help us in creating an interactive and usable user interface.
<ToolboxData("<{0}:InterfaceGenerator runat=server></{0}:InterfaceGenerator>")> _ Public Class InterfaceGenerator Inherits System.Web.UI.WebControls.WebControl ... Public Sub New(ByVal WizardObject As Object) rm = New ResourceManager("Wizard.Control.Prototype.InterfaceGenerator", _ System.Reflection.Assembly.GetExecutingAssembly()) ... End Sub ... End Class
Our resource manager points to the emebeded resource InterfaceGenerator in the Wizard.Control.Prototype Namespace in the currently executing assembly. In the general declarations part of our control we will start by declaring a couple of controls that we can reuse, these controls are going to be used to build our interface, by using them we will dynamically add controls to the interface. The following lines are declared form part of the declaration section
Private section As Panel Private mainTable As Table Private lbl As WebControls.Label Private row As TableRow Private cell As TableCell Private subTable As Table Private subRow As TableRow Private subCell As TableCell Private im As Image Private link As HtmlControls.HtmlAnchor
We will also need somewhere to store the object that gets passed to us.
Private lWizardObject As Object
When the control is initialized and the object passed to us we will use reflection to retrieve type information obout the object and a list of the Public properties in the control.
Public Sub New(ByVal WizardObject As Object) ... lWizardObject = WizardObject myType = WizardObject.GetType myPropertyInfo = myType.GetProperties(BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.DeclaredOnly) End Sub
The binding flags in the GetProperties Statement specify that “BindingFlags.Public” Only public properties should be returned, “BindingFlags.Instance” Only Non Static members should be returned and “BindingFlags.DeclaredOnly” only return properties declared in the object and not any that it may have inherited from a base class, such as the list property our object inherited from our base class.
Once we have done these basic tasks, we can now start to generate our interface.
Public Sub New(ByVal WizardObject As Object) ... 'We start by creating a new table object to contain all our controls 'and to allow us to place controls on the screen mainTable = New Table mainTable.ID = "MainTable" mainTable.Width = Unit.Percentage(100) mainTable.Height = Unit.Percentage(100) mainTable.CellPadding = 0 mainTable.CellSpacing = 0 mainTable.BorderWidth = Unit.Pixel(0) 'Create the first row and cell that will contain all our 'interface objects row = New TableRow cell = New TableCell cell.VerticalAlign = VerticalAlign.Top 'the sub Table will create our first "panel" at the top of the form 'that contains he image subTable = New Table subTable.CellPadding = 0 subTable.CellSpacing = 0 subTable.BorderWidth = Unit.Pixel(0) subTable.Width = Unit.Percentage(100) subTable.ID = "SubTable1" subRow = New TableRow subCell = New TableCell subCell.ColumnSpan = 3 subCell.CssClass = "header" im = New Image im.ImageUrl = "images/categorized.gif" subCell.Controls.Add(im) subRow.Cells.Add(subCell) subTable.Rows.Add(subRow) cell.Controls.Add(subTable) 'because we do not have a predefined list of categories 'we will loop through each property's attributes and create 'a list of all the Sections that we want to create For i As Int16 = 0 To myPropertyInfo.Length - 1 'First we get the property's details from our list of 'properties Dim myPropInfo As PropertyInfo = CType(myPropertyInfo(i), PropertyInfo) 'we the get a list of all the attributes that have been attached to 'the property since there may be more than one and 'we might not be interested in some of them Dim attributes As Object() = myPropInfo.GetCustomAttributes(False) Dim attribute As WizardAttributes For s As Int16 = 0 To attributes.Length - 1 'looping through the attributes, we check to see if any of 'them are of the Type WizardAttribute as this is the one we 'are interested in If TypeOf attributes(s) Is WizardAttributes Then 'if we find it we use directcast to cast it from type Object 'to type WizardAttribute, and stuff it into our Variable called 'Attribute an use attribute to add the sectionname to our list 'of sections. attribute = DirectCast(attributes(s), WizardAttributes) If Sections.Contains(attribute.SectionName) = False Then Sections.Add(attribute.SectionName, fields) End If End If Next Next 'We create our Style and script sections for our control CreateStyle() CreateScripts() 'now that we have a list of sections, 'we can start to create each of them Dim SectionName As String For Each SectionName In Sections.Keys CreateSection(SectionName) Next 'with our sections and fields added to the interface 'we can create our help section that sits at the bottom of the screen 'an finally add the whole lot to our control's control collection CreateHelpSection() mainTable.Rows.Add(row) Controls.Add(mainTable) End Sub
In the code above, we start by creating the table that will contain our controls and allow us to do some nice layout on the screen; we create the topmost panel of our screen that contains the toolbar graphic.
This is currently nothing but a graphic and we might want to do something with it later on, we then loop through all of our properties and reach the attributes that belong to each in order to build a list of the sections that our interface will contain. Each of these sections is collapsible and allows us to group relevant data.
We then call two functions that we use to add the style information and JavaScript that we need to our interface.
Private Sub CreateScripts() Dim ScriptCode As String = rm.GetString("Scripts") Dim Literal As New WebControls.Literal Literal.Text = ScriptCode Controls.Add(Literal) End Sub Private Sub CreateStyle() Dim StyleCode As String = rm.GetString("Style") Dim Literal As New WebControls.Literal Literal.Text = StyleCode Controls.Add(Literal) End Sub
The two methods both use our resource manager to populate a string with the text stored in our resource file relevant to each method and uses a literal Control to add this text to our interface.
After that we loop through each of the sections in our list and start to create them.
... For Each SectionName In Sections.Keys CreateSection(SectionName) Next ...
To create each section we pass the createSection Method the Section Name that it will use to create the section header and determine which properties should be rendered under it.
Private Sub CreateSection(ByVal SectionName As String) ' we start by creating the table that will be used to render ' our sections heading and Expand/Collapse Button subTable = New Table subTable.CellPadding = 0 subTable.CellSpacing = 0 subTable.BorderWidth = Unit.Pixel(0) subTable.Width = Unit.Percentage(100) subTable.ID = SectionName subRow = New TableRow subCell = New TableCell subCell.ColumnSpan = 3 subCell.CssClass = "category" 'the link that we add here expands and collapses 'our Section, the way this is achieved is by hosting the actual 'sections controls in a <DIV> which is shown and hidden with the use 'of some javascript link = New HtmlControls.HtmlAnchor link.HRef = "javascript:toggleCategory(document.getElementById('cat" & SectionName & "'), document.images('t" & SectionName & "'))" link.Attributes.Add("onmouseover", "window.status='Toggle Category'; return true") link.Attributes.Add("onmouseout", "window.status=''; return true") link.Attributes.Add("onfocus", "this.blur()") im = New Image im.ImageUrl = "images/head-.gif" im.ID = "t" & SectionName link.Controls.Add(im) subCell.Controls.Add(link) lbl = New Label lbl.Text = " " & SectionName subCell.Controls.Add(lbl) subRow.Cells.Add(subCell) subTable.Rows.Add(subRow) cell.Controls.Add(subTable) 'once we have created or section Header we move on to create the fields 'that the section contains CreateSectionFields(SectionName) row.Cells.Add(cell) mainTable.Rows.Add(row) End Sub
The Create Section Method creates a Table that is used to render the actual section header that contains a Collapse and expand button and the sections name, the CreateSection method in turn calls CreateSectionFields.
Private Sub CreateSectionFields(ByVal SectionName As String) 'We start by declaring the "gutter" or the indentation on the 'left, this is a variable on its own since we will later use it 'to contain our validation controls. Dim gutter As TableCell 'this is where we create the actual section it exists out of a 'Panel "Rendered as a <DIV>" that contains a table which contains 'our interface controls section = New Panel section.ID = "cat" & SectionName 'Here we create the table that will contain each of the controls subTable = New Table subTable.CellPadding = 0 subTable.CellSpacing = 0 subTable.BorderWidth = Unit.Pixel(0) subTable.Width = Unit.Percentage(100) fields = New System.Collections.ArrayList For i As Int16 = 0 To myPropertyInfo.Length - 1 Dim myPropInfo As PropertyInfo = CType(myPropertyInfo(i), PropertyInfo) Dim attributes As Object() = myPropInfo.GetCustomAttributes(False) Dim attribute As WizardAttributes For s As Int16 = 0 To attributes.Length - 1 If TypeOf attributes(s) Is WizardAttributes Then attribute = DirectCast(attributes(s), WizardAttributes) Exit For End If Next 'We will start by looping through each property in our list 'and checking whether they belong to the current Section If attribute.SectionName = SectionName Then 'if the property does belong to the current section 'we create a row in our table for it subRow = New TableRow gutter = New TableCell gutter.Width = Unit.Parse(15) gutter.CssClass = "gutter" gutter.VerticalAlign = VerticalAlign.Middle gutter.HorizontalAlign = HorizontalAlign.Center subRow.Cells.Add(gutter) subCell = New TableCell subCell.CssClass = "cell" subCell.Text = attribute.Label subRow.Cells.Add(subCell) subCell = New TableCell subCell.CssClass = "inputcell" 'the inputcontrol was declared earlier as an object since it could be any 'Web or HTML Control 'we will now use reflection to create an instance of a control as specified 'in the property's attributes inputControl = Activator.CreateInstance(attribute.Type) 'because we have no idea exactly what kind of control we are working with 'we use some generic methods such as Style.Add and Attributes.Add to 'add all the necessary styles and attributes to our control. 'this includes an 'onfocus event that calls client side Javascript to display help 'information inputControl.Style.Add("width", "98%") inputControl.Style.Add("border-style", "None") inputControl.Style.Add("border-width", "0pt") inputControl.Style.Add("height", "100%") inputControl.Attributes.Add("Class", "cell") inputControl.Attributes.Add("onfocus", "doHelp(this)") inputControl.ID = myPropInfo.Name inputControl.Attributes.Add("help","<b>" & attribute.Label & _ "</b><br><br>" & Microsoft.VisualBasic.IIf(attribute.HelpText = "", _ "<BR>", attribute.HelpText)) 'because we still don’t really know which control 'we, have we use the following if statement 'to look for a property we can use to display the property's 'current value to the user. this could have been don with a select case 'or a loop that checks each control against ours and 'then casts our control to the correct type where we can then use the 'correct and known property or method to set the value however there are 'about 5 (I Might have missed one or two) possible properties and many more 'possible controls. 'We then use reflection to call Get Property for our object properties 'and use the returned value to populate the relevant property of the 'control If Not attribute.Type.GetProperty("Checked", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then inputControl.Checked = _ myType.InvokeMember(myPropInfo.Name, BindingFlags.GetProperty, _ Nothing, lWizardObject, New Object() {}) ElseIf Not attribute.Type.GetProperty("Text", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then inputControl.Text = _ myType.InvokeMember(myPropInfo.Name, BindingFlags.GetProperty, _ Nothing, lWizardObject, New Object() {}) ElseIf Not attribute.Type.GetProperty("SelectedValue", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then If attribute.ListIndex > -1 Then ' in the case of a list control we use reflection to retirve the list as 'specified by the ListIndex attribute, and then use this collection ' to populate the list control with the specified items Dim listCollection As System.Collections.Specialized.StringCollection listCollection = _ myType.InvokeMember("List", BindingFlags.GetProperty, _ Nothing, lWizardObject, New Object() {attribute.ListIndex}) Dim item As String For Each item In listCollection inputControl.Items.Add(item) Next End If 'we also select the item in the list 'as specified by the SelectedIndex attribute 'or set the selectedValue to the value of the Property If attribute.SelectedIndex > -1 Then inputControl.SelectedIndex = attribute.SelectedIndex Else inputControl.SelectedValue = _ myType.InvokeMember(myPropInfo.Name, BindingFlags.GetProperty, _ Nothing, lWizardObject, New Object() {}) End If ElseIf Not attribute.Type.GetProperty("Value", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then inputControl.Value = _ myType.InvokeMember(myPropInfo.Name, BindingFlags.GetProperty, _ Nothing, lWizardObject, New Object() {}) ElseIf Not attribute.Type.GetProperty("InnerText", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then inputControl.InnerText = _ myType.InvokeMember(myPropInfo.Name, BindingFlags.GetProperty, _ Nothing, lWizardObject, New Object() {}) End If 'if the Locked attribute = True then we disable the 'control If attribute.Locked = True Then inputControl.Attributes.Add("disabled", "disabled") End If 'Here we use the structure we created at the start 'to store a pointer to our control so that we can retrieve it easily 'and some information about the property Dim field As New fieldContainer field.name = myPropInfo.Name field.field = inputControl field.fieldType = inputControl.GetType field.propertyType = myPropInfo.PropertyType fields.Add(field) 'if the Required attribute = True, we add a RequiredFieldValidator 'to the "gutter" If attribute.Required = True Then Dim validator As New WebControls.RequiredFieldValidator validator.Text = "*" validator.Style.Add("CURSOR", "help") validator.ToolTip = "This is a required field" validator.Attributes.Add("help", "<b>" & attribute.Label & "</b><br><br>" & validator.ToolTip) validator.Attributes.Add("onclick", "doHelp(this)") validator.ControlToValidate = inputControl.id validator.Font.Size = FontUnit.Parse("11px") validator.Font.Name = "Arial" gutter.Controls.Add(validator) End If ' if our poperty is a numeric property and has no validation details 'specified in the attributes thena we add a default 'Regex Validator that only allows numbers, if it is not a numeric property ' but does have a Validation Expression specified we will add the relevant 'validator to the "gutter" If myPropInfo.PropertyType Is GetType(System.Decimal) Or _ myPropInfo.PropertyType Is GetType(System.Int16) Or _ myPropInfo.PropertyType Is GetType(System.Int32) Or _ myPropInfo.PropertyType Is GetType(System.Int64) Or _ myPropInfo.PropertyType Is GetType(System.Double) Or _ myPropInfo.PropertyType Is GetType(System.Byte) Or _ myPropInfo.PropertyType Is GetType(System.Single) _ Then Dim ValidationMessage As String = _ Microsoft.VisualBasic.IIf(attribute.ValidationText = "", "Not valid for this field", attribute.ValidationText) Dim validator As New WebControls.RegularExpressionValidator validator.Text = "*" validator.Style.Add("CURSOR", "help") validator.ControlToValidate = inputControl.ID validator.Font.Size = FontUnit.Parse("11px") validator.Font.Name = "Arial" If attribute.ValidationExpression = "" Then validator.ValidationExpression = "[0-9]{1,}[.]{0,1}[0-9]{0,}" validator.ToolTip = "Only Numeric Values ex. 1.0, 0.3, 12.09872 are allowed" Else validator.ValidationExpression = attribute.ValidationExpression validator.ToolTip = ValidationMessage End If validator.Attributes.Add("help", "<b>" & attribute.Label & "</b><br><br>" & validator.ToolTip) validator.Attributes.Add("onclick", "doHelp(this)") gutter.Controls.Add(validator) ElseIf Not attribute.ValidationExpression = "" Then Dim ValidationMessage As String = _ Microsoft.VisualBasic.IIf(attribute.ValidationText = "", "Not valid for this field", attribute.ValidationText) Dim validator As New WebControls.RegularExpressionValidator validator.Text = "*" validator.Style.Add("CURSOR", "help") validator.ValidationExpression = attribute.ValidationExpression validator.ToolTip = ValidationMessage validator.Attributes.Add("help", "<b>" & attribute.Label & "</b><br><br>" & validator.ToolTip) validator.Attributes.Add("onclick", "doHelp(this)") validator.ControlToValidate = inputControl.ID validator.Font.Size = FontUnit.Parse("11px") validator.Font.Name = "Arial" gutter.Controls.Add(validator) End If 'add the control to the table subCell.Controls.Add(inputControl) subRow.Cells.Add(subCell) 'add the field to the Table subTable.Rows.Add(subRow) End If Next 'add the fields to the section section.Controls.Add(subTable) cell.Controls.Add(section) End Sub
In the CreateSectionFields Method we perform a few tasks, first we create a gutter or indentation on the left hand side of the screen so that the section fields are all slightly indented from the section header, we will later use this cell in the table to host our Validation controls. We then create the actual section a panel control that will contain the table containing our interface controls, the reason for the panel control is that it is rendered as a <DIV> by ASP.NET and can the be used to hide or show it’s contents, thereby expanding and collapsing each Section.
Inside the section we create a table to contain all our controls and supply us with a way to do some layout on the screen. The “inputcontrol” that we declared as an Object earlier is now used to cerate our Web or HTML Controls, using reflection we create an instance of the Type specified in the Property’s attributes, and then add some style information and attributes to the control on of these attributes includes an event declaration that calls a JavaScript Function that displays context sensitive help to the user.
We then determine whether the control has a property that we can use to display the property’s current value to the user and if so we use reflection the Call the Get method of our property and use the returned value to populate the controls relevant property.
Once we have done this we add create an instance of our Field Container structure, add a pointer to the field and some other relevant information to it and add it to our collection of Field Containers, we will later use these field containers to retrieve the values from our interfaces controls and update the values of the Objects Properties.
Once this is done we add the relevant validation controls to our interface to ensure that what the user enters in the interface can be used by the relevant properties, and add the fields to the section and the section to the Controls, Controls Collection.
What’s worth noticing in this method is the use of reflection to create objects, and read property Values an the customization of the validation controls with the use off attributes allowing us to display the validation message in our context sensitive help section.
After the CreateSectionFields Function has been called for each of the relevant properties and all of the sections have been created. A call is made to “CreateHelpSection”
Private Sub CreateHelpSection() 'CreateHelpSection Create a Final Section at the bottom of the page 'that will be used to render context sensitive help row = New TableRow row.Height = Unit.Percentage(100) cell = New TableCell cell.VerticalAlign = VerticalAlign.Bottom cell.Height = Unit.Percentage(100) subTable = New Table subTable.CellPadding = 5 subTable.CellSpacing = 0 subTable.BorderWidth = Unit.Point(0) subTable.Width = Unit.Percentage(100) subTable.Height = Unit.Percentage(100) subRow = New TableRow subRow.Height = Unit.Percentage(100) subCell = New TableCell subCell.Text = " " subRow.Cells.Add(subCell) subTable.Rows.Add(subRow) subRow = New TableRow subCell = New TableCell subCell.Style.Add("BORDER-TOP", "#808080 1px solid") Dim HelpTable As New Table HelpTable.CellPadding = 3 HelpTable.CellSpacing = 0 HelpTable.BorderWidth = Unit.Point(0) HelpTable.Width = Unit.Percentage(100) HelpTable.CssClass = "footer" Dim helprow As New TableRow Dim helpcell As New TableCell helpcell.CssClass = "help" helpcell.VerticalAlign = VerticalAlign.Top helpcell.Style.Add("BORDER", "#808080 1px solid") Dim helpContainer As New Label helpContainer.ID = "helpText" helpContainer.Text = "<b>Help</b><br><br><br>" helpContainer.Style.Add("HIEGHT", "60px") helpcell.Controls.Add(helpContainer) helprow.Cells.Add(helpcell) HelpTable.Rows.Add(helprow) subCell.Controls.Add(HelpTable) subCell.CssClass = "footer" subRow.Cells.Add(subCell) subTable.Rows.Add(subRow) subRow = New TableRow subCell = New TableCell subCell.CssClass = "footer" subCell.HorizontalAlign = HorizontalAlign.Right subCell.Height = Unit.Point(10) Dim reset As New HtmlControls.HtmlInputButton reset.Attributes.Add("type", "reset") reset.Attributes.Add("value", "Reset") reset.ID = "reset1" subCell.Controls.Add(reset) subCell.Controls.Add(New LiteralControl(" ")) submit = New WebControls.Button submit.Text = "Submit" submit.ID = "submit1" AddHandler submit.Click, AddressOf SubmitDetail subCell.Controls.Add(submit) subRow.Cells.Add(subCell) subTable.Rows.Add(subRow) cell.Controls.Add(subTable) row.Cells.Add(cell) End Sub
“CreateHelpSection” creates the section at the very bottom of the page where context sensitive help is displayed to the user and the Reset and Submit buttons are rendered.
And that’s it the code above is all we need to create an attributes class, create a Business object that uses these attributes and an interface generator that uses reflection and the attributes to generate a useable interface, however the interface may be useable but It’s not useful until we can get values back from it and populate our objects properties with the changes the user has made.
For this we first need a way to get the object we handed to the interface generator back when it’s been updated, for that we will create a property
Property WizardObject() As Object Get Return lWizardObject End Get Set(ByVal Value As Object) lWizardObject = Value End Set End Property
Once we have the property we can get the object back but we still need a way to update it, this is again where reflection comes in handy, in the CreateHelpSection Method we created a Submit button and added an event handler to its Click event this will allow us to trap the submit of the form and update our objects properties.
the submitDetail event Handler allows us to capture the forms submit and use the controls on the form to update our object and in turn raises an event that lets us know the objects values have been updated.
Private Sub SubmitDetail(ByVal sender As System.Object, ByVal e As System.EventArgs) Dim value As Object Dim field As Object Dim cfield As fieldContainer For Each fields In Sections.Values For Each cfield In fields field = cfield.field If Not cfield.fieldType.GetProperty("Checked", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then value = field.Checked value = Convert.ChangeType(value, cfield.propertyType) myType.InvokeMember(cfield.name, BindingFlags.SetProperty, _ Nothing, lWizardObject, New Object() {CBool(value)}) ElseIf Not cfield.fieldType.GetProperty("Text", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then value = field.Text value = Convert.ChangeType(value, cfield.propertyType) myType.InvokeMember(cfield.name, BindingFlags.SetProperty, _ Nothing, lWizardObject, New Object() {value}) ElseIf Not cfield.fieldType.GetProperty("SelectedValue", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then value = field.SelectedValue value = Convert.ChangeType(value, cfield.propertyType) myType.InvokeMember(cfield.name, BindingFlags.SetProperty, _ Nothing, lWizardObject, New Object() {value}) ElseIf Not cfield.fieldType.GetProperty("Value", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then value = field.Value value = Convert.ChangeType(value, cfield.propertyType) myType.InvokeMember(cfield.name, BindingFlags.SetProperty, _ Nothing, lWizardObject, New Object() {value}) ElseIf Not cfield.fieldType.GetProperty("InnerText", BindingFlags.Public Or BindingFlags.Instance) Is Nothing Then value = field.InnerText value = Convert.ChangeType(value, cfield.propertyType) myType.InvokeMember(cfield.name, BindingFlags.SetProperty, _ Nothing, lWizardObject, New Object() {value}) End If Next Next RaiseEvent ValuesUpdated(lWizardObject, Nothing) End Sub
Since we added all our controls to the field containers earlier we can now simply loop through each of these and retrieve their values. Using reflection we then call the Set Method of the relevant properties to update their values and raises an event to let us know the values have been updated.
That’s it we now have a way to return the new and updated object that we can then use to do whatever needs to be done.
Now we need to use our nice new interface generator.