Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

DataGrid With Well-behaved ToolTips

4.65/5 (10 votes)
19 Dec 20064 min read 1   1.5K  
A DataGrid that displays the correct ToolTip on each cell after sorting, and displays the ToolTip on a cell even when it is being edited.

Screenshot showing sorted columns and tooltip on edited cell

Contents

Introduction

There are a few problems in displaying ToolTips for the contents of DataGrid cells:

  • There is no obvious way to show ToolTips for the contents of the DataGrid cells.
  • When a DataGrid is sorted, the ToolTips need to be associated with the correct cells after the sort.
  • When a DataGridTextBoxColumn cell is selected, the cell's events might not fire because the cell is covered by a DataGridTextBox.
  • When a cell in a DataGridBoolColumn is being modified, the modified value is difficult to obtain.

These problems affect DataGrids in both .NET 1.1 and 2.0. The 2.0 DataGridView makes it much easier to display ToolTips, but some developers have to continue using 1.1 for legacy reasons, or may not want to go through the trouble of porting their DataGrids to DataGridViews.

This article explains how to solve the above problems with ToolTips in DataGrid cells.

Note: In the examples below, some details have been removed or combined for clarity. See the source Zip for a more complete example.

Displaying a ToolTip for the correct row

In a DataGrid event handler, use DataGrid.HitTestInfo to get the target row and column in the DataGrid, and use a CurrencyManager to get the corresponding row and column in the data source. If sorting is allowed in a DataGrid, the row number returned by DataGrid.HitTestInfo may refer to a different row in the data source. For example, the third row displayed in the DataGrid may be the first row in the data source. HitTestInfo returns the index for the row as it appears in the DataGrid; the CurrencyManager maps that index to the data source's row. Once the correct source cell has been found, it's easy to set the ToolTip to the contents of that cell.

C#
private void GridTest_MouseMove(object sender, MouseEventArgs e)
{
    // Determine which cell to inspect.

    DataGrid.HitTestInfo hitInfo = gridTest.HitTest(new Point(e.X, e.Y));
    CurrencyManager hitManager = (CurrencyManager)
      this.BindingContext[gridTest.DataSource, gridTest.DataMember];
        
    // If current target is valid cell, display contents in ToolTip.
    
    if (hitInfo.Row < hitManager.List.Count &&
        hitInfo.Type == DataGrid.HitTestType.Cell &&
        hitManager.List is DataView)
    {
        // Use CurrencyManager to get DataRowView corresponding to data 
        // source's row.
        
        DataRowView view = ((DataView)hitManager.List)[hitInfo.Row];
        
        // Now that row is available, set ToolTip to cell's contents or, 
        // if cell is empty, to null indicator.
        
        string tipText = 
            view.Row.IsNull(hitInfo.Column) ? "(null)" : 
                     view.Row[hitInfo.Column].ToString();
        toolTipTest.SetToolTip(gridTest, tipText);
    }
}

Displaying a ToolTip for a selected DataGridTextBoxColumn cell

When a DataGridTextBoxColumn cell is selected, the cell's DataGridTextBox takes over. It covers so much of the cell that many of the DataGrid's events are unlikely to fire in that cell. To display a ToolTip in this situation, assign an event handler to each DataGridTextBox in the DataGrid and use that event handler to display the ToolTip.

C#
private void InitializeControls()  
{
    // Assign MouseMove event handlers to all DataGridTextBoxes in DataGrid
    
    for (int tableIndex = 0; tableIndex < gridTest.TableStyles.Count; 
                             tableIndex++) 
    {  
        DataGridTableStyle tableStyle = gridTest.TableStyles[tableIndex]; 
    
        for (int columnIndex = 0; columnIndex < 
                 tableStyle.GridColumnStyles.Count; columnIndex++)
        {
            DataGridTextBoxColumn columnStyle = 
                tableStyle.GridColumnStyles[columnIndex] as DataGridTextBoxColumn; 
        
            // If the column is a DataGridTextBoxColumn (i.e. not null), 
            // assign MouseMove event handler to its TextBox.
        
            if (columnStyle != null) 
            { 
                columnStyle.TextBox.MouseMove += 
                    new MouseEventHandler(this.TextBox_MouseMove);
            }
        } 
    }
}

In the event handler, use the DataGridTextBox's text for the ToolTip:

C#
private void TextBox_MouseMove(object sender, MouseEventArgs e)
{
    // Get textbox.
    
    DataGridTextBox hitBox = sender as DataGridTextBox;
    
    if (hitBox != null)
    {
        // Set ToolTip to textbox's text or, if textbox is empty, to 
        // null indicator.
    
        string tipText = hitBox.Text == null ? "(null)" : hitBox.Text;
        toolTipTest.SetToolTip(hitBox, tipText);
    }
}

Displaying a ToolTip for an edited DataGridBoolColumn cell

For DataGridBoolColumn cells, it is more difficult to keep the ToolTip in sync with the displayed check box while the cell is being edited. Although it looks like there is a CheckBox control in the cell, it's just paint. One workaround is to use the DataGrid's MouseUp event to switch to another cell in the same row. This results in a proposed DataRowVersion that can be used to get the information to be displayed in the ToolTip. Note that this approach works only when there is more than one column in the DataGrid. If your DataGrid has only one column, you need to try one of the alternative approaches.

C#
private void GridTest_MouseUp(object sender, MouseEventArgs e)
{
    // Get info to determine which cell to inspect.
    
    DataGrid.HitTestInfo hitInfo = gridTest.HitTest(new Point(e.X, e.Y));
    CurrencyManager hitManager = (CurrencyManager)
       this.BindingContext[gridTest.DataSource, gridTest.DataMember];
    
    // If current target is valid cell, display contents in ToolTip. 
    
    if (hitInfo.Row < hitManager.List.Count &&
        hitInfo.Type == DataGrid.HitTestType.Cell)
    {
        // Use CurrencyManager to get DataRowView corresponding to data 
        // source's row.
        
        DataRowView view = ((DataView)hitManager.List)[hitInfo.Row];
        
        if (view.Row.Table.Columns[hitInfo.Column].DataType == typeof(bool))
        {
            // Current target is in bool column. Keep track of current cell.

            DataGridCell cell = gridTest.CurrentCell;

            // Set direction of column change, depending on whether current 
            // column is last one in row.

            int columnChange = 
                view.Row.Table.Columns.Count == cell.ColumnNumber + 1 ? -1 : 1;

            // Move to another column to create proposed DataRowVersion, then back
            // again so that user is still in same cell.

            gridTest.CurrentCell = 
                new DataGridCell(cell.RowNumber, cell.ColumnNumber + columnChange);
            gridTest.CurrentCell = cell;

            // If move caused proposed version to be created, get cell information 
            // from proposed version.

            if (view.Row.HasVersion(DataRowVersion.Proposed)) 
            {
                // Set ToolTip to proposed version or, if proposed version is 
                // empty, to null indicator.
                
                string tipText = 
                    view.Row.IsNull(view.Row.Table.Columns[hitInfo.Column], 
                    DataRowVersion.Proposed) ? "(null)" : 
                    view.Row[hitInfo.Column, DataRowVersion.Proposed].ToString();
                toolTipTest.SetToolTip(gridTest, tipText);
            }
        }
    }
}

Other approaches to the DataGridBoolColumn problem

Another approach is to derive a new DataGridColumnStyle for the target column(s). See MSDN for an example of this approach. The example's approach returns the current value of the underlying data source rather than the value corresponding to the displayed check box, so it takes some additional effort to make the edited value available for the ToolTip.

Yet another approach is to specify a DataGridTextBoxColumn style for bool columns, which results in the state being displayed as text rather than as a check box. This approach is not ideal because the user has to type "true" or "false" to change the value, but it does have the virtue of not requiring cell-switching to capture the change.

Respecting DataGridColumnStyle NullText

When DataGridColumnStyles have been explicitly defined, it makes sense to respect the NullText setting when displaying cell ToolTips. One way to do this is to create a method to get the display text and use it instead of displaying the hard-coded "(null)" in the GridTest_MouseMove and GridTest_MouseUp examples above.

C#
private string GetNullStyleInfo(int hitColumn)
{
    // Assume for the sake of this example that the DataGridTableStyle is
    // GridStyle. 
    
    if (GridStyle.GridColumnStyles.Count > hitColumn)
    {
        // The column has a DataGridColumnStyle. Return the NullText string.
        
        DataGridColumnStyle hitStyle = GridStyle.GridColumnStyles[hitColumn];
        return hitStyle.NullText;
    }
    
    // No DataGridColumnStyle was found. Return a default null text string.
    
    return "(null)";
}

In the GridTest_MouseMove and GridTest_MouseUp methods above, replace the hard-coded "(null)" with a call to GetNullStyleInfo(hitColumn). There's no need to modify the TextBox_MouseMove method because it already displays the correct text (i.e., the DataGridTextBox Text already has the correct text, and the hard-coded "(null)" will likely never be used).

History

  • 12-17-2006:
    • Updated article to discuss column style null text.
  • 11-25-2006:
    • Improved code and updated article.
  • 10-21-2005:
    • Added support for various data types and column styles.
  • 09-29-2005:
    • Initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here