Dotnet Maui DataTrigger not fired on Custom Control Binding - c#

I have created a custom control that is a ValidatableEntry. It has an IsValid public property (bool).
I would like to use this property to Enable/Disable a Button. For this, I think I should be able to use a DataTrigger. However it is not working. The Trigger does not fire when the IsValid property changes.
Here is a simplified version that ilustrates the problem. When the entered text is over 5 characters long, the IsValid property changes to true. However, the trigger is not fired and the button remains disabled.
An example repo can be found here: https://github.com/jokogarcia/ExampleForSO
Custom control:
public class ValidatableEntry : ContentView
{
public Entry Entry { get; set; } = new();
public int MinimumLength { get; set; }
public bool IsValid { get; set; }
public ValidatableEntry()
{
this.Entry.TextChanged += OnTextChanged;
Content = new VerticalStackLayout
{
Children = {
Entry
}
};
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
Entry entry = sender as Entry;
IsValid = entry?.Text?.Length> MinimumLength;
}
}
XAML:
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<local:ValidatableEntry
x:Name="MyEntry"
MinimumLength="5"/>
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
IsEnabled="False"
HorizontalOptions="Center" >
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference MyEntry},
Path=IsValid}"
Value="True">
<Setter Property="IsEnabled" Value="True"></Setter>
</DataTrigger>
</Button.Triggers>
</Button>
</VerticalStackLayout>

I found my own answer. I'll share it here for others that come after.
What I was missing was to implement INotifyPropertyChanged in my Custom Control. Like this:
public class ValidatableEntry : ContentView, INotifyPropertyChanged
{
[...]
public bool IsValid
{
get { return isValid; }
set
{
isValid = value;
NotifyPropertyChanged();
}
}
[...]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
EDIT: Actually, this approach later gave me problems when using DataBindings on my control. It is actually not necessary to implement INotifyPropertyChanged because ContentView already implements it. All I needed to do is call OnPropertyChanged() after updating the value.
So the better and simpler answer would be:
public class ValidatableEntry : ContentView
{
[...]
public bool IsValid
{
get { return isValid; }
set
{
isValid = value;
OnPropertyChanged();
}
}

Related

How to notify View from ViewModel without breaking MVVM?

I recently started trying out MVVM pattern in school and was wondering what the best way (if any) is to notify View from the ViewModel, letting the view know to run a method without breaking MVVM? Basically letting the view know if something was successful, like a login attempt or trying to connect to a database?
An example could be a login page, where the mainwindow should change content to a new page only if the login was successful, if not, a messagebox should show up
Edit:
I'm using .NET
What I have tried so far:
View:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:View.Pages" xmlns:ViewModels="clr-namespace:ViewModel.ViewModels;assembly=ViewModel" x:Class="View.Pages.Start_Page"
mc:Ignorable="d"
d:DesignHeight="720" d:DesignWidth="1280"
Title="Start_Page">
<Page.DataContext>
<ViewModels:Start_Page_ViewModel/>
</Page.DataContext>
Code behind it:
public Start_Page()
{
InitializeComponent();
Start_Page_ViewModel currentDataContext = DataContext as Start_Page_ViewModel;
currentDataContext.CurrentUserIDGotten += GoToMenu;
}
private void GoToMenu(int result)
{
if (result == -1)
{
MessageBox.Show("User credentials incorrect");
}
else if (result == -2)
{
MessageBox.Show("Connection failed");
}
else
{
Application.Current.MainWindow.Content = new Menu_Page();
}
}
ViewModel:
public class Start_Page_ViewModel
{
private string userName;
private string userPassword;
public string UserName { get => userName; set => userName = value; }
public string UserPassword { get => userPassword; set => userPassword = value; }
private RelayCommand logIn;
public RelayCommand LogIn => logIn;
public delegate void CurrentUserIDGottenEventHandler(int result);
public event CurrentUserIDGottenEventHandler CurrentUserIDGotten;
public Start_Page_ViewModel()
{
logIn = new RelayCommand(LogInToProgram, CanLogIn);
}
public void LogInToProgram(object o)
{
PasswordBox passwordBox = o as PasswordBox;
ViewModelController.Instance.CurrentUserID = Database_Controller.Instance.SignIn(userName, passwordBox.Password);
OnUserIDGotten(ViewModelController.Instance.CurrentUserID);
}
public bool CanLogIn(object o)
{
if (userName != null)
{
return true;
}
return false;
}
protected virtual void OnUserIDGotten(int result)
{
if (CurrentUserIDGotten != null)
{
CurrentUserIDGotten(result);
}
}
}
In pure way, without specified framework.
Create a event delegate (or listener interface), associate with view model
Register the event handler on view
Fire the event when view model is changed
Likes this
using System;
public class MainClass {
public static void Main (string[] args) {
ViewModel m = new ViewModel();
View v = new View();
v.Model = m;
m.MakeSomeChange();
}
}
public class View {
private IViewModel _model;
public IViewModel Model {
get {
return _model;
}
set {
if(_model != null) {
_model.OnChanged -= OnChanged;
}
if(value != null) {
value.OnChanged += OnChanged;
}
_model = value;
}
}
private void OnChanged(){
//update view
Console.WriteLine ("View Updated");
}
}
public delegate void ViewChangeDelegate();
public interface IViewModel {
event ViewChangeDelegate OnChanged;
}
public class ViewModel: IViewModel {
public event ViewChangeDelegate OnChanged;
public void MakeSomeChange() {
//make some change in the view Model
OnChanged.Invoke();
}
}
Generally, the ViewModel communicates with the View via databindings. The ViewModel might expose a property, like LoginSuccessful, that the the View would bind to. Then, when the property updates, the View would receive a PropertyChanged notification and change some aspect of its appearance. How it would do this varies; for example, a text property in XAML could be bound directly to an underlying ViewModel property:
<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}"/>
The ViewModel might look like:
public class UserViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public string Username {
get { return _username; }
set {
_username = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Username"));
}
}
private string _username;
public UserViewModel() { }
}
Whenever the Username property is changed on the UserViewModel class, the text box will update to display the new value.
However, this approach doesn't work for all situations. When working with boolean values, it's often useful to implement data triggers:
<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource UserViewModel}, Path=IsTaken}" Value="True">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBox>
This code extends the previous example to color the background of the text box red if the IsTaken property is set to true on the ViewModel. A nice thing about data triggers is that they reset themselves; for example, if the value is set to false the background will revert to its original color.
If you want to go the other way, and notify the ViewModel of user input or a similarly important event, you can use commands. Commands can be bound to properties in XAML, and are implemented by the ViewModel. They are called when the user performs a certain action, such a clicking a button. Commands can be created by implementing the ICommand interface.

WPF TreeView CheckBox Binding - How to populate ViewModel with checked boxes

I'm slightly confused about how to set up a CheckBox with a binding that ensures that my ViewModel is populated with all the checked fields. I have provided some of the code and a description at the bottom.
My Xaml file let's call it TreeView.xaml:
<TreeView x:Name="availableColumnsTreeView"
ItemsSource="{Binding Path=TreeFieldData, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate x:Uid="HierarchicalDataTemplate_1" ItemsSource="{Binding Path=Children, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected, Mode=TwoWay}">
<TextBlock x:Uid="TextBlock_1" Text="{Binding DisplayName.Text, Mode=OneWay}" />
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The "code behind" TreeView.xaml.cs
public partial class MultipleColumnsSelectorView : UserControl
{
public MultipleColumnsSelectorView()
{
InitializeComponent();
}
private MultipleColumnsSelectorVM Model
{
get { return DataContext as MultipleColumnsSelectorVM; }
}
}
The ViewModel (tried to include only the relevant stuff) MultipleColumnsSelectorVM:
public partial class MultipleColumnsSelectorVM : ViewModel, IMultipleColumnsSelectorVM
{
public ReadOnlyCollection<TreeFieldData> TreeFieldData
{
get { return GetValue(Properties.TreeFieldData); }
set { SetValue(Properties.TreeFieldData, value); }
}
public List<TreeFieldData> SelectedFields
{
get { return GetValue(Properties.SelectedFields); }
set { SetValue(Properties.SelectedFields, value); }
}
private void AddFields()
{
//Logic which loops over SelectedFields and when done calls a delegate which passes
//the result to another class. This works, implementation hidden
}
The model TreeFieldData:
public class TreeFieldData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<TreeFieldData> Children { get; private set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
The Problem:
The behaviour that I want is when the user checks a checkbox, it should set the IsSelected property of TreeField (it does that right now) but then I want to go back to the ViewModel and make sure that this specific TreeField is added to SelectedFields. I don't really understand what the PropertyChangedEvent.Invoke does and who will receive that event? How can I make sure that SelectedFields gets populated so when AddFields() is invoked it has all the TreeField data instances which were checked?
You could iterate through the TreeFieldData objects in the TreeFieldData collection and hook up an event handler to their PropertyChanged event and then add/remove the selected/unselected items from the SelectedFields collection, e.g.:
public MultipleColumnsSelectorVM()
{
Initialize();
//do this after you have populated the TreeFieldData collection
foreach (TreeFieldData data in TreeFieldData)
{
data.PropertyChanged += OnPropertyChanged;
}
}
private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
TreeFieldData data = sender as TreeFieldData;
if (data.IsSelected && !SelectedFields.Contains(data))
SelectedFields.Add(data);
else if (!data.IsSelected && SelectedFields.Contains(data))
SelectedFields.Remove(data);
}
}
The subscriber of the PropertyChanged event is the view, so that if you change IsSelected programmatically the view knows it needs to update.
To insert the selected TreeField into your list you would add this code to your setter.
Also, you could define the following function which makes the notification much easier if you have many properties:
private void NotifyPropertyChange([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The CallerMemberName attribute instructs the compiler to automatically insert the name of the property calling the method. The ? after PropertyChanged is a shorthand to your comparison to not null.
The setter of IsSelected can then be changed to
set
{
_isSelected = value;
if (value) { viewModel.SelectedFields.Add(this); }
else { viewModel.SelectedFields.Remove(this); }
NotifyPropertyChange();
}
Of course you would need to provide the TreeFieldData with the ViewModel instance, e.g. in the constructor.
I don't know if SelectedFields is bounded/shown in your view. If yes and you want the changes made to the list to be shown, you should change List to ObservableCollection.

Check Box binding WPF

I have 3 checkboxes. Lets call them cb1,cb2 and cb3. The cb3 should be checkëd when cb1 and cb2 are checked. How do I Implement this in WPF.? I am new to WPF.
Thanks in advance.
If you are using a ViewModel, just add a new computed property to that.
A simple view model would look like:
public class MyVieWModel : INotifyPropertyChanged
{
private bool _cb1Checked;
private bool _cb2Checked;
public bool CB1Checked
{
get { return _cb1Checked; }
set
{
_cb1Checked = value;
PropertyChanged(this, new PropertyChangedEventArgs("CB1Checked"));
PropertyChanged(this, new PropertyChangedEventArgs("CB3Checked"));
}
}
public bool CB2Checked
{
get { return _cb2Checked; }
set
{
_cb2Checked = value;
PropertyChanged(this, new PropertyChangedEventArgs("CB2Checked"));
PropertyChanged(this, new PropertyChangedEventArgs("CB3Checked"));
}
}
public bool CB3Checked
{
get { return _cb1Checked && _cb2Checked; }
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
After setting CB1Checked or CB2Checked, you need to raise the event that CB3Checked has also changed.
Your XAML would look something like (and this is from memory...):
<CheckBox IsChecked={Binding CB1Checked}" />
<CheckBox IsChecked={Binding CB2Checked}" />
<CheckBox IsChecked={Binding CB3Checked, Mode=OneWay}" />
As #wkl points out in the comments, the third checkbox should be a one-way binding since the value can't be set.
Some MVVM frameworks might make this a little easier for you, I've not used any to be able to recommend though.
You can do it like this for a single checkbox:
<CheckBox x:Name="cb3" IsChecked="{Binding Path=IsChecked, ElementName=cb2}" />
I assume the other checkbox is called cb2.
For multiple checkboxes I recommend a Binding in you DataContext.
<CheckBox IsChecked="{Binding Path=CB_1_Checked}" Content="CheckBox 1" />
public bool CB_1_Checked
{
get { return _cb_1_checked; }
set
{
_cb_1_checked = value;
OnPropertyChanged();
//Notify that CB_3_Checked may have changed:
OnPropertyChanged("CB_3_Checked");
}
}
Do this for CB1 and CB2.
Add this for CB3
//Will return 'true' when both are checked (but lacks OnPropertyChanged !)
public bool CB_3_Checked => (CB_1_Checked && CB_2_Checked);
Or with a little more options:
private bool _cb_3_checked;
public bool CB_3_Checked
{
get
{
if(CB_1_Checked && CB_2_Checked)
{
_cb_3_checked = true;
}
return _cb_3_checked;
}
set
{
_cb_3_checked = value;
OnPropertyChanged();
}
}
Read more about Bindings here.

How do you decouple your ViewModel properties validation from ViewModel?

I am using MVVMLight. This is my Department model/POCO class. I do not want to pollute it by any means.
public partial class Department
{
public int DepartmentId { get; set; }
public string DepartmentCode { get; set; }
public string DepartmentFullName { get; set; }
}
Here is the CreateDepartmentViewModel :
public class CreateDepartmentViewModel : ViewModelBase
{
private IDepartmentService departmentService;
public RelayCommand CreateDepartmentCommand { get; private set; }
public CreateDepartmentViewModel(IDepartmentService DepartmentService)
{
departmentService = DepartmentService;
this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute);
}
private Department _department = new Department();
public Department Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
private Boolean CanExecute()
{
return true;
}
private void CreateDepartment()
{
bool success = departmentService.SaveDepartment(_department);
}
}
The DepartmentCode and DepartmentFullName is bind to UI as shown below.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Department Code" Grid.Row="0"/>
<TextBox Grid.Row="0" Text="{Binding Department.DepartmentCode, Mode=TwoWay}" Margin="150,0,0,0"/>
<TextBlock Text="Department Name" Grid.Row="1"/>
<TextBox Grid.Row="1" Text="{Binding Department.DepartmentFullName, Mode=TwoWay}" ToolTip="Hi" Margin="150,0,0,0"/>
<Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}"/>
</Grid>
Before saving the Department, I need to validate that both DepartmentCode and DepartmentFullName has some text in it.
Where should my validation logic reside ? In ViewModel itself ? If so, how do i decouple my validation logic so that it is also unit testable ?
I've found the easiest way to accomplish this is to use a
System.Windows.Controls.ValidationRule
It only takes 3 straight-forward steps.
First you create a ValidationRule. This is a completely separate class that exists outside both your Model and ViewModel and defines how the Text data should be validated. In this case a simple String.IsNullOrWhiteSpace check.
public class DepartmentValidationRule : System.Windows.Controls.ValidationRule
{
public override System.Windows.Controls.ValidationResult Validate(object value, CultureInfo ultureInfo)
{
if (String.IsNullOrWhiteSpace(value as string))
{
return new System.Windows.Controls.ValidationResult(false, "The value is not a valid");
}
else
{
return new System.Windows.Controls.ValidationResult(true, null);
}
}
}
Next, specify that your TextBoxes should use an instance of your new class to perform validation on the Text entered by specifing the ValidationRules property of the Text binding. You get the added bonus of the TextBox border turning red if the Validation fails.
<TextBlock Text="Department Code" Grid.Row="0"/>
<TextBox Name="DepartmentCodeTextBox" Grid.Row="0" Margin="150,0,0,0">
<TextBox.Text>
<Binding Path="Department.DepartmentCode" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DepartmentValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="Department Name" Grid.Row="1"/>
<TextBox Name="DepartmentNameTextBox" Grid.Row="1" ToolTip="Hi" Margin="150,0,0,0">
<TextBox.Text>
<Binding Path="Department.DepartmentFullName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DepartmentValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Finally, create a Style to disable the Save button if either TextBox fails validation. We do this by binding to the Validation.HasError property of the Textbox we bound our Validation rule to. We'll name this style DisableOnValidationError just to make things obvious.
<Grid.Resources>
<Style x:Key="DisableOnValidationError" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=DepartmentCodeTextBox}" Value="True" >
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=DepartmentNameTextBox}" Value="True" >
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
And finally we set the DisableOnValidationError style on the Save button
<Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}"
Style="{StaticResource DisableOnValidationError}"/>
Now, if either of your TextBoxes fails Validation the TextBox gets highlighted and the Save button will be disabled.
The DepartmentValidationRule is completely separate from your business logic and is reusable and testable.
What about using ValidationRules class , this will decouple your model from poppluting it with validation code.
This will work great for individual controls but you can also delegate this logic to some custom validation classes , MvvmValidator framework will help you. This framework lets you write complex validation logic in the form of rules and these rules can be configured at ViewModel level and can be fired on submit button. its a nice decouple way of applying validations without populating your domian objects.
Create a DepartmentValidator class, which will be easily unit tested. Also, this class will allow you to eliminate duplication of validation in the server-side and UI scenarios.
public class DepartmentValidator
{
private class PropertyNames
{
public const string DepartmentFullName = "DepartmentFullName";
public const string DepartmentCode = "DepartmentCode";
}
public IList<ValidationError> Validate(Department department)
{
var errors = new List<ValidationError>();
if(string.IsNullOrWhiteSpace(department.DepartmentCode))
{
errors.Add(new ValidationError { ErrorDescription = "Department code must be specified.", Property = PropertyNames.DepartmentCode});
}
if(string.IsNullOrWhiteSpace(department.DepartmentFullName))
{
errors.Add(new ValidationError { ErrorDescription = "Department name must be specified.", Property = PropertyNames.DepartmentFullName});
}
if (errors.Count > 0)
{
return errors;
}
return null;
}
}
Create a DepartmentViewModel that wraps your Department model and implements IDataErrorInfo, so that you have more granular control and can display validation errors using standard Validation Templates.
public class DepartmentViewModel : IDataErrorInfo, INotifyPropertyChanged
{
private Department _model;
public DepartmentViewModel(Department model)
{
_model = model;
Validator = new DepartmentValidator();
}
public DepartmentValidator Validator { get; set; }
public string DepartmentFullName
{
get
{
return _model.DepartmentFullName;
}
set
{
if(_model.DepartmentFullName != value)
{
_model.DepartmentFullName = value;
this.OnPropertyChanged("DepartmentFullName");
}
}
}
public string DepartmentCode
{
get
{
return _model.DepartmentCode;
}
set
{
if(_model.DepartmentCode != value)
{
_model.DepartmentCode = value;
this.OnPropertyChanged("DepartmentCode");
}
}
}
public int DepartmentId
{
get
{
return _model.DepartmentId;
}
}
public string this[string columnName]
{
get
{
var errors = Validator.Validate(_model) ?? new List<ValidationError>();
if (errors.Any(p => p.Property == columnName))
{
return string.Join(Environment.NewLine, errors.Where(p => p.Property == columnName).Select(p => p.ErrorDescription));
}
return null;
}
}
public string Error
{
get
{
var errors = Validator.Validate(_model) ?? new List<ValidationError>();
return string.Join(Environment.NewLine, errors);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Expose the DepartmentViewModel, rather than the Department Model, and hook up the PropertyChanged event to the CreateDepartmentCommand so that your Save button will be automatically disabled when the department fails validation and so that you can display validation errors. Expose a ValidationErrors property.
public CreateDepartmentViewModel(IDepartmentService DepartmentService)
{
departmentService = DepartmentService;
_department = new DepartmentViewModel(new Department());
this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute);
_department.PropertyChanged += (s,a) =>
{
ValidationErrors = Department.Errors;
RaisePropertyChanged("ValidationErrors");
this.CreateDepartmentCommand.RaiseCanExecuteChanged();
}
}
public DepartmentViewModel Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
public string ValidationErrors {get; set;}
private Boolean CanExecute()
{
return string.IsNullOrEmpty(ValidationErrors);
}
Before saving the Department, you might want to validate again.
private void CreateDepartment()
{
if(Department.Error!=null)
{
ValidationErrors = Department.Error;
RaisePropertyChanged("validationErrors");
return;
}
bool success = departmentService.SaveDepartment(_department);
}
I also find this annoying as it drives you business logic into the ViewModel forcing you to accept that and leave it there or duplicate it in the Service Layer or Data Model. If you don't mind losing some of the advantages of using annotations, etc. This is an approach I have used and seen most recommended - adding errors to a ValidationDictionary from the service layer.
You can also mix these, with business logic handled as above in your service layer, and UI-only relevant validations annotated in your ViewModel.
*Note That I am answering this from a MVC perspective, but I think it is all still relevant.
Add new method in your view model (Is Valid) and Modify the CanExecte method, you can easily test this by testing the CanExecute method:
public class CreateDepartmentViewModel : ViewModelBase
{
private IDepartmentService departmentService;
public RelayCommand CreateDepartmentCommand { get; private set; }
public CreateDepartmentViewModel(IDepartmentService DepartmentService)
{
departmentService = DepartmentService;
this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute);
}
private Department _department = new Department();
public Department Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
private bool IsValid()
{
return !string.IsNullOrEmpty(this.Department.DepartmentCode) && !string.IsNullOrEmpty(this.Department.DepartmentFullName);
}
private Boolean CanExecute()
{
return this.IsValid();
}
private void CreateDepartment()
{
bool success = departmentService.SaveDepartment(_department);
}
}
You can make your Model class implement IDataErrorInfo interface.
If you don't want to pollute your Model, you can create a new class the inherits from it, and do the validation there
public class ValidDepartment : Department, IDataErrorInfo
{
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string name]
{
get
{
if (name == "DepartmentCode")
{
if (string.IsNullOrEmpty(DepartmentCode)
return "DepartmentCode can not be empty";
}
if (name == "DepartmentFullName")
{
if (string.IsNullOrEmpty(DepartmentFullName)
return "DepartmentFullName can not be empty";
}
return null;
}
}
#endregion
}
In your ViewModel replace Department with ValidDepartment
private ValidDepartment _department = new ValidDepartment ();
public ValidDepartment Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
In your View set ValidatesOnDataErrors=True to your binding controls
<TextBox Grid.Row="1" ToolTip="Hi" Margin="150,0,0,0">
<TextBox.Text>
<Binding Path="Department.DepartmentFullName"
Mode="TwoWay"
ValidatesOnDataErrors="True">
</Binding>
</TextBox.Text>
</TextBox>
Set TextBox Style and Validation.ErrorTemplate to determine how your validation will appear in the UI, for example, via Tooltip :
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
You can learn more about validation in WPF here, and here
Hope this helps
I use fluent validation in all my projects, not only to decouple, but also easily unit test my validation rules. http://fluentvalidation.codeplex.com/.
It also has a nuget package http://www.nuget.org/packages/FluentValidation/

How to update UI in MVVMLight RelayCommand scenario?

Here is a simple screen with one textblock which is "" initially, a button called "Set Text" which sets the text to the textblock and another button called "Clear text" which always clears the text in the textblock. This is how the XAML looks like.
<StackPanel>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}"></TextBlock>
<Button Content="Set Text" Command="{Binding SetTextCommand}"></Button>
<Button Content="Clear Text" Command="{Binding CancelCommand}"
IsEnabled="{Binding CanCancel, Mode=TwoWay}"/>
</StackPanel>
Here is my ViewModel code.
public class Page1VM : ViewModelBase
{
public RelayCommand SetTextCommand { get; private set; }
public RelayCommand CancelCommand { get; private set; }
public Page1VM()
{
SetTextCommand = new RelayCommand(HandleSetText, CanExecute);
CancelCommand = new RelayCommand(HandleClearButtonClick, CanExecuteCancel);
}
private void HandleSetText(string number)
{
DisplayText = number;
}
private string _displayText="";
public string DisplayText
{
get { return _displayText; }
set
{
_displayText = value;
RaisePropertyChanged("DisplayText");
RaisePropertyChanged("CanCancel");
}
}
private bool _canCancel;
public bool CanCancel
{
get
{
if (DisplayText == "")
{
return false;
}
else
{
return true;
}
}
set
{
_canCancel = value;
RaisePropertyChanged("CanCancel");
}
}
private bool CanExecute()
{
return true;
}
private bool CanExecuteCancel()
{
if (DisplayText == "")
{
return false;
}
else
{
return true;
}
}
private void HandleClearButtonClick()
{
DisplayText = "";
}
private void HandleSetText()
{
DisplayText = "Hello";
}
}
The problem : When the page is loaded, the "Clear text" button is disabled which is expected and works fine as intended.
When i click on "Set Text", i set a text to a textblock by setting a text value to property named DisplayText and also call RaisePropertyChanged("CanCancel"); but even after that my "Clear Text" button is not enabled. What can be the reason behind it ? My textblock shows the text value but the "clear text" button is still not enabled.
There's a bit mixing up going on in your example, as far as I can tell: You basically don't use the built-in 'CanExecute' mechanism of 'RelayCommand', but rebuild it yourself while still defining the CanExecute method of the 'RealyCommand'. The idea of 'CanExecute' is to automatically disbale controls whose command can't execute, so you don't need to do it manually. Returning 'true' in an 'CanExecute' method doesn't really make sense, as you don't necessarily need to have a CanExecute delegate in your RelayCommand (... = new RelayCommand(ExecuteCommand); is fine). Your scenario doesn't work because you're not calling 'RaisCanExecuteChanged()' on 'CancelCommand'.
Try the following implementation, I've removed the redundancies and inserted the missing 'RaiseCanExecuteChanged()'. See the comments for explanations:
<StackPanel>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}"></TextBlock>
<Button Content="Set Text" Command="{Binding SetTextCommand}"></Button>
<Button Content="Clear Text" Command="{Binding CancelCommand}" />
</StackPanel>
And use this simplified ViewModel:
public class Page1VM : ViewModelBase
{
public RelayCommand SetTextCommand { get; private set; }
public RelayCommand CancelCommand { get; private set; }
public Page1VM()
{
SetTextCommand = new RelayCommand(ExecuteSetText);
CancelCommand = new RelayCommand(ExecuteCancel, CanExecuteCancel);
}
private string _displayText="";
public string DisplayText
{
get { return _displayText; }
set
{
_displayText = value;
RaisePropertyChanged("DisplayText");
RaisePropertyChanged("CanCancel");
// Raise the CanExecuteChanged event of CancelCommand
// This makes the UI reevaluate the CanExecuteCancel
// Set a breakpoint in CanExecuteCancel method to make
// sure it is hit when changing the text
CancelCommand.RaiseCanExecuteChanged();
}
}
private bool CanExecuteCancel()
{
// You can simplify the statement like this:
return DisplayText != "";
}
private void ExecuteCancel()
{
DisplayText = "";
}
private void ExecuteSetText()
{
DisplayText = "Hello";
}
}

Categories