I have a simple scenario and a issue with it which I just cant seem to resolve past few days.
OK, first of all I use MVVM to bind my View on a ViewModel. I have in my view several text boxes which binds to several properties (most strings) in ViewModel (binds actually to an Custom Object (type Person, name SelectedPerson) with strings properties , object which is a property of the viewmodel). This object implements INotifyPropertyChanged and IDataErrorInfo. It has also an int property named Age. I also have in my view a button which is bound to a command in my viewmodel, a command which inside CanExecute test the SelectedPerson's properties and return true if all are correct.
Now my issue is: if I put in my Age text box from my View something not int, a red tectagle will appear (is normal, because there is an exception to the conversion), but in that specific moment, to the object behind (SelectedPerson, type Person) there isn't sent the newValue (the setter to that property Age, or the IDataErrorInfo Members don't intercept the value .... I guess it is normal because there isn't any "new" value, because I put an incorrect format in the text box in the first place).
So, maybe I repeat myself, the issue is: if the new Age (new incorrect Age) isn't set, then the Object behind still hold last value, which if it was correct then the command itself it's correct (the can execute will return true) and the button is enabled
As you can imagine I want the submit button (it's a button which saves current person details in data storage module) to be disabled when current properties don't pass through conversions methods.
PS: I used a IValueConverter class , and on that text box binded to Age, I made use of my StringToIntConverter class....but on Convert Method I don't know how to pass the SelectedPerson binded object (I just pass the text value, and return the int value)
I guess one way to do it could be by using MultiBinding scenario , but I'm not sure.
If I could pass the SelectedPerson inside Convert method from that converter I could invalidate that command from the converter itself.
Sorry for my English, I know it's far from perfect :) and thanks in advance for your time.
I think the cleanest solution would be to bind the textbox to a string property instead, and update your view model so that your IDataErrorInfo implementation for that property name attempts a string to int conversion and returns the result of that. Your CanExecute test would also then include this conversion as part of the validation test.
Related
I am designing a WPF application following MVVM. My ViewModel is exposing one Double property called DoubleValue, which is binding to a TextBox in the View. I have set "ValidatesOnDataErrors=True" for the binding. So if the user types a string which can't be converted to a Double, it display the red background.
In my ViewModel I also have a Command object, let's call SaveCommand, whose CanExecute delegate is depending on whether there is any error in the VM (my ViewModelBase class implements IDataErrorInfo, I have an overridable ValidatePropertyByName function and the validation errors are stored in a dictionary.) But now my problem is, if I give an invalid string in the TextBox, since the conversion fails, it never calls the setter of the binding property value. In another word, the ValidatePropertyByName is not called and the error dictionary remains the previous state, which normally is clean. So if now the user click the Save button (which is enabled since the error dictionary is clean), the SaveCommand executes with the previous valid double value to save. This is obviously not good.
So how can I make my ViewModel aware of such conversion errors?
UPDATE:
Some code example:
The binding property is like this:
Public Property DoubleValue As Double
Get
Return _doubleValue
End Get
Set(value As Double)
If value <> _doubleValue Then
_doubleValue = value
RaisePropertyChanged("DoubleValue")
End If
End Set
End Property
Private _doubleValue As Double
My binding is like this:
<TextBox Grid.Row="3" Text="{Binding DoubleValue, ValidatesOnDataErrors=True}" />
And now my problem is: if I give a string "XXX" in the text box, since it can't be converted to a double value, the setter of DoubleValue is never get called. And so the property value remains the previous(valid) value. Now if my SaveCommand gets executed, it will do the save operation with this previous valid value, which will make the user confused.
the most easy way is to just use string properties in your viewmodel. then you get all input from the user and can validate it in your viewmodel. the drawback is that you have to convert the values to the right type when you go to the model.
if you dont want this you have to create your own controls or better behaviors so that the use can just input values that your viewmodel expect. eg. NumericInputBehavior.
You cannot simply put these two things together. One is the regular validation inside the ViewModel. The other are control-specific problems, like unconvertible values.
So there are two possible ways to solve this:
1) Don't use a converter. Just bind the string. Inside the ViewModel you can then use the validation to check for a valid value. (More MVVM)
2) Store your ValidationErrors on the controlside and merge them with the viewmodel errors. This is not easy but a good way to create one source for binding against ALL problems within your UI. We are doing this for complex textboxes at work. This means manual code in the controls but for complex customcontrols this is OK, I believe.
edit: just to elaborate a little on the 2nd point. We are having a DependencyProperty of Type ObservableCollection inside the Control. Then you can bind this Collection to a ViewModel Property and as soon as your control moves an Error inside the collection it is available inside the viewModel. You can then use this collection inside your validation implementation. This works pretty well for larger controls.
Edit2: For the MarkInvalid Stuff I mentioned in the comment. It would look like this:
DataErrorValidationRule validationRule = new DataErrorValidationRule();
ValidationError validationError = new ValidationError(validationRule, myTextBox.GetBindingExpression(TextBox.TextProperty)) { ErrorContent = "My custom message" };
Validation.MarkInvalid(myTextBox.GetBindingExpression(TextBox.TextProperty), validationError);
You would call in from inside a TextChanged when you can't convert the new given value or
Validation.ClearInvalid(myTextBox.GetBindingExpression(TextBox.TextProperty))
Maybe that will help?
I am not sure that I fully understand the advantage of binding. For example, if I want to bind a string value to a TextBlock I need to do the following:
Create a class that extends INotifyPropertyChanged
Add a string to that class (say: MyString)
Extend the set method for MyString so that it calls another method (say: OnPropertyChanged)
Create the OnPropertyChanged method to call the PropertyChangedEventHandler event
Then I need to create a new instance of the class, set my TextBlock.DataContext to point to that class, and finally add the XAML bit for the binding.
Can someone explain the advantage of this over simply setting:
TextBlock.Text = MyString;
Thanks!
Any changes to MyString won't be automatically reflected in your UI.
Your code behind will be littered with "when this event occurs, update these pieces of data", so you'll essentially be writing your own messy data binding logic for each and every view.
The advantage is that you can both change and display the value in multiple places, without having to update some method to add another TextBlock assignment each time the value changes. Any new display control just binds itself to the property, the rest is automatic.
Now if you really just set the value in one place and show it in one control, then you're right, there's not much point.
The gain of using Data Binding isn't particularly noticeable for a TextBlock binding to a static string.
However if the value of MyString changes during application runtime it becomes much more useful - especially in a case where the object that owns that property is unaware of the TextBlock. This separation between UI and the underlying data layer can be created using a design pattern such as MVVM.
Data Binding is also useful for more complex properties such as Items in a ListBox control. Just bind the ListBox.Items to a property that is of type ObservableCollection and the UI will automatically update whenever the content of that collection changes.
I'm using IDataErrorInfo to validate my viewmodels as it allows me to use a clear xaml sintax and it's pretty straightforward once you get it.
My question is how to validate one property when another one changes because the validation does not consist only on the value on the cell but depends on others. I see that the validation method is only called when the value of the property changes.
Thanks in advance.
with IDataErrorInfo you can hook into
public string this[string columnName]
by overriding it, or writing your own.
in your case do a check that the columnName equals the property you are looking to validate and then either return a string with a value to represent a validation error, or null to say there was no error.
Also, to make sure it gets revalidated, when the dependent properties get updated, make sure you do a OnPropertyChanged for the main property
I have a ViewModel class that implements the IDataErrorInfo Interface.
In each property's set I validate the value passed in and if it fails the validation I call a method called AddError to add an error for the property. The AddError method adds an item into my underlying data type I'm using to manage the errors (a Dictionary(Of String, List(Of String))).
Currently things are working well during data entry. If the user enters invalid values for a property, the FrameworkElement that is used to input the data is highlighted with the ToolTip set to the error message.
Now, here's my problem.
Say my object contains invalid fields from the start....
For example, if I have a Person class with a required field "Name" and I create a new instance of the Person class. The Name Field will not be highlighted as an "error" because the Name Property's set method hasn't been called yet.
So, I put validation into the property's get as well.
This seems to works but......it feels like a hack. And some property validation has to be done by the model (as opposed to the ViewModel that I'm working with). The Model bubbles up the appropriate error message that should be set but I can't get the error message unless I set the Model's property.
Any advise on the best approach to implementing the IDataErrorInfo interface would be really really appreciated.
Thanks!
-Frinny
I figured out something that doesn't feel like a hack.
I also probably should have mentioned that I have a base class that all my ViewModel inherit from. It is in this base class that I am implementing the IDataErrorInfo Interface.
What I ended up doing was adding a new MustOverride method to the base ViewModel class called "Validate" that validates the property name supplied to it. If the validation fails then it adds an entry into my underlying errors dictionary.
I call the validate method in the Item property (that implements the IDataErrorInfo.Item property).
This way each derived class can contain it's own validation code and I can be sure that the method exists because it's part of the base ViewModel class.
If this isn't the best approach, please let me know.
-Frinny
I'm trying to create a UserControl that contains a AutoCompleteBox. I want to use the SelectedItem property of this AutoCompleteBox to populate other UserControls with information based on which item the User selected.
To prevent the SelectedItem to be fired every time a user "navigate" between items in the drop-down I've created a EventToCommand that executes on DropDownClosed event like this:
The command is of type:
public RelayCommand SelectedItemCommand { get; private set; }
This works fine except for when a user start typing something that has a match at the beginning, but if the user continue typing and there is no match anymore, then the DropDown is closed and no item is actually selected. This gives me an error that says:
Unable to cast object of type 'System.Windows.RoutedPropertyChangedEventArgs`1[System.Boolean]' to type 'MyProject.ViewModels.MyItem'
I tried to make a try-catch statement in the Command method for the command, but the exception seems to be fired even before I get into that method.
My question is:
How can I prevent the command from beeing fired if there is no-match (that is, no actual SelectedItem in the AutoCompleteBox)?
Are you using PassEventArgsToCommand? In that case, the RelayCommand must be RelayCommand<EventArgs> and the CommandParameter may not be used. It is a limitation of the ICommand interface, which can only have one CommandParameter. Annoying, I know, but usually I am able to use a different way to get what I want (for example by binding the SelectedItem to a property on my VM with a TwoWay binding).
Let me know,
Laurent