1

I have created a custom control to edit a custom value in a DataGridView cell. I am following the example here: How to: Host Controls in Windows Forms DataGridView Cells

I have a custom data class that is a member of a list of objects bound to the DataGridView.

internal class CustomValue
{
   // Some stuff is here.
}

I have created a custom control to edit the value in the cell that implements the IDataGridViewEditingControl interface.

internal partial class CustomValueEditControl : UserControl, IDataGridViewEditingControl
{
        DataGridView m_dataGridView;

        private bool m_valueChanged = false;

        int m_rowIndex;

        private CustomValue m_value;

        public CustomValue Value
        {
            get
            {
                return m_value;
            }
            set
            {
                m_value = value;
            }
        }

        public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
        {
        }

        public DataGridView EditingControlDataGridView
        {
            get
            {
                return m_dataGridView;
            }
            set
            {
                m_dataGridView = value;
            }
        }

        public object EditingControlFormattedValue
        {
            get
            {
                return this.Value.ToString();
            }
            set
            {
                if (value is String)
                {
                    CustomValue val;
                    if (CustomValue.TryParse((String)value, out val))
                    {
                        this.Value = val;
                    }
                    else
                    {
                        this.Value = new CustomValue();
                    }
                }
            }
        }

        public int EditingControlRowIndex
        {
            get
            {
                return m_rowIndex;
            }
            set
            {
                m_rowIndex = value;
            }
        }

        public bool EditingControlValueChanged
        {
            get
            {
                return m_valueChanged;
            }
            set
            {
                m_valueChanged = value;
            }
        }

        public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)
        {
            switch (keyData & Keys.KeyCode)
            {
                case Keys.Up:
                case Keys.Down:
                    return true;

                default:
                    return !dataGridViewWantsInputKey;
            }
        }

        public Cursor EditingPanelCursor
        {
            get
            {
                return base.Cursor;
            }
        }

        public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
        {
            return EditingControlFormattedValue;
        }

        public void PrepareEditingControlForEdit(bool selectAll)
        {
        }

        public bool RepositionEditingControlOnValueChange
        {
            get
            {
                return false;
            }
        }
}

I have set up a custom DataGridViewCell that descends from DataGridViewTextBoxCell so the cell just displays a string representation of the custom value until it is edited.

internal class CustomValueCell : DataGridViewTextBoxCell
{

    public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
    {
        base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);

        CustomValueEditControl customValueEditControl = DataGridView.EditingControl as CustomValueEditControl;

        if (this.Value == null)
        {
            customValueEditControl.Value = (CustomValue)this.DefaultNewRowValue;
        }
        else
        {
            customValueEditControl.Value = (CustomValue)this.Value;
        }
    }

    public override Type EditType
    {
        get
        {
            return typeof(CustomValueEditControl);
        }
    }

    public override Type ValueType
    {
        get
        {
            return typeof(CustomValue);
        }
    }

    public override Type FormattedValueType
    {
        get
        {
            return typeof(string);
        }
    }

    public override object DefaultNewRowValue
    {
        get
        {
            return new CustomValue();
        }
    }
}

I have created a custom DataGridViewColumn.

internal class CustomValueColumn : DataGridViewColumn
{
    public CustomValueColumn()
        : base(new CustomValueCell())
    {
    }

    public override object Clone()
    {
        CustomValueColumn clone = (CustomValueColumn)base.Clone();

        return clone;
    }

    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            // Ensure that the cell used for the template is a CustomValueCell.
            if (value != null && !value.GetType().IsAssignableFrom(typeof(CustomValueCell)))
            {
                throw new InvalidCastException("Must be a CustomValueCell");
            }

            base.CellTemplate = value;
        }
    }
}

The cell shows the value of the custom type correctly, and I can use the custom control to edit the value. When I leave the editing cell, I get a System.FormatException: Invalid cast from 'System.String' to 'CustomValue' at System.Convert.DefaultToType(IConvertable value, Type targetType, IFormatProvider provider)...

At what point is it trying to cast a string to my custom value? Should this not be handled by the CustomValueEditControl.GetEditingControlFormattedValue?

Zoltan F
  • 63
  • 7
  • It's been a while since I had to something like this, but it sounds like you need to override the `GetFormattedValue` method of the `DataGridViewCell`, see the section about it [here](https://msdn.microsoft.com/en-us/library/aa730881(v=vs.80).aspx#dtH1) (near the middle) – amura.cxg Apr 15 '16 at 20:21
  • I have tried that. I think the DataGridViewTextBoxCell already overrides that to return a string from the object. – Zoltan F Apr 15 '16 at 20:29

2 Answers2

2

So I ended up solving this by implementing a TypeConverter class.

internal class CustomValueTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }

        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            return true;
        }

        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            CustomValue customValue;

            if (CustomValue.TryParse((string)value, out customValue))
            {
                return customValue;
            }
        }

        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            value.ToString();
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }
}

Then I had to decorate the CustomValue class with a TypeConverterAttribute.

[TypeConverter(typeof(CustomValueTypeConverter))]
internal class CustomValue
{
  // Some stuff here.
}
Zoltan F
  • 63
  • 7
  • This is the correct approach. However, the workaround I described above omits parsing and therefore in my opinion is the safer way. – Tobias Knauss Aug 28 '20 at 09:32
  • I think this is working but where there is the line "value.ToString();" that should probably be "return value.ToString();" – DAG Apr 06 '21 at 14:39
1

At what point is it trying to cast a string to my custom value? Should this not be handled by the CustomValueEditControl.GetEditingControlFormattedValue?

Exactly. But the sample you are based on does not handle it correctly. You should use something like this:

public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
{
    if ((context & DataGridViewDataErrorContexts.Parsing) != 0)
    {
        // Here you should not return string, but your value
        return Value;
    }
    return EditingControlFormattedValue;
}

If you return string (which EditingControlFormattedValue property does), then the DataGridView will try to convert it to CustomValue object. The example works because DateTime has associated TypeConverter while your class apparently doesn't have.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 1
    I think I have been down this road somehow. If GetEditingControlFormattedValue returns a CustomValue, then I get a System.ArgumentException: Formatted value of the cell has the wrong type. If I change the FormattedValueType property of the cell to CustomValue, then the cell does not show the string representation when the cell is not being edited. – Zoltan F Apr 16 '16 at 00:43
  • Please see the code snippet in the answer - that's all you need to do. Add the `if` statement and handle differently `DataGridViewDataErrorContexts.Parsing` flag, – Ivan Stoev Apr 16 '16 at 07:32
  • @ZoltanF is right, it will throw an exception. But you can work around that with `override ParseFormattedValue (..) { if (formattedValue is CustomValue) return formattedValue; return base.ParseFormattedValue (..); }` – Tobias Knauss Aug 28 '20 at 09:09