0

I'm trying to make a DataGridViewColumn that inherits all of the properties of a typical NumericUpDown control. I found an answer here on StackOverflow (https://stackoverflow.com/a/55788490/692250) and a MSDN article here (https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-host-controls-in-windows-forms-datagridview-cells?view=netframeworkdesktop-4.8&redirectedfrom=MSDN#code-snippet-1).

The code from the article works well, but... I cannot assign a Maximum nor Minimum value to the control (both are stuck at the "default" Minimum of 0 and Maximum of 100).

I tried to add in code from the SO answer (by adding in a Min and Max field) but doing so locks both in to 0 and unable to change.

The question in short: how do I add the necessary properties to allow to this work.

The custom control is question:

Public Class NumericUpDownColumn
    Inherits DataGridViewColumn

    Public Sub New()
        MyBase.New(New NumericUpDownCell())
    End Sub

    Public Overrides Property CellTemplate() As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As DataGridViewCell)
            ' Ensure that the cell used for the template is a CalendarCell.
            If Not (value Is Nothing) AndAlso Not value.GetType().IsAssignableFrom(GetType(NumericUpDownCell)) Then
                Throw New InvalidCastException("Must be an Integer")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property
End Class


Public Class NumericUpDownCell
    Inherits DataGridViewTextBoxCell

    Public Sub New()
        ' Number Format
        Me.Style.Format = "0"
    End Sub

    Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, ByVal initialFormattedValue As Object, ByVal dataGridViewCellStyle As DataGridViewCellStyle)
        ' Set the value of the editing control to the current cell value.
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
        Dim ctl As NumericUpDownEditingControl = CType(DataGridView.EditingControl, NumericUpDownEditingControl)
        ctl.Value = CType(Me.Value, Decimal)
    End Sub

    Public Overrides ReadOnly Property EditType() As Type
        Get
            ' Return the type of the editing contol that CalendarCell uses.
            Return GetType(NumericUpDownEditingControl)
        End Get
    End Property

    Public Overrides ReadOnly Property ValueType() As Type
        Get
            ' Return the type of the value that CalendarCell contains.
            Return GetType(Decimal)
        End Get
    End Property

    Public Overrides ReadOnly Property DefaultNewRowValue() As Object
        Get
            ' Use the current date and time as the default value.
            Return 0
        End Get
    End Property
End Class


Class NumericUpDownEditingControl
    Inherits NumericUpDown
    Implements IDataGridViewEditingControl

    Private dataGridViewControl As DataGridView
    Private valueIsChanged As Boolean = False
    Private rowIndexNum As Integer

    Public Sub New()
        Me.DecimalPlaces = 0
    End Sub

    Public Property EditingControlFormattedValue() As Object Implements IDataGridViewEditingControl.EditingControlFormattedValue
        Get
            Return Me.Value.ToString("#")
        End Get
        Set(ByVal value As Object)
            If TypeOf value Is Decimal Then
                Me.Value = Decimal.Parse(value.ToString)
            End If
        End Set
    End Property

    Public Function GetEditingControlFormattedValue(ByVal context As DataGridViewDataErrorContexts) As Object Implements IDataGridViewEditingControl.GetEditingControlFormattedValue
        Return Me.Value.ToString("#")
    End Function

    Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As DataGridViewCellStyle) Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl
        Me.Font = dataGridViewCellStyle.Font
        Me.ForeColor = dataGridViewCellStyle.ForeColor
        Me.BackColor = dataGridViewCellStyle.BackColor
    End Sub

    Public Property EditingControlRowIndex() As Integer Implements IDataGridViewEditingControl.EditingControlRowIndex
        Get
            Return rowIndexNum
        End Get
        Set(ByVal value As Integer)
            rowIndexNum = value
        End Set
    End Property

    Public Function EditingControlWantsInputKey(ByVal key As Keys, ByVal dataGridViewWantsInputKey As Boolean) As Boolean Implements IDataGridViewEditingControl.EditingControlWantsInputKey
        ' Let the DateTimePicker handle the keys listed.
        Select Case key And Keys.KeyCode
            Case Keys.Left, Keys.Up, Keys.Down, Keys.Right, Keys.Home, Keys.End, Keys.PageDown, Keys.PageUp
                Return True
            Case Else
                Return False
        End Select
    End Function

    Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) Implements IDataGridViewEditingControl.PrepareEditingControlForEdit
        ' No preparation needs to be done.
    End Sub

    Public ReadOnly Property RepositionEditingControlOnValueChange() As Boolean Implements IDataGridViewEditingControl.RepositionEditingControlOnValueChange
        Get
            Return False
        End Get
    End Property

    Public Property EditingControlDataGridView() As DataGridView Implements IDataGridViewEditingControl.EditingControlDataGridView
        Get
            Return dataGridViewControl
        End Get
        Set(ByVal value As DataGridView)
            dataGridViewControl = value
        End Set
    End Property

    Public Property EditingControlValueChanged() As Boolean Implements IDataGridViewEditingControl.EditingControlValueChanged
        Get
            Return valueIsChanged
        End Get
        Set(ByVal value As Boolean)
            valueIsChanged = value
        End Set
    End Property

    Public ReadOnly Property EditingControlCursor() As Cursor Implements IDataGridViewEditingControl.EditingPanelCursor
        Get
            Return MyBase.Cursor
        End Get
    End Property

    Protected Overrides Sub OnValueChanged(ByVal eventargs As EventArgs)
        ' Notify the DataGridView that the contents of the cell have changed.
        valueIsChanged = True
        Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
        MyBase.OnValueChanged(eventargs)
    End Sub
End Class

Focusing on it a bit more, I found that I can assign values to the Maximum and Minimum fields as so:

        ' Set the value of the editing control to the current cell value.
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
        Dim ctl As NumericUpDownEditingControl = CType(DataGridView.EditingControl, NumericUpDownEditingControl)
        ctl.Value = CType(Me.Value, Decimal)
        ctl.Maximum = 180
        ctl.Minimum = -1
    End Sub

But I cannot seem to pass to this field via the usual means of .Maximum or .Minimum on the constructor. Any tips/advice?

Paul Williams
  • 1,554
  • 7
  • 40
  • 75

1 Answers1

1

This is an easy fix and very easy to overlook.

In your NumericUpDownCell class you need to override the Clone method to include the new Minimum and Maximum properties, for example:

First, add the new properties the NumericUpDownCell class and then override the Clone function

Public Class NumericUpDownCell
    Inherits DataGridViewTextBoxCell
    ...
    Public Property Minimum() As Integer
    Public Property Maximum() As Integer
    ...

    Public Overrides Function Clone() As Object
        Dim retVal As NumericUpDownCell = CType(MyBase.Clone, NumericUpDownCell)

        retVal.Minimum = Me.Minimum
        retVal.Maximum = Me.Maximum

        Return retVal
    End Function
End Class

Second, inside the NumericUpDownCell classes InitializeEditingControl method, add the two lines:

ctl.Minimum = Me.Minimum
ctl.Maximum = Me.Maximum

When you setup the new column, get the CellTemplate to set the new properties, as per:

Dim upDownColumn As New NumericUpDownColumn
Dim cellTemplate As NumericUpDownCell = CType(upDownColumn .CellTemplate, NumericUpDownCell)
cellTemplate.Minimum = 10
cellTemplate.Maximum = 15

Or, following you preference to setup via the constructor, add a new constructor to the NumericUpdDownColumn class as per

Public Sub New(minValue As Integer, maxValue As Integer)
    MyBase.New(New NumericUpDownCell())

    Dim template As NumericUpDownCell = CType(CellTemplate, NumericUpDownCell)
    template.Minimum = minValue
    template.Maximum = maxValue
End Sub

and then use it like:

Dim upDownColumn As New NumericUpDownColumn(100, 150)
Dim cellTemplate As NumericUpDownCell = CType(upDownColumn.CellTemplate, NumericUpDownCell)

DataGridView1.Columns.Add(upDownColumn)
JayV
  • 3,238
  • 2
  • 9
  • 14
  • 1
    @PaulWilliams I changed the name of the variable from `NumericUpDownColumn` to `upDownColumn` so things don't get confused between the object variable and the Type name. This should address the problem you mentioned. – JayV Mar 25 '21 at 09:48
  • 1
    Also, I added a new constructor to the `NumericUpDownColumn` class to address your last sentence to pass the values via the constructor – JayV Mar 25 '21 at 09:49
  • With your edits I managed to get this work quite well. I'm unsure why it's not working via the first method of .Minimum/.Maximum however. – Paul Williams Mar 25 '21 at 10:06
  • 1
    @PaulWilliams The missing `Clone` method meant that whatever you were setting wasn't being copied over to the CellTemplate - That is the only real new code I added. Everything else simply makes using the new column easier – JayV Mar 25 '21 at 10:09
  • That did it. Thanks. – Paul Williams Mar 27 '21 at 12:55