Search code examples
c#datagridviewcomboboxdatagridviewcolumndatagridviewcomboboxcell

DataGridView custom ComboBox implementation error, sharing same value


Here is my code that i used to create my own custom combobox column, my combobox is kind of special that shows treeview inside (here is code page http://www.brad-smith.info/blog/projects/dropdown-controls). the new combobox column works well except in one thing, if i select for example from the combobox and navigate to the cell beside (which is textbox cell) and then navigate to the same combobox column in the second row and select another item from the second combobox then all is fine, but if i navigate from one combobox to the one under it directly and select an item then the first combobox will select same value of the second combobox.

any help please?

public class DataGridViewTreeComboBoxColumn : DataGridViewComboBoxColumn
{
    public DataGridViewTreeComboBoxColumn() : base()
    {
        base.CellTemplate = new TreeComboBoxCell();
    }

    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            if (value != null &&
                !value.GetType().IsAssignableFrom(typeof(TreeComboBoxCell)))
            {
                throw new InvalidCastException("Must be a CalendarCell");
            }
            base.CellTemplate = value;
        }
    }
}

public class TreeComboBoxCell : DataGridViewComboBoxCell
{
    public TreeComboBoxCell()
        : base()
    {

    }

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

        TreeComboBoxEditingControl ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
        ctl.SetItems(Items);

        if (Value != null)
            ctl.SelectedNode = ctl.AllNodes.ToList().First(x => x.Tag != null && x.Tag.Equals(Value));

        ctl.SelectedNodeChanged += Ctl_SelectedNodeChanged;

    }

    public override object Clone()
    {
        TreeComboBoxCell dataGridViewCell = base.Clone() as TreeComboBoxCell;

        if (dataGridViewCell != null)
        {

        }

        return dataGridViewCell;
    }

    private void Ctl_SelectedNodeChanged(object sender, EventArgs e)
    {
        if (((TreeComboBoxEditingControl)sender).SelectedNode != null)
        Value = ((TreeComboBoxEditingControl)sender).SelectedNode.Tag;
    }

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

    public override Type ValueType
     {
         get
         {
            // Return the type of the value that CalendarCell contains. 

            return typeof(Object);
        }
     }

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

public class TreeComboBoxEditingControl : ComboTreeBox, IDataGridViewEditingControl
{
    DataGridView dataGridView;
    private bool valueChanged = false;
    int rowIndex;

    public TreeComboBoxEditingControl()
    {
        this.TabStop = false;
    }

    public void SetItems(DataGridViewComboBoxCell.ObjectCollection items)
    {
        if (Nodes != null && Nodes.Count > 0)
            return;
        Action<ComboTreeNodeCollection> addNodesHelper = nodes => {
            foreach (IGrouping<object, TreeComboBoxItem> group in items.Cast<TreeComboBoxItem>().GroupBy(x => x.Group).ToList())
            {
                ComboTreeNode parent = nodes.Add(group.Key.ToString());

                foreach (TreeComboBoxItem item in group)
                {
                    parent.Nodes.Add(item.Display).Tag = item.Value;
                }
            }
        };

        Action<ComboTreeBox> addNodes = ctb => {
            addNodesHelper(ctb.Nodes);
            ctb.Sort();
        };

        addNodes(this);
    }

    // Implements the IDataGridViewEditingControl.EditingControlFormattedValue  
    // property. 
    public object EditingControlFormattedValue
    {
        get
        {
            return GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting);
        }
        set
        {

        }
    }

    // Implements the  
    // IDataGridViewEditingControl.GetEditingControlFormattedValue method. 
    public object GetEditingControlFormattedValue(
        DataGridViewDataErrorContexts context)
    {
        if (this.SelectedNode == null)
            return null;
        return this.SelectedNode.Tag;
    }

    // Implements the  
    // IDataGridViewEditingControl.ApplyCellStyleToEditingControl method. 
    public void ApplyCellStyleToEditingControl(
        DataGridViewCellStyle dataGridViewCellStyle)
    {

    }

    // 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;
        }
    }

    // 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 OnSelectedNodeChanged(EventArgs e)
    {
        valueChanged = true;
        this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
        base.OnSelectedNodeChanged(e);
    }
}

Solution

  • I think you should add the following to your TreeComboBoxCell class:

    public override void DetachEditingControl()
    {
        var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
        if (ctl != null)
            ctl.SelectedNodeChanged -= Ctl_SelectedNodeChanged;
        base.DetachEditingControl();
    }
    

    Also modify the following method to be:

    public override void InitializeEditingControl(int rowIndex, object
        initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
    {
        base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
        var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
        ctl.SetItems(Items);
        ctl.SelectedNode = initialFormattedValue != null ? ctl.AllNodes.FirstOrDefault(x => Equals(x.Tag, initialFormattedValue)) : null;
        ctl.SelectedNodeChanged += Ctl_SelectedNodeChanged;
    }
    

    Also remove the ValueType override and let DefaultNewRowValue return null rather than 0.

    But the main problem I see is the synchronization between your cell and editor classes. The cell class is using a TreeComboBoxItem objects inside Items collection, but editor GetEditingControlFormattedValue returns an object, which is actually TreeComboBoxItem.Value property. This way the base cell class canoot translate it correctly. I'm not sure you should even inherit from DataGridViewComboBoxCell because it expects a ComboBox editor in many of it's overrides that you don't handle. It might be better to inherit from DataGridViewCell or DataGridViewTextBoxCell like in the MSDN example your code is based on. At least you can try adding the following overrides to make your cell class implementation match your current editor implementation:

    protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
    {
        if (value != null)
            foreach (TreeComboBoxItem item in Items)
                if (Equals(item.Value, value)) return item.Display;
        return base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
    }
    
    public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
    {
        return formattedValue;
    }
    

    EDIT Ok, I'm not sure what exactly doesn't work, it could be the usage. Here is a full working code:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace System.Windows.Forms
    {
        public class TreeComboBoxItem
        {
            public object Group { get; set; }
            public object Value { get; set; }
            private string display;
            public string Display { get { return display ?? (Value != null ? Value.ToString() : null); } set { display = value; } }
        }
        public class DataGridViewTreeComboBoxColumn : DataGridViewComboBoxColumn
        {
            public DataGridViewTreeComboBoxColumn()
            {
                base.CellTemplate = new TreeComboBoxCell();
            }
            public override DataGridViewCell CellTemplate
            {
                get { return base.CellTemplate; }
                set { base.CellTemplate = (TreeComboBoxCell)value; }
            }
        }
        public class TreeComboBoxCell : DataGridViewComboBoxCell
        {
            public TreeComboBoxCell() { }
            public override Type EditType { get { return typeof(TreeComboBoxEditingControl); } }
            public override void InitializeEditingControl(int rowIndex, object
                initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
            {
                base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
                var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
                ctl.SetItems(Items);
                ctl.SelectedNode = Value != null ? ctl.AllNodes.FirstOrDefault(x => Equals(x.Tag, Value)) : null;
                ctl.SelectedNodeChanged += OnEditorSelectedNodeChanged;
            }
            public override void DetachEditingControl()
            {
                var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
                if (ctl != null) ctl.SelectedNodeChanged -= OnEditorSelectedNodeChanged;
                base.DetachEditingControl();
            }
            public override object Clone()
            {
                var dataGridViewCell = base.Clone() as TreeComboBoxCell;
                if (dataGridViewCell != null)
                {
                }
                return dataGridViewCell;
            }
            protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
            {
                if (value != null)
                {
                    foreach (TreeComboBoxItem item in Items)
                        if (Equals(item.Value, value)) return (context & DataGridViewDataErrorContexts.Formatting) != 0 ? item.Display : value;
                }
                return base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
            }
            public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
            {
                return formattedValue;
            }
            private void OnEditorSelectedNodeChanged(object sender, EventArgs e)
            {
                var selectedNode = ((TreeComboBoxEditingControl)sender).SelectedNode;
                Value = selectedNode != null ? selectedNode.Tag : null;
            }
        }
        public class TreeComboBoxEditingControl : ComboTreeBox, IDataGridViewEditingControl
        {
            public TreeComboBoxEditingControl() { TabStop = false; }
            public DataGridView EditingControlDataGridView { get; set; }
            public int EditingControlRowIndex { get; set; }
            public bool EditingControlValueChanged { get; set; }
            public bool RepositionEditingControlOnValueChange { get { return false; } }
            public Cursor EditingPanelCursor { get { return Cursor; } }
            public void SetItems(DataGridViewComboBoxCell.ObjectCollection items)
            {
                if (Nodes.Count > 0) return;
                foreach (var group in items.Cast<TreeComboBoxItem>().GroupBy(x => x.Group))
                {
                    var parent = Nodes.Add(group.Key.ToString());
                    foreach (var item in group)
                        parent.Nodes.Add(item.Display).Tag = item.Value;
                }
                Sort();
            }
            protected override void OnSelectedNodeChanged(EventArgs e)
            {
                EditingControlValueChanged = true;
                EditingControlDataGridView.NotifyCurrentCellDirty(true);
                base.OnSelectedNodeChanged(e);
            }
            public object EditingControlFormattedValue
            {
                get { return GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting); }
                set
                {
                }
            }
            public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
            {
                if (SelectedNode == null) return null;
                return (context & DataGridViewDataErrorContexts.Formatting) != 0 ? SelectedNode.Text : SelectedNode.Tag;
            }
            public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
            {
                BackColor = dataGridViewCellStyle.BackColor;
                ForeColor = dataGridViewCellStyle.ForeColor;
            }
            public void PrepareEditingControlForEdit(bool selectAll)
            {
            }
            public bool EditingControlWantsInputKey(Keys key, bool dataGridViewWantsInputKey)
            {
                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;
                }
            }
        }
    }
    namespace Tests
    {
        class Parent
        {
            public string Name { get; set; }
            public override string ToString() { return Name; }
        }
        class Child
        {
            public Parent Parent { get; set; }
            public string Name { get; set; }
        }
        class TestForm : Form
        {
            public TestForm()
            {
                var parents = Enumerable.Range(1, 6).Select(i => new Parent { Name = "Parent " + i }).ToList();
                var childen = Enumerable.Range(1, 10).Select(i => new Child { Parent = parents[i % parents.Count], Name = "Child " + i }).ToList();
                var items = parents.Select((parent, i) => new TreeComboBoxItem { Value = parent, Group = "Group " + ((i % 2) + 1) }).ToArray();
                var dg = new DataGridView { Dock = DockStyle.Fill, Parent = this, AutoGenerateColumns = false };
                var c1 = new DataGridViewTreeComboBoxColumn { DataPropertyName = "Parent", HeaderText = "Parent" };
                c1.Items.AddRange(items);
                var c2 = new DataGridViewTextBoxColumn { DataPropertyName = "Name", HeaderText = "Name" };
                dg.Columns.AddRange(c1, c2);
                dg.DataSource = new BindingList<Child>(childen);
            }
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new TestForm());
            }
        }
    }