Why is my Login button always disabled? - wpf

I have a popup view with x:Name=This, on it a button delcared as follows:
<Button Content="Log in" Command="{Binding Path=LoginCommand}" CommandParameter="{Binding ElementName=This}" />
This is to gain access to the non-bindable Password property, which is a SecureString type.
In my ctor I initialise the command like so:
public LoginPopupViewModel()
{
LoginCommand = new DelegateCommand<IHavePassword>(
LogUserIn,
p => !string.IsNullOrWhiteSpace(Username));
}
I fully expect that when I type something in the Username, and change focus, the property change notification will help enable the Login button. It doesn't, so I have added the extra code, and the button still remains disabled.
public string Username
{
get { return _username; }
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
CommandManager.InvalidateRequerySuggested();
}
}
If I change the CanExecute delegate like below, only then is the button enabled:
public LoginPopupViewModel()
{
LoginCommand = new DelegateCommand<IHavePassword>(
LogUserIn,
p => true);
}
Why does this button remain disabled even when its command can execute?

I have tried a sample program and binding seems to work fine. I don't have your complete source code but you need to use RaiseCanExecuteChanged on the delegate command when you want the command to check if it needs to execute. Have you checked if the binding on the username is correct?
this.loginCommand.RaiseCanExecuteChanged(); is the key to the answer
public LoginPopupViewModel()
{
this.loginCommand = new DelegateCommand(() =>
{
MessageBox.Show("Logged In Click");
}, () =>
{
return !string.IsNullOrEmpty(UserName);
});
}
private DelegateCommand loginCommand;
private string userName;
public ICommand LoginCommand
{
get { return loginCommand; }
}
public string UserName
{
get { return this.userName; }
set
{
if (value == this.userName)
{
return;
}
this.userName = value;
OnPropertyChanged("UserName");
this.loginCommand.RaiseCanExecuteChanged();
}
}
public string Password { get; set; }

Related

Enable Disable save button during Validation using IDataErrorInfo & Update Button State

I am new to the WPF MVVM and wanted to ask a follow up question to this article:
Enable Disable save button during Validation using IDataErrorInfo
I am trying to enable/disable the button save/update if any of the many controls on the form validation failed/passed.
I have the IsValid method, that checks the validation logic on the Model and returns True/False, that will be passed on to the DelegateCommand as a predicate.
The question is: my button has the following property IsEnabled{binding IsValid}, this should check all fields to make sure that it matches the criteria in the model, returns true/false to the view model and then enables the button if all true. The problem is: Once the view model is instantiated, the DelegateCommand object is created with the validation (IsValid) at at a false state and it stays that way throughout the life of the object even though the user is filling data in the textboxes. How do I turn on the button once all the conditions are met? in other words, how to constantly keep validating and updating the IsValid in order to switch the button on once every textbox validate to true??
Thanks,
I have the following code:
The Model
public class UserModel : ObservePropertyChanged,IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
// if there is an error throw an exception
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Name")
{
if (string.IsNullOrEmpty(Name))
result = "Please enter a Name";
}
return result;
}
}
// the Josh Way
static readonly string[] ValidatedProperties =
{
"Name"
};
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
{
if (GetValidationError(property) != null) // there is an error
return false;
}
return true;
}
}
// a method that checks validation error
private string GetValidationError(string propertyName)
{
string error = null;
switch (propertyName)
{
case "Name":
error = this.ValidateName();
break;
default:
error = null;
throw new Exception("Unexpected property being validated on Service");
}
return error;
}
private string ValidateName()
{
string ErrorMsg = null;
if (string.IsNullOrWhiteSpace(Name))
{
ErrorMsg = "Name can't be empty!";
};
return ErrorMsg;
}
}
** the View Model **
public class UserViewModel:ObservePropertyChanged
{
UserModel model;
public UserViewModel()
{
presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
model = new UserModel();
}
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private string info;
public string Info
{
get { return info; }
set { info = value; OnPropertyChanged("Info"); }
}
private DelegateCommand presentCommand;
public DelegateCommand PresentCommand
{
get
{
if (presentCommand==null)
{
presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
}
return presentCommand;
}
}
private void PresentDataMethod()
{
Info = $"Your Name is: {Name}.";
}
// The ViewModel then contains a CanSave Property that reads the IsValid property on the Model:
protected bool CanSave
{
get
{
return model.IsValid;
}
}
}
** The View**
<TextBox x:Name="Name" HorizontalAlignment="Left" Height="34" Margin="285,145,0,0" TextWrapping="Wrap"
VerticalAlignment="Top" Width="248">
<Binding Path="Name"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay">
</Binding>
</TextBox>
<Button Content="Present" FontSize="20" HorizontalAlignment="Left"
Margin="285,184,0,0" VerticalAlignment="Top" Width="248" Height="35"
Command="{Binding Path=PresentCommand}"
IsEnabled="{Binding IsValid}"
>
</Button>
If all you want to do is refresh the button via the IsValid value, all you have to do is listen for any of the OTHER property changes in your ViewModel, and when that happens, tell it to refresh the IsValid binding as well (which is actually your CanSave property). Here is one way to do that:
** The View Model **
// ...
public UserViewModel()
{
// ...
this.PropertyChanged += OnViewModelPropertyChanged;
}
public void OnViewModelPropertyChanged(object sender, PropertyEventArgs e)
{
// On any property that implements "OnPropertyChanged(propname)", refresh the CanSave binding too!
OnPropertyChanged(nameof(this.CanSave));
}
// ...
BTW, it is generally good coding practice to avoid magic words like OnPropertyChanged("Name") or OnPropertyChanged("Info") because you never know when a developer will have to rename their properties, and if that happens, you won't get a compile error here and it might be hard to debug. It is best practice to use the nameof, such as OnPropertyChanged(nameof(Name)) so that you'll know you'll get a compilation error if you ever decide to change the property to, say, FirstName instead of Name.

Combobox in settingsflyout does not show selected values when opening it again

This seemed so simple but has turned into a nightmare for me. Everything works great, i can select a value and it's reported back to the view model.
Problem:
User opens the settings flyout and selects a value. User exits the flyout.
User reopens the settings flyout and there is no selected value in the combobox. The value exists in the view model though.
Scenario:
Combobox in a Settingsflyout.
<ComboBox x:Name="defaultComboBox" SelectedItem="{Binding UserSettings.DefaultAccount, Mode=TwoWay}" ItemsSource="{Binding UserAccounts}" DisplayMemberPath="CustomName">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{Binding UserAccountComboboxLoadedCommand}" CommandParameter="{Binding ElementName=defaultAccountComboBox}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
ViewModelCode:
public void Open(object parameter, Action successAction)
{
logger.LogProgress("Opened UserSettingsFlyoutView.");
UserSettings.DefaultAccount = UserAccounts.FirstOrDefault(u => u.AccountID.ToString().Equals(userSettings.DefaultAccountGuid,StringComparison.CurrentCultureIgnoreCase));
}
public CrossThreadObservableCollection<UserAccount> UserAccounts
{
get
{
try
{
return dbContext.RetrieveAllUserAccounts();
}
catch(Exception e)
{
logger.LogError("Error happened when retrieving user-accounts from secure data store Error: " + e.Message, e.ToString());
return new CrossThreadObservableCollection<UserAccount>();
}
}
}
private IProvideUserSetting userSettings;
public IProvideUserSetting UserSettings
{
get { return userSettings; }
set { userSettings = value; OnPropertyChanged("UserSettings"); }
}
UserSettings class:
private string defaultAccountGuid;
[DataMember]
public string DefaultAccountGuid
{
get { return defaultAccountGuid; }
set { defaultAccountGuid = value; OnPropertyChanged("DefaultAccountGuid"); }
}
private UserAccount defaultAccount;
[IgnoreDataMember]
public UserAccount DefaultAccount
{
get { return defaultAccount; }
set {
defaultAccount = value;
if (defaultAccount != null)
DefaultAccountGuid = defaultAccount.AccountID.ToString();
OnPropertyChanged("DefaultAccount"); }
}
I tried a version of the code and could not reproduce the issue. Could you provide more code? Is there something else setting the selected item?
Anyway, the type of item in the ItemsSource is different than the type of item for selected item. I would try changing the selected item binding to the same class in the items source.
For example, instead of the viewmodel property UserSettings, make that object type UserAccount.
Something like
private UserAccount _selectedUserAccount { get; set; }
public UserAccount SelectedUserAccount
{
get { return _selectedUserAccount; }
set
{
if (_selectedUserAccount != value)
{
_selectedUserAccount = value;
OnPropertyChanged("SelectedUserAccount");
}
}
}
Edit:
You can add a loaded event handler to your combobox, then locate the viewmodel from the code behind and set the selected item property.
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.SelectedItem =
_viewModel.UserAccounts.Where(x => x.UserAccountString == _viewModel.SelectedUserAccount.UserAccountString);
}

Working with DelegateCommand's CanExecute action

I've a ViewModel class like this in a Prism / WPF project.
public class ContentViewModel : ViewModelBase, IContentViewModel
{
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
SaveCommand = new DelegateCommand(Save, CanSave);
}
public Person Person { get; set; }
public DelegateCommand SaveCommand { get; set; }
private void Save()
{
// Save actions here...
}
private bool CanSave()
{
return Person.Error == null;
}
}
The person type used in the above ViewModel is defined as follows:
public class Person : INotifyPropertyChanged, IDataErrorInfo
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
// other properties are implemented in the same way as above...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _error;
public string Error
{
get
{
return _error;
}
}
public string this[string columnName]
{
get
{
_error = null;
switch (columnName)
{
// logic here to validate columns...
}
return _error;
}
}
}
An instance of ContentViewModel is set as DataContext of a View. Inside the View I've used binding to Person as follows:
<TextBox Text="{Binding Person.FirstName, ValidatesOnDataErrors=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
When I make changes to TextBox which is binded to Person's properties like FirstName and click Save I could see the changes in ViewModel command handler. But if any of these properties fail in validation CanSave is never executed and button never gets disabled.
How do I disable a button based on DelegateCommand's CanExecute action handler in the above scenario?
In the constructor of ContentViewModel add this line
public ContentViewModel(IPersonService personService)
{
//GetPerson
Person.PropertyChanged +=person_PropertyChanged;
}
And write an method to handle that event in which you call either CommandManager.InvalidateRequerySuggested() or SaveCommand.RaiseCanExecuteChanged()
private void person_PropertyChanged(object sender, EventArgs args)
{
CommandManager.InvalidateRequerySuggested();
//SaveCommand.RaiseCanExecuteChanged()
}
Hope this works. :-)
try this with all the properties that can change error:
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("Error");
}
}
Alternatively
switch (columnName)
{
// logic here to validate columns...
OnPropertyChanged("Error");
}
The problem you are having is that the OnPropertyChanged is not being called when the error changes.
The next step is to subscribe to the person's propertychanged event when its created, and create a handler that checks for the propertychanged and then changes the boolean variable that the command uses.
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
Person.PropertyChanged+= PersonPropertyChangedHandler;
SaveCommand = new DelegateCommand(Save, personHasError);
}
bool personHasError = false;
void PersonPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Error")
{
if(Person.Error == null)
personHasError = true;
else
personHasError = false;
}
}
Hope this works. I built this by hand and didn't check it so let me know if its buggy or whatever and ill correct it
In the nutshell - you should call yourDelegateCommand.RaiseCanExecuteChanged() when you think that your CanExecute() return value can be changed.
In your example, you should notify through INotifyPropertyChanged interface that your Person.Error property is changed, subscribes to Person.PropertyChanged event in your ContentViewModel class and call SaveCommand.RaiseCanExecuteChanged() each time when your Person.Error is changed. Please be careful - in your scenario Person.Error isn't recalculated automatically when, for example, Person.FirstName is changed - you should do this manually.
UPDATED:
public class ContentViewModel : ViewModelBase, IContentViewModel
{
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
Person.PropertyChanged += Person_PropertyChanged;
SaveCommand = new DelegateCommand(Save, CanSave);
}
private void PersonPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
private void Save()
{
// Save actions here...
}
private bool CanSave()
{
return IsErrorPresented(Person);
}
private bool IsErrorPresented(object o)
{
if (!(o is IDataErrorInfo))
return false;
var propNames = o.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => p.Name);
var o2 = (o as IDataErrorInfo);
var errors = propNames.Select(p => o2[p])
.Where(p => !String.IsNullOrEmpty(p))
.ToList();
ValidationSummary.ErrorMessages = errors;
return errors.Count > 0;
}
}
<TextBox Text="{Binding Person.FirstName,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
If you will also specify PropertyChanged as UpdateSourceTrigger, your save button will be updated during your typing..

Data validation on ShowDialog window in WPF

I have a window that I display as ShowDialog
in the window I have some textboxes binding to object that implement INotifyPropertyChannges and IDataErrorInfo.
I want that the OK button will enabled just if all thextboxes validted
and I want that just if the user click on OK buton the next move will occur.
I can bind the button to ICommand and check the textboxes valitation in CanExcute() but then what can I do in the Excute? the object dont know about the window.
I can also check the textboxes valitation and then raise event that all valid and enable the OK button but then there will be dupliacte code because I checked already in the IDataErrorInfo implmention.
So what is the right way?
Thanks in advance
You CanExecute should look like this.
public bool CanExecuteOK
{
get
{
if (DataModelToValidate.Error == null && DataModelToValidate.Errors.Count == 0) return true;
else return false;
}
}
Here Error and Errors properties are nothing but Wrapper over this[string propertyName] (implemented implicitly for IDataErrorInfo).
Here is Sample Model Class:
public class SampleModel: IDataErrorInfo, INotifyPropertyChanged
{
public SampleModel()
{
this.Errors = new System.Collections.ObjectModel.ObservableCollection<string>();
}
private string _SomeProperty = string.Empty;
public string SomeProperty
{
get
{
return _SomeProperty;
}
set
{
if (value != _SomeProperty)
{
_SomeProperty= value;
RaisePropertyChanged("SomeProperty");
}
}
}
....
....
//this keeps track of all errors in current data model object
public System.Collections.ObjectModel.ObservableCollection<string> Errors { get; private set; }
//Implicit for IDataErrorInfo
public string Error
{
get
{
return this[string.Empty];
}
}
public string this[string propertyName]
{
get
{
string result = string.Empty;
propertyName = propertyName ?? string.Empty;
if (propertyName == string.Empty || propertyName == "SomeProperty")
{
if (string.IsNullOrEmpty(this.SomeProperty))
{
result = "SomeProperty cannot be blank";
if (!this.Errors.Contains(result)) this.Errors.Add(result);
}
else
{
if (this.Errors.Contains("SomeProperty cannot be blank")) this.Errors.Remove("SomeProperty cannot be blank");
}
}
......
return result;
}

CommandBinding question. How to enable command button

My code is here>>
public class Player:INotifyPropertyChanging
{
string addressBar;
public string Url
{
get {
return addressBar;
}
set { addressBar = value; OnPropertyChanged("Url"); }
}
public Regex regVillage = new Regex(#"\?doc=\d+&sys=[a-zA-Z0-9]{2}");
RelayCommand _AddAttackTask;
public ICommand AddAttackTask
{
get {
if (_AddAttackTask == null)
{
_AddAttackTask = new RelayCommand(param =>
{
}, param => this.CanAttack);
}
return _AddAttackTask;
}
}
public Boolean CanAttack
{
get{
if (Url == null) return false;
return regVillage.IsMatch(Url);
}
}
}
On the xaml, i have textbox and button. Textbox binded by url, button binded by AddAttackTask. When i change textbox value,Url changed.Main target is When changing url, button bring to enable or disable. But button always disabled.
I'm getting RelayCommand class from WPF Apps With The Model-View-ViewModel Design Pattern
What is wrong on my code?
Please fix my command binding!
I found it yourself.
Must call CommandManager.InvalidateRequerySuggested(); function after changing property
public string Url
{
get {
return addressBar;
}
set { addressBar = value; OnPropertyChanged("Url");
CommandManager.InvalidateRequerySuggested();
}
}

Resources