I believe that my questions are all going to revolve around the internal workings of DataGridView.
While this is specifically written in VB.Net, it is definitely a general Visual Studio object question generically related to anyone working with .Net.
I've been trying to find a forum where I can get some interaction and help to move forward.
Microsoft's websites seem to be all questions and no answers.

I'm using VB.Net under Visual Studio 2005 SP2.

I need to have a DataColumn that is dynamically visible based on other data in the same DataRow.

After a lot of unsuccessful testing with various methods of accomplishing this, I came across the concept of Custom Cells/Columns.

I started with several slightly varying examples and everything seemed to be falling into place. I tried to research each property and attribute as I incrementally learned a lot about the internal workings of Windows controls in general and DataGridView in particular.

The confusing symptom is that as I scroll down through the grid, the painting of the custom cells is at best unreliable.

I created a minimum test case to eliminate as many variables as possible and I combined all of the source into one Form1.vb as follows:

Option Explicit On
Option Strict On
Public Class Form1
  Public m_OrderDetails As Generic.List(Of OrderDetail)
  Public m_dgv As New System.Windows.Forms.DataGridView

  Public Enum GridViewColumnType
    TextBox
    InvisibleTextBox
  End Enum

  Public Sub New()
    MyBase.New()
    InitializeComponent()
    ' fake the usual load procedures with minimum so this screen can stand alone
    m_OrderDetails = New Generic.List(Of OrderDetail)
    Dim LineCounter As Int32 = 0
    Dim ItemCounter As Int32 = 0
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.Percent)
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.ItemCodes)
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.Percent)
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.Dollars)
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.Percent)
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.ItemCodes)
    GenerateSetOfLines(LineCounter, ItemCounter, OrderDetail.ProductItemDealerSplitType.Percent)
  End Sub

  Private Sub GenerateSetOfLines(ByRef linecount As Int32, ByRef itemcount As Int32, ByVal splittype As OrderDetail.ProductItemDealerSplitType)
    itemcount += 1
    linecount += 100
    Dim od As New OrderDetail(linecount, "ITEM" & itemcount.ToString, splittype)
    m_OrderDetails.Add(od)
    linecount += 100
    od = New OrderDetail(linecount, String.Empty, splittype)
    m_OrderDetails.Add(od)
    linecount += 100
    od = New OrderDetail(linecount, String.Empty, splittype)
    m_OrderDetails.Add(od)
  End Sub

  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Me.Size = New Size(350, 175)
    With m_dgv
      .Name = "DataGridView1"
      .Location = New Point(0, 0)
      .Dock = DockStyle.Fill
      .AutoGenerateColumns = False
      .AllowUserToAddRows = False
      .AllowUserToDeleteRows = False
      AddCol(m_dgv, "Line#", GridViewColumnType.TextBox, "DisplayLineNumberColumn", "DisplayLineNumber", True, DataGridViewContentAlignment.MiddleLeft, "00000")
      AddCol(m_dgv, "Item", GridViewColumnType.TextBox, "ItemCodeColumn", "ItemCode", True, DataGridViewContentAlignment.MiddleLeft, "")
      AddCol(m_dgv, "Split Type", GridViewColumnType.InvisibleTextBox, "SplitTypeColumn", "DealerSplitType", True, DataGridViewContentAlignment.MiddleLeft, "")
      .DataSource = m_OrderDetails
    End With
    Me.Controls.Add(m_dgv)
    m_dgv.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCells)
    AddHandler m_dgv.CellFormatting, AddressOf DataGridView1_CellFormatting
  End Sub

  Private Sub AddCol(ByVal grid As DataGridView, ByVal headertext As String, ByVal columntype As GridViewColumnType, ByVal name As String, ByVal propertyname As String, ByVal protect As Boolean, ByVal align As Windows.Forms.DataGridViewContentAlignment, ByVal format As String)
    Dim newcol As DataGridViewColumn
    Select Case columntype
      Case GridViewColumnType.InvisibleTextBox
        newcol = New InvisibleTextBoxColumn
      Case Else
        newcol = New DataGridViewTextBoxColumn
    End Select
    newcol.Name = name
    newcol.DataPropertyName = propertyname
    newcol.HeaderText = headertext
    newcol.ReadOnly = protect
    newcol.DefaultCellStyle.Alignment = align
    newcol.DefaultCellStyle.Format = format
    grid.Columns.Add(newcol)
  End Sub

  Private Sub DataGridView1_CellFormatting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs)
    If e.ColumnIndex <> m_dgv.Columns.Item("SplitTypeColumn").Index Then
      Exit Sub
    End If
    Dim invtextbox As InvisibleTextBoxCell = CType(m_dgv.Item(e.ColumnIndex, e.RowIndex), InvisibleTextBoxCell)
    invtextbox.Invisible = (m_dgv.Item("ItemCodeColumn", e.RowIndex).Value.ToString = String.Empty)
    Dim sb As New System.Text.StringBuilder
    sb.Append("CellFormat: ")
    sb.Append(m_dgv.Item("DisplayLineNumberColumn", e.RowIndex).Value.ToString)
    sb.Append(" ")
    sb.Append(m_dgv.Item("ItemCodeColumn", e.RowIndex).Value.ToString)
    sb.Append(" ")
    sb.Append(invtextbox.Instance.ToString)
    sb.Append(" ")
    sb.Append(invtextbox.Invisible.ToString)
    Debug.Print(sb.ToString)
  End Sub
End Class

Public Class OrderDetail
  Private m_DisplayLineNumber As Int32
  Private m_ItemCode As String
  Private m_DealerSplitType As ProductItemDealerSplitType

  Public Enum ProductItemDealerSplitType
    Percent = 0
    Dollars = 1
    ItemCodes = 2
  End Enum

  Public Sub New(ByVal displaylinenumber As Int32, ByVal itemcode As String, ByVal dealersplittype As ProductItemDealerSplitType)
    m_DisplayLineNumber = displaylinenumber
    m_ItemCode = itemcode
    m_DealerSplitType = dealersplittype
  End Sub

  Public ReadOnly Property DealerSplitType() As ProductItemDealerSplitType
    Get
      Return m_DealerSplitType
    End Get
  End Property

  Public ReadOnly Property DisplayLineNumber() As Int32
    Get
      Return m_DisplayLineNumber
    End Get
  End Property

  Public ReadOnly Property ItemCode() As String
    Get
      Return m_ItemCode
    End Get
  End Property
End Class

Public Class InvisibleTextBoxCell
  Inherits Windows.Forms.DataGridViewTextBoxCell

  Private InvisibleValue As Boolean
  Private InstanceValue As Int32

  Public Property Instance() As Int32
    Get
      Return Me.InstanceValue
    End Get
    Set(ByVal value As Int32)
      Me.InstanceValue = value
    End Set
  End Property

  Public Property Invisible() As Boolean
    Get
      Return Me.InvisibleValue
    End Get
    Set(ByVal value As Boolean)
      Me.InvisibleValue = value
      If Me.DataGridView IsNot Nothing Then
        If Me.DataGridView.ReadOnly OrElse _
          Me.DataGridView.Rows.Item(Me.RowIndex).ReadOnly OrElse _
          Me.DataGridView.Columns.Item(Me.ColumnIndex).ReadOnly Then
        Else
          Me.ReadOnly = value
        End If
      End If
    End Set
  End Property

  Public Overrides Function Clone() As Object
    Dim Cell As InvisibleTextBoxCell = CType(MyBase.Clone(), InvisibleTextBoxCell)
    Cell.Invisible = Me.Invisible
    LastInstanceId += 1
    Cell.Instance = LastInstanceId
    Return Cell
  End Function

  Public Sub New()
    MyBase.New()
    Me.InvisibleValue = False
    LastInstanceId += 1
    Me.InstanceValue = LastInstanceId
  End Sub

  Protected Overrides Sub Paint(ByVal graphics As Drawing.Graphics, ByVal clipBounds As Drawing.Rectangle, ByVal cellBounds As Drawing.Rectangle, ByVal rowIndex As Integer, ByVal elementState As Windows.Forms.DataGridViewElementStates, ByVal value As Object, ByVal formattedValue As Object, ByVal errorText As String, ByVal cellStyle As Windows.Forms.DataGridViewCellStyle, ByVal advancedBorderStyle As Windows.Forms.DataGridViewAdvancedBorderStyle, ByVal paintParts As Windows.Forms.DataGridViewPaintParts)
    Dim sb As New System.Text.StringBuilder
    sb.Append("InvTxtPaint: ")
    sb.Append(MyBase.DataGridView.Rows.Item(rowIndex).Cells.Item("DisplayLineNumberColumn").Value.ToString())
    sb.Append(" ")
    sb.Append(MyBase.DataGridView.Rows.Item(rowIndex).Cells.Item("ItemCodeColumn").Value.ToString())
    sb.Append(" ")
    sb.Append(Me.InstanceValue.ToString)
    sb.Append(" ")
    sb.Append(Me.InvisibleValue.ToString.ToUpper)
    Debug.Print(sb.ToString)
    If Me.InvisibleValue Then
      ' The textbox cell is invisible, so paint the border and background
      ' Draw the background of the cell, if specified.
      If (paintParts And Windows.Forms.DataGridViewPaintParts.Background) = Windows.Forms.DataGridViewPaintParts.Background Then
        Dim cellBackground As New Drawing.SolidBrush(cellStyle.BackColor)
        graphics.FillRectangle(cellBackground, cellBounds)
        cellBackground.Dispose()
      End If
      ' Draw the cell borders, if specified.
      If (paintParts And Windows.Forms.DataGridViewPaintParts.Border) = Windows.Forms.DataGridViewPaintParts.Border Then
        PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
      End If
    Else
      ' The textbox cell is not invisible, so let the base class handle the painting.
      MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)
    End If
  End Sub
End Class

Public Class InvisibleTextBoxColumn
  Inherits Windows.Forms.DataGridViewTextBoxColumn

  Public Sub New()
    Me.CellTemplate = New InvisibleTextBoxCell()
  End Sub
End Class

The only other code is a Public variable called LastInstanceId in a module file so that I could generate an identifier for each created Custom Cell.

Structurally I am binding the grid to a Generic.List of Custom Business Objects. I believe there is enough evidence here that something very unusual is going on but I can't imagine what some of the answers might lead to.

As the form is first loaded, the grid appears entirely correct and the following Output messages are produced:

CellFormat: 100 ITEM1 3 False
InvTxtPaint: 100 ITEM1 3 FALSE
CellFormat: 200 7 True
InvTxtPaint: 200 5 FALSE
CellFormat: 300 9 True
InvTxtPaint: 300 5 FALSE
CellFormat: 400 ITEM2 11 False
InvTxtPaint: 400 ITEM2 5 FALSE
CellFormat: 500 13 True
InvTxtPaint: 500 5 FALSE
CellFormat: 600 15 True
InvTxtPaint: 600 5 FALSE
CellFormat: 100 ITEM1 3 False
InvTxtPaint: 100 ITEM1 3 FALSE
CellFormat: 200 7 True
InvTxtPaint: 200 7 TRUE
CellFormat: 300 9 True
InvTxtPaint: 300 9 TRUE
CellFormat: 400 ITEM2 11 False
InvTxtPaint: 400 ITEM2 11 FALSE
CellFormat: 500 13 True
InvTxtPaint: 500 13 TRUE
CellFormat: 600 15 True
InvTxtPaint: 600 15 TRUE

Scrolling down one line at a time works correctly but clicking below the scrollbar handle to cause an entire page at a time produces the following Output:

CellFormat: 600 15 True
InvTxtPaint: 600 15 TRUE
CellFormat: 700 ITEM3 17 False
InvTxtPaint: 700 ITEM3 5 FALSE
CellFormat: 800 19 True
InvTxtPaint: 800 5 FALSE
CellFormat: 900 21 True
InvTxtPaint: 900 5 FALSE
CellFormat: 1000 ITEM4 23 False
InvTxtPaint: 1000 ITEM4 5 FALSE
CellFormat: 1100 25 True
InvTxtPaint: 1100 5 FALSE

The InstanceId's and Invisible properties are totally out of control starting with line 700.

So, my questions go something like this:

Why does a Custom Cell have to have a Clone method at all and how is functionally used?
Why would there ever be 2 instances of the same Custom Cell at the same point in time?
Where are all of the 'missing' InstanceId's?
Why isn't the Paint method inside the Custom Cell class getting the same instance that was just immediately processed by the CellFormatting event?
When I intentionally avoid interacting directly with the form and I move some other window in front of it and away again, the resulting repaint is again entirely correct without any of the InstanceId 'problems'. Why?

When I decided to do this as a learning experience, I didn't know how much I was going to need to understand.

Then again, there could just be a bug somewhere in this code that I can't see because I have been looking at it for too long.

Thank you in advance for any insights and your time.

Recommended Answers

All 6 Replies

I believe the answer to your original question, a dynamically visible column, lies in the property Visible for the Columns collection.

If DataGridView1.Rows(243).Cells(2).Value.Equals("some value") Then
   DataGridView1.Columns(5).Visible = False
End If

That was one of several painful alternatives that I tried before deciding to learn about Custom Cells.

It didn't make the cell invisible only for the specific row.
It proceeded to make the column visible and invisible as the grid was populated.
Whatever the last painted row decided to do, that was the way the column was left.

I need every row that has a non-blank value in the cell in column 'A' to cause the cell in column 'B' to be visible.
Otherwise, the cell in column 'B' has to be invisible, protected, and not editable.

This feature is correctly implemented by some third party controls, but why should such a simple concept not be supported by a standard grid?

Then I do apologize. I have no idea of how to implement that.

Nobody else has any input to offer?

Nobody else has any input to offer?

Hi kmh. If making cell invisible means making it invisible to all intents and purposes, then you might consider using a DatagridViewCellstyle to mimic invisibility, something like code below: button1 creates dummy grid button2 makes a cell pseudo invisible based on a condition

Public Class Form1
    Dim dgv As DataGridView
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        dgv = New DataGridView
        Dim creatureColumn As New DataGridViewTextBoxColumn()
        Dim legsColumn As New DataGridViewTextBoxColumn()
        creatureColumn.HeaderText = "Creature"
        legsColumn.HeaderText = "Legs"
        dgv.Columns.Add(creatureColumn)
        dgv.Columns.Add(legsColumn)
        dgv.Rows.Add(3)
        With dgv.Rows(0)
            .Cells(0).Value = "Dog"
            .Cells(1).Value = 4
        End With
        With dgv.Rows(1)
            .Cells(0).Value = "Man"
            .Cells(1).Value = 2
        End With
        With dgv.Rows(2)
            .Cells(0).Value = "Caterpillar"
            .Cells(1).Value = "Not Sure"
        End With
        Me.Controls.Add(dgv)


    End Sub


    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

        Dim InvisibleStyle As New DataGridViewCellStyle(dgv.DefaultCellStyle)
        With InvisibleStyle
            .ForeColor = .BackColor
            .SelectionForeColor = .SelectionBackColor
        End With

        Dim row As DataGridViewRow
        For Each row In dgv.Rows
            If Not IsNumeric(row.Cells(1).Value) Then
                With row.Cells(1)
                    .ReadOnly = True
                    .Style = InvisibleStyle
                End With
            End If
        Next
    End Sub
End Class

Thanks for the suggestion but as I said I earlier went through a lot of unsuccessful testing.

I don't remember exactly why but i had attempted to do custom cell styles also.

I really need to know where I can get information on the internal processing for DataGridView in general, and Custom Cells/Column in particular.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.