Group Customization Demo

This sample demonstrate how to customize the layout of groups. It adds a custom section for each group and displays the summary section above the group details. This sample also shows how to derive custom elements from a group and child table, and how to hook them into a customized engine object.

The following is an image of the sample.

CustomSectionInGroup screenshot

In the form's ctor(), a new GroupingEngineFactory is assigned, which has the modified GridChildTable in order to have an extra section.


    		// GroupingEngineFactory provides a modified GridChildTable that adds an extra section.
    		GridEngineFactory.Factory = new GroupingEngineFactory();  

You will be able to see the GroupingEngineFactory implementation in CustomEngine.cs. GroupingEngineFactory returns a customized grid engine by overriding the CreateEngine() method.


    		/// <summary>
    		/// GroupingEngineFactory provides a trimmed down GridGroup which eliminates overhead of not needed preview rows,
    		/// header and footer cells.
    		/// </summary>
    		public class GroupingEngineFactory : GridEngineFactoryBase
    		{
    			// Add this line in your forms ctor:
    			// GroupingEngineFactory provides a modified GridChildTable that adds an extra section.
    			// GridEngineFactory.Factory = new GroupingEngineFactory();
    			public override GridEngine CreateEngine()
    			{
    				return new GroupingEngine();
    			}
    		}  

The grouping engine derives from the grid engine and overrides the CreateChildTable() method in order to return a customized child table.  It also overrides the CreateGroup() method in order to return a customized group.

    		public class GroupingEngine : GridEngine
    		{
    			public override ChildTable CreateChildTable(Element parent)
    			{
    				return new GroupingChildTable(parent);
    			}
    			public override Group CreateGroup(Section parent)
    			{
    				return new GroupingGroup(parent);
    			}
    		}  

The extra section to be displayed at the top is derived from the empty section providing the element count and the section width.

The grouping child table is derived from the ChildTable class and implements IGridGroupOptionsSource. It overrides the OnInitializeSections method in order to add the sections that should be shown in the top-level group in the following order:

    		// GroupingChildTable class.
    		protected override void OnInitializeSections(bool hasRecords, SortColumnDescriptorCollection fields)
    		{
    			// Caption
    			this.Sections.Add(this.ParentTableDescriptor.CreateCaptionSection(this));

    			// Column Headers
    			this.Sections.Add(this.ParentTableDescriptor.CreateColumnHeaderSection(this));

    			// ExtraSection
    			this.Sections.Add(new ExtraSection(this));

    			// AddNewRecord
    			AddNewRecordSection addNewRecordSectionBeforeDetails = this.ParentTableDescriptor.CreateAddNewRecordSection(this);
    			addNewRecordSectionBeforeDetails.IsBeforeDetails = true;
    			this.Sections.Add(addNewRecordSectionBeforeDetails);

    			// Details (Records or Groups)
    			if (hasRecords)
    				this.Sections.Add(this.ParentTableDescriptor.CreateRecordsDetails(this, fields));
    			else
    			this.Sections.Add(this.ParentTableDescriptor.CreateGroupsDetails(this, fields));

    			// Summary
    						this.Sections.Add(this.ParentTableDescriptor.CreateSummarySection(this));
    		}  

The Grouping group is derived from the Group class and implements IGridGroupOptionsSource. It overrides the OnInitializeSections method in order to add the sections that should be shown in the group in the following order:

    		// GroupingGroup class
    		protected override void OnInitializeSections(bool hasRecords, SortColumnDescriptorCollection fields)
    		{
    			// Caption
    			this.Sections.Add(this.ParentTableDescriptor.CreateCaptionSection(this));

    			// Extra section
    			this.Sections.Add(new ExtraSection(this));

    			// Summary
    			this.Sections.Add(this.ParentTableDescriptor.CreateSummarySection(this));

    			// AddNewRecord
    			AddNewRecordSection addNewRecordSectionBeforeDetails = this.ParentTableDescriptor.CreateAddNewRecordSection(this);
    			addNewRecordSectionBeforeDetails.IsBeforeDetails = true;
    			this.Sections.Add(addNewRecordSectionBeforeDetails);

    			// Details (Records or Groups)
    			if (hasRecords)
    				this.Sections.Add(this.ParentTableDescriptor.CreateRecordsDetails(this, fields));
    			else
    				this.Sections.Add(this.ParentTableDescriptor.CreateGroupsDetails(this, fields));
    		}  

Three cells in the extra sections are covered by handling the table model's QueryCoveredRange event. You can see the extra section, Orders, covering the order ID, customer ID, and employee ID in the above images.

    		int extraSectionCoverCols = 3;
    		private void TableModel_QueryCoveredRange(object sender, GridQueryCoveredRangeEventArgs e)
    		{
    			GridTable thisTable = this.gridGroupingControl1.Table;
    			if (e.RowIndex < thisTable.DisplayElements.Count)
    			{
    				Element el = thisTable.DisplayElements[e.RowIndex];
    				if (el is ExtraSection)
    				{
    					// Cover some cells of the extra section (specified with extraSectionCoverCols).
    					int startCol = el.GroupLevel + 1;  // Add +1 so we have place for column header
    					if (e.ColIndex >= startCol && e.ColIndex <= this.extraSectionCoverCols + el.ParentTableDescriptor.GroupedColumns.Count)
    					{
    						e.Range = GridRangeInfo.Cells
    						(e.RowIndex, startCol, e.RowIndex, this.extraSectionCoverCols + el.ParentTableDescriptor.GroupedColumns.Count);
    						e.Handled = true;
    					}
    				}
    			}
    		}  

The extra section is populated by handling the QueryCellStyleInfo event. The extra section’s Freight column is assigned with the freight-average value by getting the summary values using the GridEngine.GetSummaryText method.

    		// QueryCellStyleInfo event handler in Form1.cs.
    		// Using that column you could try and identify the summary that should be displayed in this cell.
                    		  if( column.MappingName == "Freight")
                    		  {
                        			  // Calling this method to demonstrate different alternatives to get the summary text.
                        			  e.Style.Text = GetSummaryText(el.ParentGroup, "SummaryRow 1", "FreightAverage");

                        			   // Easier method is to simply call the built-in routine.
                        			 e.Style.Text = GridEngine.GetSummaryText(el.ParentGroup, "SummaryRow 1", "FreightAverage");
                    		  }  

Also, a record indicator is drawn for the extra section, if it becomes a current element, by handling the grouping grid's TableControlCellDrawn event.


    		private void gridGroupingControl1_TableControlCellDrawn(object sender, GridTableControlDrawCellEventArgs e)
    		{
    			GridTableCellStyleInfo style = e.TableControl.Model[e.Inner.RowIndex, e.Inner.ColIndex];
    			GridTableCellStyleInfoIdentity id = style.TableCellIdentity;
    			Element el = id.DisplayElement;
    			if (el is ExtraSection)
    			{
    				if (id.ColIndex == 0)
    				{
    					// Row Header and ExtraSection is CurrentElement.
    					if (el == el.ParentTable.CurrentElement)
    					{
    						// Draw Record Indicator.
    						try
    						{
    							Rectangle iconBounds = Rectangle.FromLTRB
    							(e.Inner.Bounds.Right-15, e.Inner.Bounds.Top, e.Inner.Bounds.Right, e.Inner.Bounds.Bottom);
    							iconBounds.Offset(-2, 0);
    							IconPainter.PaintIcon(e.Inner.Graphics, iconBounds, Point.Empty, "SFARROW.BMP", style.TextColor);
    						}
    						catch
    						{}
    					}
    				}
    			}
    		}