Customizing the Windows Forms DataGrid

Embed Size (px)

Citation preview

  • 8/6/2019 Customizing the Windows Forms DataGrid

    1/22

    1

    Customizing the Windows Forms DataGrid

    Microsoft Visual Studio .NET

    Summary: The following demonstrates how easy it is to use the Windows Forms DataGrid in

    Microsoft Visual Studio .NET for basic display of tabular data on a form. In addition, a

    demonstration on how to customize the DataGrid's default behavior is included, allowing you more

    flexibility in how you can use the DataGrid. (24 printed pages)

    Download the sample code file datagridcustomization.exe in the Microsoft Download Center.

    Contents

    Synopsis

    Introduction

    The Basics

    Customizing the Grid

    Conclusion

    Synopsis

    The following discussion steps you through customizing the look and functionality of a Windows

    Forms DataGrid. A DataGridBoolColumn is derived that exposes a BoolValChanged event that is

    raised anytime the checkbox value changes. Also derived is a DataGridTextBoxColumn that exposes

    a CellFormatting event that allows control of the cell's Font, ForeColor and BackColor on a cell-

    by-cell basis. The DataGrid's CurrentCellChanged event is used to force the focus to go to a

    particular column, no matter which column was initially clicked in a row. Also shown is how to

    dynamically change the appearance of cells in a DataGrid, depending upon the value of key cells.

    Here, a key cell is a cell whose value determines the appearance of other cells. Further

    demonstrated is how to make a check box column react to the initial click in the cell to change its

    checked state and how to control which columns appear in the DataGrid, as well as their order,

    independent of how the columns occur in the underlying DataSource. Finally, it is demonstrated

    how to use the Windows Forms ToolTip control to display tips that vary row-by-row in the

    DataGrid. In short, a DataGrid should look as the one in Figure 1:

    http://download.microsoft.com/download/c/9/8/c985dbd0-2ba9-4c75-86ba-a108e64b0970/datagridcustomization.exehttp://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_01#wnf_custdatagridtopic_01http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_02#wnf_custdatagridtopic_02http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_03#wnf_custdatagridtopic_03http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_04#wnf_custdatagridtopic_04http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_05#wnf_custdatagridtopic_05http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_05#wnf_custdatagridtopic_05http://msdn.microsoft.com/en-us/library/system.windows.forms.datagrid.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridboolcolumn.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridtextboxcolumn.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagrid.currentcellchanged.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagrid.currentcellchanged.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridtextboxcolumn.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridboolcolumn.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagrid.aspxhttp://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_05#wnf_custdatagridtopic_05http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_04#wnf_custdatagridtopic_04http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_03#wnf_custdatagridtopic_03http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_02#wnf_custdatagridtopic_02http://msdn.microsoft.com/en-us/library/ms996485.aspx#wnf_custdatagridtopic_01#wnf_custdatagridtopic_01http://download.microsoft.com/download/c/9/8/c985dbd0-2ba9-4c75-86ba-a108e64b0970/datagridcustomization.exe
  • 8/6/2019 Customizing the Windows Forms DataGrid

    2/22

  • 8/6/2019 Customizing the Windows Forms DataGrid

    3/22

    3

    1. On the File menu, select New, and then select Projects to create a new WindowsApplication project. Name the project CustomDataGrid.

    2. From the Toolbox, drag a DataGrid onto the displayed Form. Size it to generally fill theForm, and set its Anchor property to all four sides. The anchored Form in Visual Studio

    should look similar to Figure 2.

    Figure 2 The Anchored Form in Visual Studio

    3. Next, from the View menu, select the Server Explorer Window. Under DataConnections, open the Northwind node from the Tables list. Drag the Products table

    onto your Form on the Design Surface. After doing so, two components should display in

    the Components tray at the bottom of the Design Surface, a SqlConnection1 and a

    SqlDataAdapter1, as shown in Figure 3.

  • 8/6/2019 Customizing the Windows Forms DataGrid

    4/22

    4

    Figure 3 Form with SqlConnection and SqlDataAdapter Components

    4. Right-click the SqlDataAdapter1 component and select Generate DataSet. The GenerateDataSet dialog box appears, as shown in Figure 4. Press ENTER to accept the default

    action, which is to create a typed dataset, and place an instance of this dataset into your

    Component tray.

  • 8/6/2019 Customizing the Windows Forms DataGrid

    5/22

    5

    Figure 4 Generating the DataSet

    5. On the Design Surface, click the DataGrid, and then on its property grid, set theDataSource property to DataSet11Products.

    6. Add a Form Load event handler by double-clicking an empty spot on the Form. In thisevent handler, type the following single line of code:

    Copy

    Me.SqlDataAdapter1.Fill(Me.DataSet11)

    7. Finally, compile and run your project. The grid should appear as illustrated in Figure 5.

  • 8/6/2019 Customizing the Windows Forms DataGrid

    6/22

    6

    Figure 5 The DataGrid Produced with the Designer and One Line of Code

    Customizing the Grid

    Customization 1: Columns and Column Order

    For the DataGrid, both the columns that appear in the DataGrid need to be controlled, as well as

    the order of their appearance. The columns and column order of the default DataGrid produced by

    the Designer is determined by the SQL Query generated as part of creating the SqlDataAdapter.

    From the default DataGrid, it is necessary to remove both the SupplierID and the CategoryID. Also,

    the Discontinued column must be moved so that it is the very first column in the DataGrid instead

    of the last column.

    It is possible to go back and manually adjust this SQL Query to control which columns would

    appear in the DataGrid, and their order of appearance. But instead, the following demonstrates

    how to add a DataGridTableStyle to the DataGrid. Once the DataGridTableStyle is added, it is

    possible to control which columns appear in the DataGrid, and their order of appearance by whichDataGridColumnStyles are added to the DataGridTableStyle GridColumnStyles collection. The

    GridColumnStyles used by the DataGrid are determined at the point the DataGridTableStyle is

    added to the DataGrid.TableStyle collection. If the TableStyle.GridColumnStyle has not been

    populated with this collection by this point, a default set of ColumnStyles is created and used by

    the DataGrid. However, if DataGridColumns are specifically added to a DataGridTableStyle before

    adding the DataGridTableStyle to the DataGrid.TableStyles collection, then the columns that

    appear in the DataGrid are exactly those in the specified DataGridTableStyle.GridColumnStyles

    collection, and the column order will be the same as the order in the

    DataGridTableStyle.GridColumnStyles collection.

    Before examining the code snippets that set the columns and column order, first look at the

    DataGridColumnStyle class. This is an abstract class. Normally, either the DataGridTextBoxColumnor the DataGridBoolColumn classes (the two DataGridColumnStyle derived classes shipped with the

    .NET Framework) are used. The main purpose of these classes is to control the appearance of a

    column in the DataGrid. For example, the DataGridBoolColumn makes the column look and behave

    like a check box. Later in the discussion, more is derived from these two classes to further

    customize the appearance and behavior of the columns.

    The correspondence between a particular column in a DataTable, and a particular

    DataGridColumnStyle object is made through the DataGridColumnStyle.MappingName property.

    This property is the one required property that is needed to be set when creating a

    DataGridColumnStyle. Other DataGridColumnStyle properties of interest include Header, ReadOnly

    and Width.

    The default DataGrid has the following ten columns in this order: ProductID, ProductName,

    SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel and

    Discontinued. In the customized DataGrid, only eight columns are necessary in the following order:

    Discontinued, ProductID, ProductName, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder

    and ReorderLevel.

    The following is a modified Form Load event handler that will create the DataGridTableStyle (step

    1), create the DataGridColumnStyles and add them to the GridColumnStyles collection (step 2), and

    finally, add the DataGridTableStyle to the DataGrid.TableStyles property (step 3).

    CopyPrivateSubForm1_Load(ByValsenderAsSystem.Object,_

    http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldataadapter.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridtablestyle.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridtablestyle.gridcolumnstyles.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.mappingname.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.headertext.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.readonly.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.width.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.width.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.readonly.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.headertext.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridcolumnstyle.mappingname.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridtablestyle.gridcolumnstyles.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.datagridtablestyle.aspxhttp://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldataadapter.aspx
  • 8/6/2019 Customizing the Windows Forms DataGrid

    7/22

    7

    ByValeAsSystem.EventArgs)HandlesMyBase.LoadMe.SqlDataAdapter1.Fill(Me.DataSet11)

    'Step1:CreateaDataGridTableStyle&' setmappingnametotable.

    DimtableStyleAsNewDataGridTableStyle()tableStyle.MappingName="Products"

    'Step2:CreateDataGridColumnStyleforeachcol' wewanttoseeinthegridandinthe' orderthatwewanttoseethem.

    'Discontinued.DimdiscontinuedColAsNewDataGridBoolColumn()discontinuedCol.MappingName="Discontinued"discontinuedCol.HeaderText=""discontinuedCol.Width=30'turnofftristate

    discontinuedCol.AllowNull=FalsetableStyle.GridColumnStyles.Add(discontinuedCol)

    'Step2:ProductIDDimcolumnAsNewDataGridTextBoxColumn()column.MappingName="ProductID"column.HeaderText="ID"column.Width=30tableStyle.GridColumnStyles.Add(column)

    'Step2:ProductNamecolumn=NewDataGridTextBoxColumn()

    column.MappingName

    =

    "ProductName"

    column.HeaderText="Name"column.Width=140tableStyle.GridColumnStyles.Add(column)

    'Step2:QuantityPerUnit

    column=NewDataGridTextBoxColumn()column.MappingName="QuantityPerUnit"column.HeaderText="QuantityPerUnit"tableStyle.GridColumnStyles.Add(column)

    'Step2:UnitPricecolumn=NewDataGridTextBoxColumn()

    column.MappingName="UnitPrice"column.HeaderText="UnitPrice"tableStyle.GridColumnStyles.Add(column)

    'Step2:UnitsInStockcolumn=NewDataGridTextBoxColumn()column.MappingName="UnitsInStock"column.HeaderText="UnitsInStock"tableStyle.GridColumnStyles.Add(column)

    'Step2:UnitsOnOrdercolumn=NewDataGridTextBoxColumn()column.MappingName="UnitsOnOrder"

    column.HeaderText="UnitsOnOrder"tableStyle.GridColumnStyles.Add(column)

  • 8/6/2019 Customizing the Windows Forms DataGrid

    8/22

  • 8/6/2019 Customizing the Windows Forms DataGrid

    9/22

    9

    DimdiscontinuedColumnAsInteger=0DimvalAsObject=Me.DataGrid1(_

    Me.DataGrid1.CurrentRowIndex,_discontinuedColumn)

    DimproductDiscontinuedAsBoolean=CBool(val)IfproductDiscontinuedThen

    Me.DataGrid1.CurrentCell=_NewDataGridCell(_

    Me.DataGrid1.CurrentRowIndex,_discontinuedColumn)

    EndIfEndSub

    Customization 3: Make the Check Box Respond to One Click

    In a DataGrid with a check box column, the default behavior requires two clicks to actually change

    the checked value. The first click sets focus to the cell, and the second click will change the checked

    state. To modify this behavior for this example, make the checked value change on the first click.Handle this by listening to the DataGrid.Click event, and checking for a hit in the check box cell. If it

    is the first click after the cell becomes current, explicitly change the Boolean value in the grid. To

    track the first click after the cell becomes current, add a variable to the form, and set it in the

    existing CurrentCellChanged handler.

    The following are the code snippets that implement this one-click behavior. In the Click handler,

    take the mouse position and pass it to the DataGrid's HitTest method in order to decide which cell

    is clicked. The goal is to act on the first click in the check box cell after the current cell has changed.

    There is also a check to avoid a problem that occurs when the AddNew row is clicked that appears

    at the bottom of the DataGrid. This check involves making sure the row that is clicked is a real row

    that is bound to the datasource. Do this by comparing the clicked row to the Count property of

    the BindingManagerBase.

    CopyPrivateafterCurrentCellChangedAsBoolean=FalsePrivateSubdataGrid1_Click(ByValsenderAsSystem.Object,_

    ByValeAsSystem.EventArgs)HandlesDataGrid1.Click

    DimdiscontinuedColumnAsInteger=0DimptAsPoint=Me.dataGrid1.PointToClient(_

    Control.MousePosition)DimhtiAsDataGrid.HitTestInfo=_

    Me.dataGrid1.HitTest(pt)DimbmbAsBindingManagerBase=_

    Me.BindingContext(Me.dataGrid1.DataSource,_Me.dataGrid1.DataMember)

    IfafterCurrentCellChanged_AndAlsohti.Row

  • 8/6/2019 Customizing the Windows Forms DataGrid

    10/22

    10

    PrivateSubdataGrid1_CurrentCellChanged(_ByValsenderAsSystem.Object,_ByValeAsSystem.EventArgs)_

    HandlesDataGrid1.CurrentCellChanged

    'ifclickonadiscontinuedrow,thenset'currentcelltocheckbox

    DimdiscontinuedColumnAsInteger=0DimvalAsObject=Me.DataGrid1(_

    Me.DataGrid1.CurrentRowIndex,_discontinuedColumn)

    DimproductDiscontinuedAsBoolean=CBool(val)

    IfproductDiscontinuedThenMe.DataGrid1.CurrentCell=_NewDataGridCell(Me.DataGrid1.CurrentRowIndex,_

    discontinuedColumn)EndIf'addthislineafterCurrentCellChanged=True

    EndSub'dataGrid1_CurrentCellChanged

    Customization 4: Changing Cell BackColor, ForeColor and Font

    To customize the way a DataGrid cell is drawn, it is necessary to override the Paint method in a

    derived DataGridTextBoxColumn class. When the DataGrid with a DataGridTableStyle associated

    with it needs to paint a cell, it calls the Paint method of the DataGridTextBoxColumn. The

    arguments in this call include a ForeBrush and BackBrush that controls the colors used to draw the

    cell. Therefore, to control the colors used to paint a cell on a per cell basis, it is possible to override

    the virtual Paint method of the DataGridTextBoxColumn class, and change the brushes

    according to the particular cell being drawn.

    The following strategy is to add an event to the derived DataGridTextBoxColumn. In the derived

    Paint method, this event is fired to allow listeners to provide the BackColor and ForeColor to be

    used for the cell being painted. Once this event is in place, it is only necessary to write an event

    handler to control the color of a grid cell. Also derived are special EventArgs to provide the event

    listener with the row index and column index values, and to allow the event listener to set the color

    properties.

    Creating the Event Arguments Class

    In Solution Explorer, right-click the project, and add a new class named

    DataGridFormatCellEventArgs, which is inherited from EventArgs. The following is a list of the

    public properties of this class and a note on how they are used. The first three properties are

    passed into the event handler through the argument; the last seven properties are set by the event

    listener to indicate how the cell should be drawn.

    Column (Integer): The column number of the cell being painted. Row (Integer): The row number of the cell being painted. CurrentCellValue (Object): The current cell value. TextFont (Font): The font to be used to draw text in the cell. BackBrush (Brush): The brush used to paint the cell's background. ForeBrush (Brush): The brush used to paint the text in the cell. TextFontDispose (Boolean): True to call the TextFont.Dispose.

    BackBrushDispose (Boolean): True to call the BackBrush.Dispose. ForeBrushDispose (Boolean): True to call the ForeBrush.Dispose.

  • 8/6/2019 Customizing the Windows Forms DataGrid

    11/22

    11

    UseBaseClassDrawing (Boolean): True to call the MyBase.Paint.The dispose properties included as part of the DataGridFormatCellEventArgs are set by the listener

    if the Paint override should call the Dispose method on the objects that implement IDisposable.

    For example, if dynamically creating a BackBrush every time a grid cell is drawn, use the Paint

    method to call Dispose on the brush after it is finished with its drawing. On the other hand, if asingle brush is created and cached that will be used to provide the BackBrush for some cells, it is

    not desirable to have the Paint method to call Dispose on the cached brush. These dispose

    properties let the listener tell the Paint override how to handle the Dispose calls.

    The following is how the actual class code should appear. Before the class definition, add the

    delegate that determines the signature of the event handler required for the SetCellFormat event

    that will be defined as part of the derived DataGridTextBoxColumn.

    CopyPublicDelegateSubFormatCellEventHandler(_

    ByValsenderAsObject,_

    ByValeAsDataGridFormatCellEventArgs)

    PublicClassDataGridFormatCellEventArgsInheritsEventArgs

    PublicSubNew(ByValrowAsInteger,_ByValcolAsInteger,_ByValcellValueAsObject)

    EndSub'NewEndClass

    Creating the Derived DataGridTextBoxColumn

    The next step is to add the class code for the derived DataGridTextBoxColumn. This is done in the

    same way, right-clicking the project in Solution Explorer and adding a new class named

    FormattableTextBoxColumn. To add the stub for the Paint override, select (Overrides) in the left

    drop-down list on top of the editing window, and then select the second Paint method (the one

    with seven arguments) listed in the right drop-down list at the top of the editing window. The

    following is what the code will look like.

    Note Notice the SetCellFormat event declaration is also added, using the event delegate

    FormatCellEventHandler that was defined in the previous class file.

    Copy

    PublicClassFormattableTextBoxColumnInheritsDataGridTextBoxColumn

    PublicEventSetCellFormatAsFormatCellEventHandler

    ProtectedOverloadsOverridesSubPaint()

    EndSub'PaintEndClass

    Implementing the Paint Override

    http://msdn.microsoft.com/en-us/library/system.idisposable.aspxhttp://msdn.microsoft.com/en-us/library/system.idisposable.aspx
  • 8/6/2019 Customizing the Windows Forms DataGrid

    12/22

    12

    The first thing to do in the Paint override is to fire the SetCellFormat event. It is through this event

    that an event listener will provide the format preferences that are to be used with this cell. The

    steps are to create the DataGridFormatCellEventArgs, initializing the row, column and current

    value, and then raise the event with this argument. Finally, after the event has been raised, query

    the event argument to see what formatting actions need to be done.

    Raising the Event

    The following is the code snippet that creates the argument and fires the event. The variables

    source and rowNum are passed into the Paint method.

    CopyDimeAsDataGridFormatCellEventArgs=Nothing

    'GetthecolumnnumberDimcolAsInteger=_Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me)

    'Createtheeventargsobjecte=NewDataGridFormatCellEventArgs(_

    rowNum,col,_Me.GetColumnValueAtRow([source],rowNum))

    'firetheformattingeventRaiseEventSetCellFormat(Me,e)

    Color Formatting

    Once the event has returned, inspect the properties of the DataGridFormatCellEventArgs object to

    find out about the colors in which the cell should appear. The two brushes, foreBrush and

    backBrush, used to draw the cell are passed as arguments to the Paint method. To change the cell

    colors, swap the old brushes for the new brushes before calling the Paint method's base class

    implementation. The following is a code snippet that illustrates this idea:

    Copy'assumewewillcallthebaseclassDimcallBaseClassAsBoolean=True

    'checkthebrushesreturnedfromtheeventIfNot(e.BackBrushIsNothing)Then

    backBrush=e.BackBrushEndIfIfNot(e.ForeBrushIsNothing)Then

    foreBrush=e.ForeBrushEndIf

    'checktheUseBaseClassDrawingpropertyIfNote.UseBaseClassDrawingThen

    callBaseClass=FalseEndIf

    IfcallBaseClassThenMyBase.Paint(g,bounds,_

    [source],rowNum,backBrush,_foreBrush,alignToRight)

    EndIf

  • 8/6/2019 Customizing the Windows Forms DataGrid

    13/22

    13

    The previous snippet sets the brushes depending upon the properties of the

    DataGridFormatCellEventArgs object. Depending upon the UseBaseClassDrawing value, it calls the

    base class. Calling the base class with the modified brushes is how to affect the cell color. Recall

    that this method will be called for each cell in a column as it is drawn.

    Handling the TextFont Property

    Trying to change the font used to draw in the cell adds a real complication to the Paint override.

    The reason is that the font used to draw the text is not a simple parameter passed as an argument

    in the Paint method. Instead, the font used to draw the cell text is originally gotten from the

    DataGrid.Font property. But this font is cached, and is not reset from the DataGrid.Font property

    for each cell. Thus, the first impulse, dynamically changing DataGrid.Font for each cell does not

    work. Therefore, to change the font of the cell, it is necessary to actually draw the string so that the

    font can be specified. Doing so adds significant work to try to mimic exactly the output of the

    standard DataGrid. The example will not go to extremes in this respect, but will be satisfied to get

    similar output, but not exactly the same output as with the unaltered DataGrid. The following is thecode snippet that is used to implement drawing the string with a newly specified font.

    Copy'ifTextFontset,thenmustcalldrawstringIfNot(e.TextFontIsNothing)Then

    TryDimcharWidthAsInteger=_

    Fix(Math.Ceiling(g.MeasureString("c",_e.TextFont,20,_StringFormat.GenericTypographic).Width))

    DimsAsString=_

    Me.GetColumnValueAtRow([source],

    _

    rowNum).ToString()DimmaxCharsAsInteger=_

    Math.Min(s.Length,bounds.Width/charWidth)

    Tryg.FillRectangle(backBrush,bounds)g.DrawString(s.Substring(0,maxChars),_

    e.TextFont,foreBrush,_bounds.X,bounds.Y+2)

    CatchexAsExceptionConsole.WriteLine(ex.Message.ToString())

    EndTry

    Catch'emptycatchEndTrycallBaseClass=False

    EndIf

    The first part of the code approximates the number of characters that can be displayed in the

    current cell width. It does so by approximating an average character size, and dividing this value

    into the cell width. It would be possible to do this more rigorouslyrepeatedly calling

    MeasureString to get the exact number of characters that would fitbut doing so makes the

    output significantly different than what the default DataGrid produces. Using this average

    character size approach is much quicker, and gives results much closer to the default DataGrid.

  • 8/6/2019 Customizing the Windows Forms DataGrid

    14/22

    14

    After getting the number of characters, draw the background and call Graphics.DrawString passing

    it the font and brush desired for use. Finally, set the callBaseClass flag so the baseclass Paint

    method is not later called, undoing all of the previous work.

    Using the New FormattableTextBoxColumn Class

    To use the newly derived column, go back to the form code, and swap out the occurrences of

    DataGridTextBoxColumn with FormattableTextBoxColumn. Then for each column, wire up the

    listener for the SetCellFormat event exposed by the derived class. There exists the option of using a

    different event handler for each column. But the requirements (coloring rows) allow the use of the

    same handler for all the columns. The following is the code for the typical column after these

    changes:

    Copy'ProductNamecolumn=NewFormattableTextBoxColumn()column.MappingName="ProductName"

    column.HeaderText="Name"column.Width=140AddHandlercolumn.SetCellFormat,AddressOfFormatGridRow

    tableStyle.GridColumnStyles.Add(column)

    Notice that an instance of a derived class is now being created, FormattableTextBoxColumn. Also, a

    handler is added, FormatGridRow, for the SetCellFormat event.

    The SetCellFormat Event Handler

    In the SetCellFormat handler, the brushes used to set the forecolor and backcolor of the cell whose

    row and column indexes are passed in through the event arguments will be set. If the cell is on thecurrent row of the grid, use one set of colors and font. If the cell is on a row that has the

    Discontinued Boolean value set to true, use another set of colors. The following is the code that

    declares and defines these cached GDI+ objects.

    Copy'formvariablestocacheGDI+objectsPrivatedisabledBackBrushAsBrushPrivatedisabledTextBrushAsBrushPrivatecurrentRowFontAsFontPrivatecurrentRowBackBrushAsBrush

    'InForm1_LoadcreateandcachesomeGDI+objects.Me.disabledBackBrush=_

    NewSolidBrush(SystemColors.InactiveCaptionText)Me.disabledTextBrush=_

    NewSolidBrush(SystemColors.GrayText)Me.currentRowFont=_

    NewFont(Me.DataGrid1.Font.Name,_Me.DataGrid1.Font.Size,_FontStyle.Bold)

    Me.currentRowBackBrush=Brushes.DodgerBlue

    In the code for the event handler, members of the DataGridFormatCellEventArgs value that arepassed to display particular rows with special effects will be set. Discontinued rows will be grayed

  • 8/6/2019 Customizing the Windows Forms DataGrid

    15/22

    15

    out, and the current row will be displayed with a DodgerBlue background using a bold font. The

    following is the handler that will implement these effects:

    CopyPrivateSubFormatGridRow(ByValsenderAsObject,_

    ByValeAsDataGridFormatCellEventArgs)DimdiscontinuedColumnAsInteger=0'Conditionallysetpropertiesinedependingupone.Rowande.Col.DimdiscontinuedAsBoolean=CBool(_

    IIf(e.ColumndiscontinuedColumn,_Me.DataGrid1(e.Row,discontinuedColumn),_e.CurrentCellValue))

    'Checkifdiscontinued?

    Ife.Column>discontinuedColumnAndAlso_CBool(Me.DataGrid1(e.Row,discontinuedColumn))Then

    e.BackBrush=Me.disabledBackBrushe.ForeBrush=Me.disabledTextBrush

    'currentrow?

    ElseIfe.Column>discontinuedColumnAndAlso_e.Row=Me.DataGrid1.CurrentRowIndexThen

    e.BackBrush=Me.currentRowBackBrushe.TextFont=Me.currentRowFont

    EndIfEndSub

    Recall that there are actually several FormattableTextBoxColumn objects in the

    DataGrid.TableStyle(0).GridColumnStyles collection. In fact, every column, except the Boolean

    column, is using one of these objects. In addition, each of these objects has a SetCellFormat event,

    to which is being listened. However, each of these events is using the same handler,

    FormatGridRow, which was previously listed. Thus, the same formatting logic is being applied toevery cell (except the Boolean cell) in a row. Therefore, the formatting is effectively setting row

    styles in that all cells in the row will show the same formatting behavior, even though the

    formatting is being done on a cell-by-cell basis.

    Take a Look

    The following is a picture of the grid at this point. Notice that the checked rows are grayed out,

    showing the item is discontinued, and the current row is DodgerBlue with a bold font.

  • 8/6/2019 Customizing the Windows Forms DataGrid

    16/22

    16

    Figure 7 The DataGrid with Colored Rows

    The initial display looks pretty good. But there is a small problem that needs some effort to handle.

    The problem is that a click on a checked first column unchecks the cell properly, but does not

    redraw the row. This row needs to be refreshed anytime the check value changes. To handle this

    problem, it is necessary to derive from DataGridBooleanColumn and add an event handler that

    fires an event anytime the bool value changes. This will allow reaction to the changing click, andforces the row to be redrawn reflecting the new click value.

    Customization 5: Adding a Boolean Column with a Value Changed

    The Event Arguments

    Start by adding a new class derived from EventArgs that holds the information the

    BoolValueChanged event will require. In particular, properties will be added that give the row and

    column of the cell being changed, as well as add properties to the new Boolean value. The

    following is the BoolValueChangedEventArgs class. The missing implementation uses private

    fields only to shadow the public properties with the fields being initialized in the constructor. The

    delegate will also be defined, BoolValueChangedEventHandler, for this event at the beginning of

    this file.

    CopyPublicDelegateSubBoolValueChangedEventHandler(_

    ByValsenderAsObject,_ByValeAsBoolValueChangedEventArgs)

    PublicClassBoolValueChangedEventArgsInheritsEventArgs

    PublicSubNew(ByValrowAsInteger,_

    ByValcolAsInteger,_ByValvalAsBoolean)

  • 8/6/2019 Customizing the Windows Forms DataGrid

    17/22

    17

    ...EndSub'New

    PublicPropertyColumn()AsInteger

    ...EndProperty

    PublicPropertyRow()AsInteger...

    EndProperty

    PublicReadOnlyPropertyBoolValue()AsBoolean...

    EndPropertyEndClass'BoolValueChangedEventArgs

    The Derived DataGridBoolColumn Class

    Now for the derived class, ClickableBooleanColumn, three methods will be overridden to handle

    different aspects of monitoring the Boolean value in the cell. The first override is Edit. This method

    is called when a cell needs to go into edit mode. In the override, some private field members will

    be set to reflect the current Boolean state as well as the fact that the current mode is in edit mode.

    The second override is the same Paint method that was previously used in

    FormattableTextBoxColumn. Here, instead of trying to modify the cell appearance, this method is

    used to track the change in the Boolean value, and to raise the BoolValueChanged event if it does

    change. The third override is the Commit method. This method is called when the editing is

    complete, and the value should be committed to the datasource. This method is used to turn off

    the fields that were set in the Edit override.

    There are some technical points that need to be handled. The Boolean value can change becauseof a click in the cell, or a pressed spacebar, while the cell has focus. The helper method,

    ManageBoolValueChanging, is used to monitor these possibilities. Furthermore, it raises the

    BoolValueChanged event if the value is changed. To monitor the mouse click, the

    ManageBoolValueChanging checks the Control.MousePosition and Control.MouseButtons shared

    (static) properties. However, checking the KeyState of the spacebar is problematic. Resort to an

    Interop call into Win32 API GetKeyState to handle this problem.

    The following is the code for the class:

    CopyPublicClassClickableBooleanColumn

    InheritsDataGridBoolColumn

    'changedeventPublicEventBoolValueChanged_

    AsBoolValueChangedEventHandler

    'setvariablestostarttrackingboolchangesProtectedOverloadsOverridesSubEdit(...)

    Me.lockValue=TrueMe.beingEdited=TrueMe.saveRow=rowNumMe.saveValue=CBool(_

    MyBase.GetColumnValueAtRow(_[source],rowNum))

  • 8/6/2019 Customizing the Windows Forms DataGrid

    18/22

    18

    MyBase.Edit(...)EndSub'Edit

    'overriddentohandleBoolChangeevent

    ProtectedOverloadsOverridesSubPaint(...)DimcolNumAsInteger=_

    Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me)

    'usedtohandletheboolchangingManageBoolValueChanging(rowNum,colNum)

    MyBase.Paint(...)

    EndSub'Paint

    'turnofftrackingboolchangesProtectedOverridesFunctionCommit(...)AsBoolean

    Me.lockValue=TrueMe.beingEdited=False

    ReturnMyBase.Commit(dataSource,rowNum)EndFunction'Commit

    'fieldstotrackboolvaluePrivatesaveValueAsBoolean=FalsePrivatesaveRowAsInteger= 1PrivatelockValueAsBoolean=FalsePrivatebeingEditedAsBoolean=FalsePublicConstVK_SPACEAsInteger=32

    'neededtogetthespacebarchangingoftheboolvalue_

    Shared

    Function

    GetKeyState(

    _

    ByValnVirtKeyAsInteger)AsShortEndFunction

    'firetheboolchangeeventifthevaluechangesPrivateSubManageBoolValueChanging(_

    ByValrowNumAsInteger,_ByValcolNumAsInteger)

    DimmousePos_AsPoint=Me.DataGridTableStyle.DataGrid.PointToClient(_

    Control.MousePosition)DimdgAsDataGrid=Me.DataGridTableStyle.DataGridDimisClickInCellAsBoolean=_

    Control.MouseButtons=MouseButtons.LeftAndAlso_dg.GetCellBounds(dg.CurrentCell).Contains(mousePos)

    DimchangingAsBoolean=_dg.FocusedAndAlsoisClickInCell_OrElseGetKeyState(VK_SPACE)

  • 8/6/2019 Customizing the Windows Forms DataGrid

    19/22

    19

    DimeAsNewBoolValueChangedEventArgs(_rowNum,colNum,saveValue)

    RaiseEventBoolValueChanged(Me,e)EndIf

    IfsaveRow=rowNumThenlockValue=False

    EndIfEndSub'ManageBoolValueChanging

    EndClass

    Using the ClickableBooleanColumn

    To use the special column style, create an instance of it when setting up the columnstyle for the

    discontinuedCol. A handler for the new BoolValueChanged event will also be added.

    Copy' Discontinued.

    DimdiscontinuedColAsNewClickableBooleanColumn()discontinuedCol.MappingName="Discontinued"discontinuedCol.HeaderText=""discontinuedCol.Width=30'turnofftristatediscontinuedCol.AllowNull=FalseAddHandlerdiscontinuedCol.BoolValueChanged,_AddressOfBoolCellClickedtableStyle.GridColumnStyles.Add(discontinuedCol)

    The BoolValueChanged Event Handler

    In the event handler, the row must be forced to be redrawn using the new value of the Boolean

    cell. Anytime the Boolean value changes, the event handler invalidates the rectangle associated

    with a row to reflect the proper format. To force a redraw of a row, use a RefreshRow helper

    method. In the event handler, the Boolean value is forced to be stored by ending the edit mode.

    Doing so allows the FormatGridRow method to obtain the proper value of the Boolean cell.

    Without this call, only the initial value of the cell is available through the indexed DataGrid.

    CopyPrivateSubBoolCellClicked(ByValsenderAsObject,_

    ByValeAsBoolValueChangedEventArgs)DimgAsDataGrid=Me.DataGrid1

    DimdiscontinuedColAsNew_ClickableBooleanColumn=_g.TableStyles(0).GridColumnStyles("Discontinued")

    g.EndEdit(discontinuedCol,e.Row,False)RefreshRow(e.Row)g.BeginEdit(discontinuedCol,e.Row)

    EndSub

    'Forcesarepaintofgivenrow.PrivateSubRefreshRow(ByValrowAsInteger)

    DimrAsRectangle=_Me.dataGrid1.GetCellBounds(row,0)

    r=NewRectangle(r.Right,r.Top,_Me.dataGrid1.Width,r.Height)

    Me.dataGrid1.Invalidate(r)

  • 8/6/2019 Customizing the Windows Forms DataGrid

    20/22

    20

    EndSub'RefreshRow

    There is one last problem to handle before clicking the check box to be certain that the cell

    behaves as expected with the row changing colors with each click. If a checkbox is clicked that is

    not checked and is not on the current row, then the click sets the row in the current row colors,

    instead of the discontinued colors. Furthermore, the problem appears strictly to be a refresh

    problem because dragging the grid on and off the display will cause the row to be redrawn using

    the expected discontinued format. This refresh problem can be handled by forcing the row to be

    refreshed during the existing dataGrid1_Clickevent handler. In that event, the code is executed to

    change the Boolean value, and also to refresh the row.

    CopyIfafterCurrentCellChangedAndAlso_

    hti.Row

  • 8/6/2019 Customizing the Windows Forms DataGrid

    21/22

    21

    Me.BindingContext(g.DataSource,g.DataMember)Ifhti.Row

  • 8/6/2019 Customizing the Windows Forms DataGrid

    22/22