在 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。如果控件停用并因此可以分离,列表框并不总是隐藏自己。老实说控制好像有点乱
我正在尝试使用 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。如果控件停用并因此可以分离,列表框并不总是隐藏自己。老实说控制好像有点乱