Customizing the Windows Forms DataGrid

xiaoxiao2021-03-06  81

Customizing the Windows Forms DataGrid

Dr. Clay Burch, .NET MVPSYNCFusion, Inc.

January 2003

Applies TO: 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 ~ You can use the datagrid. (24 Printed Pages)

Download The Sample File DataGridCustomization.exe in The Microsoft Download Center.

Contents

SynopsisIntroductionThe BasicsCustomizing The GridClusion

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 Column Occur in The Underly .. Ing 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: Figure 1 The Customized DataGrid

Introduction

The following contains a goal that is two-fold. The first goal is to show how easy it is to use the Windows Forms DataGrid for basic display of tabular data on a form. The second goal is to demonstrate how to customize the DataGrid's default behavior Allowing More Flexibility In How To Use the DataGrid. in Doint So, IT IS Demonstrated How To Create Classes That Can Beufactosed In Projects To Enable Similar Functionality In Applications.

If using the Designer to display a DataGrid using a DataTable is a familiar practice, skip ahead to the section titled Customizing the Grid. The following examples use VB .NET to show code snippets. The code samples include both VB .NET and C # sample projects .THE Basics

Start out by creating a Windows Forms Application in Visual Studio .NET, dropping a DataGrid onto the Form, and adding a DataSource. Do all of this from the Designer. To bring up and display a DataGrid bound to an ADO.NET data source, exactly one line of code is written. to allow users to edit the displayed values ​​and save them back to the data source, a second line of code is written. This discussion assumes that the DataBase Samples that ship with the .NET Framework are installed. IF not, you can do so from this link (Change The Hard Disk Drive Location, Depending On your Visual Studio .NET Install location):

C: / Program Files / Microsoft Visual Studio.Net/frameworksdk/samples/startsamples.htm

.

On the File menu, select New, and then select Projects to create a new Windows Application project. Name the project CustomDataGrid. From the Toolbox, drag a DataGrid onto the displayed Form. Size it to generally fill the Form, 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 Next, from the View menu, select the Server Explorer Window. Under Data Connections, 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. Figure 3 Form with SqlConnection and SqlDataAdapter Components Right-click the SqlDataAdapter1 component and select Generate DataSet. The Generate DataSet 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. Figure 4 Generating the DataSet On the Design Surface, click the DataGrid, and then on its property grid, set the DataSource property to DataSet11Products. Add a Form Load event handler by double-clicking an empty spot on the Form In this event handler, type the following single line of code:. Me.SqlDataAdapter1.Fill (Me.DataSet11) Finally, compile and run your project The grid should appear. AS Illustrate in Figure 5. Figure 5 The DataGrid Producted with The Designer and One Line of Code

Customizing the grid

Customization 1: Column 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 which DataGridColumnStyles are added to the DataGridTableStyle GridColumnStyles collection. The GridColumnStyles used by the D ataGrid 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 DataGridTextBoxColumn or 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) .

Private Sub Form1_Load (Byval Sender As System.Object, _

Byval e as system.eventargs) Handles mybase.loadMe.sqldataadapter1.fill (me.dataset11)

'Step 1: Create a DataGridtablesTyle &

'set mappingname to Table.

DIM TableStyle As New DataGridtableStyle ()

TableStyle.MappingName = "products"

'Step 2: Create DataGridColumnStyle for Each COL

'we want to see in the grid and in the grid

'Order That We Want to See.

'Discontinued.

DIM DiscontinueDcol as new datagridBoolcolumn ()

DiscontinueDcol.mappingname = "discontinued"

DiscontinueDcol.Headertext = ""

DiscontinueDcol.width = 30

'Turn Off Tristate

DiscontinueDcol.allownull = false

TableStyle.GridColumnStyles.add (discontinuedcol)

'Step 2: ProductID

DIM column as new datagridtextboxcolumn ()

Column.mappingname = "productID"

Column.Headertext = "id"

Column.width = 30

TableStyle.GridColumnStyles.Add (Column)

'Step 2: ProductName

Column = new DataGridTextBoxColumn ()

Column.mappingname = "productname"

Column.Headertext = "name"

Column.width = 140

TableStyle.GridColumnStyles.Add (Column)

'Step 2: QuantityPerUnit

Column = new DataGridTextBoxColumn ()

Column.mappingname = "QuantityPerUnit"

Column.Headertext = "QuantityPerUnit"

TableStyle.GridColumnStyles.Add (Column)

'Step 2: Unitprice

Column = new DataGridTextBoxColumn ()

Column.mappingname = "Unitprice"

Column.Headertext = "Unitprice"

TableStyle.GridColumnStyles.Add (Column)

'Step 2: UnitsInstock

Column = new DataGridTextBoxColumn ()

Column.mappingname = "UnitsInstock"

Column.Headertext = "UnitsInstock" Tablestyle.GridColumnStyles.Add (Column)

'Step 2: Unitsonorder

Column = new DataGridTextBoxColumn ()

Column.mappingname = "Unitsonorder"

Column.Headertext = "Unitsonorder"

TableStyle.GridColumnStyles.Add (Column)

'Step 2: REORDERLEVEL

Column = new DataGridTextBoxColumn ()

Column.mappingname = "repyrlevel"

Column.Headertext = "REORDERLEVEL"

TableStyle.GridColumnStyles.Add (Column)

'Step 3: Add the tablestyle to the datagrid

Me.DataGrid1.tables.add (TableStyle)

End Sub

Figure 6 is The DataGrid After Adding The Previous Code. The Column Should Be Exactly The Columns Previously Specified As Needed, and The Discontinued Column Should Appear First.

Figure 6 DataGrid Showing Specific Column in a Specific Order

Customization 2: Change Click Behavior on a Discontinued Row

In the following example, say that for a Discontinued product, the user should be unable to edit any cell in that row unless the user first removes the Discontinued check mark. Therefore, when the user clicks in any cell in a Discontinued row, instead of That Cell Getting Focus, The Focus Shifts To The First Column, Discontinued.

. To handle this requirement, add a handler for the CurrentCellChanged event In that handler, if the value in the Discontinued column in the current row is true, directly set the current cell to the Discontinued column See the following code example.:

Private sub DataGrid1_currentcellchanged (_

Byval sender as system.Object, _

ByVal e as system.eventargs)

Handles DataGrid1.currentCellchanged

'IF Click ON A Discontinued Row, Then Set Currentcell to Checkbox

DIM DiscontinuedColumn As INTEGER = 0DIM VAL As Object = Me.DataGrid1 (_

Me.DataGrid1.currentrowindex, _

DiscontinueDColumn

Dim ProductDiscontinued As Boolean = CBOOL (VAL)

IF productdiscontinued then

Me.DataGrid1.currentcell = _

New DataGridcell (_

Me.DataGrid1.currentrowindex, _

DiscontinueDColumn

END IF

End Sub

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 .

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.

Private aftercurrentcellchanged as boolean = false

Private sub DataGrid1_click (Byval Sender As System.Object, _byval e as system.eventargs) Handles DataGrid1.click

DIM DiscontinuedColumn As INTEGER = 0

Dim Pt as point = me.datagrid1.pointtoclient (_ _

Control.MousePosition)

Dim hti as datagrid.hittestinfo = _

Me.DataGrid1.hittest (PT)

DIM BMB As BindingManagerBase = _

Me.BindingContext (Me.DataGrid1.datasource, _

Me.DataGrid1.DataMember)

IF atcurrentcellchanged _

Andalso hti.row

Andalso hti.type = dataGrid.hittesttype.cell_

Andalso hti.column = discontinuedcolumn dam

Me.DataGrid1 (HTI.ROW, DiscontinuedColumn) = _

Not cbool (Me.DataGrid1 (hti.row, _

DiscontinueDColumn))))

END IF

AfTercurrentCellchanged = false

End Sub 'DataGrid1_click

'Add a line to this existing handler

Private sub DataGrid1_currentcellchanged (_

Byval sender as system.Object, _

ByVal e as system.eventargs)

Handles DataGrid1.currentCellchanged

'IF Click ON A Discontinued Row, The Set

'Currentcell to Checkbox

DIM DiscontinuedColumn As INTEGER = 0

DIM VAL As Object = Me.DataGrid1 (_

Me.DataGrid1.currentrowindex, _

DiscontinueDColumn

Dim ProductDiscontinued As Boolean = CBOOL (VAL)

IF productdiscontinued then

Me.DataGrid1.currentcell = _

New DataGridcell (Me.DataGrid1.currentrowindex, _

DiscontinueDColumn

END IF

'add this line

AfTercurrentcellchanged = TRUE

End Sub '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 IN Place, IT IS ONLY Necessary to Write An Event Handler To Control The Color of A Grid Cell. Also Deerived Are Special EventArgs To Provide ...........

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 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 OT her hand, if a single 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.

Public Delegate Sub FormatcelleTHandler (_

Byval sender as object, _

ByVal e as datagridformatcelleventargs)

Public Class DataGridFormatCelleventargs

Inherits EventArgs

Public Sub New (ByVal Row As INTEGER, _BYVAL COL As INTEGER, _

BYVAL CELLVALUE As Object)

End Sub 'NEW

END CLASS

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 Formelthandler That Was Defined In The Previous Class File.

Public Class FormatTableTextBoxColumn

Inherits DataGridTextBoxColumn

Public Event SetCellFormat as FormatcelletionLer

Protected overloads overrides sub posket (.........)

End Sub 'Paint

END CLASS

Implementing the Paint Override

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 argument to see what formatting actions need to be done.

Raising the evenet

................................... ...CRIPLILE, TELILS.

Dim e as datagridformatcelleventargs = Nothing'get the column number

DIM col As integer = _

Me.DataGridtables.gridcolumnStyles.Indexof (me)

'Create the Eventargs Object

E = New DataGridFormatCelleventargs (_

Rownum, col, _

Me.GetColumnValueatrow ([Source], Rownum))

'fire the formatting time

RaiseEvent setcellFormat (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:

'Assume We Will Call The Baseclass

DIM Callbaseclass as boolean = true

'Check The Brushes Returned from The Event

IF not (e.backbrush is nothing) THEN

Backbrush = E.BACKBRUSH

END IF

IF not (E.Forebrush Is Nothing) THEN

Forebrush = E.Forebrush

END IF

'Check The Usebaseclassdrawing Property

IF not e.usebaseclassdrawing then

CallbaseClass = FALSE

END IF

If CallbaseClass Then

Mybase.paint (g, bounds, _

[Source], Rownum, Backbrush, _

Forebrush, aligntoright)

END IF

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 the code snippet that is used to implement drawing the string with a newly specified font.'if TextFont Set, Then Must Call Drawstring

IF not (e.TextFont is nothing) THEN

Try

Dim charwidth as integer = _

Fix (Math.ceiling (G.MeasureString ("C", _

E.TextFont, 20, _

StringFormat.GenericTyPographic) .width))

DIM S as string = _

Me.GetColumnValueatrow ([Source], _

Rownum) .toString ()

Dim maxChars as integer = _

Math.min (S.Length, Bounds.width / Charwidth)

Try

g.FillRectangle (Backbrush, Bounds)

g.drawstring (s.Substring (0, Maxchars), _

E.TextFont, Forebrush, _

Bounds.x, bounds.y 2)

Catch exception

Console.writeline (ex.Message.toString ())

END TRY

Catch 'EMPTY CATCH

END TRY

CallbaseClass = FALSE

END IF

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 rigorously- repeatedly calling MeasureString to get the exact number of characters that would fit-but 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.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 Column. The Following Is The Change for the Typical Column After these Changes:

'ProductName

Column = new formattablextboxcolumn ()

Column.mappingname = "productname"

Column.Headertext = "name"

Column.width = 140

AddHandler Column.SetcellFormat, Addressof FormatGridrow

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 the current 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.

'Form Variables to Cache GDI Objects

Private disabledbackbrush as brush

Private disabledtextbrush as brush

Private currentrowfont as font

Private CurrentrowBackBrush As Brush

'In form1_load create and cache Some GDI Objects.

Me.disableDBackBrush = _

New solidbrush (SystemColors.inactiveCaptionText)

Me.disabledtextbrush = _

New solidbrush (systemcolors.graytext)

Me.currentrowfont = _

New font (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 are passed to display particular rows with special effects will be set. Discontinued rows will be grayed 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:

Private subormatgridrow (Byval Sender as Object, _

ByVal e as datagridformatcelleventargs)

DIM DiscontinuedColumn As INTEGER = 0

'Conditionally set Properties in E Depending Upon E.ROW AND E.COL.DIM Discontinued As Boolean = CBOOL (_

IIF (E.COLUMN <> DiscontinuedColumn, _

Me.DataGrid1 (E.Row, DiscontinuedColumn), _

E.currentcellValue)))

'Check if discontinued?

IF E.COLUMN> DiscontinuedColumn Andalso_

CBOOL (Me.DataGrid1 (E.ROW, DiscontinuedColumn) Then

E.BackBrush = me.disabledbackbrush

E.Forebrush = me.disabledtextbrush

'Current Row?

Elseif E.COLUMN> DiscontinuedColumn Andalso_

E.Row = me.datagrid1.currentrowindex then

E.BackBrush = me.currentrowbackbrush

e.TextFont = me.currentrowfont

END IF

End Sub

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 to every cell (except the Boolean cell) in a row. Therefore, the .

Take a LOOK

....................... ..

Figure 7 The DataGrid with color 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, and forces 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.

Public Delegate Sub BoolValueChangeDeventhandler (_

Byval sender as object, _

Byval e as boolvaluechangedendeventargs)

Public Class BoolValueChangeDeventArgs

Inherits EventArgs

Public Sub New (Byval Row As INTEGER, _

Byval col As integer, _

BYVAL VAL AS BOOLEAN)

.

End Sub 'NEW

Public property column () AS integer

.

End Property

Public property row () AS Integer

.

End Property

Public Readonly Property BoolValue () as boolean

.

End Property

End Class' BoolValueChangeDeventArgs

The Derived DataGridBoolColumn ClassNow 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 ..

There are some technical points that need to be handled. The Boolean value can change because of 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:

Public Class ClickableBooleancolumn

Inherits DataGridBoolcolumn

'Changed Event

Public Event BoolValuechanged_

As BoolValueChangeDeventHandler

'Set Variables to Start Tracking Bool ChangesProtected Overloads Overrides Sub Edit (...

Me.lockValue = true

Me.beingedited = true

Me.saverow = rownum

Me.savevalue = CBOOL (_ _

Mybase.getcolumnvalueatrow (_

[Source], rownum)

Mybase.edit (...)

End Sub 'Edit

'Overridden to Handle Boolchange Event

Protected overloads overrides sub paint (..)

DIM colnum as integer = _

Me.DataGridtables.gridcolumnStyles.Indexof (me)

'use to handle the boolchanging

ManageBoolValueChanging (Rownum, Colnum)

Mybase.paint....

End Sub 'Paint

'Turn Off Tracking Bool Changes

Protected overrides function commit (..) AS Boolean

Me.lockValue = true

Me.beingedited = false

Return mybase.commit (Datasource, Rownum)

End Function 'Commit

'Fields to TRACK BOOL VALUE

Private savevalue as boolean = false

Private saverow as integer = -1

Private LockValue As Boolean = FALSE

Private beingedited as boolean = false

Public const vk_space as integer = 32

'Needed to get the space bar changing of the bool value

_

Shared function getKeyState (_

Byval nvirtkey as integer) AS SHORT

END FUNCTION

'Fire the Bool Change Event If The Value Changes

Private Sub ManageBoolValueChanging (_

Byval rownum as integer, _

Byval colnum as integer

Dim Mousepos_

As point = me.dataGridtablestyle.dataGrid.pointtoclient (_

Control.MousePosition)

DIM DG As DataGrid = Me.DataGridtablesTyle.DataGrid

Dim isclickincell as boolean = _

Control.mousebuttons = mousebuttons.left andalso _

Dg.Getcellbounds (Dg.currentcell) .Contains (MousePOS)

DIM Changing as boolean = _

Dg.focused andalso isclickincell _orelse getKeyState (vk_space) <0

'or Spacebar

IF not LockValue Andalso_

Beinged andalso _

Changing andalso _

Saverow = rownum dam

SaveValue = not Savevalue

LockValue = false

'Fire the Event

Dim e as new boolvaluechangedeventArgs (_

Rownum, Colnum, Savevalue

RaiseEvent BoolValueChanged (ME, E)

END IF

If SAVEROW = Rownum Then

LockValue = false

END IF

End Sub 'ManageBoolValueChanging

END CLASS

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.

'Discontinued.

DIM DiscontinueDcol as new clickablebooleancolumn ()

DiscontinueDcol.mappingname = "discontinued"

DiscontinueDcol.Headertext = ""

DiscontinueDcol.width = 30

'Turn Off Tristate

DiscontinueDcol.allownull = false

AddHandler DiscontinueDcol.BoolValueChanged, _

Addressof BoolcellClicked

TableStyle.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.

Private sub boolclicked (Byval Sender As Object, _Byval E as BoolValueChangeDeventArgs)

Dim g as datagrid = me.dataGrid1

DIM DiscontinueDcol as new _

ClickableBooleancolumn = _

g.tablestyles (0) .gridColumnStyles ("discontinued")

G.Endedit (DiscontinueDcol, E.ROW, FALSE)

Refreshrow (E.ROW)

g.BegineDit (DiscontinuedCol, E.ROW)

End Sub

'Forces a repaint of given row.

Private sub refreshrow (Byval Row As Integer)

Dim r as reccTangle = _

Me.DataGrid1.Getcellbounds (Row, 0)

R = New Rectangle (R.right, R.top, _

Me.DataGrid1.width, r.height)

Me.DataGrid1.invalidate (r)

End Sub '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 have refresh the row.

IF aftercurrentcellchanged andalso _

HTI.ROW

hti.type = DataGrid.hittesttype.cell andalso _

hti.column = 0 THEN

Me.DataGrid1 (HTI.ROW, 0) = _

Not cbool (me.datagrid1 (hti.row, 0))

Refreshrow (HTI.ROW)

END IF

Customization 6: adding row-dependent tooltips

The Windows Forms ToolTip class can be used to add a ToolTip for an entire DataGrid. To do so, drag the ToolTip component onto the Form and set the ToolTip property that is added to every control on the form. However, implementing a ToolTip in this manner does not allow the tip to be varied when moving from row to row (or cell to cell) in the DataGrid. The goal is to display a ToolTip that can be varied from row to row. Using the same strategy, ToolTips could be implemented that vary from cell to cell or column to column.Start by adding two fields to the Form class, hitRow and ToolTip1. These fields are initialized in Form1_Load, and are dynamically changed in the dataGrid1_MouseMove event handler that was added. in the MouseMove, check That The Cursor is over a new row, andiff the tooltip with diffrest text. See The Following Code Example:

Private Hitrow AS Integer

Private tooltip1 as system.windows.Forms.Tooltip

'In form1_load

Me.hitrow = -1

Me.Tooltip1 = new tooltip ()

Me.Tooltip1.initialdeLay = 300

AddHandler Me.DataGrid1.mousemove, _

Addressof DataGrid1_mousemove

PRIVATE SUB DATAGRID1_MOUSEMOVE (_

Byval sender as object, _

ByVal e as mouseeventargs)

Dim g as datagrid = me.datagrid1

Dim hti as datagrid.hittestinfo = _

g.hittest (New Point (E.x, E.Y))

DIM BMB As BindingManagerBase = _

Me.BindingContext (g.datasource, g.dataMember)

IF hti.row

hti.type = DataGrid.hittesttype.cell andalso _

HTI.ROW <> Hitrow Then

IF not (me.tooltip1 is nothing) Andalso _

Me.Tooltip1.Active Then

Me.Tooltip1.active = false 'Turn IT OFF

END IF

DIM TIPTEXT AS STRING = "" "

If CBOOL (ME.DataGrid1 (hti.row, 0)).

Tiptext = me.dataGrid1 (hti.row, 2) .tostring () & _ "discontinued"

IF tiptext <> "" ""

'new hit row

Hitrow = HTI.ROW

Me.Tooltip1.SetTooltip (Me.DataGrid1, Tiptext)

'Make it Active So it can show

Me.Tooltip1.active = true

Else

Hitrow = -1

END IF

Else

Hitrow = -1

END IF

END IF

End Sub 'DataGrid1_Mousemove

Figure 8 DataGrid Showing Row-Dependent Tooltips

Conclusion

The final DataGrid uses two derived DataGridColumnStyles to handle cell formatting and monitor Boolean value changes. The formatting is accomplished by overriding the derived classes' Paint method, and raising an event to allow a listener to furnish format information based on a row and column index. .

Also used are form level events to control DataGrid behavior. The DataGrid.CurrentCellChanged event was used to force the current cell to a particular cell in the row, no matter which cell in the row was clicked. The DataGrid.Clicked event was used to make the CheckBox column responds to the first click, instead of requiring two clicks to change its value. Finally, the DataGrid.MouseMove event was used to make ToolTips dynamic by changing from row to row.

转载请注明原文地址:https://www.9cbs.com/read-95103.html

New Post(0)