DataGridView自定义ComboBox实现错误,共享同一个值

DataGridView custom ComboBox implementation error, sharing same value

这是我用来创建自己的自定义组合框列的代码,我的组合框有点特殊,它在内部显示树视图(这里是代码页 http://www.brad-smith.info/blog/projects/dropdown-controls)。 新的组合框列工作得很好,除了一件事,如果我 select 例如从组合框导航到旁边的单元格(这是文本框单元格),然后导航到第二行中的相同组合框列和 select 第二个组合框中的另一个项目然后一切都很好,但是如果我从一个组合框直接导航到它下面的那个并且 select 一个项目然后第一个组合框将 select 与第二个相同的值组合框。

有什么帮助吗?

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

我认为你应该在 TreeComboBoxCell class 中添加以下内容:

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

同样修改如下方法为:

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

同时删除 ValueType 覆盖并让 DefaultNewRowValue return null 而不是 0

但我看到的主要问题是您的单元格和编辑器 classes 之间的同步。单元格 class 使用 Items 集合中的 TreeComboBoxItem 个对象,但编辑器 GetEditingControlFormattedValue return 是一个对象,实际上是 TreeComboBoxItem.Value 属性。这样基本单元格 class 就无法正确翻译它。我不确定您是否应该从 DataGridViewComboBoxCell 继承,因为它需要一个 ComboBox 编辑器来覆盖您不处理的许多内容。最好从 DataGridViewCellDataGridViewTextBoxCell 继承,就像您的代码所基于的 MSDN 示例一样。至少您可以尝试添加以下覆盖以使您的单元格 class 实现与您当前的编辑器实现相匹配:

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 好的,我不确定到底是什么不起作用,可能是用法。这是一个完整的工作代码:

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());
        }
    }
}