C# DateTimePicker DataBinding Parse event not working - c#

I have a datetimepicker that I am binding with nullable Date/Time column in dataset. I applied Format event successfully for null and not null object value. But, when I uncheck dtp control it does not get set to null in the dataset.
This is my code:
dtpBirthdate.DataBindings.Add(new Binding("Value", bsStaff, "birthDate", true));
dtpBirthdate.DataBindings["Value"].Format += new ConvertEventHandler(dtpFormat);
dtpBirthdate.DataBindings["Value"].Parse += new ConvertEventHandler(dtpParse);
Format and Parse events:
private void dtpFormat(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if(b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if(dtp != null)
{
if (e.Value == null || e.Value == DBNull.Value)
{
dtp.Checked = false;
dtp.CustomFormat = " ";
e.Value = false;
}
else
{
dtp.Checked = true;
dtp.CustomFormat = "dd-MMM-yyyy";
dtp.Value = (DateTime) e.Value;
}
}
}
}
private void dtpParse(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if (b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (dtp.Checked == false)
{
e.Value = DBNull.Value;
}
else
{
e.Value = dtp.Value;
}
}
}
}
After debugging, I found that it goes to infinite loop between parse and format events. What is wrong with my code?
Edit: There is also a datagridview binded to bsStaff bindingsource.

The following should fix the issue (see the comments in code):
private void dtpFormat(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if(b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (e.Value == null || e.Value == DBNull.Value)
{
dtp.Checked = false;
dtp.CustomFormat = " ";
// e.Value = false;
// To prevent dtp.Value property setter setting Checked back to true
e.Value = dtp.Value;
}
else
{
dtp.Checked = true;
dtp.CustomFormat = "dd-MMM-yyyy";
//dtp.Value = (DateTime) e.Value;
// dtp.Value will be set to e.Value from databinding anyway
}
}
}
}
private void dtpParse(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if (b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (dtp.Checked == false)
{
e.Value = DBNull.Value;
}
else
{
//e.Value = dtp.Value;
// Do nothing, e.Value is already populated with dtp.Value
}
}
}
}
But the whole idea is wrong from the beginning because it's based on data binding infrastructure hacks (the typical XY problem - to overcome the lack of DateTime? value property in DTP). Convert and Parse events are supposed to perform value conversion from data source value to control value and vice versa. They are not supposed to read or write control properties (it breaks the whole encapsulation), the information is provided through e.Value and e.DesiredType and the handlers are supposed to change the e.Value based on that information.
The right way is to create custom control inheriting DateTimePicker and implementing a (shadow) DateTime? Value property. The property getter and setter can apply the necessary logic (they are allowed to read/modify other properties). Then replace DTP controls with that custom control and simply bind to "Value" property w/o any binding event handlers.
Update: Here is a quick and dirty implementation of non binding approach mentioned:
public class CustomDateTimePicker : DateTimePicker
{
public CustomDateTimePicker()
{
Format = DateTimePickerFormat.Custom;
SetValueCore(null);
}
new public DateTime? Value
{
get { return Checked ? base.Value : (DateTime?)null; }
set
{
if (Value != value)
SetValueCore(value);
}
}
private void SetValueCore(DateTime? value)
{
if (value == null)
Checked = false;
else
base.Value = value.Value;
UpdateCustomFormat();
}
protected override void OnValueChanged(EventArgs eventargs)
{
UpdateCustomFormat();
base.OnValueChanged(eventargs);
}
private void UpdateCustomFormat()
{
CustomFormat = Value != null ? "dd-MMM-yyyy" : " ";
}
}

You are casting "Binding b = sender as Binding" before the null check. check if the sender is null before casting and you should be fine.

I noticed that you are using a Databinding event capture for both controls but on your first dtpFormat event handler you don't check for databinding values first.
Imho this line of code:
if (e.Value == null || e.Value == DBNull.Value)
needs to be changed to
if (e.Value == DBNull.Value || e.Value == null)

The issue is you need to set e.Value to something; but if you change it, it will fire parse again. Try setting it to it's original value.
e.Value = dtp.Value;
Here is a link to someone who has ran into this before. They were not using your DbNull.Value, but other than that it is nearly identical to what you are doing.
http://blogs.interknowlogy.com/2007/01/21/winforms-databinding-datetimepicker-to-a-nullable-type/

dtpParse is setting e.Value = dbNull.Value which will fire the dtpFormat as the value has changed which in turn sets e.Value = false which is different to dbNull.Value which will again fire dtpParse. Try removing the e.Value = false from dtpFormat

Related

Clear cell contents in a data bound datagridview

I am trying to implement a delete functionality in my DataGridView to clear the contents of highlighted cells.
One of the columns contains a double, and when the value is less than zero I display it as blank. If the user edits the cell to blank, this is handled via the CellParsing event.
The DataGridView is databound using a BindingSource and BindingList.
The issue I'm having is that when I change the cell Value to blank via my clear function, the CellParsing event does not fire and I get a FormatException saying that "" is not a valid value for Double. When the user clears the cell, the CellParsing event fires and everything happens as expected.
The reason I am setting the value to blank is that some of the columns are text and others are numbers, and I'd like to be able to delete them all at once.
I've googled and searched through StackOverflow and haven't found something yet that will solve my issue. Is there a way to route this through the CellParsing event or some other obvious solution that I'm missing?
See the CellParsing and clearing code below.
System::Void dataGridViewWells_CellParsing(System::Object^ sender, System::Windows::Forms::DataGridViewCellParsingEventArgs^ e)
{
//Handle blank values in the mass column
e->ParsingApplied = false;
if(this->dataGridViewWells->Columns[e->ColumnIndex]->HeaderText == "Mass (ng)")
{
if(e->Value->ToString() == "" || e->Value->ToString() == " ")
{
e->Value = -1.0;
e->ParsingApplied = true;
}
}
}
void DeleteHighlightedCells(DataGridView^ dgv)
{
try
{
System::Windows::Forms::DataGridViewSelectedCellCollection^ sCells = dgv->SelectedCells;
for(int i = 0; i < sCells->Count; i++)
{
if(!sCells[i]->ReadOnly)
{
dgv->Rows[sCells[i]->RowIndex]->Cells[sCells[i]->ColumnIndex]->Value = "";
}
}
}
catch(Exception^ e)
{
LogError("Unable to delete contents of DataGridView cells: " + e->ToString());
}
}
System::Void dataGridViewWells_KeyDown(System::Object^ sender, System::Windows::Forms::KeyEventArgs^ e)
{
if(e->Control && e->KeyCode == Keys::C)
{
this->CopyContentsToClipBoard(this->dataGridViewWells);
}
if(e->Control && e->KeyCode == Keys::V)
{
this->PasteContentsFromClipBoard(this->dataGridViewWells);
}
if(e->KeyCode == Keys::Delete)
{
this->DeleteHighlightedCells(this->dataGridViewWells);
}
}
I ended up abandoning the CellParsing event and putting in a custom TypeConverter for my class property as was done in this question.
TypeConverter code:
public ref class MassTypeConverter : System::ComponentModel::TypeConverter
{
public:
virtual bool CanConvertFrom(System::ComponentModel::ITypeDescriptorContext^ context, Type^ sourceType) override
{
return sourceType == String::typeid || System::ComponentModel::TypeConverter::CanConvertFrom(context, sourceType);
}
virtual Object^ ConvertFrom(System::ComponentModel::ITypeDescriptorContext^ context, System::Globalization::CultureInfo^ culture, Object^ value) override
{
if(value != nullptr && value->GetType() == String::typeid)
{
//
double converted;
bool success = double::TryParse((String^)value, converted);
if(((String^)value) == "" || ((String^)value) == " ")
{
converted = -1.0;
success = true;
}
if(success)
{
return converted;
}
}
return System::ComponentModel::TypeConverter::ConvertFrom(context, culture, value);
}
};
And then put the following above my class Property:
[System::ComponentModel::TypeConverter(MassTypeConverter::typeid)]

Change text of a cell on dataGridView_CellFormatting

I have a Datagridview column with a value as int. I have a condition for the value status, if(status == 1) then change text to Active, else change text to Not Active.
Any help would be much appreciated.
private void bindListToGridView(List<Employee> list)
{
// DataTable dt2 = convertListToDataTable(list);
// more codes here.
using (dt2)
{
// more codes here.
dataGridView1.Columns[8].Name = "Status";
dataGridView1.Columns[8].HeaderText = "Status";
dataGridView1.Columns[8].DataPropertyName = "status";
dataGridView1.DataSource = dt2;
}
}
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
string columnName = dataGridView1.Columns[e.ColumnIndex].Name;
if (columnName == "Status")
{
int value = (int)dataGridView1.Rows[e.RowIndex].Cells["Status"].Value;
if (value == 1)
{
// set the cell text = Active
}
else
{
// set the cell text = Not Active
}
}
}
To avoid changing the underlying integer value, you can set the cell's format to the desired text:
if (value == 1)
{
dataGridView1.Rows[e.RowIndex].Cells["Status"].Style.Format = "Active";
}
else
{
dataGridView1.Rows[e.RowIndex].Cells["Status"].Style.Format = "Not Active";
}
Or more easily, as LarsTech pointed out, just equivalently set:
e.Value = "Active";
This works because we are already in the CellFormatting event handler and the Value property of DataGridViewCellFormattingEventArgs is the formatted converted value.

DataGridView attempting to save bound data with formatted value?

I am attempting to format a time value from a mssql database. The value is stored in the database as an int with hhmm as the format(for example: 1900 or 2130) I am unable to edit the database format as it is used by other software as well.
It is automatically bound to the DataGridView, and this has worked perfectly. However the specification I am working to says that it MUST show as a standard time format. So I have used the CellFormatting Event to show the times like 19:00 and 21:30.
It shows the value fine, however when I go to edit this value using the DataGridView it doesn't change it back to the unformatted value so I get the formatted value in the edit box. If I don't remove the colon I get an exception about saying Input is Invalid.
It was my understanding that the formatted value and the true value are not the same and this wouldn't cause an issue.
My code for formatting his below:
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == -1)
return;
string CellName = dataGridView1.Columns[e.ColumnIndex].Name;
if (timeFormattedCells.Contains(CellName))
{
if (e.Value != null)
{
try
{
string currentFormat = e.Value.ToString();
if (currentFormat.Length == 3)
{
currentFormat = "0" + currentFormat;
}
DateTime dateTime = DateTime.ParseExact(currentFormat, "HHmm",CultureInfo.InvariantCulture);
currentFormat = dateTime.ToShortTimeString();
e.Value = currentFormat;
e.FormattingApplied = true;
}
catch (FormatException)
{
e.FormattingApplied = false;
}
}
}
}
I also need to ensure that the user cannot put an invalid time like 2405 or 1299.
Does anyone have a suggestion that might help me out?
Thanks
As long as your CellFormatting event is formatting the way you want, I would probably check the validity of the time first and then convert it to int...I would try something like this:
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
bool valid;
DataGridView dgv = (DataGridView)sender;
DataGridViewCell cell = dgv.Rows[e.RowIndex].Cells[e.ColumnIndex];
// Regex pattern acceptable for a time format
Regex timeRegex = new Regex("^([0-1]?[0-9]|[2][0-3]):([0-5][0-9])$");
if (cell.Value == null || cell.Value.ToString() == string.Empty)
{
cell.Style.BackColor = Color.White;
cell.ToolTipText = string.Empty;
cell.Tag = "empty";
valid = false;
}
// If the regex does not match then the format is invalid
else if (!timeRegex.IsMatch(cell.Value.ToString()))
{
cell.Style.BackColor = Color.Beige;
cell.ToolTipText = "Invalid format.";
cell.Tag = "invalid";
valid = false;
}
else
{
cell.Style.BackColor = Color.White;
cell.ToolTipText = string.Empty;
cell.Tag = "valid";
valid = true;
}
if(valid)
{
string timeToConvert = cell.Value.ToString();
string timeConvertReady = timeToConvert.Replace(":", "");
int timeAsInt = Convert.ToInt32(timeConvertReady);
updateTimeInDatabase(timeAsInt);
}
}
updateTimeInDatabase(timeAsInt) would be a method that sends the update command. This could also be a TableAdpater method or whatever you are using. If it was a TableAdapter the syntax would look something like testTableAdapter.Update(timeAsInt);

Verify a Color Has Been Inputted in a DataGridView

I have a datagridview where one of the columns is a color column. I want to ensure the user enters a valid color, otherwise leave the cell blank.
When the cell is starting from blank, the following code gives me a null reference exception. Fyi, I am doing this in the CellLeave event.
Answer I had to move the code to the CellFormatting event. For some reason, the value at the cell does not get updated until a certain unknown point. For my check, I need to do it before something I was doing in the CellFormatting event. Moving the code there fixed my problem.
private void dataGridView1_CellFormatting(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex.Equals(2))
{
Regex test = new Regex("[0-255],[0-255],[0-255]");
Match m = test.Match(this.dataGridView1.CurrentCell.Value.ToString());
if(!m.Success)
{
this.dataGridView1.CurrentCell.Value = "";
}
}
}
To validate a string you could use this method:
private static bool IsValidColorString(string input)
{
if (String.IsNullOrEmpty(input))
return false;
string[] parts = input.Split(',');
if (parts.Length != 3)
return false;
foreach (string part in parts)
{
int val;
if (!int.TryParse(part, out val) || val < 0 || val > 255)
return false;
}
return true;
}
The best place to use it I believe is the DataGridView.CellParsing event handler:
private void dataGridView1_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
if (e.Value == null || e.ColumnIndex != 2) // Skip empty cells and columns except #2
return;
string input = e.Value.ToString();
if (!IsValidColorString(input))
e.Value = String.Empty; // An updated cells's value is set back to the `e.Value`
}
An updated cells's value is set back to the e.Value instead of the DataGridView.CurrentCell.Value.
Try this for your match:
Match m = test.Match((this.dataGridView1.CurrentCell.Value ?? "").ToString());
This will replace a null value with an empty string when matching.
Do you perhaps need to check to see if there is a value within the cell before you try and match a Regex?
if (e.ColumnIndex.Equals(2))
{
if(this.dataGridView1.CurrentCell.Value != null)
{
...
CurrentCell.Value.ToString()
Causing the error..
CurrentCell.value=null
null value can't cast using the tostring() method,that's why you are getting ther null reference exception.
try this..
string val="";
if(dataGridView1.CurrentCell.Value==null)
{
val=""
}
els
else
{
val=convert.tostring(dataGridView1.CurrentCell.value);
}
Match m = test.Match(val);
if(!m.Success)
{
this.dataGridView1.CurrentCell.Value = "";
}

Check Control.Value for data

I have several different controls (TextBoxes, DateTimePickers, MaskedTextBoxes) on a form that I would like to check to see if they contain any data. I have the following code in the Click event of my "Save" button:
private void radBtnSave_Click(object sender, EventArgs e)
{
this.Cancelled = false;
bool bValid = true;
foreach(Control control in this.Controls)
{
if (control.Tag == "Required")
{
if (control.Text == "" || control.Text == null)
{
errorProvider.SetError(control, "* Required Field");
bValid = false;
}
else
{
errorProvider.SetError(control, "");
}
}
}
if (bValid == true)
{
bool bSaved = A133.SaveData();
if (bSaved != true)
{
MessageBox.Show("Error saving record");
}
else
{
MessageBox.Show("Data saved successfully!");
}
}
}
This works fine for the TextBoxes and MaskedEditBoxes, however, it does not work for the DateTimePickers. For those, I know I need to check the .Value property, but I cannot seem to access that from the Control object (i.e. "control.Value == "" || control.Value == null").
Am I missing something obvious? Any suggestions of modifications I can make to this code to allow me to check the DateTimePicker values (or just to improve the code at all) will be greatly appreciated.
You need to cast them to a DateTimePicker:
DateTimePicker dtp = control as DateTimePicker;
if(dtp !=null)
{
//here you can access dtp.Value;
}
Also, use String.IsNullOrEmpty(control.Text) in the first part of your code.
There is no Value property for Controls; DateTimePicker, for example, creates its own property that is unique to it.
Unfortunately for you, there is no fully generic way of handling this from a single loop of Control objects. The best you can do is something along the lines of this:
if(control is DateTimePicker)
{
var picker = control as DateTimePicker;
// handle DateTimePicker specific validation.
}
You'll need to do something like this:
foreach(Control control in this.Controls)
{
if (control.Tag == "Required")
{
DateTimePicker dtp = control as DateTimePicker;
if (dtp != null)
{
// use dtp properties.
}
else if (control.Text == "" || control.Text == null)
{
errorProvider.SetError(control, "* Required Field");
bValid = false;
}
else
{
errorProvider.SetError(control, "");
}
}
}

Categories