in

 

Chad Myers' Blog

Department of Problem Prevention

Example of Removing Some Pain: Grid Fluent API

So on a previous project, we were making use a 3rd party WinForms UberGrid which, of course has a monstrous designer that spits out tons of code into your InitializeComponent() method in your .designer file. I think you know what I'm talking about, but for those who wish to experience it in all its glory, check out this:

Infragistics.Win.Appearance appearance4 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance1 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance2 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance3 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance7 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance6 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance5 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance9 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance11 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance10 = new Infragistics.Win.Appearance();
Infragistics.Win.Appearance appearance8 = new Infragistics.Win.Appearance();
this._lineItemsGrid = new Infragistics.Win.UltraWinGrid.UltraGrid();
((System.ComponentModel.ISupportInitialize)(this._lineItemsGrid)).BeginInit();
this.SuspendLayout();
// 
// _lineItemsGrid
// 
appearance4.BackColor = System.Drawing.SystemColors.Window;
appearance4.BorderColor = System.Drawing.SystemColors.InactiveCaption;
this._lineItemsGrid.DisplayLayout.Appearance = appearance4;
this._lineItemsGrid.DisplayLayout.AutoFitStyle = Infragistics.Win.UltraWinGrid.AutoFitStyle.None;
this._lineItemsGrid.DisplayLayout.BorderStyle = Infragistics.Win.UIElementBorderStyle.Solid;
this._lineItemsGrid.DisplayLayout.CaptionVisible = Infragistics.Win.DefaultableBoolean.False;
this._lineItemsGrid.DisplayLayout.EmptyRowSettings.ShowEmptyRows = true;
appearance1.BackColor = System.Drawing.SystemColors.ActiveBorder;
appearance1.BackColor2 = System.Drawing.SystemColors.ControlDark;
appearance1.BackGradientStyle = Infragistics.Win.GradientStyle.Vertical;
appearance1.BorderColor = System.Drawing.SystemColors.Window;
this._lineItemsGrid.DisplayLayout.GroupByBox.Appearance = appearance1;
appearance2.ForeColor = System.Drawing.SystemColors.GrayText;
this._lineItemsGrid.DisplayLayout.GroupByBox.BandLabelAppearance = appearance2;
this._lineItemsGrid.DisplayLayout.GroupByBox.BorderStyle = Infragistics.Win.UIElementBorderStyle.Solid;
this._lineItemsGrid.DisplayLayout.GroupByBox.Hidden = true;
appearance3.BackColor = System.Drawing.SystemColors.ControlLightLight;
appearance3.BackColor2 = System.Drawing.SystemColors.Control;
appearance3.BackGradientStyle = Infragistics.Win.GradientStyle.Horizontal;
appearance3.ForeColor = System.Drawing.SystemColors.GrayText;
this._lineItemsGrid.DisplayLayout.GroupByBox.PromptAppearance = appearance3;
this._lineItemsGrid.DisplayLayout.MaxColScrollRegions = 1;
this._lineItemsGrid.DisplayLayout.MaxRowScrollRegions = 1;
appearance7.BackColor = System.Drawing.SystemColors.Highlight;
appearance7.ForeColor = System.Drawing.SystemColors.HighlightText;
this._lineItemsGrid.DisplayLayout.Override.ActiveRowAppearance = appearance7;
this._lineItemsGrid.DisplayLayout.Override.AllowAddNew = Infragistics.Win.UltraWinGrid.AllowAddNew.No;
this._lineItemsGrid.DisplayLayout.Override.AllowColMoving = Infragistics.Win.UltraWinGrid.AllowColMoving.NotAllowed;
this._lineItemsGrid.DisplayLayout.Override.AllowColSizing = Infragistics.Win.UltraWinGrid.AllowColSizing.Free;
this._lineItemsGrid.DisplayLayout.Override.AllowColSwapping = Infragistics.Win.UltraWinGrid.AllowColSwapping.NotAllowed;
this._lineItemsGrid.DisplayLayout.Override.AllowDelete = Infragistics.Win.DefaultableBoolean.False;
this._lineItemsGrid.DisplayLayout.Override.AllowRowFiltering = Infragistics.Win.DefaultableBoolean.False;
this._lineItemsGrid.DisplayLayout.Override.AllowRowSummaries = Infragistics.Win.UltraWinGrid.AllowRowSummaries.False;
this._lineItemsGrid.DisplayLayout.Override.AllowUpdate = Infragistics.Win.DefaultableBoolean.False;
this._lineItemsGrid.DisplayLayout.Override.BorderStyleCell = Infragistics.Win.UIElementBorderStyle.Dotted;
this._lineItemsGrid.DisplayLayout.Override.BorderStyleRow = Infragistics.Win.UIElementBorderStyle.Dotted;
appearance6.BackColor = System.Drawing.SystemColors.Window;
this._lineItemsGrid.DisplayLayout.Override.CardAreaAppearance = appearance6;
appearance5.BorderColor = System.Drawing.Color.Silver;
appearance5.FontData.Name = "QuickType Mono";
appearance5.FontData.SizeInPoints = 10F;
appearance5.TextTrimming = Infragistics.Win.TextTrimming.EllipsisCharacter;
this._lineItemsGrid.DisplayLayout.Override.CellAppearance = appearance5;
this._lineItemsGrid.DisplayLayout.Override.CellClickAction = Infragistics.Win.UltraWinGrid.CellClickAction.RowSelect;
this._lineItemsGrid.DisplayLayout.Override.CellPadding = 0;
this._lineItemsGrid.DisplayLayout.Override.ColumnSizingArea = Infragistics.Win.UltraWinGrid.ColumnSizingArea.EntireColumn;
appearance9.BackColor = System.Drawing.SystemColors.Control;
appearance9.BackColor2 = System.Drawing.SystemColors.ControlDark;
appearance9.BackGradientAlignment = Infragistics.Win.GradientAlignment.Element;
appearance9.BackGradientStyle = Infragistics.Win.GradientStyle.Horizontal;
appearance9.BorderColor = System.Drawing.SystemColors.Window;
this._lineItemsGrid.DisplayLayout.Override.GroupByRowAppearance = appearance9;
appearance11.TextHAlignAsString = "Left";
this._lineItemsGrid.DisplayLayout.Override.HeaderAppearance = appearance11;
this._lineItemsGrid.DisplayLayout.Override.HeaderStyle = Infragistics.Win.HeaderStyle.WindowsXPCommand;
appearance10.BackColor = System.Drawing.SystemColors.Window;
appearance10.BorderColor = System.Drawing.Color.Silver;
this._lineItemsGrid.DisplayLayout.Override.RowAppearance = appearance10;
this._lineItemsGrid.DisplayLayout.Override.RowSelectorHeaderStyle = Infragistics.Win.UltraWinGrid.RowSelectorHeaderStyle.SeparateElement;
this._lineItemsGrid.DisplayLayout.Override.RowSelectors = Infragistics.Win.DefaultableBoolean.True;
this._lineItemsGrid.DisplayLayout.Override.RowSizing = Infragistics.Win.UltraWinGrid.RowSizing.Fixed;
this._lineItemsGrid.DisplayLayout.Override.SelectTypeCell = Infragistics.Win.UltraWinGrid.SelectType.None;
this._lineItemsGrid.DisplayLayout.Override.SelectTypeCol = Infragistics.Win.UltraWinGrid.SelectType.None;
this._lineItemsGrid.DisplayLayout.Override.SelectTypeRow = Infragistics.Win.UltraWinGrid.SelectType.Single;
this._lineItemsGrid.DisplayLayout.Override.SummaryDisplayArea = Infragistics.Win.UltraWinGrid.SummaryDisplayAreas.None;
appearance8.BackColor = System.Drawing.SystemColors.ControlLight;
this._lineItemsGrid.DisplayLayout.Override.TemplateAddRowAppearance = appearance8;
this._lineItemsGrid.DisplayLayout.ScrollBounds = Infragistics.Win.UltraWinGrid.ScrollBounds.ScrollToFill;
this._lineItemsGrid.DisplayLayout.ScrollStyle = Infragistics.Win.UltraWinGrid.ScrollStyle.Immediate;
this._lineItemsGrid.Dock = System.Windows.Forms.DockStyle.Top;
this._lineItemsGrid.Location = new System.Drawing.Point(0, 0);
this._lineItemsGrid.Name = "_lineItemsGrid";
this._lineItemsGrid.Size = new System.Drawing.Size(908, 226);
this._lineItemsGrid.TabIndex = 0;
this._lineItemsGrid.Text = "_lineItemsGrid";

Rather than having to deal with all that code and all the potential for inconsistencies in style, behavior, etc in all the various screens in which grids would appear, we decided to wrap the bulk of the building of appearances into a fluent API that ended up looking something like this:

new GridLayout(_lineItemsGrid)
    .AutoFitColumns()
    .EditingDisabled()
    .AddColumn("Id").ThatIsHidden()
    .AddColumn("Description").WithWidth(225)
    .AddColumn("Quantity").WithWidth(40).FormatValueAs("F4").AlignRight()
    .AddColumn("Size").WithWidth(50).AlignRight().FormatValueAs("F4")
    .AddColumn("Price").WithWidth(50).AlignRight().FormatValueAs("C4")
    .OnRowSelected((values) => _model.ViewItem(values["Id"]))
    .OnRowDoubleClick((values) => _model.EditItem(values["Id"]));

Now, when a feature came in for us to change certain styles or behavior of all the grids (i.e. make the headers look like THIS) we changed it in one place and it affected everything.

We considered using a grid-wrapping UserControl for this type of reuse and behavior, but it turns out that about half of the code was common among all grids and half was specific to each form. So we would've had to build a kitchen-sink type UserControl that ultimately would've taken us down the path of exposing much of the raw grid API to each form which was not as desirable.

Other benefits we gained by this approach:

  1. Testing was much easier because the GridLayout builder could embed certain controls and special tags on controls in order to highlight things for StoryTeller/Fit testing.  Also, we could test that certain conventions were being followed by all the forms rather than having each form build its own grid and having to test that it did it correctly, we could just test/enforce that the GridLayout builder worked properly.
  2. When we started making the switch to WPF, we were able to switch to a WPF grid control with only a one-line change to each view (to change the type declaration of _lineItemsGrid).
  3. Easier integration with various services like user permissions (i.e. certain users can never see the 'Margin' column in any grid)

Building a Fluent Interface To Drive Settings On Another Object

Building a fluent interface like this was pretty easy: just make each call return the instance of GridLayout! 

Here's an example method:

public GridLayout AutoFitColumns()
{
    _grid.DisplayLayout.AutoFitStyle = AutoFitStyle.ResizeAllColumns;
    return this;
}

Building up the columns required that I remember the last column that was added so I can keep context.

For example:

public GridLayout AddColumn(string key)
{
    _lastColumn = _grid.DisplayLayout.Bands[0].Columns.Add(key);
    return this;
}

public GridLayout ThatIsHidden()
{
    _lastColumn.Hidden = true;
    return this;
}

public GridLayout Header(string header)
{
    _lastColumn.Header.Caption = header;
    return this;
}
Published Jun 28 2008, 11:37 PM by chadmyers
Filed under: ,

Comments

 

Florian Potschka said:

How true. Especially GUI code often tends to be extreme verbose and repetitive. So a fluent API is a great benefit for this kind of code.

June 29, 2008 2:48 AM
 

Dew Drop - June 29, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - June 29, 2008 | Alvin Ashcraft's Morning Dew

June 29, 2008 6:59 AM
 

Macca said:

Chad,

Nice, thanks for taking the time to elaborate on your previous post (and so quickly too!). Very useful indeed.

June 29, 2008 7:09 AM
 

Jeff Certain said:

Chad,

I just finished doing something similar for a bunch of our "helper methods" for these same controls. I'd been wondering about the Appearance verbosity, and now I've got a good pointer for the right road to go down.

Thanks!

Jeff

June 30, 2008 9:10 AM
 

Derick Bailey said:

Is it worth creating an IGridLayout interface that can be injected, so that this layout code can be unit tested? (i have my own opinion, but want to see what you think)

June 30, 2008 1:41 PM
 

Chad Myers said:

Derick:

You could do that as an interaction test. What we ended up doing was just checking that the Grid had the columns we expected in the end. I was less concerned with the interaction with the GridLayout FI than I was with the end state (in this case).

It made for easier state based testing so we could do things like:

layoutForm.Grid.ShouldHaveColumn("Description");

June 30, 2008 1:55 PM
 

Derick Bailey said:

that's basically what I was thinking, too. The only reason I would bother with DI for testability would be when the layout of the control was truly really a business concern. Off-hand, though, I can't think of any real examples where the layout would be the business concern, except for the business of custom controls.

June 30, 2008 2:02 PM
 

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

July 1, 2008 5:57 PM
 

infragistics | Start a new day said:

Pingback from  infragistics  | Start a new day

July 16, 2008 8:47 AM
 

infragistics | Found love here said:

Pingback from  infragistics  | Found love here

July 16, 2008 8:58 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About chadmyers

Chad Myers is a senior .NET software developer specializing in enterprise software designs and architectures. He has over 10 years of software development experience and a proven track record of Agile, test-driven project leadership using both Microsoft and open source tools. He is community leader who speaks at the Austin .NET User's Group, the ADNUG Code Camp, and participates in various development communities and open source projects.
Copyright Los Techies 2007. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems