About 18 months ago we decided to make the jump from the standard Visual Studio Windows Forms toolset to Infragistics’ NetAdvantage.

The decision was motivated primarily by frustration with the limitations of the DataGridView.  We use grids all over the place in our application, and it is important to users that they be able to organize the data by grouping it, filtering it and reordering the columns.  It seemed a waste of time to build our own extensions to DataGridView to provide these capabilities when there were so many 3rd party tools that had already done that work.  After trying out a few different packages, I decided that the Infragistics toolset had the best combination of features, price and learning curve.

Of course, the downside to using any 3rd party tools is the dreaded “lock-in” — as you build code around those tools it becomes harder and harder to drop the tool if you no longer like it.  Fortunately we haven’t had much reason to regret the decision so far. NetAdvantage has proven to be quite stable, and the support that Infragistics offers through their Knowledge Base and, in particular, their Forums means that we haven’t been stuck for long on any programming problems.

There are, however, a number of areas where the best way of implementing a feature using NetAdvantage isn’t obvious.  So, I thought it might be helpful to fellow Infragistics users to publish a series of articles sharing some coding tips.

One caveat: we haven’t kept up with Infragistics’ quarterly updates to their product, so these tips are based on  NetAdvantage 2007 Version 1.  It’s possible that workarounds are not needed with more recent versions, though the other tips are probably still valid:

Row Filters

NetAdvantage includes a relatively sophisticated runtime GUI to allow users to enter values or formulas for filtering a column.  The options even include regular expressions, as shown in the screenshot, though admittedly most users (and, sadly, many developers) have no idea how to make use of them.  

All of these same filters are also available to your code.  For example, to provide the user with the ability to toggle between a simple view and a detailed view of the data, it is often easiest (and fastest) to load all of the details into the grid, then use a filter to hide all of the detail rows when the user selects the “simple view” option.  If there isn’t a column which distinguishes the detail rows that you want to filter out, just add one to the underlying List — your code can filter on columns that aren’t even visible in the grid!

Here’s a generalized method for setting a filter which displays only rows which contain specific values in a specific column:

public static void Infragistics_FilterSetValues(UltraGridBand band, string strColumnName,
object[] strValues)
{
band.ColumnFilters[strColumnName].ClearFilterConditions(); // clear any previous filter
band.ColumnFilters[strColumnName].LogicalOperator = FilterLogicalOperator.Or;
for (int i = 0; i < strValues.Length; i++)
{
band.ColumnFilters[strColumnName].FilterConditions.Add( FilterComparisionOperator.Equals,
strValues[i]);
}
}

To prevent the user from changing the filter on a particular column:

band.Columns["StrMarketCode"].AllowRowFiltering = DefaultableBoolean.False;

To prevent the user from changing the filter on any column of the grid:

band.Override.AllowRowFiltering = DefaultableBoolean.False;

If you allow users to filter the rows themselves, then you may need to know when they’ve changed the filters so that you can adjust any related data on the screen.  For example, if displaying some summary statistics in a separate grid you’ll need to update the summary when the user filters out some data .

A good event to use is AfterRowFilterChanged.  You can call the following method to get the new filters for the column:

public static void Infragistics_FilterGetValues(UltraGridBand band,
string strColumnName, out FilterComparisionOperator[] filterOperators, out object[] filterValues)
{
FilterConditionsCollection filters = band.ColumnFilters[strColumnName].FilterConditions;
filterOperators = new FilterComparisionOperator[filters.Count];
filterValues = new object[filters.Count];
for (int i=0; i < filters.Count; i++)
{
filterOperators[i] = filters[i].ComparisionOperator;
filterValues[i] = filters[i].CompareValue;
}
}

Turning off unwanted grid features

The first time you drag an Infragistics UltraGrid onto a window and fire it up, you’ll be surprised to find that users can:

  • see every column in the underlying Data Source, including child Lists or Tables (which appear as drill-down rows in the grid)
  • split the grid into regions (similar to Excel’s Split feature)
  • prevent selected columns from scrolling (similar to Excel’s Freeze feature)

These features are all enabled by default.  You can easily use the Designer GUI to turn them off, but in order to enforce a consistent set of rules for all our grids I found it preferable to write some common code that sets some global defaults:

//default is to not allow user to split into multiple sections
grid.DisplayLayout.MaxColScrollRegions = 1;
// don’t allow multiple row scroll regions either
grid.DisplayLayout.MaxRowScrollRegions = 1;
// set number of bands (drill down levels)
grid.DisplayLayout.MaxBandDepth = intNumBands;

if (intNumBands == 1)
grid.DisplayLayout.ViewStyle = ViewStyle.SingleBand;
else
// only display the expansion indicator when child bands exist
grid.DisplayLayout.Override.ExpansionIndicator = ShowExpansionIndicator.CheckOnDisplay;

// scroll grid contents immediately by default
grid.DisplayLayout.ScrollStyle = ScrollStyle.Immediate;
// change background of advanced filter cells that contain values (to catch the user’s eye)
grid.DisplayLayout.Override.FilterCellAppearanceActive.BackColor = Color.PaleGoldenrod;

// set band-level settings for each band
for (int intBandCounter = 0; intBandCounter < intNumBands; intBandCounter++)
{
// support multiple column sorting by default
grid.DisplayLayout.Bands[intBandCounter].Override.HeaderClickAction = HeaderClickAction.SortMulti;

// prevent push-pin from appearing in header
grid.DisplayLayout.Bands[intBandCounter].Override.FixedHeaderIndicator = FixedHeaderIndicator.None;

// don’t use Row Layout as default — otherwise can’t set visible position of columns
grid.DisplayLayout.Bands[intBandCounter].UseRowLayout = false;
}

Saving Grid Settings

With so many grid customizations available to the user, it’s important to save the settings so that the user doesn’t have to redo the customizations each time they run the application. Fortunately, Infragistics provides methods for serializing and deserializing all of the grid settings using XML.  We use the .Net AppSettings class to save the XML strings in a configuration file.  (Incidentally, Infragistics provides most of its grid features in the selection list of its combo box too, and this same code can be used to save settings for an UltraCombo control).

The following code saves the settings — we do this automatically in the Form_Closing event. (Note that I embed the application version number and the user’s language setting at the beginning — the calling code may need to make some adjustments if these settings are loaded by a later version or in a different language).

MemoryStream stream = new MemoryStream();
ourGrid.DisplayLayout.SaveAsXml(stream);
clsGlobal.Infragistics_SaveSettings(“ourGridLayout”, stream);




public static void Infragistics_SaveSettings(string strComponentID, MemoryStream stream)
{


byte[] bytLayout = stream.ToArray();
ASCIIEncoding ascEncoding = new ASCIIEncoding();
//string strSettings = ascEncoding.GetString(bytLayout);
//add a prefix with the product version and current language
string strPrefix = “!!” + Application.ProductVersion + “, ” +


clsGlobal.AppSettings[strComponentID] = Array_Concatenate(bytPrefix, bytLayout);

}

In the Form_Load event, we call the following method for each of the components.  Note that I use the version and language settings that I stored in the configuration file to warn the calling code if either has changed.  This is particularly important for the language, since all of the header captions and other displayable text are embedded in the settings string.  If the user has switched to another language (unlikely for real users, but common for our salespeople), the calling code should update all of this text with the correct language.

public static bool Infragistics_LoadSettings(string strComponentID, out MemoryStream stream,
out bool blnSettingsOK, out bool blnCaptionsOK)
{
// out parms must be initialized
blnSettingsOK = false;
blnCaptionsOK = false;
// load saved grid layouts
byte[] bytLayout = new byte[0];try
{
bytLayout = (byte[])clsGlobal.AppSettings[strComponentID];
}
catch (System.Configuration.SettingsPropertyNotFoundException)
{
Debug_WriteToLog(“Infragistics_LoadSettings – property not found: ” + strComponentID);
stream = new MemoryStream();
return false;
// ignore property not found — it won’t exist the first time the program is run
}if (!Object_IsNullOrBlank(bytLayout))
{
int intOffset = Array.IndexOf(bytLayout, Byte.Parse(“254″));
if (intOffset < 0)
{
// shouldn’t happen – not a valid layout
stream = new MemoryStream();
return false;
}

// strip off the prefix
ASCIIEncoding ascEncoding = new ASCIIEncoding();
string strPrefix = ascEncoding.GetString(bytLayout, 0, intOffset);
if (strPrefix.StartsWith(“!!”))
{
int intEndOffset = strPrefix.IndexOf(“!!”, 2);
if (intEndOffset > 0)
{
strPrefix = strPrefix.Substring(2, intEndOffset – 2);
string[] strParts = Array_FromDelimitedList(strPrefix);
if (strParts.Length >= 1)
{
blnSettingsOK = (strParts[0] == Application.ProductVersion);
}
if (blnSettingsOK & (strParts.Length >= 2))
{
blnCaptionsOK = (strParts[1] == NLS.ThisLanguage);
}
}
}

stream = new MemoryStream(bytLayout, intOffset + 1, bytLayout.Length – intOffset – 1);
return true;
}
else
{
stream = new MemoryStream();
return false;
}

}

Incidentally, don’t forget that users may make a change to the grid settings that they don’t want to keep (and they may do it inadvertently without noticing at the time), so be sure to give users a way to revert to the default settings for your application.