views:

1890

answers:

3

Hi everyone,

I have a DataGridView with custom columns.

But when i add a "DataGridViewComboBoxColumn" and give to it a list with my custom class as datasource then i had the following error:

The following exception occurred in the DataGridView: System.ArgumentException: DataGridViewComboBoxCell value is not valid.

New EDIT: 4/9/2009

Here is my situation:

I've DataBase table called "smsParts" and i have in it these columns "ID" , "ParentID" , "Name" ......

i made class called "smsPart" have these properties:

public int ID
public smsPart Parent
public string Name

.... and others

I've method called "GetsmsParts" return "List<smsPart>".

So i want to make DGV that contain three columns : ID , Name , Parent.

I want the parent culomn to be ComboBoxColumn to select which part is the parent of selected part.

So for that reasone i made "DataGridViewComboBoxColumn" and set it's Datasource the same DataSource for the hole DataGridView "Which is the GetsmsParts method":

    DataGridViewComboBoxColumn comboCulomn = new DataGridViewComboBoxColumn();
    comboCulomn.DataSource = listParts;
    comboCulomn.DataPropertyName = "Parent";
    comboCulomn.DisplayMember = "Name";
    comboCulomn.ValueMember = "ID";
    comboCulomn.Name = "Parent";
    dgvParts.Columns.Add(comboCulomn);

But i always have this error message:

The following exception occurred in the DataGridView: System.ArgumentException: DataGridViewComboBoxCell value is not valid.

please help me ):

+2  A: 

Hi, try assigning the name of the datafield for the ValueMember property of clm2. While you're specifying that the value type is typeof(smsType), you're not telling the ComboBox column which field to use for the value.

EDIT
Wait a second: Is your smsType some complex type or something? I'm not sure if any restrictions apply here, but for a sample you should use something like int or string or so (anything you'd normally expect to be stored as a database field).

Also, of course, the type of the DataGridView's underlying data source's column (in your example called "Type") must also be of the same type as the ValueMember!

EDIT 2
On your second comment: Imagine a database table called "tbl" that contains (among others) one column called "Type" that is of type Integer. You're displaying the contents of that table in your DataGridView and you want the user to be able to select values for the "Type" column from a combo box. This is about the scenario you're talking about.

  1. it is not possible to store complex types in like the one you're using in database columns, so you can not use complex types for the Value field in a DataGridViewComboBoxColumn.
  2. To perform the data binding for the entire grid, you must bind the grid to the database table "tbl". To create the DataGridViewComboBoxColumn, you need to assign a list of possible values to the column and tell the column the field in the DataGridView's datasource it stores the selected value to, which field is used as a display value and which field is used as the value that is stored in the underlying datasource's column.

That means in the sample (assuming the data source for the column contains properties "Value" and "Name"):

DataGridViewComboBoxColumn col = new ...
col.DataSource = columnDataSource;
col.DisplayMember = "Name";
col.ValueMember = "Value";
col.DataPropertyName = "Type";

This is all. However, the type of the property you assign to "ValueMember" can not be a complex type (class/struct) if I recall correctly...

Thorsten Dittmar
Ok, I'm using custom type but not complex it's just have two properties "int and string". and for the second note i didn't understand what you mean so please explain for me.Thank you very much.
Wahid Bitar
I correct my code and set the "ValueMember" to "ID" which it integer but still have the same error ??.
Wahid Bitar
I just read that DataGridView and Combo Box Column are set to the same data source! I don't think that's possible. Even though they contain the same data, try to create two distinct data sources, one for the grid and one for the column.
Thorsten Dittmar
+2  A: 

DataGridViewComboBoxColumn limits input to values in the DataSource. I had this same problem. I was trying to set the field value outside the DGV. I was binding the DGV to a DataTable. If I set a DataRow["somefield"] to a value not in DataSource, I would receive the error you are receiving.

I eventually created a descendant of DataGridViewColumn that support a ComboBox editor and allowed for values not in the DataSource.

I can post the code if you would like to see it.

EDIT: Here's a ComboBox column example

using System;

using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.ComponentModel; using System.Drawing; using System.Windows.Forms.VisualStyles;

namespace YourNamespaceHere { /// /// DataGridView TextBox column with Items support. ///

public class DropTextBoxColumn : DataGridViewColumn
{
    [Browsable(false)]
    public IEnumerable<string> Items { get; set; }

    public ComboBoxStyle DropDownStyle { get; set; }

    public DropTextBoxColumn() : base(new DropTextBoxCell()) 
    {
        DropDownStyle = ComboBoxStyle.DropDown;
    }

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

public class DropTextBoxCell : DataGridViewTextBoxCell
{
    [Browsable(false)]
    public string[] Items { get; set; }

    public DropTextBoxCell() : base() { }


    protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
    {
        base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);

        //draw a drop down button
        if ( (cellState & DataGridViewElementStates.Selected) != 0) 
        {
            var cb = cellBounds;
            var r = new Rectangle(cb.Right - cb.Height, cb.Top, cb.Height, cb.Height);
            //ComboBoxRenderer.DrawTextBox(graphics, cb, formattedValue as string, this.Style.Font ?? DataGridView.Font, ComboBoxState.Normal);
            ComboBoxRenderer.DrawDropDownButton(graphics, r, ComboBoxState.Normal);            
        }
    }
    public override void InitializeEditingControl(int rowIndex, object
        initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
    {
        // Set the value of the editing control to the current cell value.
        base.InitializeEditingControl(rowIndex, initialFormattedValue,
            dataGridViewCellStyle);
        DropTextBoxEditingControl ctl =
            DataGridView.EditingControl as DropTextBoxEditingControl;

        var value = this.Value.ToString();

        ctl.Loading = true;
        DropTextBoxColumn col = DataGridView.Columns[this.ColumnIndex] as DropTextBoxColumn;
        ctl.DropDownStyle = col.DropDownStyle;

        ctl.Items.Clear();
        if (col.Items != null)
            ctl.Items.AddRange(col.Items.ToArray());

        ctl.EditingControlFormattedValue = value;
        ctl.Loading = false;

    }

    public override Type EditType
    {
        get
        {
            // Return the type of the editing contol that CalendarCell uses.
            return typeof(DropTextBoxEditingControl);
        }
    }

    public override Type ValueType
    {
        get
        {
            // Return the type of the value that CalendarCell contains.
            return typeof(string);
        }
    }

    public override object DefaultNewRowValue
    {
        get
        {
            // Use the current date and time as the default value.
            return string.Empty;
        }
    }
}

class DropTextBoxEditingControl : ComboBox, IDataGridViewEditingControl
{
    DataGridView dataGridView;
    private bool valueChanged = false;
    int rowIndex;
    public bool Loading { get; set; }
    int originalIndex = -1;

    public DropTextBoxEditingControl()
    {
        //this.Format = DateTimePickerFormat.Short;
        DropDownStyle = ComboBoxStyle.DropDown;
        FlatStyle = FlatStyle.Flat;     
    }

    // Implements the IDataGridViewEditingControl.EditingControlFormattedValue 
    // property.
    public object EditingControlFormattedValue
    {
        get
        {
            return Text;
        }
        set
        {

            if (value is String)
            {
                if (DropDownStyle == ComboBoxStyle.DropDown)
                    Text = value.ToString();
                else
                {
                    SelectedIndex = originalIndex = Items.IndexOf(value);                        
                }                    
            }
        }
    }

    // Implements the 
    // IDataGridViewEditingControl.GetEditingControlFormattedValue method.
    public object GetEditingControlFormattedValue(
        DataGridViewDataErrorContexts context)
    {
        return EditingControlFormattedValue;
    }

    // Implements the 
    // IDataGridViewEditingControl.ApplyCellStyleToEditingControl method.
    public void ApplyCellStyleToEditingControl(
        DataGridViewCellStyle dataGridViewCellStyle)
    {
        this.Font = dataGridViewCellStyle.Font;
    }

    // Implements the IDataGridViewEditingControl.EditingControlRowIndex 
    // property.
    public int EditingControlRowIndex
    {
        get
        {
            return rowIndex;
        }
        set
        {
            rowIndex = value;
        }
    }

    // Implements the IDataGridViewEditingControl.EditingControlWantsInputKey 
    // method.
    public bool EditingControlWantsInputKey(
        Keys key, bool dataGridViewWantsInputKey)
    {
        // Let the DateTimePicker handle the keys listed.
        //switch (key & Keys.KeyCode)
        //{
        //    case Keys.Left:
        //    case Keys.Up:
        //    case Keys.Down:
        //    case Keys.Right:
        //    case Keys.Home:
        //    case Keys.End:
        //    case Keys.PageDown:
        //    case Keys.PageUp:
        //        return true;
        //    default:
        //        return !dataGridViewWantsInputKey;
        //}

        return DroppedDown;

    }

    // Implements the IDataGridViewEditingControl.PrepareEditingControlForEdit 
    // method.
    public void PrepareEditingControlForEdit(bool selectAll)
    {
        // No preparation needs to be done.
    }

    // Implements the IDataGridViewEditingControl
    // .RepositionEditingControlOnValueChange property.
    public bool RepositionEditingControlOnValueChange
    {
        get
        {
            return false;
        }
    }

    // Implements the IDataGridViewEditingControl
    // .EditingControlDataGridView property.
    public DataGridView EditingControlDataGridView
    {
        get
        {
            return dataGridView;
        }
        set
        {
            dataGridView = value;
        }
    }

    // Implements the IDataGridViewEditingControl
    // .EditingControlValueChanged property.
    public bool EditingControlValueChanged
    {
        get
        {
            return valueChanged;
        }
        set
        {
            valueChanged = value;
        }
    }

    // Implements the IDataGridViewEditingControl
    // .EditingPanelCursor property.
    public Cursor EditingPanelCursor
    {
        get
        {
            return base.Cursor;
        }
    }
    protected override void OnSelectedItemChanged(EventArgs e)
    {
        if (Loading) return;

        // Notify the DataGridView that the contents of the cell
        // have changed.
        valueChanged = true;
        this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
        base.OnSelectedItemChanged(e);
    }
    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        if (Loading || DroppedDown) return;

        // Notify the DataGridView that the contents of the cell
        // have changed.
        valueChanged = true;
        this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
        base.OnSelectedIndexChanged(e);


        SendKeys.Send("{ENTER}");
    }
    protected override void OnTextChanged(EventArgs e)
    {
        if (Loading) return;

        valueChanged = true;
        this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
        base.OnTextChanged(e);
    }
    protected override void OnDropDownClosed(EventArgs e)
    {
        if (originalIndex != SelectedIndex)
        {
            valueChanged = true;
            this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
        }
        base.OnDropDownClosed(e);

    }
    protected override void OnDropDown(EventArgs e)
    {
        //set dropdown width to accomodate items
        var g = CreateGraphics();      
        DropDownWidth = 
            Items.Cast<string>().Max(s => 
            {
                var size = g.MeasureString(s, Font);
                return size.Width.To<int>() + 30;
            });
        base.OnDropDown(e);
    }
    protected override void OnEnter(EventArgs e)
    {
        base.OnEnter(e);
        DroppedDown = true;
    }
}

}

Here's example usage

 var dc = new DropTextBoxColumn();
        dc.Name = "FieldName";
        dc.DataPropertyName = "FieldName";
        dc.DropDownStyle = ComboBoxStyle.DropDownList;

        var items = dc.Items = new string[]{ "one", "two", "three" };

        items.Insert(0, "<None>");

        dc.Items = items;

        DirectGrid.Columns.Insert(1,dc);
Steve
I would like to see that code, thanks!
Hoser
@Hoser - added sample. Hope it helps!
Steve
A: 

@Steve, I have copied your code into my c# project, and this is exactly what I needed. There is only a major problem: When you add a new row to the datagridview while selecting an item from the combobox, and you click another cell to commit the change, the new row dissapears and an error occurs. Do you have a fully working example of this great control?

Regards

Joost