INotifyDataErrorInfo not raising error changed in code behind - c#

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.

Related

ObjectListView editing doesn't work

I'm trying to create a simple listbox with ObjectListView (WinForm, C#). The goal is to have a single value (a double) and a check box.
I want to be able to edit the double value by Single Click, so here are the relevant lines of code from my MyWindow.Designer.cs file (i've left out the default values for efficiency):
this.olvDepths = new BrightIdeasSoftware.ObjectListView();
this.olvColumn1 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
...
this.olvDepths.CellEditActivation = BrightIdeasSoftware.ObjectListView.CellEditActivateMode.SingleClick;
this.olvDepths.CheckBoxes = true;
this.olvDepths.CheckedAspectName = "IsDefault";
this.olvDepths.FullRowSelect = true;
//
// olvColumn1
//
this.olvColumn1.AspectName = "Depth";
this.olvColumn1.Text = "";
this.olvColumn1.IsEditable = true;
I then create a list of my class (ShieldingEntry) and use the olvDepths.SetObjects() with the list. My ShieldingEntry class looks like this:
public class ShieldingEntry
{
public double Depth { get; set; }
public bool IsDefault { get; set; }
}
However, when I click the field, it doesn't go into edit mode. I've also tried the DoubleClick, SingleClickAlways, and F2Only modes and they don't work either.
The Checkbox works fine.
************** I have additional information *********************
I've pulled and build the ObjectListView source, so I could step through it.
I put a breakpoint in the OLV StartCellEdit method and it gets called and appears to setup and select the control appropriately. It just never appears...
As I noted in the comments on the answer below, I've got this control on a tabbed dialog, and if I switch to another tab, then back, the control works fine.
What am I missing?
I've used ObjectListView before, and here is what I had to do:
Handle the CellEditStarting event. This event is raised when the cell goes into edit mode. Since OLV doesn't really have built-in editors, you have to make your own. Then handle the CellEditFinishing event to validate the data before putting it back into your model.
So first, handling the CellEditStarting event:
private void objlv_CellEditStarting(object sender, CellEditEventArgs e)
{
//e.Column.AspectName gives the model column name of the editing column
if (e.Column.AspectName == "DoubleValue")
{
NumericUpDown nud = new NumericUpDown();
nud.MinValue = 0.0;
nud.MaxValue = 1000.0;
nud.Value = (double)e.Value;
e.Control = nud;
}
}
This creates your editing control. If you want to make sure the size is right, you can set the size of the control (in this case a NumericUpDown) to the cell bounds using e.CellBounds from the event object.
This will show the editor when you click in the cell. Then you can handle the editor finished event to validate the data:
private void objlv_CellEditFinishing(object sender, CellEditEventArgs e)
{
if (e.Column.AspectName == "DoubleValue")
{
//Here you can verify data, if the data is wrong, call
if ((double)e.NewValue > 10000.0)
e.Cancel = true;
}
}
I don't think handling it is required, but its good practice to validate data from the user.
The editing control in the CellEditStarting event can be any control, even a user defined one. I've used a lot of user defined controls (like textboxes with browse buttons) in the cell editor.
[Edit]
I uploaded an example here dropbox link that seems to work. Might not be in the exact view as needed, but seems to do the job.
For anyone else with this problem. I had it specifically when trying to edit a 'null' value in a decimal? on the OLV on a tab page. Solution for me was to set UseCustomSelectionColors to 'False'. I didn't look elsewhere to see if it was reported as a bug. Seems like a bug.

MVVM approved method for disabling certain characters in textbox?

Is there a way to disable certain characters from being written into a wpf textbox without using code in the code behind file?
I have a few int fields that are bound to text boxes that I would like to limit to keys 0-9 only. If I enter anything else I do get the red validation error but that is not enough.
I recommend using IDataErrorInfo for WPF validation since WPF already understands how to use it, and its easy to implement.
You have to add the interface on your class, and the required methods will look like this:
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (columnName == "YourProperty")
{
int property = Convert.ToInt32(YourProperty);
if (property < 0 || property > 9)
return "The value must be between 0 and 9";
}
return string.Empty;
}
}
#endregion
Next, you need to set ValidatesOnDataErrors=True in your TextBox binding so it runs the validation whenever the property changes.
When something has gone wrong, it adds a red border on your control and the message you put on your validation:
You can read more about how to use the interface:
WPF: Validation made easy with IDataErrorInfo
Really simple WPF form data validation - how to?
In fact, this task is not possible enterily in xaml.
At some point, you need to write some code.
These are some links could be useful
http://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/
http://soumya.wordpress.com/2010/05/09/wpf-simplified-part-15-data-validation/

Filtering CollectionViewSource

I want to make a ComboBox bound to my data, with a filter. For that I've created a TextBox and a ComboBox. In the code behind I read a file and generate objects of class Channel that are stored as items of the ComboBox. Although the compiler throws no error the filtering doesn't work properly. If I write something the data is gone, if I erase, it's back. After trying and trying I've realized that if I started typing "myNamespace.myChannel" (Unico.Canal) the data remained, but don't filter. Strange behaviour, indeed. I suspect that I've put something in wrong place.
(for better understanding I've translated the code, Canal=Channel)
Here is the scheme of my code:
namespace Unico
{
public partial class ControlesArchivo : UserControl, INotifyPropertyChanged
{
public ControlesArchivo()
{
InitializeComponent();
}
public ObservableCollection<Channel> myListChannels //with INotifyPropertyChanged implemented. But I think I don't need it.
private void loadButton_Click(object sender, RoutedEventArgs e)
{
File loadedFile = new File();
loadedFile.read(); //Generates a bunch of data in lists.
foreach (Channel mychan in loadedFile.channels) //Just duplicating the data (maybe this can be avoided)
{
myListChannels.Add(mychan);
}
var view = CollectionViewSource.GetDefaultView(this.miListaDeCanales);
view.Filter = delegate(object o)
{
if (o.ToString().Contains(myTextBox.Text)) //Delicate place
{
return true;
}
return false;
};
myComboBox.ItemsSource = view;
DataContext = this;
}
private void myTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
((ICollectionView)myComboBox.ItemsSource).Refresh();
myComboBox.SelectedIndex = 0;
}
}
}
The data is bound in XAML with:
ItemsSource="{Binding view}"
EDIT: I think I know where is the problem: I'm not specifing the property to filter. I mean, what you see in the ComboBox is the property channelName of the class Channel listed in myListChannels. When I'm setting the filter, shouldn't I let know what I'm filtering? How could I write this? Thank you very much.
Yes your assumption is correct.
I'm assuming with your translations,
public ObservableCollection<Channel> myListChannels;
is actually
public ObservableCollection<Canal> miListaDeCanales;
with the class Canal in the namespace Unico
Update:
In your filter try using the property that is rendered in the ComboBox than use the ToString() on the object(o) if you've not overridden ToString() from System.Object.
try switching
if (o.ToString().Contains(myTextBox.Text))
to
if (((Canal)o).NameProperty.Contains(myTextBox.Text))
^^ that should fix your issue.
Do you have a DataTemplate for ComboBox.ItemTemplate in xaml. That will explain why you see the valid value rendered in the ComboBox, else all the ComboBoxItem's will also render as Unico.Canal

How to force validation errors update on View from ViewModel using IDataErrorInfo?

I have a MVVM-based Window with many controls, and my Model implements IDataErrorInfo.
There is also a SaveCommand button, which performs validation by analysing Model.Error property.
The view displays the default red border around controls with errors only when I change the value of a particular control, or when I notify about the change of that property using PropertyChanged.
How can I force View to display all Validation errors even when I didn't touch the controls?
All my validation bindings include ValidatesOnDataErrors=True, NotifyOnValidationError=True.
I know one solution is to have an aggregate box with all the errors, but I would prefer to display errors on per-control basis.
I don't want to trigger Model.NotifyPropertyChanged for each bound property from ViewModel.
I use WPF 4.0, not Silverlight, so INotifyDataErrorInfo won't work.
You mention that you don't want to raise property changed for the properties you bind to, but that's really the simplest way to accomplish this. Calling PropertyChanged with no parameter will raise for all properties in your viewmodel.
Alternatively you can update the bindings (and force revalidation) on any control like this:
myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource();
The best solution I've found so far that works is to change DataContext to null and back to the instance of ViewModel.
This triggers the update for controls on the view that has DataContext bound to InnerViewModel:
public void ForceUpdateErrors() {
var tmpInnerVM = _mainViewModel.InnerViewModel;
_mainViewModel.InnerViewModel = null;
_mainViewModel.InnerViewModel = tmpInnerVM;
}
It's recommended to check if no data is lost after this trick. I had a case that this code triggered source update for ComboBox.SelectedItem with null but I managed to solve it. It was caused by using a resource-based BindingProxy and the order of DataContext=null propagation across control hierarchy.
This 'Hack' worked for me temporarily, to force the InotifyChanged event, just assign that control back it's own content. Do this before evaluating the HasError function of bindings. For example a textbox would be:
((TextBox)child).Text = ((TextBox)child).Text;
And then a complete example(before I hear this is not true MVVM, I directly got a handle on the grid for ease of showing this code snipet)
public bool Validate()
{
bool hasErr = false;
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(grd); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(grd, i);
if (child is TextBox)
{
bool pp = BindingOperations.IsDataBound(child, TextBox.TextProperty);
if (pp)
{
((TextBox)child).Text = ((TextBox)child).Text;
hasErr = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).HasError;
System.Collections.ObjectModel.ReadOnlyCollection<ValidationError> errors = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).ValidationErrors;
if (hasErr)
{
main.BottomText.Foreground = Brushes.Red;
main.BottomText.Text = BindingOperations.GetBinding(child, TextBox.TextProperty).Path.Path.Replace('.', ' ') + ": " + errors[0].ErrorContent.ToString();
return false;
}
}
}
if (child is DatePicker)
{
...
}
}
return true;
}

DataSet validation in ColumnChanging event

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(...).

Categories

Resources