PropertyChangedEvent and CanExecute issue - c#

I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}

I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.

I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl

Related

Extending dynamic dispatch to call functions in the view model?

I'm using MVVM in a Xamarin application, I have an interface to navigate between pages:
public interface INavigate
{
INavigate Next();
INavigate Previous();
string ViewTitle { get; }
}
In the implementing views:
public partial class V2Upload : ContentView, INavigate
{
public string ViewTitle => "Upload photos";
public INavigate Next()
=> new V3AdDetail();
public INavigate Previous()
=> new V1Agreement();
}
and in the view model
I have a property of type INavigate:
public INavigate CurrentAddItemStep
{
get { return _currentAddItemStep; }
set { Set(ref _currentAddItemStep, value); }
}
and the Content property of the parent view is bound to this property:
when next button is clicked I execute this code:
CurrentAddItemStep = CurrentAddItemStep.Next();
ViewTitle = CurrentAddItemStep.ViewTitle;
now a validation method is required before navigating to the next page for all the Content views..
I want to keep the MVVM pattern as clean as possible by not writing business code in the view, for example in the V2Upload view the File1 and File2 properties of the view model shouldn't be null:
private bool ValidateFiles(){
return (File1 ?? File2) != null;
}
but since the navigating is done dynamically in run-time, I can't know which view is the current view.
I'm thinking to use reflection , to know what is the name of the view (but this will break the whole design)
Another option is to provide a function parameter to the Next method, but also how to provide it in the design time from the view model?
This is what I'm doing now:
public INavigate Next()
{
if (((ViewModel.AddItemViewModel)BindingContext).ValidateFiles())
return new V3AdDetail();
else
return this;
}
but again, I'm accessing the view model from the view (and had to change the ValidateFiles method from private to public), which I want to avoid

Mvvm model validation with INotifyDataErrorInfo

my model implements the INotifyDataErrorInfo interface to validate it's properties, and it works fine, but the probleme is, the property HasErrors is by default false, so when i run my app at the first time and click save (form is empty) the view raise no errors, and the data is saved.
here is a snipet of my viewmodel
public LoggingViewModel()
{
_loggingCommand = new RelayCommand(checkCredentials, canExecuteLogginForm);
_logingModel = new LoggingModel();
// I raise this event in the 'OnErrorsChanged' method in the model,
// so my ViewModel can subscribe and check the 'HasErrors' property.
_logingModel.FormIsValid += (o, e) => _loggingCommand.RaiseCanExecuteChanged();
}
private bool canExecuteLogginForm()
{
return !_logingModel.HasErrors;
}
how do you handle this situation in your app?
for more info i created this github repos.
As the LogginModel is actually in an invalid state originally you should call the ValidateForm() method in its constructor to actually set it into this state and populate the _errors dictionary in order for the HasErrors property to return true as it should:
public class LoggingModel : PocoBase
{
public LoggingModel()
{
ValidateForm();
}
[Display(Name = "Name")]
[MaxLength(32), MinLength(4)]
public string UserName
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
[Required]
public string Password
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
}
ViewModel logic is correct.
Problem is in your validation logic inside the model which is returning HasErrors = False when HasErrors = true.
Take a look at how you are setting/returning/evaluating HasErrors.
Are you validating the Model on property get?
public bool HasErrors
{
get
{
bool hasErrors = false; // Default true here?
// Validation logic ...
return hasErrors;
}
}
Are you storing the HasError value in a property and setting it somewhere else?
public LoggingModel()
{
HasErrors = true; // Default true here?
}
public bool HasErrors { get; set; } // Gets set via validation logic
Just some ideas, like I said if you can show the structure on how you handle INotifyDataErrorInfo validation I can give a better answer.

C# SQLite INotifyPropertyChanged implementation

I'm using SQLite in my application to store some Message objects. I display a list of Message items in "View 1". When I change a property in the Edit View "View 2", I want the property to also change in the list.
Classes
Message.cs
class Message : INotifyPropertyChanged
{
private uint _id;
public uint Id
{
get
{
return _id;
}
set
{
// Trigger INotifyPropertyChanged
Set("Id", ref _id, value);
}
}
private string _content;
public string Content
{
get
{
return _content;
}
set
{
// Trigger INotifyPropertyChanged
Set("Content", ref _content, value);
}
}
...
}
MessageViewModel.cs
class MessageViewModel : INotifyPropertyChanged
{
private Message message;
...
private string _content;
public string Content
{
get
{
return message.Content;
}
set
{
// Set value
message.Content = value;
// Trigger INotifyPropertyChanged
RaisePropertyChanged();
}
}
...
}
View 1
View1.xaml
The datacontext is View1ViewModel
View1ViewModel.cs
private List<MessageViewModel> _messages;
public List<MessageViewModel> Messages
{
get
{
return _messages;
}
set
{
// Trigger INotifyPropertyChanged
Set("Messages", ref _messages, value);
}
}
...
private async void loadMessages()
{
// Get the messages from SQLite database
var messages = await newMessages();
Messages = new MessageViewModelCollection(messages);
}
View 2
View2ViewModel.cs
private MessageViewModel _message;
public MessageViewModel Message
{
get
{
return _message;
}
set
{
// Trigger INotifyPropertyChanged
Set("Message", ref _message, value);
}
}
...
private async void loadMessage()
{
// Get the message from SQLite database by Id
var message = await newMessage(messageId);
Message = new MessageViewModel(message);
}
The functions newMessages and newMessage(uint messageId) return new Message objects from the database.
I normally use the INotifyPropertyChanged implementation, but this doesn't work. I query the database 2 times, once for the list (View 1), and once for the edit page (View 2). The SQLite returns two different copies of the Message object, therefore the INotifyPropertyChanged meganism will not work (Only in the current page, not the pages in the backstack).
I could fix the problem by re-using the Message item from the list, but I cannot always do this in all views.
Is there a way to make the INotifyPropertyChanged work in this scenario? Or do I need a different approach to update the values?
You need a different approach. IMHO, the best thing would be to separate the data model from the view model. So have a MessageVm that binds to the WPF view, and put the logic into it to update itself appropriately (along with firing any necessary INotifyPropertyChanged's) from the Message objects that get passed into it.
There are some automatic mapping tools like Automapper which might, to some degree, alleviate the pain that comes along with this approach.
But you really should separate the view model from the data model, the decoupling of these layers is one of the principle tenets of WPF programming.

ReactiveCommand return values and View feedback loops

I'm familiar with the concepts of MVVM and have used MvvmCross, but am trying out ReactiveUI and trying to wrap my head around some concepts.
I'm writing a tool in WPF (maybe branching to other frameworks) for designers to create and edit data files that are then used by another end-user program. I have a ViewModel that represents a DataModel document and want to perform validation on the data to inform the designers of any potentially breaking behavior. The underlying classes look like this:
public class DataModel
{
// member data here
public void Validate(Validator validator)
{
// perform specific complex validation here and add errors to validator
}
}
// aggregator for validation errors
public class Validator
{
public IList<Error> Errors { get; }
}
The ViewModel should have a ReactiveCommand Validate that the View can bind to a button, but once it's done, I want to display a dialog to the user showing the validation errors or that none were found. Is there a direct way to pass Validator.Errors back to the View, or would I have to create an IObservable or ReactiveList property for the View to subscribe?
Begin Edit
Thanks to help in the comments, I can figure out how to save on user confirmation using UserErrors. Still trying to figure out return values for validation. Here's what I have so far in my ViewModel:
public class ViewModel
{
public DataModel Model { get; }
public ReactiveCommand<List<Error>> { get; protected set; }
public ViewModel(DataModel model)
{
Model = model;
Validate = ReactiveCommand.CreateAsyncObservable<List<Error>>(_ =>
{
Validator validator = new Validator();
Model.Validate(validator);
// not sure how to turn validator.Errors into the IObservable<List<Error>> that ReactiveUI wants me to return.
});
}
}
Would it be better to make Validate a ReactiveCommand<Error> and just call validator.Errors.ToObservable()? Can I still iterate through the errors in my View?
End Edit
Similarly, I would like to have a save function that first performs validation. If no validation errors are found, it saves the DataModel to file. If errors are found, the View should inform the user and get confirmation before saving. What's the ReactiveUI way of handling this feedback loop of:
Execute Save Command -> validation (possibly call Validate command?) -> if errors then request confirmation from View -> save on confirmation or do nothing
As mentioned in comments there are some examples here:
https://github.com/reactiveui/ReactiveUI/blob/master/docs/basics/errors.md
and:
https://github.com/reactiveui/ReactiveUI.Samples/tree/master/ReactiveUI.Samples.Routing
In regards to the different dialogs for different error types, one approach is you could base it off the RecoveryCommand. Eg show the different options based on what you provide, when you fire the UserError you can provide the RecoveryCommands and then do the custom logic based on it.
Then in the view model of where you handle the errors you can do something like:
// The show command will return the decision from the user how to proceed with a error.
// The UserError will have a number of recovery options associated with it, which the dialog
// will present to the user. In testing mode this will likely be the test triggering the recovery command.
// We will wait until one of those recovery commands is executed, then get the result from it.
ShowCommand = ReactiveCommand.CreateAsyncObservable(x =>
{
var userError = x as UserError;
// We must always have a valid user error.
if (userError == null)
{
return Observable.Throw<RecoveryOptionResult>(new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Expected a UserError but got {0}", x)));
}
Error = userError;
Message = Error.ErrorMessage;
// This fancy statement says go through all the recovery options we are presenting to the user
// subscribe to their is executing event, if the event fires, get the return result and pass that back
// as our return value for this command.
return (Error.RecoveryOptions.Select(cmd => cmd.IsExecuting.SelectMany(_ => !cmd.RecoveryResult.HasValue ? Observable.Empty<RecoveryOptionResult>() : Observable.Return(cmd.RecoveryResult.Value)))).Merge().Take(1);
});
I assume as you said that only one command will be executed. I basically will combine the different recovery commands IsExecuting into one, and when the first one is clicked I assume that is the recovery option I want.
Then where you throw the recovery command you can handle it how you need:
var retryCommand = new RecoveryCommand("Retry") { IsDefault = true };
retryCommand.Subscribe(_ => retryCommand.RecoveryResult = RecoveryOptionResult.RetryOperation);
var userError = new UserError(errorMessage, errorResolution, new[] { retryCommand, RecoveryCommand.Cancel });
switch (await UserError.Throw(userError))
{
case RecoveryOptionResult.RetryOperation:
await Setup();
break;
case RecoveryOptionResult.FailOperation:
case RecoveryOptionResult.CancelOperation:
if (HostScreen.Router.NavigateBack.CanExecute(null))
{
HostScreen.Router.NavigateBack.Execute(null);
};
break;
default:
throw new ArgumentOutOfRangeException();
}
Another approach may be to derive off UserError class, and show the different dialogs based on which class type comes up. Eg keep a dictionary of controls that you want to display based on the class type. When you register your handler for displaying the error dialog just show the appropriate dialog.
Below example shows the usage of Commands to do validation. I used an inbuilt do-nothing command called NavigationCommands.Search. Button:Click calls DataModel:Validate which if finds errors, populates the passed in Validator.
MainWindow.xaml
<Window ...>
<Window.CommandBindings>
<CommandBinding Command="NavigationCommands.Search" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Grid>
...
<Button Content="Button" Command="NavigationCommands.Search" HorizontalAlignment="Left" Margin="229,28,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
Mainwindow.xaml.cs
namespace WpfCommands
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DataModel dm = new DataModel();
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
Validator myValidator = new Validator();
dm.Validate(myValidator);
if (myValidator.Errors.Count > 0)
{
MessageBox.Show("Errors !");
// do something with errors
}
}
}
}
DataModel.cs
namespace WpfCommands
{
public class DataModel
{
public void Validate(Validator v)
{
// do some validation
v.Errors.Add(new Error() { Message = "Error1" });
}
}
// aggregator for validation errors
public class Validator
{
IList<Error> _errors = new List<Error>();
public IList<Error> Errors { get { return _errors; } }
}
public class Error
{
public string Message { get; set; }
}
}

Windows 10 (Universal Windows App) data validation

I was trying to figure out how to do the data validation under UWP, but according to what I have found out, there is basically nothing I can implemented yet.
Due to that I tried to implement my custom validation logic. Problem I have now is, that I am showing error information on one TextBlock rather than directly under the specific TextBox which contains data error.
This is what I do at the moment:
public class Customer : ViewModel
{
private string _Name = default(string);
public string Name { get { return _Name; } set { SetProperty(ref _Name, value); OnPropertyChanged("IsValid"); } }
private string _Surname = default(string);
public string Surname { get { return _Surname; } set { SetProperty(ref _Surname, value); OnPropertyChanged("IsValid"); } }
private DateTime _DateOfBirth = default(DateTime);
public DateTime DateOfBirth { get { return _DateOfBirth; } set { SetProperty(ref _DateOfBirth, value); OnPropertyChanged("IsValid"); } }
public int ID { get; set; }
public bool IsValid
{
get
{
//restart error info
_ErrorInfo = default(string);
if (string.IsNullOrWhiteSpace(Name))
_ErrorInfo += "Name cannot be empty!" + Environment.NewLine;
if (string.IsNullOrWhiteSpace(Surname))
_ErrorInfo += "Surname cannot be empty!" + Environment.NewLine;
//raise property changed
OnPropertyChanged("ErrorInfo");
return !string.IsNullOrWhiteSpace(Name) &&
!string.IsNullOrWhiteSpace(Surname);
}
}
private string _ErrorInfo = default(string);
public string ErrorInfo { get { return _ErrorInfo; } set { SetProperty(ref _ErrorInfo, value); } }
}
Question:
How to adjust my code, so that rather than having one label with all error information, I can assign label under each textbox and display validation error there? Should I use Dictionary for this? If yes, how can I bind it to my View?
I have quickly become a fan of using Prism, see this wonderful demonstration User input validation with Prism and data annotations on the UWP.
Its better than anything I could type here.
You can make a flyout inside a textbox.
As soon as the textbox loses focus with wrong input, the flyout shows up .
You can set the placament of the flyout on top/bottom/side of the textbox.
Best of luck !
The problem with Prism is that it uses a string indexer. But Bind in uwp just will not allow string indexes... Integers only! There are also some key features lacking such as coordination between entity view models and between them and the context.
I've done some R&D and it seems that the following are key elements of a good validator in uwp
- use of strings as the binding target, to avoid dropping conversion exceptions
- tracking conversion errors separately from validation errors
- base class for the validating view model AND automatically generated derived classes specifying the property names
- events to tie multiple view models together so that multiple parts of the ui are kept consistent
- centralized error count and save / revert ability associated with the context
Anything out there that can do that? If so then I haven't found it yet.
sjb

Categories