I hit a breakthrough this morning and got this working quite nicely. I'll see if I can explain:
I created my own DataGridViewColumn called DeliverableAttributeColumn:
public class DeliverableAttributeColumn : DataGridViewColumn
{
public DeliverableAttributeColumn()
: base(new DeliverableAttributeCell())
{
}
public override DataGridViewCell CellTemplate
{
get { return base.CellTemplate; }
set
{
if (value != null && !value.GetType().IsAssignableFrom(typeof(DeliverableAttributeCell)))
throw new InvalidCastException("Must be a DeliverableAttributeCell");
base.CellTemplate = value;
}
}
}
I then created my own DataGridViewTextBoxCell called DeliverableAttributeCell:
public class DeliverableAttributeCell : DataGridViewTextBoxCell
{
private DeliverableTemplateAttributeSearchListItem _templateAttribute;
private ProjectDeliverableAttributeSearchListItem _deliverableAttribute;
private Type _editType;
public DeliverableAttributeCell()
{
_editType = typeof(DataGridViewTextBoxEditingControl);
}
public void SetAttributes(DeliverableTemplateAttributeSearchListItem templateAttribute,
ProjectDeliverableAttributeSearchListItem deliverableAttribute)
{
_templateAttribute = templateAttribute;
_deliverableAttribute = deliverableAttribute;
switch (_templateAttribute.DataType)
{
case AttributeDataType.Text:
_editType = typeof(TextAttributeEditingControl);
break;
case AttributeDataType.Decimal:
_editType = typeof(DecimalAttributeEditingControl);
break;
default: throw new NotImplementedException("Data type is not yet implemented.");
}
}
public override Type EditType
{
get { return _editType; }
}
public DeliverableTemplateAttributeSearchListItem TemplateAttribute
{
get { return _templateAttribute; }
protected set { _templateAttribute = value; }
}
public ProjectDeliverableAttributeSearchListItem DeliverableAttribute
{
get { return _deliverableAttribute; }
protected set { _deliverableAttribute = value; }
}
public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
switch (_templateAttribute.DataType)
{
case AttributeDataType.Text:
InitializeTextEditingControl(initialFormattedValue, dataGridViewCellStyle);
break;
case AttributeDataType.Decimal:
InitializeDecimalEditingControl(initialFormattedValue, dataGridViewCellStyle);
break;
default:
InitializeUnassignedEditingControl(initialFormattedValue, dataGridViewCellStyle);
break;
}
}
#region Editing Control Initializers
private void InitializeUnassignedEditingControl(object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
// I hope this isn't needed
DataGridViewTextBoxEditingControl textEditingControl = this.DataGridView.EditingControl as DataGridViewTextBoxEditingControl;
if (textEditingControl != null)
{
textEditingControl.BorderStyle = BorderStyle.None;
textEditingControl.Text = (string)this.Value;
}
}
private void InitializeTextEditingControl(object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
TextAttributeSearchListItem textAttribute = (TextAttributeSearchListItem)_templateAttribute;
TextAttributeEditingControl editingControl = this.DataGridView.EditingControl as TextAttributeEditingControl;
if (editingControl != null)
{
editingControl.BorderStyle = BorderStyle.None;
editingControl.MaxLength = textAttribute.MaxLength;
editingControl.Multiline = textAttribute.Multiline;
editingControl.AcceptsReturn = editingControl.Multiline;
var value = initialFormattedValue as string;
editingControl.Text = value == null ? string.Empty : (string)value;
}
else
throw new Exception("There was a problem when attempting to initialize the cell editing control.");
}
private void InitializeDecimalEditingControl(object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
DecimalAttributeSearchListItem decimalAttribute = (DecimalAttributeSearchListItem)_templateAttribute;
DecimalAttributeEditingControl editingControl = this.DataGridView.EditingControl as DecimalAttributeEditingControl;
if (editingControl != null)
{
editingControl.BorderStyle = BorderStyle.None;
editingControl.MaxLength = 11;
editingControl.Multiline = false;
editingControl.AcceptsReturn = false;
editingControl.TextAlign = HorizontalAlignment.Right;
var value = initialFormattedValue as string;
editingControl.Text = value == null ? string.Empty : (string)value;
}
else
throw new Exception("There was a problem when attempting to initialize the cell editing control.");
}
#endregion
protected override object GetFormattedValue(object value,
int rowIndex, ref DataGridViewCellStyle cellStyle,
TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
{
object formattedValue = base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
return formattedValue == null ? string.Empty : (string)formattedValue;
}
}
This now allows my cell to cleverly detect which data type of the attribute is to be edited and use that when InitializeEditingControl is called. I've only written support so far for the text attribute, which I could have just used a DataGridViewTextBoxEditingControl for, but I needed to understand this in a bit more detail. With a little help from Reflector, I finally got my control to edit and store the value properly:
public class TextAttributeEditingControl: TextBox, IDataGridViewEditingControl
{
public TextAttributeEditingControl()
{
this.TabStop = false;
}
#region IDataGridViewEditingControl Members
public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
{
this.Font = dataGridViewCellStyle.Font;
if (dataGridViewCellStyle.BackColor.A < 255)
{
// The NumericUpDown control does not support transparent back colors
Color opaqueBackColor = Color.FromArgb(255, dataGridViewCellStyle.BackColor);
this.BackColor = opaqueBackColor;
this.EditingControlDataGridView.EditingPanel.BackColor = opaqueBackColor;
}
else
{
this.BackColor = dataGridViewCellStyle.BackColor;
}
this.ForeColor = dataGridViewCellStyle.ForeColor;
this.TextAlign = HorizontalAlignment.Left;
}
public DataGridView EditingControlDataGridView { get; set; }
public object EditingControlFormattedValue
{
get { return this.GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting); }
set { this.Text = (string)value; }
}
public int EditingControlRowIndex { get; set; }
public bool EditingControlValueChanged { get; set; }
public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)
{
switch ((keyData & Keys.KeyCode))
{
case Keys.Prior:
case Keys.Next:
if (!this.EditingControlValueChanged)
{
break;
}
return true;
case Keys.End:
case Keys.Home:
if (this.SelectionLength == this.Text.Length)
{
break;
}
return true;
case Keys.Left:
if (((this.RightToLeft != RightToLeft.No) || ((this.SelectionLength == 0) && (base.SelectionStart == 0))) && ((this.RightToLeft != RightToLeft.Yes) || ((this.SelectionLength == 0) && (base.SelectionStart == this.Text.Length))))
{
break;
}
return true;
case Keys.Up:
if ((this.Text.IndexOf("\r\n") < 0) || ((base.SelectionStart + this.SelectionLength) < this.Text.IndexOf("\r\n")))
{
break;
}
return true;
case Keys.Right:
if (((this.RightToLeft != RightToLeft.No) || ((this.SelectionLength == 0) && (base.SelectionStart == this.Text.Length))) && ((this.RightToLeft != RightToLeft.Yes) || ((this.SelectionLength == 0) && (base.SelectionStart == 0))))
{
break;
}
return true;
case Keys.Down:
{
int startIndex = base.SelectionStart + this.SelectionLength;
if (this.Text.IndexOf("\r\n", startIndex) == -1)
{
break;
}
return true;
}
case Keys.Delete:
if ((this.SelectionLength <= 0) && (base.SelectionStart >= this.Text.Length))
{
break;
}
return true;
case Keys.Return:
if ((((keyData & (Keys.Alt | Keys.Control | Keys.Shift)) == Keys.Shift) && this.Multiline) && base.AcceptsReturn)
{
return true;
}
break;
}
return !dataGridViewWantsInputKey;
}
public Cursor EditingPanelCursor
{
get { return Cursors.Default; }
}
public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
{
return this.Text;
}
public void PrepareEditingControlForEdit(bool selectAll)
{
if (selectAll)
this.SelectAll();
else
this.SelectionStart = this.Text.Length;
}
public bool RepositionEditingControlOnValueChange
{
get { return false; }
}
#endregion
private void NotifyDataGridViewOfValueChange()
{
this.EditingControlValueChanged = true;
this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
this.NotifyDataGridViewOfValueChange();
}
}
Once this was all in place, I tested it with the following code to populate a target DataGridView and see my new column, cell, and editing control in action:
private void PopulateDataGridView(ProjectDeliverableSearchListItem deliverable, DeliverableTemplateSearchListItem template)
{
// Query lists of attributes used and not yet used
ProjectDeliverableAttributeSearchList deliverableAttributes = new ProjectDeliverableAttributeSearchList();
deliverableAttributes.ProjectDeliverableID = deliverable.ID;
deliverableAttributes.Refresh();
DeliverableTemplateAttributeSearchList allAttributes = new DeliverableTemplateAttributeSearchList();
allAttributes.DeliverableTemplateID = template.ID;
allAttributes.Refresh();
DeliverableAttributeColumn attributesColumn = new DeliverableAttributeColumn();
attributesColumn.Name = "Value";
this.dgvAttributes.Columns.Clear();
this.dgvAttributes.Columns.Add("Title", "Title");
this.dgvAttributes.Columns.Add(attributesColumn);
foreach (var templateAttribute in allAttributes)
{
// Find the value that was saved
var deliverableAttribute = deliverableAttributes.FirstOrDefault(attribute => attribute.DeliverableTemplateAttributeID == templateAttribute.ID);
// Create the row with the values from the database (if they exist yet)
int rowIndex = this.dgvAttributes.Rows.Add(templateAttribute.Title, deliverableAttribute != null ? deliverableAttribute.Value : null);
var row = this.dgvAttributes.Rows[rowIndex];
// Title cell must be read only
row.Cells[0].ReadOnly = true;
// Set the attribute type of the value cell. Cell defaults to "DataGridViewTextBoxEditingControl". See DeliverableAttributeCell.cs
var deliverableAttributeCell = (DeliverableAttributeCell)row.Cells[1];
deliverableAttributeCell.SetAttributes(templateAttribute, deliverableAttribute);
}
}
Since this has some specialized code for my project, I obviously can't go pasting all that stuff too, so I hope you can understand what they do. A "SearchList" is just a query builder that populates itself with multiple values from the database as a special "SearchListItem" type. A deliverable template is a top level template which has an associated designer, and a project deliverable is an instance of a template.
I have some links to similar questions like this, if this doesn't help you:
Since I'm still working on this, I might come up with some problems that I can post here, so add alerts where necessary, and all comments are welcome.
Software Developer, MCP