Introduction
In this article, I will describe a small proof of concept project for an idea which has been floating around for a long time, but doesn't seem to have a name. I've dubbed it the 'code-first user interface', by analogy with code-first development in Microsoft Entity Framework. Its main goal is to greatly reduce the amount of work required to create a program's user interface.
I need to apologize here for the incompleteness and inconsistencies in the code. I've been working on it for ages during my limited spare time, so I felt I had to get the article out before I died of old age. Also I'd like to get some early feedback, to see if this idea is worth pursuing. Please bear with me.
But I'm going to start with some complaints...
Don't Repeat Yourself
The impetus behind this is simply to reduce the amount of code required to implement a user interface. Currently, UI development seems to entail writing the same thing in multiple ways. For instance, recently I had to create a small test program as part of my professional work. It was a WPF application to display and edit items in a list. I've recreated a simplified version of it here. I started with this class like this:
[Serializable]
public class ProductItem: Notifier
{
public ProductItem() { }
public ProductItem(string number, ProductTypeEnum type)
{
_productNumber = number;
_productType = type;
}
private string _productNumber;
public string ProductNumber { get { return _productNumber; }
set { SetProperty(ref _productNumber, value); } }
private ProductTypeEnum _productType;
public ProductTypeEnum ProductType { get { return _productType; }
set { SetProperty(ref _productType, value); } }
private string _title;
public string Title { get { return _title; } set { SetProperty(ref _title, value); } }
private decimal _price;
public decimal Price { get { return _price; } set { SetProperty(ref _price, value); } }
private int _stockLevel;
public int StockLevel { get { return _stockLevel; } set { SetProperty(ref _stockLevel, value);
}
}
I needed to display a list of these, so I had to define a list view in XAML, and bind it to my objects properties:
<listview name="listView1" grid.row="1" itemssource="{Binding}"
mousedoubleclick="listView1_MouseDoubleClick" keyup="listView1_KeyUp" allowdrop="True">
<listview.view>
<gridview>
<gridviewcolumn header="Product Number" displaymemberbinding="{Binding ProductNumber}" />
<gridviewcolumn header="Type" displaymemberbinding="{Binding ProductType}" />
<gridviewcolumn header="Title" displaymemberbinding="{Binding Title}" />
<gridviewcolumn header="Price" displaymemberbinding="{Binding Price}" />
<gridviewcolumn header="Stock Level" displaymemberbinding="{Binding StockLevel}" />
</gridview>
</listview.view>
</listview>
This gives me a form like this:
I also needed a form to create and edit the items:
<window x:class="ProductList.ProductItemForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:ProductList"
title="Product Item" width="478.373" height="194.895"
minwidth="478" sizetocontent="WidthAndHeight">
<window.resources>
<objectdataprovider methodname="GetValues"
objecttype="{x:Type System:Enum}"
x:key="ProductTypeEnumValues">
<objectdataprovider.methodparameters>
<x:type typename="c:ProductTypeEnum" />
</objectdataprovider.methodparameters>
</objectdataprovider>
</window.resources>
<stackpanel name="stackPanel1" minwidth="250">
<grid minwidth="250" height="115">
<grid.rowdefinitions>
<rowdefinition height="27" />
<rowdefinition height="27" />
<rowdefinition height="27" />
</grid.rowdefinitions>
<grid.columndefinitions>
<columndefinition width="153" />
<columndefinition width="370*" minwidth="100" />
</grid.columndefinitions>
<label grid.row="0" x:name="label10"
margin="10,0,38,0" content="Product Number" />
<textbox grid.row="0" grid.column="1" margin="11,4,19,2"
x:name="ProductNumTextBox" text="{Binding ProductNumber,
UpdateSourceTrigger=PropertyChanged}" />
<label grid.row="1" x:name="EventTypeText"
margin="10,0,38,0" content="Type" />
<combobox grid.row="1" margin="9,3,21,3" x:name="ProductType"
selectionchanged="EventType_SelectionChanged"
selectedvalue="{Binding ProductType}" grid.column="1"
minwidth="100" itemssource="{Binding Mode=OneWay,
Source={StaticResource ProductTypeEnumValues}}" />
<label grid.row="2" x:name="label7"
margin="10,0,38,0" content="Title" />
<textbox grid.row="2" margin="9,1,21,5" x:name="TitleTextBox"
text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" grid.column="1" />
<label content="Price" horizontalalignment="Left" margin="10,0,0,0"
grid.row="3" verticalalignment="Top" width="79" height="27" />
<textbox grid.row="3" margin="9,0,21,6" x:name="TitleTextBox_Copy"
text="{Binding Price, UpdateSourceTrigger=PropertyChanged}" grid.column="1" />
<label content="Stock Level" horizontalalignment="Left"
margin="10,0,0,0" grid.row="4"
verticalalignment="Top" width="79" height="27" />
<textbox grid.row="4" margin="10,0,20,6"
x:name="TitleTextBox_Copy1"
text="{Binding StockLevel, UpdateSourceTrigger=PropertyChanged}"
grid.column="1" rendertransformorigin="0.498,2.857" />
</grid>
:
Which looks like this:
I feel this is all a bit mechanical and repetitive. And we are repeating the same information - the names of the properties, the type and so on. This is clear if we need to add a new property to the class - we will have to add it in three places, not just one. We have to add it to the class, the XAML for the list and the XAML for the form. We could almost write an algorithm:
for each public property of the class
add a column to the list view and bind it to the property
add a label and field to the form, and bind it to the property
I feel we need to apply two of the fundamental principals of programming here:
- Don't repeat yourself (DRY)
- If it's repetitive and mechanical, automate it.
Too Many Files
Another problem I have with current user interface development is the amount of 'cruft' it requires. For example, if you fire up Visual Studio Community and create a sample MVC project, here's what you get:
We end up with 191 files in 33 folders, taking up over 9 MB! But the real essence of this application can be described with just a couple of objects - the TodoItem
and the TodoItemList
in the Model folder. So my question is why do I need to see all these views, controllers, data transfer objects, JavaScript and CSS files? I feel this is making application development more complex than it should be.
The Code-First User Interface
My proposal is that we simplify user interface code by adopting a method similar to code-first development in Microsoft Entity Framework. In the code-first approach, we can concentrate on the design of our 'domain' entities without having to write separate code to map them to database tables. We just code the classes that describe our application and the mapping is done automaticaly, using sensible conventions:
A mapping layer (the Object-Relational Mapper) is responsible for translating our model classes into relational database tables. [This article is a good introduction to the subject: http://msdn.microsoft.com/en-us/data/jj193542.]
The code-first user interface works the same way, but now a mapping layer (the Object-UI Mapper) creates the user interface using 'reflected' information about the model classes:
So we can just code the model classes, and the OUIM is responsible for creating the user interface. If we modify a model class, no further changes are required - the mapping process will recreate the UI. Furthermore, we could create user interfaces for different UI frameworks without having to rewrite most of our application. We just change the OUIM.
The efficacy of this does rely on a few assumptions, however:
- There is a one-one mapping between model objects and their views.
- There is a one-one mapping between properties of model objects and either list columns or form fields.
- We can infer the appearance of the UI by using the type information of the properties and a small number of attributes.
There will be cases were these assumptions do not hold, but in my experience this covers a large swathe of an application's user interface.
Existing 'Code-First UI' Systems
There are a couple of existing examples of this approach that you may already be familiar with: the Windows Forms Property Grid and Dynamic Data Entities.
Property Grid
Here's what we get if we drop a Windows Forms PropertyGrid
onto a form and bind it to a ProductItem
object, as defined above:
We have very quickly generated a user interface to edit our object, but I think we could fairly describe it as 'Spartan'. We can edit all the fields, but we are stuck with just a text box for most of them.
Dynamic Data Entities
Dynamic Data Entities for ASP.NET allows us to quickly create a user interface from a data model. We just define this:
public class ProductModel : DbContext
{
public ProductModel()
: base("name=ProductModel")
{
}
public virtual DbSet<ProductItem> Products { get; set; }
}
Then, we automatically get a list view and an editing form, like this:
This is exactly the kind of thing we want - the user interface has been generated for us just using the type information in the model. But:
- It only works for the web
- We still don't get those up/down controls for the numeric fields.
- There are still lots of additional files added to the project that we probably don't need to see:
Sample Implementation
To demonstrate the code-first approach I've implemented a small demonstration project, which you can download using the links at the top of the article. In this section, I'll go through some of the major features of the object-UI mapping. For starters though, here's what we get if we use it to display a Product
object as defined above. The code required to do this will be trivial:
ProductItem product = new ProductItem();
UI.ShowDialog(product);
This is the result (I've added some more fields in this incarnation):
Hopefully, this would be more acceptable to an end-user than the property grid shown above.
Now I'll present some examples of the code to UI mapping in more detail.
Simple Text Field
The simplest case is just a string
property without any additional attributes:
public string simpleTextField { get; set; }
Result:
We get a label and a single-line TextBox
. Note that we have not had to specify the label to use for the control - it has been derived from the property name by splitting it on each capital letter. So if our properties follow the 'camelback' naming convention, we will get acceptable labels.
Only Public Properties are Shown
Private
and static
properties are ignored:
private string privateProperty { get; set; }
public static string staticProperty { get; set; }
Result:
No control is displayed for these properties.
Overriding the Label
We can override the label by using the DiplayName
attribute:
[DisplayName("Another text field")]
public string simpleTextField2 { get; set; }
Result:
Masks
We can specify a mask for a text field:
[Mask("(LLL) 000-0000")]
public string maskedTextField { get; set; }
Result:
Numeric Fields
Numeric fields are shown with an up-down control:
public int integerField { get; set; }
public decimal decimalField { get; set; }
public double doubleField { get; set; }
Result:
Selecting the Control
You can override how a property is displayed with the Display
attribute:
[Display(ControlType.TextBox)]
public int another_integer { get; set; }
Result:
Setting the Range and Step
You can define the range and increment of a numeric field with attributes:
[CodeFirstUIFramework.Range(40.0, 200.0), CodeFirstUIFramework.Increment(0.1)]
public float beatsPerMinute { get; set; }
Result:
Booleans
Booleans are shown as a check box:
public bool booleanField { get; set; }
Result:
Enums
Enumerations are shown as combo boxes:
public enum TestEnum { Zero, One, Two, Three, Four, Five }
public TestEnum enumField { get; set; }
Result:
Flag Enums
We show a 'flags
' field as a set of check boxes:
[Flags]
public enum TestFlags { Olives=1, Pepperoni=2, Capers=4, ExtraCheese=8, Anchovies=16, Chillies=32 }
public TestFlags flagsField { get; set; }
Result:
Date and Time
DateTime
fields are shown with a date picker:
public DateTime a_date_time { get; set; }
public Date a_date { get; set; }
Result:
Structs
Given a structure definition like this:
public struct TimeOfDay
{
public TimeOfDay(UInt16 hours, UInt16 minutes, UInt16 seconds)...
[Range(0,23)]
public UInt16 Hours...
[Range(0, 59)]
public UInt16 Minutes...
[Range(0, 59)]
public UInt16 Seconds...
public override string ToString()...
}
Then a field of that type will be shown as a text field with a pop-up editor:
public TimeOfDay timeOfDay { get; set; }
Result:
Clicking the button gives us another form:
And here's the field after clicking OK:
We can use attributes to use a group of in-line controls instead:
[Display(LabelOption.Group, ControlType.Inline)]
public TimeOfDay timeOfDay { get; set; }
Which will appear like this:
File Names
String
s with the FileName
attribute will display a file picker:
[FileName(Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*", ForSave = false)]
public string fileName { get; set; }
Result:
Clicking the button brings up a FilePicker
dialog:
After selecting a file and pressing OPEN:
Object References
If there is a list registered for an object, then we will get a list picker:
public Country country { get; set; }
Result:
On clicking the button:
After pressing OK:
Lists
A list of objects will display as an embedded list control:
public List<orderline> _orderLines = new List<orderline>
public List<orderline> OrderLines { get { return _orderLines; } }
Result:
Points of Interest
One tricky point during implementation was getting the semantics right for OK/Cancel. We want the following code to work as expected:
if( UI.ShowDialog(product) )
{
}
else
{
}
There are a number of ways we could achieve this:
- Data is only copied from the controls to the object properties when OK is clicked (effectively not using binding).
- Bind the controls to the properties, but somehow restore the state of the object on cancel.
- Create a clone of the object and bind to that. On OK click, copy the clone back to the original object.
We want to use proper dynamic binding, as we want any 'calculated' fields to update as editing progresses.
In the end (after various experiments), I chose the last option. But this presented a problem - there is no simple way to clone an object in C# if you do not know its type. This does not work:
object clone = objectToCopy.MemberwiseClone();
This will not compile, as MemberwiseClone()
is a protected
member. In the end, I had to resort to this:
public static object CloneObject(object objectToCopy)
{
object returnValue = null;
Type t = objectToCopy.GetType();
if (t.IsSerializable)
returnValue = CopyBySerialization(objectToCopy);
else
returnValue = CopyFields(objectToCopy);
return returnValue;
}
private static object CopyFields(object objectToCopy)
{
Type t = objectToCopy.GetType();
object targetObject = Activator.CreateInstance(t);
CopyFields(objectToCopy, targetObject);
return targetObject;
}
private static void CopyFields(object objectToCopy, object targetObject)
{
Type t = objectToCopy.GetType();
if (targetObject.GetType() != t)
throw new ArgumentException("Trying to copy fields of incompatible types");
var fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
foreach (FieldInfo info in fields)
{
object value = info.GetValue(objectToCopy);
info.SetValue(targetObject, value);
}
}
private static object CopyBySerialization(object objectToCopy)
{
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, objectToCopy);
memoryStream.Position = 0;
object returnValue = binaryFormatter.Deserialize(memoryStream);
memoryStream.Close();
memoryStream.Dispose();
return returnValue;
}
If anyone knows of a better way, I'd like to hear about it.
Future Development
There are numerous ways this code could be improved and developed. Here are some I've thought of so far:
- Different UI frameworks. I've implemented for Windows Forms, as that's what I have most familiarity with. But it would be good if we could also create an interface for the web, and for the new Microsoft 'Universal Apps'.
- Actions. Currently we only support editing data. It would be good to extend the interface generation to support actions. For instance,
public
methods marked with an [Action]
attribute could be used to add a button to the form, which calls the method when clicked. - Attributes. At the moment, the code is only interpreting its own attributes and a few from
System.ComponentModel
. It should be extended to garner useful information from System.ComponentModel.DataAnnotations
. - Groups. If properties have a
group
attribute, we could perhaps put them on separate tabs.
History
- 1.0.0 | Initial Release | 09 Aug 2015