Infragistics NetAdvantage Tips – Part 3: Editing with the UltraGrid


In this installment of the Infragistics NetAdvantage series, I’m going to revisit the UltraGrid, which I wrote about in Part 1

The UltraGrid (nee WinGrid) is one of those programming tools that "just works", as long as you’re happy with the way it works.  If you just want to give users a way to view and edit data in Lists or DataTables the UltraGrid works quite well out of the box.  It also provides a lot of useful features that the standard DataGridView doesn’t include and which require virtually no coding to implement: filtering, grouping (similar to a pivot table), and Excel export, to name just three.

However, if you’re one of those finicky developers who modifies their code to match the design, and not vice versa, then the UltraGrid’s rich feature set, backed by a vast set of properties and methods, becomes a steep learning curve.  How do you paste the clipboard into a selected range of cells, like Excel does?  How do you allow the user to navigate through the grid using the Enter key, also like Excel does?  How do you make certain rows or certain columns read-only at runtime?

Well, RTFM is a good start.  The NetAdvantage documentation is excellent, far better than most development tools.  In addition to the standard listing of classes, properties, methods and events, the help file includes "How Do I?" and "Walkthroughs" sections for many of their controls, covering some of the more commonly used non-obvious features.  Their online KnowledgeBase is a good source of sample code, though it can be hard to sift out the obsolete material.  Their online forums are also a great resource.  Infragistics employees closely monitor the forums and provide answers to almost all of the questions — every time that I’ve used the forum I’ve come away with the answer I needed.

But, of course, the first thing that most developers do to find an answer is "Google it".  And that is probably how you came to be reading my humble blog.  So, without further ado, here is my list of non-obvious answers to common problems when editing with the Infragistics UltraGrid.

Changing the editable state of a cell

One of the first things that tripped me up when working with the UltraGrid is that it isn’t possible to specify that "column 2 in the rows 3, 4 and 7 should be read-only".  The "column 2" part is not a problem, but you can set the editable state of cells in the active row only.  So, your best bet is to handle the ActiveRowActivate event, and add code like the following:

if (grid.ActiveRow.IsDataRow)
{
	grid.ActiveRow.Band.Columns["columnkeyname"].TabStop = false;
	grid.ActiveRow.Band.Columns["columnkeyname"].CellActivation = Activation.NoEdit
}

There are a number of things in this short piece of code that require some explanation for those not accustomed to the ways of the UltraGrid:

  • The IsDataRow property is necessary to make sure that you’re not trying to set the edit state of a row that isn’t displaying your data, such as the header rows that the UltraGrid automatically inserts when using its Group By feature.
  • The Band property is used to identify which drill-down level the row belongs to.  An example is the screenshot at the right, from Infragistics’ website, showing a grid with 3 bands.  And if you’re working with a plain vanilla grid with just 1 set of rows?  Tough, you’ve still got a band, like it or not, so get used to coding a Band reference for rows, columns and cells.
  • The only .CellActivation setting that allows users to Tab past a non-editable cell is "disabled", which also prevents the user from selecting the cell for copying into the clipboard.  So, you’ll generally want to use Activation.NoEdit and set the TabStop separately.


Changing the editable state of a row

You might spend hours looking in vain for the setting to make a row non-editable.  You can set the edit state for columns and cells, but not rows.  Here’s some code to do it the hard way:

public static void Infragistics_SetAllColumnsEditState(UltraGrid grid, bool blnEditable,
	bool blnAllowTab)
{
	UltraGridBand band = grid.ActiveRow.Band;

	for (int i = 0; i < band.Columns.Count; i++)  // columns belong to a band, not a row
	{
		if (!band.Columns[i].Hidden) // don't mess with hidden columns!
		{
			if (blnEditable)
			{
				column.TabStop = true;
				column.CellActivation = Activation.AllowEdit;
			}
			else
			{
				column.TabStop = blnAllowTab;
				column.CellActivation = Activation.NoEdit;
			}
		}
	}
}

Notice that Columns belong to a Band, not a Row.  Cells belong to a Row, but the CellActivation property doesn’t exist for Cells, just Columns.  Go figure.

Translating Grid Text

The UltraGrid is unusually verbose for a screen component: there’s text in the filter settings (as shown in the screenshot), text in the column chooser, text in the Group By interface, and so on.  All those prompts and instructions are better than Ikea-like pictograms, I suppose, but it’s all in English. 


If you need to support other languages, you’ll need to provide your own translations.   To do so, you’ll need a reference to the Infragistics ResourceCustomizer class, and a list of the keys used to identify each piece of text.

            Infragistics.Shared.ResourceCustomizer rc = Infragistics.Win.UltraWinGrid.Resources.Customizer;
            rc.SetCustomizedString("GroupByBoxDefaultPrompt", NLS.TxDragaColumnHeadr);
            rc.SetCustomizedString("ColumnChooserButtonToolTip", NLS.TxFieldChooser);
            rc.SetCustomizedString("ColumnChooserDialogCaption", NLS.TxFieldChooser);
            rc.SetCustomizedString("FilterClearButtonToolTip_RowSelector", NLS.TxClearAllFilterCriteria);
            // and so on...

The "NLS" in the above code is a reference to the project’s resource class where we store all our text translations.

Selecting All Rows in the Grid

This is another common task that seems more convoluted than necessary.  Rather than just tell the UltraGrid "Select All", you need to tell it "Add to the collection of selected rows all the data rows that are currently visible in the grid":

grid.Selected.Rows.AddRange(grid.Rows.GetFilteredInNonGroupByRows());

Make Enter Work Like Tab

A grid looks like a spreadsheet, so UltraGrid should work like Excel, right?  Your users will probably think so, but UltraGrid’s designers don’t entirely agree.  By default, pressing Enter in a grid cell won’t do anything.  The solution is extremely non-obvious, but fortunately it’s covered in the Infragistics KnowledgeBase article KB2028:

Infragistics.Win.UltraWinGrid.GridKeyActionMapping newKam;

foreach (Infragistics.Win.UltraWinGrid.GridKeyActionMapping ugKam in Grid.KeyActionMappings)
{
	if (ugKam.KeyCode == Keys.Tab)
	{
		newKam = new Infragistics.Win.UltraWinGrid.GridKeyActionMapping(Keys.Enter, ugKam.ActionCode, ugKam.StateDisallowed, ugKam.StateRequired, ugKam.SpecialKeysDisallowed, ugKam.SpecialKeysRequired);
		Grid.KeyActionMappings.Add(newKam);
	}
}

Select A Range of Cells

By default, when the user drags the mouse down a column of cells the grid will select cells row-by-row, similar to a Word document.  Most users will expect cell selection to work like Excel, not Word, and allow a rectangular region of cells to be selected.   Fortunately, turning on this support is a one-liner, but the trick is that you’ll need to put that 1 line in the InitializeLayout event:

private void grid_InitializeLayout(object sender, InitializeLayoutEventArgs e)
{

		// allow drag select like in Excel
		e.Layout.Override.SelectTypeCell = SelectType.Extended;

}

Paste Into A Range of Cells

This is another case where users will expect your grid to emulate Excel.  If you a copy a cell into the clipboard then select a rectangular set of cells and click Paste, they’ll want the contents of the clipboard to be pasted into the full range of selected cells, repeating the value(s) as often as necessary.  Unfortunately, to UltraGrid paste means "paste once", so you’ll have to do the work yourself:

int intNumRows;
int intNumCols;
UltraGridCell startingCell = GetSelectedCells(grid, out intNumRows, out intNumCols);
if (startingCell != null)
{
	string[,] strClipboardCells = Array_FromClipboard(strHeaderValues);
	int intClipboardRows = strClipboardCells.GetUpperBound(0) + 1;
	int intClipboardCols = strClipboardCells.GetUpperBound(1) + 1;
	// if only 1 cell is selected, assume that the user wants to paste the entire clipboard (Excel's behaviour)
	if ((intNumRows == 1) & (intNumCols == 1))
	{
		intNumRows = intClipboardRows;
		intNumCols = intClipboardCols;
	}
	if ((intClipboardRows > 0) &
		(intClipboardCols > 0))
	{
		int intClipboardRow = 0;
		int intClipboardCol = 0;
		int intCurrentRow = 0; // row index within the selection
		int intCurrentCol = 0; // col index within the selection
		int intStartingColOfClipboardBlock = 0; // col index within selection of start of current paste block
		int intStartingRowOfClipboardBlock = 0; // row index within selection of start of current paste block
		UltraGridCell currentCell = startingCell;
		UltraGridRow currentRow = currentCell.Row;
		UltraGridCell startingCellOfClipboardBlock = startingCell;
		UltraGridColumn col; // current column to be pasted in the grid
		UltraGridRow row; //current row to be pasted in the grid

		while (intCurrentRow < intNumRows)
		{
			// paste the value
			currentCell.Value = strClipboardCells[intClipboardRow, intClipboardCol];

			// advance to next cell in clipboard
			intClipboardCol += 1;
			// advance to next cell in selection block
			intCurrentCol += 1;
			// advance to the next cell in the grid
			col = currentCell.Column.GetRelatedVisibleColumn(VisibleRelation.Next);
			// if we've reached the last cell of the clipboard, or last cell of the selection block, or last cell of
			//   the grid, need to wrap around
			if ((intClipboardCol >= intClipboardCols) | (intCurrentCol >= intNumCols) | (col == null))
			{
				intClipboardRow += 1;
				intClipboardCol = 0;
				row = currentCell.Row.GetSibling(SiblingRow.Next);
				intCurrentRow += 1;
				intCurrentCol = intStartingColOfClipboardBlock;

				// if we've reached the last row of the clipboard, or last row of the selection block, or last row of
				//   the grid, need to advance to next set of columns to be pasted
				if ((intClipboardRow >= intClipboardRows) | (intCurrentRow >= intNumRows) | (row == null))
				{
					// start pasting a new clipboard block, unless we've reached the end of the selection
					intCurrentRow = intStartingRowOfClipboardBlock;
					intClipboardCol = 0;
					intClipboardRow = 0;
					intCurrentCol = intStartingColOfClipboardBlock + intClipboardCols;
					intStartingColOfClipboardBlock = intCurrentCol;
					col = currentCell.Column.GetRelatedVisibleColumn(VisibleRelation.Next);
					if ((intCurrentCol >= intNumCols) | (col == null))
					{
						// reached last column of paste selection, advance to next set of rows to be pasted into
						intCurrentRow += intClipboardRows;
						intCurrentCol = 0;
						intStartingColOfClipboardBlock = 0;
						intStartingRowOfClipboardBlock = intCurrentRow;
						if (row != null)
						{
							currentCell = row.Cells[startingCell.Column.Key];
							startingCellOfClipboardBlock = currentCell;
						}
						// else, we've reached the end of the grid, so presumably intRow is > intNumRows and the
						//   while loop will end
					}
					else
					{
						// start pasting a new block in the same row as the previous block
						currentCell = startingCellOfClipboardBlock.Row.Cells[col.Key];
						startingCellOfClipboardBlock = currentCell;
					}
				}
				else
				{
					// wrap around to next cell
					currentCell = row.Cells[startingCellOfClipboardBlock.Column.Key];

				}
			}
			else
			{
				currentCell = currentCell.Row.Cells[col.Key];

			}
		}
	}
}

Much of this code is pretty mundane stuff that I’m including to complete the solution, but a couple of odd methods stand out. The call to currentCell.Column.GetRelatedVisibleColumn(VisibleRelation.Next) is a complicated solution to a seemingly easy requirement: get the next column to the right.  Columns in UltraGrid are indexed according to their position in the data source, not on the screen. Similarly, to move to the next row you can’t just increment the row index: that won’t skip past filtered-out rows and hidden row.  If you’re using drill-down rows then row navigation becomes even more complicated, so the approach I use is currentCell.Row.GetSibling(SiblingRow.Next).   Surprisingly, there is no collection in the UltraGrid that presents the grid cells in the same order that the user sees them — your code will have to figure that out on the fly in order to navigate through the grid cell by cell.

Notice that the instruction that does the actual pasting is a one-liner that is overshadowed by all the grid navigation code — picking that line out of the pile is sort of like playing "Where’s Waldo".

The Array_FromClipboard method called by this code is pretty self-explanatory, but the GetSelectedCells method qualifies as non-obvious because of the same navigation problems described above.  It’s easy to get the collection of selected cells — grid.Selected.Cells — but that collection of cells isn’t nicely organized into rows and columns for you.  To figure out how many rows and columns are selected, and which of the cells is the upper left corner of the selection, you’ll need to do a bit of work:

// initialize
int intFirstSelectedRow = Int32.MaxValue;
int intFirstSelectedCol = Int32.MaxValue;

int intLastSelectedRow = -1;
int intLastSelectedCol = -1;
UltraGridCell upperLeftCell = null;

foreach (UltraGridCell cell in grid.Selected.Cells)
{
	bool blnFirstCol = false;
	bool blnFirstRow = false;
	if (cell.Row.VisibleIndex < intFirstSelectedRow)
	{
		// use visible row -- otherwise, hidden network babies cause problems
		intFirstSelectedRow = cell.Row.VisibleIndex;
		blnFirstRow = true;
	}
	if (cell.Row.VisibleIndex > intLastSelectedRow)

		intLastSelectedRow = cell.Row.VisibleIndex;
	if (cell.Column.Header.VisiblePosition < intFirstSelectedCol)
	{
		intFirstSelectedCol = cell.Column.Header.VisiblePosition;
		blnFirstCol = true;
	}
	if (cell.Column.Header.VisiblePosition > intLastSelectedCol)
		intLastSelectedCol = cell.Column.Header.VisiblePosition;
	if (blnFirstCol & blnFirstRow)
		upperLeftCell = cell;
}

intNumSelectedCols = intLastSelectedCol - intFirstSelectedCol + 1;
intNumSelectedRows = intLastSelectedRow - intFirstSelectedRow + 1;

Notice that the VisibleIndex and VisiblePosition properties are used instead of Index and Position.  The rows’ Index property includes filtered-out rows, while the columns’ Position property includes hidden columns and does not account for the fact that UltraGrid allows users to drag columns to a new position.

Tabbing Into the Grid

OK, for my last trick of the day, I’d like to cover yet another scenario that should "just work", but doesn’t.  If a user tabs into a grid, they’ll expect focus to be on the first editable cell so that they can just start typing.  By default, when you tab into UltraGrid it gives the focus to… nothing. As far as I can tell, it isn’t possible for users to start editing a grid without using the mouse.  After much trial and error, I ended up adding the following code to the grid’s Enter event:

if (grid.ActiveCell == null)
{
	if (grid.ActiveRow == null)
	{
		if (grid.Rows.Count == 0)
			return; // empty grid, so no way to give a cell focus
		grid.ActiveRow = grid.Rows[0];
	}
	// make the leftmost cell the active cell -- will find the first editable cell below
	grid.ActiveCell = grid.ActiveRow.Cells["theKey"];
}
UltraGridCell cell = grid.ActiveCell;
if (!cell.CanEnterEditMode)
	// find the next editable cell
	grdRatings.PerformAction(UltraGridAction.NextCell);
if (grdRatings.ActiveCell != null)
{
	while (!grid.ActiveCell.CanEnterEditMode)
	{
		// have we reached the end?
		grid.PerformAction(UltraGridAction.NextCell);
		if (grid.ActiveCell == null)
			return; // give up
		if (grid.ActiveCell == cell)
			return; // we aren't going anywhere, give up
		if ((grid.ActiveCell.Row.Index == 0) & (grid.ActiveCell.Column.Key == "theKey"))
			return; // looped around, so give up
		cell = grid.ActiveCell;

	}
	if (cell.CanEnterEditMode)
		grid.PerformAction(UltraGridAction.EnterEditMode);

	grid.ActiveRowScrollRegion.ScrollCellIntoView(grid.ActiveCell, grid.DisplayLayout.ColScrollRegions[0]);
}

Ye gods, that’s a lot of work for such a simple and commonplace task!  I guess that’s why they pay us coders the big bucks. 

Notice that the list line of the above code sample contains a reference to something called a column scroll region.  Oooh, what’s that?  It’s a method of divided up your grid rows into sections that scroll independently, similar to Excel’s Split feature.  It’s also filled with more pitfalls than the Temple of Doom.  Sounds like a good topic for a future article!


Update on November 26, 2008:

In response to the question in the comments section about overriding the default UltraGrid error messages… 

In general the best way to prevent the Infragistics grid from displaying its error messages to the user is to handle the grid’s Error event.  Just set the e.Cancel parm to true.  This Infragistics help page includes a good example of the things you can do in the Error event.

In the case of null values, you can set the column’s Nullable property  to tell the grid that null values are allowed. If your grid’s data source is a database table then UltraGrid will automatically set this property to an appropriate value for the column — otherwise you’ll need to set this property yourself. The property is set to whatever value you want to display in the column when it is empty, which is usually a zero length string: e.g. col.Nullable = Infragistics.Win.UltraWinGrid.Nullable.EmptyString; 

This entry was posted in .Net, Programming. Bookmark the permalink.

12 Responses to Infragistics NetAdvantage Tips – Part 3: Editing with the UltraGrid

  1. Raj says:

    This is one of the most useful article on UltraGrid. I have spent hours to figure out these little stuffs before i stumbled on your writeup. Thanks a ton.

  2. Nguyen Thanh Tung says:

    Thanks u very much. This article is really useful for me. I’m a newcomer to Infragistics. I want to ask u a question: As above about “Translating Grid Text” but I don’t want to wrap again this text. I want to cancel show this default error message when input value to cell in grid. I want to fire event to catch error(e.x: cell is not null) then I’ll show my message and cancel default error message. Can you show me the way? or the event, please. Thanks u again.

  3. Chris says:

    For UltraGrid Cells, there is no CellActivation Property, but there is an Activation Property that works similarly.

    so to set row 7 column 3 to NoEdit it would be
    grid.Rows[7].Cells[3].Activation = Activation.NoEdit

  4. Wayne says:

    Do u have a VB.Net example?

  5. Matt says:

    Thanks a lot! Figuring out how to select multiple cells was killing me. I was disabling edit mode by cancelling the beforeEnterEditMode event but it still wasn’t allowing a multiple cell selection. Setting the Activation mode to NoEdit did the trick! You’re a lifesaver.

  6. James says:

    Thanks!!!

    This saved LOADS of effort – particularly the “by design” tab-into-grid feature that Infragistics forced upon us!

    You’re da man!

    James

  7. Nauman says:

    how can i duplication a row for example i want a duplicate row when user press shift + tab

  8. caesar says:

    Many Thank for u,
    this article so much useful for me..

    regards,

    caesar carlos

  9. Patrick Lynch says:

    My god, this intensive data entry screen with four grids was killing me when tabbing through it. Where did the focus go??? Now it’s a slam dunk, thanks for the Tabbing Into code, you are a god!

  10. abhi says:

    can any body tell me
    how can i edit the new row in the ultra grid.

  11. Rashed says:

    This article is greate, so let me ask you several question:
    1- how to make the grid support right to left, If there is no way then how can I modify the default behaviour of the resizign columns to act as it is right to left.
    2- what is the event of After resizing the column.
    3- how can I make the text in column shrink if the column gets very narrow.
    4- how can i let the row indicator in the right?

    Thanks RHD

    • Dan says:

      Hi,

      Good questions, but the only one that I know the answer to is #2: the AfterColPosChanged event is fired when a column is resized (as well as when a column is moved). The event handler has a parameter that tells you whether a column was resized, and which column it was.

      For #3, I don’t think there is a property setting that would cause the text to be automatically changed to fit the column width. You can change the column to use a vertical caption, rather than the default horizontal caption, by setting the ColumnHeader’s TextOrientation property.

      For #1 and #4, I don’t think the Infragistics controls have any settings to better support right-to-left languages, but I don’t have any experience there. You can try posting your question to the Infragistics forum (http://forums.infragistics.com/forums/178.aspx) to see if the experts have some advice.

      Thanks,

      Dan.