The Seductive Visual Designer 2008-07-15

An effective tool in the toolbox

Creating a user interface with a Visual Designer, makes a lot of sense due to the WYSIWYG experience. Shortening the feedback loop from editing content, to verification of the result. Prototyping, demoing and trying out different solutions, are the key strengths of the Visual Designer.

Moving towards a GUI-guide

Every tool has its shortcomings, no matter how good it is. Due to the flexibility of the Visual Designer, controls can be placed anywhere on the view, and size, color almost anything can be changed. So in order to create a uniform user experience across multiple views, a GUI-guide is often created.

The GUI-guide is usually written in Word, with examples and descriptions of how to layout and compose the GUI. So all GUI work is a manual merge process, of the Visual Designer and the GUI-guide.

Alternatives

Letting go of the Visual Designer sounds counter productive to most developers, but as with most tools there is a golden rule of when (not) to use it.

"It depends"

Its hard, and takes years of experience. Using any tool in the toolbox too much or too little, could have severe impact on productivity, maintainability and pretty much any other aspect of development.

When I first learned about database normalization, I though "what is it good for?". When I finally figured out why I should use it, I started to use it everywhere. Temporal data, auditing, you name it - everything was normalized. It took me years of experience, trying different levels of normalization before I was able to do it properly.

So instead of blindly using the tool, we should ask.

"Is this tool optimal for the task at hand, are there any alternatives?"

So if we need a GUI where there are multiple variations of the same theme, think "OrderDetailsView", "CustomerDetailsView", etc. Is the Visual Designer optimal for creating views, with components arranged after certain rules?

I don't think so, the Visual Designer exposes all behavior of all controls, and have no mechanism that allow rules to be applied across multiple views. You might think that User Controls fit the bill, but there is a big drawback with User Controls - They don't stack or combine! In my experience User Controls offer the worst form of reusability, I have ever encountered (An alternative could be a light weight micro controller).

So the Visual Designer exposes too much flexibility, and not enough automatability(is that a word?). I have seen dozens and dozens of views where tab order is not set correctly. Why, because it is a manual process to apply it. I have seen endless hours wasted because some control, was 1-10 pixels out of alignment or buttons was glued to the top and not the bottom of the view. Clearly the Visual Designer is not ideal for solving the problem at hand.

Automation

The art of codifying rules and processes, in essence what software developers do. We usually automate business rules for customers, but some times it is useful to automate our own business. Build scripts and Continuous Integration are good examples, but the possibilities are nearly endless. Figure out the most repetitive tasks you do, and automate them!

The framework trap

I often fall in this one, when automating some of my tedious tasks. I start to over design the automation, taken into account requirements that does not exist yet. Frameworks should not be build up front, but rather harvested from several projects.

Example

Suppose we have the following GUI-guides, for details views.

  1. All fields should have a label with a description.
  2. All fields should have a editing control. (textbox for text and numbers, checkbox for boolean and combobox for enums).
  3. All fields should be in a single column with label first and then the textbox next to it.
  4. Fields have a "xxx2" field, should be align next to each other in 2 columns. (name and name2 should be in a single row in 2 columns).
  5. Tab order goes from left to right and top to down. (name->name2->address->address2->country->email).

In the real world, the list would go on and on.

In Windows Forms we could manually create every details view, or we could create a details view builder.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Windows.Forms;
   4:  
   5: namespace NonVisualDesigner
   6: {
   7:     public class DetailsViewBuilder
   8:     {
   9:         private readonly List _detailsViewFieldModels;
  10:  
  11:         public DetailsViewBuilder()
  12:         {
  13:             _detailsViewFieldModels = new List();
  14:         }
  15:  
  16:         public DetailsViewBuilder AddField()
  17:         {
  18:             _detailsViewFieldModels.Add(new DetailsViewFieldModel());
  19:             return this;
  20:         }
  21:  
  22:         public DetailsViewBuilder WithName(string name)
  23:         {
  24:             _detailsViewFieldModels[_detailsViewFieldModels.Count - 1].Name = name;
  25:             return this;
  26:         }
  27:  
  28:         public DetailsViewBuilder WithType(Type type)
  29:         {
  30:             _detailsViewFieldModels[_detailsViewFieldModels.Count - 1].Type = type;
  31:             return this;
  32:         }
  33:  
  34:         public Panel Build()
  35:         {
  36:             Panel panel = new Panel();
  37:  
  38:             int tabOrderIndex = 0;
  39:             int rowIndex = 0;
  40:             DetailsViewFieldModel previousDetailsViewFieldModel = null;
  41:             
  42:             TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
  43:             foreach (DetailsViewFieldModel detailsViewFieldModel in _detailsViewFieldModels)
  44:             {
  45:                 Label label = new Label();
  46:                 label.Name = detailsViewFieldModel.Name + "Label";
  47:                 label.Text = detailsViewFieldModel.Name;
  48:                 Control editingControl;
  49:                 if(detailsViewFieldModel.Type == typeof(bool))
  50:                 {
  51:                     editingControl = new CheckBox();
  52:                 }
  53:                 else if (detailsViewFieldModel.Type.IsEnum)
  54:                 {
  55:                     editingControl = new ComboBox();
  56:                 }
  57:                 else
  58:                 {
  59:                     editingControl = new TextBox();
  60:                 }
  61:                 editingControl.Name = detailsViewFieldModel.Name + editingControl.GetType().Name;
  62:                 editingControl.TabIndex = tabOrderIndex;
  63:  
  64:                 int labelColumnIndex = 0;
  65:                 int editingControlColumnIndex = 1;
  66:  
  67:                 if (previousDetailsViewFieldModel != null && previousDetailsViewFieldModel.Name + "2" == detailsViewFieldModel.Name)
  68:                 {
  69:                     labelColumnIndex = labelColumnIndex + 2;
  70:                     editingControlColumnIndex = editingControlColumnIndex + 2;
  71:                     rowIndex--;
  72:                 }
  73:  
  74:                 tableLayoutPanel.Controls.Add(label, labelColumnIndex, rowIndex);
  75:                 tableLayoutPanel.Controls.Add(editingControl, editingControlColumnIndex, rowIndex);
  76:  
  77:                 previousDetailsViewFieldModel = detailsViewFieldModel;
  78:  
  79:                 rowIndex++;
  80:                 tabOrderIndex++;
  81:             }
  82:             tableLayoutPanel.Dock = DockStyle.Fill;
  83:             panel.Controls.Add(tableLayoutPanel);
  84:             return panel;
  85:         }
  86:     }
  87:  
  88:     public class DetailsViewFieldModel
  89:     {
  90:         public string Name;
  91:         public Type Type;
  92:     }
  93: }

A customer details view could then be created like this.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Windows.Forms;
   4:  
   5: namespace NonVisualDesigner
   6: {
   7:     public class CustomerDetailsView : Form
   8:     {
   9:         public CustomerDetailsView()
  10:         {
  11:             Panel panel = new DetailsViewBuilder()
  12:                 .AddField().WithName("Name").WithType(typeof (string))
  13:                 .AddField().WithName("Name2").WithType(typeof(string))
  14:                 .AddField().WithName("Address").WithType(typeof(string))
  15:                 .AddField().WithName("Address2").WithType(typeof(string))
  16:                 .AddField().WithName("Phone").WithType(typeof(string))
  17:                 .AddField().WithName("Fax").WithType(typeof(string))
  18:                 .AddField().WithName("Email").WithType(typeof(string))
  19:                 .AddField().WithName("Gender").WithType(typeof(Gender))
  20:                 .AddField().WithName("Active").WithType(typeof(bool))
  21:                 .Build();
  22:             panel.Dock = DockStyle.Fill;
  23:             Controls.Add(panel);
  24:         }
  25:     }
  26:  
  27:     public enum Gender
  28:     {
  29:         Male,
  30:         Female
  31:     }
  32: }

It looks like this.

BuilderCustomerDetailsView

Conclusion

Carefully consider if a tool is appropriate to solve a problem, always try to find alternatives. Automate guidelines and rules written in non-executable form. Save your self from the pain of manual, repetitive and error prone tasks.

1 comments:

Frank said...

Nice article! Please note that dotnet's reflection can do a lot more. Check out a simple form which shows properties of a class. When pressing OK, properties of int, double, guid etc. are verified: http://blog.denouter.net/2008/08/simple-reflection-form.html