在 DataGridView 中创建自动完成列

Creating AutoComplete column in DataGridView

我正在尝试使用 C# 在 WinForms 中使用 DataGridView 创建一个自动完成列。我已经设法使用 DataGridView 的 EditingControlShowing 事件让它工作并且工作正常。

然而,按照正常的自动完成文本框,筛选列表仅显示基于 "Starting With" 的数据筛选。为了解决这个问题,我使用了 here 的自动完成文本框,它允许使用自定义列表框搜索子字符串。

以这个自定义控件为基础,我创建了一个继承DataGridViewColumn的自定义控件。问题是 ListBox 控件不显示与 gridview 单元格内联。这是代码 -

public class DataGridViewAutoCompleteColumn : DataGridViewColumn
{
    public DataGridViewAutoCompleteColumn()
        : base(new DataGridViewAutoCompleteCell())
    {
    }

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


}

public class DataGridViewAutoCompleteCell : DataGridViewTextBoxCell
{

    public DataGridViewAutoCompleteCell()
        : base()
    {
        // Use the short date format.
        this.Style.Format = "d";
    }

    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);
        AutoCompleteEditingControl ctl = DataGridView.EditingControl as AutoCompleteEditingControl;
        ctl.AutoCompleteList = this.AutoCompleteList;
        // Use the default row value when Value property is null.
        if (this.Value == null)
        {
            ctl.Text = (string)this.DefaultNewRowValue;
        }
        else
        {
            ctl.Text = (string)this.Value;
        }
    }

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

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

            return typeof(String);
        }
    }

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

    public List<String> AutoCompleteList { get; set; }
}

class AutoCompleteEditingControl : AutoCompleteTextbox, IDataGridViewEditingControl
{
    DataGridView dataGridView;
    private bool valueChanged = false;
    int rowIndex;

    public AutoCompleteEditingControl()
    {

    }

    // Implements the IDataGridViewEditingControl.EditingControlFormattedValue 
    // property.
    public object EditingControlFormattedValue
    {
        get
        {
            return this.Text;
        }
        set
        {
            if (value is String)
            {
                try
                {
                    // This will throw an exception of the string is 
                    // null, empty, or not in the format of a date.
                    this.Text = (String)value;
                }
                catch
                {
                    // In the case of an exception, just use the 
                    // default value so we're not left with a null
                    // value.
                    this.Text = String.Empty;
                }
            }
        }
    }

    // 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;
        this.ForeColor = dataGridViewCellStyle.ForeColor;
        this.BackColor = dataGridViewCellStyle.BackColor;
    }

    // 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 OnTextChanged(EventArgs eventargs)
    {
        // Notify the DataGridView that the contents of the cell
        // have changed.
        valueChanged = true;
        this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
        base.OnTextChanged(eventargs);
    }
}

请指教我这里做错了什么。

我现在已经完成了其中的一些,虽然它们真的很强大,但比实际情况要复杂一些。

首先,您没有在列级别的任何地方传递 AutoCompleteList。这意味着它需要在每个单元格上进行设置,这可能很有用,但数据网格通常不是这样工作的。所以它需要是列 class 的 属性,因为这是您可以设置它的地方。

此外,如果列 class 具有任何自定义属性,则需要重写 Clone() 方法以维护这些属性。实现中的某些东西意味着没有这个它们就无法工作。 您可能想要公开 CaseSensitive 和 MinTypedCharacters 的属性。

public class DataGridViewAutoCompleteColumn : DataGridViewColumn
{
    public DataGridViewAutoCompleteColumn()
        : base(new DataGridViewAutoCompleteCell())
    {
    }

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

    [Browsable(true)]
    public List<string> AutoCompleteList
    {
        get; set;
    }

    [Browsable(true)]
    public int MinTypedCharacters { get; set; }
    [Browsable(true)]
    public bool CaseSensitive { get; set; }

    public override object Clone()
    {
        DataGridViewAutoCompleteColumn clone = (DataGridViewAutoCompleteColumn)base.Clone();
        clone.AutoCompleteList = this.AutoCompleteList;
        clone.MinTypedCharacters = this.MinTypedCharacters;
        clone.CaseSensitive = this.CaseSensitive;
        return clone;
    }
}

在单元格 class 上,我们仍然可以通过修改它的自动完成列表 属性.

来使用单元格级别列表作为覆盖
private List<string> _autoCompleteList;
public List<String> AutoCompleteList
{
    get
    {
        if (_autoCompleteList == null)
            return ((DataGridViewAutoCompleteColumn)this.OwningColumn).AutoCompleteList;
        else
            return
                _autoCompleteList;
    }
    set
    {
        _autoCompleteList = value;
    }
}

然后您需要在 InitializeEditingControl 中传递这些设置。您可以像这样访问列对象:

DataGridViewAutoCompleteColumn col = (DataGridViewAutoCompleteColumn)this.OwningColumn;

接下来 DataGridViewCell.Value get 访问器中存在一个错误,这意味着您无法在 InitializeEditingControl() 方法中安全地使用它。它有时会尝试使用无效的 rowIndex。您应该改用 GetValue(rowIndex)。

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);
    AutoCompleteEditingControl ctl = DataGridView.EditingControl as AutoCompleteEditingControl;
    ctl.AutoCompleteList = this.AutoCompleteList;
    // Use the default row value when Value property is null.
    if (this.Value == null)
    {
        ctl.Text = (string)this.DefaultNewRowValue;
    }
    else
    {
        ctl.Text = (string)this.GetValue(rowIndex);  // this line can't use this.Value
    }
}

其他问题,包括您首先提出的问题,都在 AutoCompleteTextBox 中 class(您尚未发布相关代码)

它的 ParentForm 方法在创建控件时创建空引用。
但比这个更基本的是,列表视图显示在表单上,​​当编辑控件位于容器内时,或者在本例中为数据网格视图,它无法正常工作。

编辑控件的

this.Location 在单元格中时将为 ~0,0。您需要将其转换为坐标。

// in the AutoCompleteTextBox itself
    private Form ParentForm
    {
        get
        {
            if (this.Parent != null)
                return this.Parent.FindForm();
            else
                return null;
        }
    }

    private void UpdateListBoxItems()
    {
        // if there is a ParentForm
        if ((ParentForm != null))
        {
    // this will get the position relative to the form, use instead of this.Location
            Point formposition = this.ParentForm.PointToClient(this.Parent.PointToScreen(this.Location));
            // get its width
            panel.Width = this.Width;
            // calculate the remeining height beneath the TextBox
            panel.Height = this.ParentForm.ClientSize.Height - this.Height - formposition.Y;
            // and the Location to use
            panel.Location = formposition + new Size(0, this.Height);
            // Panel and ListBox have to be added to ParentForm.Controls before calling BingingContext
            if (!this.ParentForm.Controls.Contains(panel))
            {
                // add the Panel and ListBox to the PartenForm
                this.ParentForm.Controls.Add(panel);
            }
            ((CurrencyManager)listBox.BindingContext[CurrentAutoCompleteList]).Refresh();
        }
    }

但是还有其他问题超出了这个问题的范围,比如我在编辑值时有时会在 UpdateListBoxItems() 中遇到 ArgumentOutOfRange。如果控件停用并因此可以分离,列表框并不总是隐藏自己。老实说控制好像有点乱