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:
- 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.
- 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).
- 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; }