.NET Forum / Windows Forms / WinForm Controls / November 2007
IDataGridViewEditingControl validation
|
|
Thread rating:  |
Paul S - 12 Nov 2007 14:26 GMT Hi
I have implemented my own TimeEditorControl:
public class TimeEditorControl : IDataGridViewEditingControl
with the input mask hh:mm. If the user enter ex. 4 and leaves the field, the value of the control is 4:. This is of course wrong and gives a problem when the user enters the field again. I see 2 posibilities:
Prevent the user from leaving a field with bad data or add zeroes to make the data valid. How do I implement the 2 solutions ?
Thanks Paul S
 Signature Paul S
Linda Liu[MSFT] - 13 Nov 2007 06:52 GMT Hi Paul,
It seems that your TimeEditorControl class is derived from the MaskedTextBox class, right?
If so, you can set the Mask property of the TimeEditorControl to "00:00", set the PromptChar property to '0' and set the TextMaskFormat property to IncludePromptAndLiterals, so that the Text property of the TimeEditorControl returns "04:00" if the user enters 4 and leaves the field.
There is another case, e.g. the user enters "56:34" in the TimeEditorControl, which is not a valid time. So we should also validate whether the value the user enters is a valid value of time.
As you have mentioned, there're two kinds of options to choose: preventing the user from leaving the current cell with bad data or correcting the bad data automatically.
BTW, from the OOP viewpoint, it would be better to valid the data in the derived DataGridViewCell class level or in the custom editing control level, rather than in the DataGridView level.
For the first option, we can do this easily in the DataGridView level, i.e. handle the CellValidating event of the DataGridView. However, it's hard to do this in the DataGridViewCell level, because DataGridViewCell class doesn't expose such an event.
For the second option, we can handle the Leave event of the TimeEditorControl and if the data is invalid, reset the value to the default value.
The following is a sample for the second option:
class MyMaskedTextBox : MaskedTextBox,IDataGridViewEditingControl { DataGridView dataGridView; private bool valueChanged = false; int rowIndex;
public MyMaskedTextBox() { this.TextMaskFormat = MaskFormat.IncludePromptAndLiterals; this.PromptChar = '0'; this.Mask = "00:00"; this.Leave += new EventHandler(MyMaskedTextBox_Leave); }
void MyMaskedTextBox_Leave(object sender, EventArgs e) { if(!IsValidHHMM(this.Text)) { this.Text = "00:00"; } } private bool IsValidHHMM(string text) { DateTime result; bool canParse = DateTime.TryParse(text, out result); if (canParse) return true; else return false; }
// Implements the IDataGridViewEditingControl.EditingControlFormattedValue // property. public object EditingControlFormattedValue { get { return this.Text; } set { String newValue = value as String; if (newValue != null) { if (IsValidHHMM(newValue)) { this.Text = newValue; } } } }
// 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.Right: case Keys.Home: case Keys.End: return true; default: return false; } }
// 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 e) { // Notify the DataGridView that the contents of the cell // have changed. if (EditingControlDataGridView != null) { valueChanged = true; this.EditingControlDataGridView.NotifyCurrentCellDirty(true); base.OnTextChanged(e); } } }
public class MaskedTextBoxColumn : DataGridViewColumn { public MaskedTextBoxColumn() : base(new MaskedTextBoxCell()) { }
public override DataGridViewCell CellTemplate { get { return base.CellTemplate; } set { // Ensure that the cell used for the template is a CalendarCell. if (value != null && !value.GetType().IsAssignableFrom(typeof(MaskedTextBoxCell))) { throw new InvalidCastException("Must be a MaskedTextBoxCell"); } base.CellTemplate = value; } } }
public class MaskedTextBoxCell : DataGridViewTextBoxCell { public MaskedTextBoxCell() : base() { } 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); MyMaskedTextBox ctl = DataGridView.EditingControl as MyMaskedTextBox; if (this.Value != null) { ctl.Text = this.Value.ToString(); } }
public override Type EditType { get { // Return the type of the editing contol that CalendarCell uses. return typeof(MyMaskedTextBox); } }
public override Type ValueType { get { // Return the type of the value that CalendarCell contains. return typeof(string); } }
public override object DefaultNewRowValue { get { // Use the current date and time as the default value. return "00:00"; } } }
Hope this helps. If you have any question, please feel free to let me know.
Sincerely, Linda Liu Microsoft Online Community Support
================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Paul S - 13 Nov 2007 08:44 GMT Hi Linda Your solution doesn't prevent the user from leaving the field - how can I that ?
Just an additional question:
Your sample uses String as ValueType
public override Type ValueType { get { // Return the type of the value that CalendarCell contains. return typeof(string); } }
wouldn't it be more correct to use DateTime or does that course other problems ?
The DefaultNewRowValue could just return ex. New DateTime(1900, 1,1,0,0,0) The formatting would display the time part.
 Signature Paul S
> Hi Paul, > [quoted text clipped - 296 lines] > Linda Liu > Microsoft Online Community Support Linda Liu[MSFT] - 13 Nov 2007 11:09 GMT Hi Paul,
> Your solution doesn't prevent the user from leaving the field - how can I that ?
You're right. If you'd like to prevent the user from leaving the field, you may subscribe and handle the CellValidating event of the DataGridView. In the event handler, if the data is invalid, set the DataGridViewCellValidatingEventArgs.Cancel property to true.
> wouldn't it be more correct to use DateTime or does that course other problems ?
Yes, I agree with you and using the type of DateTime won't cause any problem. The following is the modified sample code. Only the MyMaskedTextBox and MaskedTextBoxCell classes need some modifications.
class MyMaskedTextBox : MaskedTextBox,IDataGridViewEditingControl { ... // add a new property called Value, which is of type DateTime DateTime datetimevalue; public DateTime Value { set { if (value.GetType() == typeof(DateTime)) { String newValue = Convert.ToDateTime(value).ToString("yyyy/MM/dd HH:mm:ss"); this.Text = newValue.Substring(11, 5); } datetimevalue = value; } } // modify the EditingControlFormattedValue propery as follows public object EditingControlFormattedValue { get { string datetimevaluestr = datetimevalue.ToString("yyyy/MM/dd HH:mm:ss"); string formattedstring = datetimevaluestr.Substring(0, 11) + this.Text + datetimevaluestr.Substring(16, 3); return formattedstring; } set { if (value.GetType() == typeof(string)) { if (DateTime.TryParse(value, out this.datetimevalue)) { String newvaluestr = Convert.ToDateTime(newvalue).ToString("yyyy/MM/dd HH:mm:ss"); this.Text = newValue.Substring(11, 5); } } } } .... }
public class MaskedTextBoxCell : DataGridViewTextBoxCell { public MaskedTextBoxCell() : base() { // set the Format to "t" so that only the time part will be displayed in the cell this.Style.Format = "t"; } public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) { ... if (this.Value != null && this.Value.GetType() == typeof(DateTime)) { ctl.Value = Convert.ToDateTime(this.Value); } } public override Type ValueType { get { // Return the type of the value that CalendarCell contains. return typeof(DateTime); } } public override object DefaultNewRowValue { get { // Use the current date and time as the default value. return DateTime.Parse("1900/1/1 00:00:00"); } } ... }
Hope this helps. If you have any question, please feel free to let me know.
Sincerely, Linda Liu Microsoft Online Community Support
================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Paul S - 13 Nov 2007 13:04 GMT Hi Linda Thanks - i was hoping to hear you say that I could do some Cancel=true in my control instead of handling it in the DataGrid. I think i makes my implementation of the control - not complete !.
 Signature Thanks Paul S
> Hi Paul, > [quoted text clipped - 123 lines] > > This posting is provided "AS IS" with no warranties, and confers no rights. Linda Liu[MSFT] - 14 Nov 2007 11:21 GMT Hi Paul,
Thank you for your reply!
After doing more research on this issue, I found a way to prevent the user from leaving the current cell if the entered value is not valid, i.e. subscribe the CellValidating event of the referenced DataGridView within the MyMaskedTextBox class.
The following is the modified complete sample:
class MyMaskedTextBox : MaskedTextBox,IDataGridViewEditingControl { DataGridView dataGridView; private bool valueChanged = false; int rowIndex;
// add a new property called Value, which is of type DateTime DateTime datetimevalue; public DateTime Value { set { if (value.GetType() == typeof(DateTime)) { String newValue =Convert.ToDateTime(value).ToString("yyyy/MM/dd HH:mm:ss"); this.Text = newValue.Substring(11, 5); } datetimevalue = value; } }
public MyMaskedTextBox() { this.TextMaskFormat = MaskFormat.IncludePromptAndLiterals; this.PromptChar = '0'; this.Mask = "00:00"; } void dataGridView_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { if (this.dataGridView.EditingControl != null && this.dataGridView.EditingControl.GetType() == typeof(MyMaskedTextBox)) { Console.WriteLine("data grid view cell validating"); if (!IsValidHHMM(this.Text)) { e.Cancel = true; } } } private bool IsValidHHMM(string text) { DateTime result; bool canParse = DateTime.TryParse(text, out result); if (canParse) return true; else return false; }
// Implements the IDataGridViewEditingControl.EditingControlFormattedValue // property. public object EditingControlFormattedValue { get { string datetimevaluestr =datetimevalue.ToString("yyyy/MM/dd HH:mm:ss"); string formattedstring = datetimevaluestr.Substring(0, 11)+ this.Text + datetimevaluestr.Substring(16, 3); Console.WriteLine("get editing control formatted value:" + formattedstring); return formattedstring; } set { if (value.GetType() == typeof(string)) { if (DateTime.TryParse(value.ToString(), out this.datetimevalue)) { String newvaluestr =Convert.ToDateTime(this.datetimevalue).ToString("yyyy/MM/dd HH:mm:ss"); this.Text = newvaluestr.Substring(11, 5); } } }
}
// 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.Right: case Keys.Home: case Keys.End: return true; default: return false; } }
// 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; dataGridView.CellValidating += new DataGridViewCellValidatingEventHandler(dataGridView_CellValidating); } }
// 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 e) { // Notify the DataGridView that the contents of the cell // have changed. if (EditingControlDataGridView != null) { valueChanged = true; this.EditingControlDataGridView.NotifyCurrentCellDirty(true); base.OnTextChanged(e); } } }
public class MaskedTextBoxColumn : DataGridViewColumn { public MaskedTextBoxColumn() : base(new MaskedTextBoxCell()) { }
public override DataGridViewCell CellTemplate { get { return base.CellTemplate; } set { // Ensure that the cell used for the template is a MaskedTextBoxCell. if (value != null && !value.GetType().IsAssignableFrom(typeof(MaskedTextBoxCell))) { throw new InvalidCastException("Must be a MaskedTextBoxCell"); } base.CellTemplate = value; } } }
public class MaskedTextBoxCell : DataGridViewTextBoxCell {
public MaskedTextBoxCell() : base() { // set the Format to "t" so that only the time part will be displayed in the cell this.Style.Format = "t";
} 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); MyMaskedTextBox ctl = DataGridView.EditingControl as MyMaskedTextBox;
if (this.Value != null && this.Value.GetType() == typeof(DateTime)) { ctl.Value = Convert.ToDateTime(this.Value); } else { ctl.Value = Convert.ToDateTime(DefaultNewRowValue); } }
public override Type EditType { get { // Return the type of the editing contol that MaskedTextBoxCell uses. return typeof(MyMaskedTextBox); } }
public override Type ValueType { get { // Return the type of the value that MaskedTextBoxCell contains. return typeof(DateTime); } }
public override object DefaultNewRowValue { get { return DateTime.Parse("1900/1/1 00:00:00"); } } }
The above code works well on my side. Please try it in your project to see if there's any problem and let me know the result.
Sincerely, Linda Liu Microsoft Online Community Support
Paul S - 14 Nov 2007 12:08 GMT It works ! So thanks a lot, Linda.
Just one minor thing I noticed - it doesn't beep when the user tries to leave the cell with inlvalid data - I could of course just make the beep myself in the validate event but isn't there a more 'correct' way of doing it ?
 Signature Paul S
> Hi Paul, > [quoted text clipped - 296 lines] > get > { Linda Liu[MSFT] - 15 Nov 2007 06:11 GMT Hi Paul,
Thank you for quick feedback!
> it doesn't beep when the user tries to leave the cell with inlvalid data I don't see it is the default behavior of a DataGridView. But if we edit in a DataGridViewTextBoxCell in a DataGridView and then press the Esc key, the DataGridView does beep.
To mimic this behavior, let the MyMaskedTextBox handle the Esc key so that a beep will be generated when the user presses the Esc key in a MaskedTextBoxCell which is in edit mode.
As for reminding the user that the data he/she enters in the MaskedTextBoxCell is invalid, I suggest you show a balloon ToolTip on this cell.
The following is the modified code based on the sample I provided in my last reply.
class MyMaskedTextBox : MaskedTextBox,IDataGridViewEditingControl { ... // add a ToolTip to remind the user of the invalid data ToolTip tooltip = new ToolTip(); public MyMaskedTextBox() { ... // initialize the ToolTip component this.tooltip.IsBalloon = true; this.tooltip.ToolTipIcon = ToolTipIcon.Error; this.tooltip.ToolTipTitle = "Invaide Time data"; } void dataGridView_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { if (this.dataGridView.EditingControl != null && this.dataGridView.EditingControl.GetType() == typeof(MyMaskedTextBox)) { if (!IsValidHHMM(this.Text)) { e.Cancel = true; // show the ToolTip to remind the user tooltip.Show("", this); tooltip.Show("Please correct the invalid data!", this, this.Width / 2, this.Height / 2); } else { // hide the ToolTip tooltip.Hide(this); } } } // handle the Esc key by ourselves protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if (e.KeyCode == Keys.Escape) { this.dataGridView.CancelEdit(); this.dataGridView.RefreshEdit(); this.dataGridView.CurrentCell.DetachEditingControl(); DataGridViewCell cell = this.dataGridView.CurrentCell; this.dataGridView.CurrentCell = null; this.dataGridView.CurrentCell = cell; } } public bool EditingControlWantsInputKey(Keys key, bool dataGridViewWantsInputKey) { // Let the MyMaskedTextBox handle the keys listed. switch (key & Keys.KeyCode) { case Keys.Left: case Keys.Right: case Keys.Home: case Keys.End: // add this to let the MaskedTextBox handle the Esc key case Keys.Escape: return true; default: return false; } } ... }
Please try it in your project to see if it is what you want and let me know the result.
Sincerely, Linda Liu Microsoft Online Community Support
Linda Liu[MSFT] - 15 Nov 2007 07:10 GMT Hi Paul,
I found a little problem on the code I gave you just now, i.e. if you edit in a MaskedTextBoxCell and then edit in a DataGridViewTextBoxCell and then edit in the previous MaskedTextBoxCell and select another cell at last, an exception is thrown "Cannot access a dispose object. Object name:MyMaskedTextBox".
The reason of this problem is that when you first edit in a MaskedTextBoxCell, DataGridView creates an instance of MyMaskedTextBox and this instance of MyMaskedTextBox subscribes the CellValidating event of the DataGridView.
Then you edit in a DataGridViewTextBoxCell and DataGridView creates an instance of TextBox and disposes the previous instance of MyMaskedTextBox. Note that the previous instance of MyMaskedTextBox still subscribes the CellValidating event of the DataGridView.
When you edit in the previous MaskedTextBoxCell for the second time, DataGridView creates a new instance of MyMaskedTextBox and disposes the instance of TextBox. Of course, the new instance of MyMaskedTextBox subscribes the CellValidating event of the DataGridView.
Now if you select another cell, the CellValidating event of the DataGridView is raised and both the previous instance and new instance of MyMaskedTextbox are notified and the corresponding event handlers are executed. The problem is that the previous instance of MyMaskedTextBox has already been disposed and we cannot access to a disposed object. Thus the exception is thrown.
To solve this problem, override the Dispose(bool) method in the MyMaskedTextBox class and unsubscribe the CellValidating event of the DataGridView. The following is a sample:
class MyMaskedTextBox : MaskedTextBox,IDataGridViewEditingControl { ... protected override void Dispose(bool disposing) { this.dataGridView.CellValidating -= new DataGridViewCellValidatingEventHandler(dataGridView_CellValidating); base.Dispose(disposing); } ... }
Hope this helps.
Sincerely, Linda Liu Microsoft Online Community Support
Paul S - 15 Nov 2007 07:16 GMT Thanks a lot for the hint, Linda
 Signature Paul S
> Hi Paul, > [quoted text clipped - 47 lines] > Linda Liu > Microsoft Online Community Support Paul S - 14 Nov 2007 12:12 GMT Hi Linda Forget about the beep and thanks again for your help
 Signature Paul S
> Hi Paul, > [quoted text clipped - 296 lines] > get > {
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|