Browse DevX
Sign up for e-mail newsletters from DevX


The Baker's Dozen: 13 Productivity Tips for the Windows Forms DataGrid : Page 5

New developers often struggle with the .NET DataGrid when trying to replicate grid functionality from other platforms, while more experienced developers lament the deficiencies of the .NET DataGrid to address end user requirements. These tips will help both newbies and proficient developers work with the DataGrid.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Tip 9: Highlighting a Cell Based on a Condition
A question I see frequently with regard to the .NET DataGrid is how to highlight a cell in a different color based on a runtime condition. I come from the FoxPro world, where a developer could make use of DynamicForeColor and DynamicBackColor to shade a cell based on an inline IF statement or a function.

For me, this capability was the coolest feature of the Visual FoxPro grid. The .NET DataGrid does not have native support for this. Some of the third-party DataGrids do, but those implementations each require some code, and are not as simple as the Visual FoxPro approach. This is perhaps the most powerful feature of the grid class presented in this article.

In the example, I want to highlight the deduction balance in red if the balance is higher than a user-entered value at runtime. You need to:

  • Write a function that returns True or False for whether the column should be highlighted. The function is called from within the Grid class, and the function receives three parameters. The three parameters are the DataRow object for the current row position, the current row position value, and the string value for the current cell.
  • Implement logic in the function to determine whether the current cell should be highlighted.
  • Place the function in a separate class from the user interface form.
  • Supply the name of the function to the grid, along with the class and assembly name in which it resides. The Grid class invokes the function through reflection to determine whether each cell should be highlighted.
Let's work our way backward. In the test form, there is a checkbox for whether to highlight a cell in a particular gradient fill if the balance exceeds the value specified in a textbox. If checked, the following call is made to the grid Container object:

Color clrBackFrom = Color.Red; Color clrBackTo = Color.Blue; Color clrForeFrom = Color.White; Color clrForeTo = Color.Yellow; Assembly aCurrAssembly; aCurrAssembly = Assembly.GetExecutingAssembly(); string cAssemblyName; cAssemblyName = aCurrAssembly.Location.ToString(); oGrid.ColorColumn("Balance",cAssemblyName, "FormManager","CheckBalance",clrBackFrom, clrBackTo, clrForeFrom, clrForeTo); this.Refresh();

This forces the Grid class to call the CheckBalance function in the FormManager class, which is part of the current assembly. (If the function resides in a different assembly, the name of that assembly must be provided). CheckBalance is invoked every time the balance column is painted. (I'll cover the internals of that later).

Next, you need to create a FormManager class and include a CheckBalance function. The CheckBalance function reads the cell value passed to it, and compares it to a property that was bound to the balance threshold textbox column on this main form so that the balance function doesn't have to query the contents of the textbox.

Decimal nThreshold; nThreshold = Decimal.Parse(this.cBalance.ToString()); Decimal nCurrentCell; nCurrentCell = Decimal.Parse(cValue); if(nCurrentCell > nThreshold) return true; else return false;

This specific example doesn't make use of the entire DataRow object passed to it. However, the IF test could be changed to see if the value of the current cell is more than half the value of the amount column. In that instance, the code reads:

Decimal nThreshold; nThreshold = Decimal.Parse(Dr["amount"].ToString())* .5; Decimal nCurrentCell; nCurrentCell = Decimal.Parse(cValue); if(nCurrentCell > nThreshold) return true; else return false;

Finally, let's take a look at how this code is implemented. The CheckBalance function is invoked from within the DataGrid column's Paint function. This means creating a class derived from the native DataGridTextBoxColumn class, and overriding the Paint event. As the main function of the Paint event is to draw a string using a specific font, location, and brush object to determine the color and texture, you need to capture the string for the grid cell before it is drawn and override the default Brush with your own custom Brush if the function to be invoked (CheckBalance) returns True.

That's a mouthful! OK, let's break the custom Paint event down. First, you need to capture the object for the current cell and convert to a string:

object oCell; string cCellValue; oCell = this.GetColumnValueAtRow(source, rowNum); cCellValue = oCell.ToString();

Then you need to look up the mapping name (column name) in DtGridDef to see whether a custom function was specified. If the Paint event is running for the Balance column, it will find that a function has been defined (CheckBalance). In that case, you need to run it.

You know the function name (as well as the class name and assembly to which it belongs), so you can utilize reflection to invoke the function. To make life a little easier, I've provided a function in the ReflectionLibrary called RunMethod (which I'll talk about at the end of this section). You need to pass a few things to this function, and you'll recall that any function used for this purpose receives the current data row, the row position, and the value of the current cell:

object[] args = new object[3]; DataRow dr=oGrid.dvGrid.DefaultView[rowNum].Row; args[0] = dr; args[1] = rowNum; args[2] = cCellValue; ReflectionLibrary.ReflectionLibrary oReflection = new ReflectionLibrary.ReflectionLibrary(); lChangeAppearance = (bool)oReflection.RunMethod(cFunctionAssembly, cFunctionClass, cFunction, args);

Because the custom method could return many different data types, the generic reflection function RunMethod returns an object and you're responsible for casting it to the necessary data type. In this case, you want to see if CheckBalance returned True or False.

Finally, if the function returned True, you need to change the foreground and background color Brush. Then you can call FillRectangle and DrawString at the end, specifying either the default color Brush or the new color Brush.

backBrush = new LinearGradientBrush(bounds, clrBackFrom, clrBackTo, LinearGradientMode.BackwardDiagonal); foreBrush = new LinearGradientBrush(bounds, clrForeFrom, clrForeTo, LinearGradientMode.BackwardDiagonal); g.FillRectangle(backBrush, bounds); g.DrawString(cCellValue, FontToUse,foreBrush, x, bounds.Y);

(For more in-depth details on GDI+, check out the two-part article by Markus Egger in the May/June and July/August 2003 issues of CoDe Magazine).

OK, I promised to talk a little more about the reflection library and invoking functions. After I became more comfortable with reflection, I put together a few separate functions to address most of my needs. It just goes against my grain to have LoadAssembly, GetType, and InvokeMethod sprayed all over my code. And speaking of which, those are some of the main .NET Framework functions to execute a method dynamically at runtime. In the ReflectionLibrary, you'll find the function RunMethod, which receives Assembly name, Class name, and Function name. It also receives an object array with the arguments to be passed to the function.

First, the assembly is loaded:

Assembly aAssembly = Assembly.LoadFrom(cAssembly);

Then the assembly is scanned to locate the desired class:

foreach(Type tType in aAssembly.GetTypes()) { if(tType.Name.ToString().ToUpper()== cClassName.ToUpper())

Once the class is found, create a method object for the function, activate the class, invoke the method object, and pass the argument object. The invocation always returns an object which you can cast to whatever type of parameter your custom object returned.

MethodInfo MyMethodInfo = tType.GetMethod(cFunctionName); object oActivator = Activator.CreateInstance(tType); oReturn = MyMethodInfo.Invoke(oActivator,args);

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date