Creating forms through .Net code-behind
Why do we need Dynamic Forms
Generating data entry forms in WPF is not difficult. In fact, it is very easy and convenient to generate forms dynamically. Form generation at runtime has three major advantages over forms designed at design time:
1. You can add/remove controls without changing the code.
2. Control names and other properties can be changed without changing the code.
3. Layout of the form can be changed without changing the code.
Let’s take the example of a simple membership subscription form. The following are the initial UI requirements of this form as communicated by the customer:
1. The form will contain a label and a textbox for email address.
2. The form will contain a label and a textbox for password.
3. The form will contain a label and a textbox for first name.
4. The form will contain a label and a textbox for last name.
5. The form will contain a scrollable read-only multi-line textbox displaying the terms of use.
6. The form will contain a checkbox which says Agree to Terms
7. The form will contain a submit button.
This simple form can be designed within minutes by any .NET programmer. However, if tomorrow, the customer comes back and requests an additional text box to capture user’s phone number, the programmer needs to change the code to include these UI elements. If the customer comes back after every one month and requests the same type of change, then the work becomes redundant and a lot of useful time is wasted in adding text or check boxes or other UI elements.
The solution to this problem is to switch from creating static to dynamic forms.
What are Dynamic Forms
A dynamic form is one about which we are not sure how it will look like at runtime. A dynamic form is created in code-behind and in design-view may appear vacant because UI elements are not included in the form by the programmer at design-time.
It is painted dynamically at runtime. The controls to be painted on the form are usually stored in a database. The application simply fetches the list of UI elements to be painted from the database and for each element, creates an appropriate control on the form.
If any change is required in UI, all we need to do is add/remove/update records in the database. The client can be given a simple tool/script to do the changes in the database or the programmer could do so for them. If the programmer is charging for 5 hours to make this change but he is spending only 1 hour due to dynamic form, then it implies that the company is getting free money for additional 4 hours. This is a great strategy to increase revenues.
How to Implement Dynamic Forms
We will implement a sample dynamic form in WPF client application. The following are the assumptions:
Database
The database has the following tables:
a. Table to store form information
Form(FormId, Title, Description)
b. Table to store UI elements properties
Controls(ControlId, Name, DisplayName, Type, IsReadonly, DefaultValue)
c. Table to store values of controls like combo box/drop down list, etc.
ControlValues(ControlId, Value)
d. Table to associate many-to-many relationship of forms and controls
FormControls (FormId, ControlId, DisplayOrder )
Here is the sample data:
Form:
(1, 'Subscription', 'Membership Subscription Form')
Controls:
1, 'txtEmail', 'Email Address', 2, 'N', NULL
2, 'txtPassword', 'Password', 2, 'N', NULL
3, 'txtFName', 'First Name', 2, 'N', NULL
4, 'txtLName', 'Last Name', 2, 'N', NULL
5, 'txtTerms', 'Terms of Use', 2, 'Y', 'Some agreement text'
6, 'chkAgree', 'Agree to Terms', 6, 'N', NULL
7, 'btnSubmit', 'Submit', 10, 'N', NULL
FormControls (FormId, ControlId, DisplayOrder )
1,1,3
1,2,4
1,3,1
1,4,2
1,5,5
1,6,6
1,7,7
So, we defined a form in which the controls will appear in this order:
First Name, Last Name, Email Address, Password, Terms of Use, Agree to Terms, and Submit.
Application
The data access layer is available to read data from the tables. The layer can be written in ADO .NET or LINQ, or some other technology, all we need is a list a methods to perform operations on database tables.
We will use Stack Panels to display controls on the form. A stack panel is basically a container which can contain multiple controls to display either horizontally or vertically. We will display controls in rows, each row will be implemented through a stack panel, so that all the controls contained in one stack panel will appear in one row.
StackPanel1
StackPanel2
StackPanel3
StackPanel4
StackPanel5
StackPanel6
StackPanel7
The following code demonstrates how we are fetching the list of controls through database and how are we painting them dynamically on the form:
public void PaintForm(int FormID)
{
InitializeComponent();
// get form details
Form ThisForm = GetFormDetailsFromDB(FormId);
// fill up static content on the form first
lblHeading.Content = ThisForm.Title;
lblPurpose.Content = ThisForm.Purpose;
lblDescription.Content = ThisForm.Description;
// get the list of controls to paint on this form
var ControlsSet = (List)ThisContext.GetFormControls(FormId);
foreach (var control in ControlsSet)
{
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Horizontal;
sp.Margin = new Thickness(3);
switch (control.Type)
{ // text box
case 2:
{
TextBlock textBlock = new TextBlock();
textBlock.Text = control.DisplayName + ": ";
textBlock.Width = 80;
TextBox textBox = new TextBox();
textBox.Name = control.Name;
textBox.Width = 150;
this.RegisterControl(textBox.Name, textBox);
if (control.DefaultValue != String.Empty)
textBox.Text = control.DefaultValue;
if (control.IsReadOnly != null)
{
if (control.IsReadOnly.ToUpper() == "Y")
textBox.IsReadOnly = true;
}
sp.Children.Add(textBlock);
sp.Children.Add(textBox);
break;
}
case 3:
{ // combo box
TextBlock textBlock = new TextBlock();
textBlock.Text = control.DisplayName + ": ";
textBlock.Width = 80;
ComboBox comboBox = new ComboBox();
comboBox.Width = 80;
comboBox.Name = control.Name;
this.RegisterControl(comboBox.Name, comboBox);
var values = GetControlValues(control.Id);
foreach (var ThisValue in (IEnumerable)values)
{
comboBox.Items.Add(ThisValue.Value);
}
if (control.DefaultValue != String.Empty)
comboBox.SelectedValue = control.DefaultValue;
if (control.IsReadOnly != null)
{
if (control.IsReadOnly.ToUpper() == "Y")
comboBox.IsEnabled = false;
}
sp.Children.Add(textBlock);
sp.Children.Add(comboBox);
break;
}
case 5:
{ // date picker
TextBlock textBlock = new TextBlock();
textBlock.Text = control.DisplayName + ": ";
textBlock.Width = 80;
DatePicker datePicker = new DatePicker();
datePicker.Name = control.Name;
this.RegisterControl(datePicker.Name , datePicker);
if (control.DefaultValue != String.Empty)
datePicker.SelectedDate = Convert.ToDateTime(control.DefaultValue);
if (control.IsReadOnly != null)
{
if (control.IsReadOnly.ToUpper() == "Y")
datePicker.IsEnabled = false;
}
sp.Children.Add(textBlock);
sp.Children.Add(datePicker);
break;
}
case 6:
{ // check box
CheckBox checkBox = new CheckBox();
checkBox.Name = control.Name;
checkBox.Content = control.DisplayName;
this.RegisterControl(checkBox.Name , checkBox);
if (control.DefaultValue != null)
{
if (control.DefaultValue == "Y")
checkBox.IsChecked = true;
else
checkBox.IsChecked = false;
}
if (control.IsReadOnly != null)
{
if (control.IsReadOnly.ToUpper() == "Y")
checkBox.Enabled = false;
}
sp.Children.Add(checkBox);
break;
}
case 10:
{ // button
Button button = new Button();
button.Name = control.Name;
button.Content = control.DisplayName;
button.Width = 50;
sp.Children.Add(button);
break;
}
default:
{
break;
}
}
FormBase.Children.Add(sp);
}
}
private void RegisterControl(string ControlName, Control ThisControl)
{ // register control because we will need to read the value entered by user on form submission
if (this.FindName(ControlName) != null)
this.UnregisterName(ControlName);
this.RegisterName(ControlName, ThisControl);
}
You must be wondering that how are we determining the order of controls to display in rows(stack panels). Well, the method GetFormDetailsFromDB should do a select query on FormControls table and order by DisplayOrder. In this way, we will simply start painting controls in the same order as that of the record-set we received.
Reading Forms Data Entered by User
The logic is pretty simple here. Go through all stack panels in the form and search each control by its name. The following code will read the values entered by user in the form. We are storing the values in a hashtable ControlValues for future use.
public void ReadFilledForm(int FormId)
{
Hashtable ControlsValues = new Hashtable();
var ControlsMap = GetFormControls(FormId)
// now save form values
foreach(var control in ControlsMap)
{
foreach (StackPanel sp in FormBase.Children)
{
Control ThisControl = (Control) FindName(control.Name);
if (ThisControl != null)
{
if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.TextBox"))
{
TextBox NewTextBox = (TextBox)ThisControl;
ControlsValues.Add(control.Id, NewTextBox.Text);
}
if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.ComboBox"))
{
ComboBox NewComboBox = (ComboBox)ThisControl;
ControlsValues.Add(control.Id, NewComboBox.SelectedValue);
}
else if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.CheckBox"))
{
CheckBox NewCheckBox = (CheckBox)ThisControl;
if (NewCheckBox.IsChecked == true)
ControlsValues.Add(control.Id, "Y");
else
ControlsValues.Add(control.Id, "N");
}
if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.DatePicker"))
{
DatePicker NewDatePicker = (DatePicker)ThisControl;
ControlsValues.Add(control.Id, NewDatePicker.SelectedDate);
}
break;
}
}
}
}
I hope the above code snippets will help readers in getting acquainted with dynamic forms creation in WPF. The same code can be reused with little changes in ASP .NET web forms.
Cheers!
JS