DataSet validation in ColumnChanging event - c#

In my partial class containing the DataSet event I have the following:
protected override void OnColumnChanging(System.Data.DataColumnChangeEventArgs e)
{
switch (e.Column.ColumnName)
{
case "ColumnA":
{
int value = GetValue(e.ProposedValue.ToString());
if (value == -1)
{
e.Row.SetColumnError("ColumnA", string.Format("ColumnA could not map [{0}] to a valid value", e.ProposedValue));
//e.ProposedValue = "";
}
break;
}
base.OnColumnChanging(e);
}
When I check for errors and get the column errors for my rows I see the appropriate message when GetValue(...) returns -1. I also see that the column in that has the bad data still contains that bad value. I was under the impression that calling SetColumnError(...) would reject the change made to that column (ColumnA) as per: How to: Validate Data During Column Changes
Reject the proposed value by setting the column error (SetColumnError)
from within the column-changing event handler.
So when I try to do something like the following:
TypedDataSet set = new TypedDataSet();
TypedDataTable.TypedDataRow row = set.TypedDataTable.NewRow();
row.ColumnA = "Bad Data";
set.TypedDataTable.AddTypedDataRow(row);
I'll see the validation code execute, but the value of ColumnA retains: "Bad Data". If I go as far as setting e.ProposedValue = null I can see the value change.
Update
Add event handlers for either RowsChanging or ColumnsChanging also produce similar results.
public override void BeginInit()
{
base.BeginInit();
TypedRowChanging += new TypedRowChangeEventHandler(TypedDataTable_TypedRowChanging);
ColumnChanging += new DataColumnChangeEventHandler(TypedDataTable_ColumnChanging);
}
Code in both of the event handlers is trivial and will call e.Row.SetColumnError("ColumnA", "some error"). So my original question remains:
What should be happening the in case where a column error is set on a column? Should it retain value, become null, 42?

According to the doc you linked, you don't want to override OnColumnChanged - you want to add an eventhandler for the ColumnChanged event. Try using the sample code provided there for a starting point.

After much testing and work I've come to the conclusion that the column retains the value when a column error is set on a specific column. My current solution is to check for errors on the entire DataSet and then proceed to aggregate those errors into a format for error handling
TypedDataSet set = new TypedDataSet();
TypedDataTable.TypedDataRow row = set.TypedDataTable.NewRow();
row.ColumnA = "Bad Data";
set.TypedDataTable.AddTypedDataRow(row);
// At this point the DataSet has column errors and can be checked for...
if (set.HasErrors)
{
// Build some error string container here
TypedDataSet.TypedDataRow row = tempSet.TypedDataTable.Single();
var errors = from column in row.GetColumnsInError()
select row.GetColumnError(column);
foreach (var error in errors)
{
// Add to error container here
}
}
After this I check if my error container has any data in it. I suppose it makes sense for the column to retain value so that the programmer has access to that value and can include that it in the call to SetColumnError(...).

Related

Int field in Winform does not update null values in SQL Server database using C#

In my Winforms C# application, I have fields with Int data type and they are set to accept null values in SQL Server database (allow nulls).
In the forms I have some textboxes which are bound to those int data type fields. If I don't enter anything while creating a new record, it accepts. If I enter a number in the textbox, it also accepts it, and then if I delete it, it doesn’t accept it anymore and even doesn't allow me to move to the next field.
If I set its value as null or "" through code, it simply ignores and does not even update changes which I made in other non int text fields.
I am using following method to update.
this.Validate();
this.itemsbindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.sBSDBDataSet);
What can I do for the textbox to accept null values?
I have tried following.
IDTextBox.Text = "";
IDTextBox.Text = null;
I have tried following with the help of above solutions (specially Mr. Ivan) and this is how it worked out.
To clear the int field on the form:
IDTextBox.Text = String.Empty;
Then on Designer.cs file of the form, as suggested by Mr. Ivan, I searched for 'IDtextbox.DataBindings.Add' and replaced
this.IDTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.itemsbindingSource, "PictureID", true));
with
this.IDTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.itemsbindingSource, "PictureID", true, System.Windows.Forms.DataSourceUpdateMode.OnValidation, ""));
It took me a whole day to search and finally I posted my problem here, and it got solved in 1 hour.
This seems to be one of the WF data binding bugs. I can't say what exactly is causing it, but in order to make it work one should set Binding.NullValue property to "" (empty string, the default is null).
I couldn't find a way to do that in the designer, and also it would be quite annoying to locate all text boxes needed. So I would suggest you the following quick-and-dirty approach. Create a helper method like this:
public static class ControlExtensions
{
public static void FixTextBoxBindings(this Control control)
{
if (control is TextBox)
{
foreach (Binding binding in control.DataBindings)
if (binding.NullValue == null) binding.NullValue = "";
}
foreach (Control child in control.Controls)
child.FixTextBoxBindings();
}
}
and then simply include the following in your form Load event:
this.FixTextBoxBindings();
TextBox dont accept null value.
You can check if it null and set String.Empty;
If(dbValue == null)
{
IDTextBox.Text = String.Empty;
}
else
{
// here set value to your textbox
}

Reject changes on a single DataColumn in a DataRow

I want to reject changes made to a single DataColumn on a DataRow within a DataTable.
I made this test example:
DataTable table = new DataTable();
table.Columns.Add("testColumn1");
table.Columns.Add("testColumn2");
DataRow row = table.NewRow();
row["testColumn1"] = "This change should be preserved";
row["testColumn2"] = "This change should be rejected";
table.Rows.Add(row);
row.RejectChanges();
This rejects all changes made to the row. In my case the user might have some unsaved changes in one of the other columns, so using this will not work for this scenario.
I am looking for a similar functionality that reverts changes for "testcolumn2" only, for example:
row["testColumn2"].RejectChanges();
I looked up the documentation for the DataColumn class and could not find any similar method to DataRow.RejectChanges:
https://msdn.microsoft.com/en-us/library/system.data.datacolumn(v=vs.110).aspx
Is this possible to do with the C# framework or do I have to use an alternative solution?
Alternative solutions are also appreciated.
Seems to me the simplest solution it to set the value of the column to its original value using DataRowVersion:
row["testColumn2"] = row["testColumn2", DataRowVersion.Original];
There is one problem with this approach if you are using a transaction to batch together some updates. Let's say within the transaction you call Adapter.Update to write changes to the DB, but the DB rejects them due to a constraint violation. Even when I call transaction.RollBack() I am still getting the new version when I ask for the Original.
It's as though .Net calls AcceptChanges without making sure the transaction will not be rolled back. This might require a separate question to Stack Overflow to get a solution.
You can use the event DataTable.ColumnChanging. This triggers every time a change is made to a DataColumn in the table.
table.ColumnChanging += OnColumnChanging;
The method OnColumnChanging checks that the column name equals the column I want to backup, and that the call is not coming from the "revert value" part of the code (refer to the boolean isRevertingChanges).
If both of these are true, it creates a backup column (This is skipped if it already exists). Finally it writes the value that is about to be overwritten to the backup column on the row.
private bool isRevertingChanges;
void OnColumnChanging(object sender, DataColumnChangeEventArgs e)
{
if (!isRevertingChanges && e.Column.ColumnName.Equals(TestColumn2))
{
if (!e.Row.Table.Columns.Contains(TestColumn2Backup))
e.Row.Table.Columns.Add(TestColumn2Backup);
e.Row[TestColumn2Backup] = e.Row[TestColumn2];
}
}
Since the criteria for reverting changes is irrelevant in this case I have replaced it with if(true) so the revert-part of the code is triggered every time.
First it sets the isRevertingChanges variable to true (this is to prevent OnColumnChanging() from making a backup of the value that is being reverted), then it reverts the column back to its original value and finally it sets isRevertingChanges back to false so OnColumnChanging() will continue backing up values every time the column change.
if (true)
{
isRevertingChanges = true;
row[TestColumn2] = row[TestColumn2Backup];
isRevertingChanges = false;
}
Here is the full source code:
[TestClass]
public class RejectDataColumnChanges
{
private bool isRevertingChanges;
private const string TestColumn1 = "testColumn1";
private const string TestColumn2 = "testColumn2";
private const string TestColumn2Backup = "testColumn2Backup";
private const string PreservedMessage = "This change should be preserved";
private const string RejectedMessage = "This change should be rejected";
[TestMethod]
public void RejectDataColumnChangesTest()
{
DataTable table = new DataTable("testTable1");
table.Columns.Add(TestColumn1);
table.Columns.Add(TestColumn2);
DataRow row = table.NewRow();
row[TestColumn1] = PreservedMessage;
row[TestColumn2] = PreservedMessage;
table.Rows.Add(row);
table.ColumnChanging += OnColumnChanging;
row[TestColumn2] = RejectedMessage;
Assert.AreEqual(PreservedMessage, row[TestColumn1]);
Assert.AreEqual(RejectedMessage, row[TestColumn2]);
Assert.AreEqual(PreservedMessage, row[TestColumn2Backup]);
if (true)
{
isRevertingChanges = true;
row[TestColumn2] = row[TestColumn2Backup];
isRevertingChanges = false;
}
Assert.AreEqual(PreservedMessage, row[TestColumn1]);
Assert.AreEqual(PreservedMessage, row[TestColumn2]);
Assert.AreEqual(PreservedMessage, row[TestColumn2Backup]);
}
void OnColumnChanging(object sender, DataColumnChangeEventArgs e)
{
if (!isRevertingChanges && e.Column.ColumnName.Equals(TestColumn2))
{
if (!e.Row.Table.Columns.Contains(TestColumn2Backup))
e.Row.Table.Columns.Add(TestColumn2Backup);
e.Row[TestColumn2Backup] = e.Row[TestColumn2];
}
}
}

Devexpress Reporting get Row data by click in it

I try get all values from the row(where was click) of XtraReport.
I don't know why, but xrTableRow1_PreviewClick and xrTable1_PreviewClick don't work.
Works only xrTableCell4_PreviewClick. And through 'e.Brick.Text' I can get only value in cell.
private void xrTableCell1_PreviewClick(object sender, PreviewMouseEventArgs e)
{
var tmp = e.Brick.Text;
}
If I try:
GetCurrentRow()
It gives only first row. It's not what I need. )
How can I get all values from the row where was click? Or how I can get Row Index where click was?
My report looks like in designer:
Thanks in advance!
There is no simple way to access the corresponding data row using brick methods. When all the required bricks are generated, the report does not refer to a data source anymore. In other words, bricks do now know about the underlying data. They just contain a text, image, or other controls.
The simplest way to accomplish this task is to handle the XRControl's BeforePrint event and pass the corresponding data object (DataRowView or a custom object) to the XRControl's Tag property:
private void xrLabel_BeforePrint(object sender, System.Drawing.Printing.PrintEventArgs e) {
// Obtain the current label.
XRLabel label = (XRLabel)sender;
//Obtain the parent Report
XtraReportBase parentReport = label.Report;
//Get the data object
object currentData = parentReport.GetCurrentRow();
//Pass this object to Tag
label.Tag = currentData;
}
After that, it's possible to access the data object via the e.Brick.Value property in the PreviewClick event handler.
VB.net code to capture the values of any field in detail bank in preview mode.
Dim myo As Object
dim myacct as long
For i = 0 To e.Brick.Parent.Bricks.Count
Try
myo = e.Brick.Parent.Bricks.Item(i) ' cant get name w/o this ???
If myo.brickowner.name = "XrLabel14" Then ' test for desired label
myacct = myo.text 'capture the value
Exit For
End If
Catch oerr As Exception
'log error here
End Try
Next

Set Combobox Items and DataSource

I'm having a problem where every time I build my solution, the compile succeeds but when I run my program it will error as the forms designer.cs file has had the data source for my custom comboboxes added to it automatically; resulting in an exception stating
Items collection cannot be modified when the DataSource property is set.
Any ideas on what might be the problem? I've tried setting the data source after the initialize component method but this results in a different error as the unit type is null..
The type of data source is set in a property for the control and below is the relevant code
form.Designer.cs (this is generated for you not a custom cs file called designer)
//
// cmbWheelUnitCR
//
this.cmbWheelUnitCR.DataSource = ((object)(resources.GetObject("cmbWheelUnitCR.DataSource")));
this.cmbWheelUnitCR.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbWheelUnitCR.FormattingEnabled = true;
this.cmbWheelUnitCR.Items.AddRange(new object[] {
"mm",
"yd"});
My custom combobox
public string UnitType
{
get { return m_unitType; }
set { m_unitType = value;
this.DataSource = Units.Instance.UnitTypes(m_unitType);}
}
public UnitComboBox()
{
InitializeComponent();
}
I was able to solve this accidentally by setting the data source within an override onLoad event..

INotifyDataErrorInfo not raising error changed in code behind

I am experiencing issued performing validation from the codebehind. My data is displayed in a datagrid. One of the columns (type) is a drop down and when the drop down menu is changed it triggers a DropDownClosed Event which is handled in the code behind.
What I am trying to achieve is to validate the content of the following column to match the newly selected type in the drop down. If it does not match i want a validation error to be displayed on the grid. I implemented my validation using the INotifyDataErrorInfo interface and it works really well except when I use it in the code behind. When the code behind calls the validation the ValidationSummary of the datagrid is never updated. What I am doing wrong here ??? When using the debugger I can clearly see the errors being added to the Errors dictionnary of the interface...
Here is the handler:
private void TypeBoxChanged(object sender, EventArgs e)
{
ComboBox box = (sender as ComboBox);
IncomingPolicy row = (IncomingPolicy)box.DataContext;
string ruleTypeValue = TypeList.GetKeyForText(box.SelectedItem.ToString());
//check if the type is the same
if(row.TypeWrapper == ruleTypeValue)
return;
if (row.ValidateRule(ruleTypeValue))
{
//SAVE the record
}
else
{
row.RaiseErrorsChanged("RuleWrapper");
}
}
The validate rule method will based on the ruletypevalue call this method
public bool ValidateRegularExpression(string property, string value, string expression, string errorMessage)
{
bool isValid = true;
Regex regex = new Regex(expression);
Match match = regex.Match(value);
if (match.Success)
{
RemoveError(property, errorMessage);
}
else
{
AddError(property, errorMessage, false);
isValid = false;
}
return isValid;
}
I followed the sample implementation on MSDN http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifydataerrorinfo%28VS.95%29.aspx
Some time earlier I've implemented validation helpers and created the sample solution for both interfaces IDataErrorInfo and INotifyDataErrorInfo:
http://vortexwolf.wordpress.com/2011/10/01/wpf-validation-with-idataerrorinfo/
Source code
The main implementation is here:
this.PropertyChanged += (s, e) =>
{
// if the changed property is one of the properties which require validation
if (this._validator.PropertyNames.Contains(e.PropertyName))
{
this._validator.ValidateProperty(e.PropertyName);
OnErrorsChanged(e.PropertyName);
}
}
You should always call the OnErrorsChanged (or RaiseErrorsChanged in your case) method regardless of success of validation: if the property is invalid - the red border will be displayed, if it is valid - the bound control will be returned to its normal state.

Categories

Resources