Enable Disable Button base on validation in WPF - c#

I have a WPF Form, that has a text box and a button. I am validating the text box to have only characters. Validation works fine, But I need to disable the button if there are validation error and enable them if there are no validation errors.
Below is my code:
<TextBox Name="tbProductName" Grid.Column="1" HorizontalAlignment="Left" Height="25" Margin="4,9,0,0" Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Width="213"
Text="{Binding Path = ProductCode, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True,NotifyOnValidationError=True}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
<Button Name ="btnDownload" Content="Download" Grid.Column="2" HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="87" Height="24" Click="btnDownload_Click"/>
public class ViewModel: System.ComponentModel.INotifyDataErrorInfo
{
private readonly Dictionary<string, ICollection<string>> _validationErrors = new Dictionary<string, ICollection<string>>();
private readonly Model _productCode = new Model();
public string ProductCode
{
get { return _productCode.ProductCode; }
set
{
_productCode.ProductCode = value;
ValidateModelProperty(value, "ProductCode");
}
}
protected void ValidateModelProperty(object value, string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
PropertyInfo propertyInfo = _productCode.GetType().GetProperty(propertyName);
IList<string> validationErrors =
(from validationAttribute in propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>()
where !validationAttribute.IsValid(value)
select validationAttribute.FormatErrorMessage(string.Empty))
.ToList();
_validationErrors.Add(propertyName, validationErrors);
RaiseErrorsChanged(propertyName);
}
/* Raise the ErrorsChanged for all properties explicitly */
protected void ValidateModel()
{
_validationErrors.Clear();
ICollection<ValidationResult> validationResults = new List<ValidationResult>();
ValidationContext validationContext = new ValidationContext(_productCode, null, null);
if (!Validator.TryValidateObject(_productCode, validationContext, validationResults, true))
{
foreach (ValidationResult validationResult in validationResults)
{
string property = validationResult.MemberNames.ElementAt(0);
if (_validationErrors.ContainsKey(property))
{
_validationErrors[property].Add(validationResult.ErrorMessage);
}
else
{
_validationErrors.Add(property, new List<string> { validationResult.ErrorMessage });
}
}
}
/* Raise the ErrorsChanged for all properties explicitly */
RaiseErrorsChanged("ProductCode");
}
#region INotifyDataErrorInfo members
public event EventHandler<System.ComponentModel.DataErrorsChangedEventArgs> ErrorsChanged;
private void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new System.ComponentModel.DataErrorsChangedEventArgs(propertyName));
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)
|| !_validationErrors.ContainsKey(propertyName))
return null;
return _validationErrors[propertyName];
}
public bool HasErrors
{
get { return _validationErrors.Count > 0; }
}
#endregion
}
public class Model
{
[Required(ErrorMessage = "You must enter a product code to download.")]
[RegularExpression(#"^[a-zA-Z]+$", ErrorMessage = "The Product Code must only contain letters (a-z, A-Z).")]
public string ProductCode { get; set; }
}
How can I set the isEnabled property of the button to Validation.HasErrors?

Ideally your button would bind it's "Command" property to a public ICommand viewmodel property.
The CanExecute method would be evaluated, returning true or false. The button would be enabled/disabled accordingly.
You can read more about ICommand, along with an implementation of the interface here.
Below are the changes required, assuming your using the RelayCommand (See Fig. 3) implementation described in the aforementioned article.
Register the command
private readonly ICommand _downloadCommand = new RelayCommand(OnDownload, CanDownload);
Used for binding:
public ICommand DownloadCommand { get { return _downloadCommand; } }
Methods to invoke when the command is executed:
private void OnDownload(object parameter) { ... Do your download code here ... }
private bool CanDownload(object parameter) { return HasErrors == false; }
Update your XAML binding:
<Button Content="Download" Grid.Column="2" HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="87" Height="24" Command="{Binding DownloadCommand}" />

I would say use the same technique for the Binding you used for the rest of your code!!
In your xaml
<Button Name ="btnDownload" IsEnabled="{Binding Path= BtnIsEnabled} click="btnDownload_Click"/>
In your code
public bool BtnIsEnabled
{
get { return this._BtnIsEnabled; }
set
{
this._BtnIsEnabled = value;
base.OnPropertyChanged("BtnIsEnabled");
}
}
private bool _BtnIsEnabled;
and then where ever you want to disable your Button go something like
BtnIsEnabled = Validation.HasErrors ? false : true ;
Obviously this is a pseudo scribble. Unless you're after something very specific that I can't seem to find out. Seems you used binding in your code already and I would guess you know how to.

Related

Removing selectedItem from ObservableCollection using MVVM

I want to remove an item from an ObservableCollection while adhering to MVVM. I understand the task, I think I understand the logic pretty well and have implemented it, but the item is never removed in the view.
I have traced the application with breakpoints and the value of selectedProject is being read correctly. I also added variables to check the Collection size before and after the remove statement, which were the same value so it therefore does not remove the item. My question is why? What have I missed? What rules have I not adhered to? Pretty new to .NET.
**I am using a WCF Service, to return an ObservableCollection of Projects from my CodeFirst DB and this is called as soon as a user opens the Projects view.
View
<ListBox ItemsSource="{Binding ProjectList, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedProject}" SelectedIndex="{Binding ProjectIndex}" BorderThickness="0" Margin="60,195,218.8,212.4">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProjectName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding DeleteCommand}" Content="Up" HorizontalAlignment="Left" Margin="563,195,0,0" VerticalAlignment="Top" Height="35" Width="75"/>
ViewModel
private ObservableCollection<Project> _projectList;
public ObservableCollection<Project> ProjectList
{
get
{
var q = client.ReturnProjects().ToList();
_projectList = new ObservableCollection<Project>(q.ToList());
return _projectList;
}
set
{
_projectList = value;
OnPropertyChanged("ProjectList");
}
public int SelectedProject
{
get { return _selectedProject; }
set
{
_selectedProject = value;
OnPropertyChanged("SelectedProject");
}
}
The method executed by the command is as follows, the command is being hit and the method called.
public void DeleteProject()
{
if (SelectedProject != null)
{
ProjectList.Remove(SelectedProject);
}
}
You need a two-way-binding for the SelectedItem property.
View
<ListBox ItemsSource="{Binding ProjectList}"
SelectedItem="{Binding SelectedProject, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding DeleteCommand}"
Content="Delete"
HorizontalAlignment="Right"
VerticalAlignment="Bottom" />
ViewModel, Model and ICommand Implementation
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var q = new[] { new Project() { Name = "A" }, new Project() { Name = "B" }, new Project() { Name = "C" } };
ProjectList = new ObservableCollection<Project>(q);
}
private ObservableCollection<Project> _projectList;
public ObservableCollection<Project> ProjectList
{
get
{
return _projectList;
}
set
{
_projectList = value;
OnPropertyChanged("ProjectList");
}
}
Project _selectedProject;
public Project SelectedProject
{
get { return _selectedProject; }
set
{
_selectedProject = value;
OnPropertyChanged("SelectedProject");
}
}
public ICommand DeleteCommand => new SimpleCommand(DeleteProject);
private void DeleteProject()
{
if (SelectedProject != null)
{
ProjectList.Remove(SelectedProject);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Project
{
public string Name { get; set; }
}
public class SimpleCommand : ICommand
{
Action _execute;
public SimpleCommand(Action execute)
{
this._execute = execute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
_execute();
}
}
I think OnPropertyChanged("ProjectList") needs to be called after deleting the item to raise the notification for updating the view .
The ObservableCollection has to interact with the model layer.
Maybe you need this:
https://blogs.msdn.microsoft.com/bethmassi/2009/05/08/using-the-wpf-observablecollection-with-ef-entities/

How to bind with Mode=OneWay and only propagate changes back on save?

Question
How can I make it so that changes to a note are only propagated back to the list, when the Save button is clicked instead on "lost focus"?
And the Save button should only be enabled when the note has been changed.
UI
The example application looks like this:
The current behaviour is:
Clicking on a note puts its text into the TextBox; that's fine.
The changed text from the TextBox gets written back to the list when the TextBox loses the focus (default binding behaviour); but I only want that to happend when the Save button is clicked.
The Save button is always activated because the CanExecute(object parameter) isn't correctly implemented yet; it should only get activated when the TextBox text is different from the selected note's text.
My research so far
Option 1: Some Internet sources say to bind a different property to the TextBox and to programmatically check whether it is different from the SelectedItem of the ListView. I would have hoped that there was a way without introducing a third property in addition to the already existing ListOfNotes and SelectedNote.
Option 2: Some Internet sources recommend to configure Mode=OneWay so that clicking an item in the ListView updates the TextBox, but not the other way around. This sounds like the solution I would prefer, but I wasn't able to figure out from the code examples how to raise an event programmatically so that the change in the TextBox gets written back to the ListView when the Save button is clicked.
I've found other Stackoverflow questions that seem to be similar to mine, but the answers to those haven't helped me fix the problem:
WPF databinding after Save button click
Code
This example currently does two-way binding on focus lost. How do I need to change it to get the above described behaviour?
https://github.com/lernkurve/WpfBindingOneWayWithSaveButton
MainWindow.xaml
<Window x:Class="WpfBindingOneWayWithSaveButton.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfBindingOneWayWithSaveButton="clr-namespace:WpfBindingOneWayWithSaveButton"
mc:Ignorable="d"
Title="MainWindow" Height="188.636" Width="299.242">
<Window.DataContext>
<wpfBindingOneWayWithSaveButton:MainWindowsViewModel />
</Window.DataContext>
<Grid>
<GroupBox Header="List of notes" HorizontalAlignment="Left" VerticalAlignment="Top" Height="112" Width="129" Margin="0,24,0,0">
<ListView ItemsSource="{Binding ListOfNotes}" SelectedItem="{Binding SelectedNote}" DisplayMemberPath="Text" HorizontalAlignment="Left" Height="79" VerticalAlignment="Top" Width="119" Margin="0,10,-2,0"/>
</GroupBox>
<GroupBox Header="Change selected note" HorizontalAlignment="Left" Margin="134,24,0,0" VerticalAlignment="Top" Height="112" Width="151">
<Grid HorizontalAlignment="Left" Height="89" Margin="0,0,-2,0" VerticalAlignment="Top" Width="141">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="101*"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding SelectedNote.Text}" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="121" Margin="10,7,0,0" Grid.ColumnSpan="2"/>
<Button Command="{Binding SaveCommand}" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="121" Margin="10,35,0,0" Grid.ColumnSpan="2"/>
</Grid>
</GroupBox>
</Grid>
</Window>
MainWindowsViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class MainWindowsViewModel
{
public ObservableCollection<Note> ListOfNotes { get; set; }
public Note SelectedNote { get; set; }
public ICommand SaveCommand { get; set; }
public MainWindowsViewModel()
{
ListOfNotes = new ObservableCollection<Note>
{
new Note { Text = "Note 1" },
new Note { Text = "Note 2" }
};
SaveCommand = new SaveCommand(this);
}
}
}
SaveCommand.cs
using System;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
// What should go here?
return true;
// Pseudo code
// return (is the TextBox text different from the original note text)
}
public void Execute(object parameter)
{
// What should go here?
// Pseudo code
// Let WPF know that the TextBox text has changed
// Invoke the binding so it propagates the TextBox text back to the list
}
public event EventHandler CanExecuteChanged;
}
}
Note.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfBindingOneWayWithSaveButton
{
public class Note : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Bind the text to the CommandParameter of the SaveButton so it gets passed to the Save method for updating.
<TextBox x:Name="NoteTextBox" Text="{Binding SelectedNote.Text, Mode=OneTime}" ../>
<Button Command="{Binding SaveCommand}"
CommandParameter="{Binding ElementName=NoteTextBox, Path=Text}",
Content="Save" />
and
public bool CanExecute(object parameter)
{
return vm.SelectedNote.Text != parameter as string;
}
public void Execute(object parameter)
{
vm.SelectedNote.Text = parameter as string;
}
Option one is the easiest to implement, you will need to clone the Note object and set it to a separate property.
in your xaml, change your list view to the following so it now binds the SelectedIndex instead of the SelectedItem.
<ListView ItemsSource="{Binding ListOfNotes}" SelectedIndex="{Binding SelectedIndex}" DisplayMemberPath="Text" ...
And change TextBox to the following so it updates the binding as you type
<TextBox Text="{Binding Path=SelectedNote.Text, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" ...
In Note.cs we add the Clone() method.
public class Note : INotifyPropertyChanged
{
public Note Clone()
{
return new Note()
{
Text = this.Text
};
}
//... The rest stays the same
}
In MainWindowsViewModel.cs we add new properties for the SelectedIndex and clone the object when we detect a index has changed. We also need to add INotifyPropertyChanged so we can update the SelectedNote from the codebehind when we do the Clone()
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
_selectedNote = value;
OnPropertyChanged();
}
}
//... The rest stays the same
}
In SaveCommand.cs we add the logic for CanExecute and add the subscriptions to CommandManager.RequerySuggested, this automatically makes it requery the CanExecute any time any binding changes. This can be a little ineffecent, if you wanted to you could expose a RaiseCanExecuteChanged() publicly but it would be MainWindowsViewModel responsibility to call it any time vm.SelectedIndex or vm.SelectedNote.Text changed.
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
UPDATE: Here is a updated version that does not use CommandManager
MainWindowsViewModel.cs
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
RecheckSaveCommand();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
if (_selectedNote != null)
{
PropertyChangedEventManager.RemoveHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
_selectedNote = value;
if (_selectedNote != null)
{
PropertyChangedEventManager.AddHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
OnPropertyChanged();
}
}
private void SelectedNoteTextChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
RecheckSaveCommand();
}
private void RecheckSaveCommand()
{
var command = this.SaveCommand as WpfBindingOneWayWithSaveButton.SaveCommand; //"this." and "WpfBindingOneWayWithSaveButton." are not necessary but I wanted to be explicit.
if (command != null)
{
command.RaiseCanExecuteChanged();
}
}
//...
}
SaveCommand.cs
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
You should not use OneWay but rather an UpdateSourceTrigger of value Explicit. BindingGroups can do this for you though, here's a simple example:
<!-- For change observation -->
<TextBlock Text="{Binding Text}"></TextBlock>
<StackPanel>
<StackPanel.BindingGroup>
<BindingGroup x:Name="EditGroup"></BindingGroup>
</StackPanel.BindingGroup>
<TextBox Text="{Binding Text}"></TextBox>
<Button>
<Button.Command>
<local:CommitGroupCommand BindingGroup="{x:Reference EditGroup}"/>
</Button.Command>
Save
</Button>
</StackPanel>
public class CommitGroupCommand : ICommand
{
public BindingGroup BindingGroup { get; set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
BindingGroup.UpdateSources();
}
}
(You could add a validation rule to your binding that requires the value to be different and use that for the CanExecute implementation.)
Using this method allows you to bind directly to the object you intend to edit, so you don't need to copy around values first.

error binding isEnabled to button in xaml

I am new to xaml, WPFs, C# and the MVVM paradigm. I have started with an app based on this example project, in the selected excerpts i want to disable the authenticate button from the LoginPageViewModel after the authenticate button has been clicked(There is no point clicking the button if you are authenticated). I have got command binding working, as well as text control binding between the view and ViewModel. my LoginPageViewModel is based on a abstract class that inherits from INotifyPropertyChanged
The setter AuthenticateButtonEnabled is working, but it is not binding to the isEnabled proprerty on the form. My question is, what could I have missed, and How can i trace the binding between a View and a ViewModel?
the LoginPageView.xaml button:
<Button x:Name="authenticateButton" Content="{x:Static res:Strings.LoginPage_authenticateButton_content}"
Grid.Column="2" Margin="53,4,0,10"
Grid.Row="2" FontSize="16"
IsEnabled="{Binding Path=AuthenticateButtonEnabled}"
Command="{Binding Path=AuthenticateCommand}" HorizontalAlignment="Left" Width="87"/>
the viewModel
private String _username;
private String _responseTextBlock;
private String _linkTextBlockURI;
private String _linkTextBlockText;
private bool _authenticateButtonEnabled;
...
private async void Authenticate()
{
ResponseTextBlock = Strings.LoginPage_responseBlock_content_checking;#this works!
AuthenticateButtonEnabled = false;
return;
}
....
public bool AuthenticateButtonEnabled
{
get { return _authenticateButtonEnabled; }
set { _authenticateButtonEnabled = value; OnPropertyChanged("AuthenticateButtonEnabled"); }
}
// this is in the abstract class.
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
If you want to have both: command and AuthenticateButtonEnabled, then simply check for this property in CanExecute delegate and vise-versa in property setter update command.
Here is implementation with DelegateCommand and some improvements which you may find useful:
bool _isAuthenticateButtonEnabled;
public bool IsAuthenticateButtonEnabled
{
get { return _isAuthenticateButtonEnabled; }
set
{
_isAuthenticateButtonEnabled = value;
OnPropertyChanged();
AuthenticateCommand.Update();
}
}
// the base could class could actually implement this
void OnPropertyChanged([CallerMemberName] string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
public DelegateCommand AuthenticateCommand { get; }
// view model constructor
public ViewModel()
{
AuthenticateCommand = new DelegateCommand(o =>
{
... // some actions when command is executed
}, o =>
{
bool somecondition = ...; // some condition to disable button, e.q. when executing command
return somecondition && IsAuthenticateButtonEnabled;
});
}
This will let you to have both: property to enable/disable button, which can be used in binding (to another control, e.g. CheckBox.IsChecked) and command which can have independent condition to disable button when command shouldn't be executed (typically in async command delegate, when it performs a long running command, but for this you may want to check this answer.).
if you bind the command Property of the Button to an ICommand Property in your Viewmodel, then you do NOT need to handle the IsEnabled Property of the Button because its handled by the CanExecute Method of the ICommand implementation.
google for RelayCommand or DelegateCommand
Thanks to the posters for your help, I wanted to share the working solution for others. I used the DelegateCommand, but had to change some parts in the loginPageViewModel to make it work: I also updated the xaml so that the controls were all inactive after a successful authentication.
the loginPage xaml:
<Label x:Name="usernameLabel" Content="{x:Static res:Strings.LoginPage_usernameLabel_content}" HorizontalAlignment="Left" Margin="10,4,0,0" Grid.Row="0" VerticalAlignment="Top" Width="130" FontSize="16" Height="36" Grid.Column="1"/>
<TextBox x:Name="usernameTextBox" Grid.Column="2" Grid.Row="0" TextWrapping="Wrap"
Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding AuthenticateButtonEnabled}"
Margin="10,5,0,6" FontSize="16" HorizontalAlignment="Left" Width="130" TextChanged="usernameTextBox_TextChanged"/>
<Label x:Name="passwordLabel" Content="{x:Static res:Strings.LoginPage_passwordLabel_content}" Margin="10,5,0,0" Grid.Row="1" VerticalAlignment="Top" FontSize="16" Height="36" Grid.RowSpan="2" HorizontalAlignment="Left" Width="130" Grid.Column="1"/>
<PasswordBox x:Name="passwordBox" Grid.Column="2" Margin="10,0,0,9"
PasswordChanged="PasswordBox_PasswordChanged"
IsEnabled="{Binding AuthenticateButtonEnabled}"
Grid.Row="1" FontSize="16" HorizontalAlignment="Left" Width="130"/>
<Button x:Name="authenticateButton" Content="{x:Static res:Strings.LoginPage_authenticateButton_content}"
Grid.Column="2" Margin="53,4,0,10"
Grid.Row="2" FontSize="16"
IsEnabled="{Binding AuthenticateButtonEnabled}"
Command="{Binding Path=AuthenticateCommand}" HorizontalAlignment="Left" Width="87"/>
the loginPageViewModel:
....
private bool _authenticateButtonEnabled;
private DelegateCommand _authenticateCommand;
public bool AuthenticateButtonEnabled {
get { return _authenticateButtonEnabled; }
set
{
_authenticateButtonEnabled = value;
DynamicOnPropertyChanged(); // this is so named to not content with onPropertyChanged defined elsewhere.
AuthenticateCommand.Update();
}
}
...
public DelegateCommand AuthenticateCommand
{
get {
if (_authenticateCommand == null)
{
_authenticateCommand = new DelegateCommand(Authenticate, AuthenticateEnded);
}
return _authenticateCommand;
}
}
private bool AuthenticateEnded(object obj) {
return _authenticateButtonEnabled;
}
private async void Authenticate(object obj)
{
AuthenticateButtonEnabled = false;
ResponseTextBlock = Strings.LoginPage_responseBlock_content_checking;
i3SoftHttpClient _httpClient = new i3SoftHttpClient();
i3SoftUser _i3SoftUser;
AuthenticateCommand.CanExecute(false);
....
// if authentication does not succeed - turn the buttons back on.
AuthenticateCommand.CanExecute(true);
}
and to the Delegate command class i added:
public void Update()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}

WPF Record didn't update even with the use of INotifyPropertyChanged

I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?
Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
<businessLogic:PatientMgr x:Key="PatientsViewModel" />
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StretchDirection="Both" Stretch="Uniform"
Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}"
/>
</Border>
<TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
<ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath = "FullName"
SelectedIndex="0"
SelectionChanged="FullNameComboBox_OnSelectionChanged"
/>
<TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>
Here is my BusinessLogicLayer
public class PatientMgr :INotifyPropertyChanged
{
#region Fields
private readonly PatientDb _db;
private Patient _entity;
private List<Patient> _entityList;
private ObservableCollection<Patient> _comboBoxItemsCollection;
private Patient _selectedItem;
#endregion
#region Properties
public Patient Entity
{
get { return _entity; }
set
{
if (Equals(value, _entity)) return;
_entity = value;
OnPropertyChanged();
}
}
public List<Patient> EntityList
{
get { return _entityList; }
set
{
if (Equals(value, _entityList)) return;
_entityList = value;
OnPropertyChanged();
}
}
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
public Patient SelectedItem
{
get { return _selectedItem; }
set
{
if (Equals(value, _selectedItem)) return;
_selectedItem = value;
OnPropertyChanged();
}
}
#endregion
#region Constructor
public PatientMgr()
{
_db = new PatientDb();
Entity = new Patient();
EntityList = new List<Patient>();
Parameters = new Patient();
ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
SelectedItem = ComboBoxItemsCollection[0];
}
#endregion
public List<Patient> RetrieveMany(Patient parameters)
{
return _db.RetrieveMany(parameters);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:
public partial class YourUserControl: UserControl
{
private PatientMgr PatientsViewModel;
public YourUserControl()
{
InitializeComponent();
PatientsViewModel = new PatientMgr();
DataContext = PatientsViewModel;
}
public void AddComboItem(Patient item)
{
PatientsViewModel.ComboBoxItemsCollection.Add(item);
}
public void UpdateComboItem(Patient item)
{
//your update code
}
After removing static bindings, your XAML should looks like this:
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage" ...
Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.
EDIT:
To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:
Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.
In PatientMgr Code:
public int LastValidIndex
{
get { return _lastIndex; }
set
{
if (value == -1) return;
_lastIndex = value;
OnPropertyChanged();
}
}
public string CurrentFullName
{
get
{
return SelectedItem.FullName;
}
set
{
var currentItem = SelectedItem;
ComboBoxItemsCollection.RemoveAt(LastValidIndex);
currentItem.FullName = value;
ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
SelectedItem = currentItem;
}
}
In UserControl.Xaml :
<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding LastValidIndex}"
IsTextSearchEnabled="False"
Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath = "FullName"
/>
I don’t like this:
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
Try this:
OnPropertyChanged(“ComboBoxItemsCollection”);
And, are you sure this equals is resolved right?
if (Equals(value, _comboBoxItemsCollection)) return;
Try to debug it …
Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.
Here is what i've just tried:
Simple Patient model with its FullName property:
public class Patient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _FullName;
public string FullName
{
get { return _FullName; }
set
{
_FullName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
This is the XAML:
<Window x:Class="PatientsStack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatientsStack"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PatientsMgr x:Key="PatientsViewModel"/>
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
<StackPanel>
<ComboBox x:Name="FullNameComboBox" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
TextSearch.TextPath="FullName"
DisplayMemberPath="FullName"
SelectedItem="{Binding SelectedPatient}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
VerticalAlignment="Top"
IsTextSearchEnabled="False"
SelectedIndex="0">
</ComboBox>
<Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
<Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
</StackPanel>
</Grid>
This two pieces right here did the job:
SelectedItem="{Binding SelectedFilter}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.
As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.
Here is the ViewModel for this:
public class PatientsMgr : INotifyPropertyChanged
{
private ObservableCollection<Patient> _ComboBoxItemsCollection;
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get
{
return _ComboBoxItemsCollection;
}
set
{
_ComboBoxItemsCollection = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
}
}
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
}
}
public ICommand ChangeNameCommand { get; set; }
public ICommand AddPatientCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public PatientsMgr()
{
ComboBoxItemsCollection = new ObservableCollection<Patient>();
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
AddPatientCommand = new RelayCommand<Patient>(AddPatient);
}
public void ChangePatientName(Patient patient)
{
patient.FullName = "changed at request";
}
public void AddPatient(Patient p)
{
ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
}
}
I am posting my RelayCommand too, but i am sure you have it defined for your actions:
public class RelayCommand<T> : ICommand
{
public Action<T> _TargetExecuteMethod;
public Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public bool CanExecute(object parameter)
{
if (_TargetExecuteMethod != null)
return true;
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
T tParam = (T)parameter;
if (_TargetExecuteMethod != null)
_TargetExecuteMethod(tParam);
}
}

Enable,Disable button upon check/uncheck of checkboxes

I have a view that contains a list box with various items in button form and a checkbox for each of the button. XAML:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox x:Name="CheckFavorite"
Width="Auto"
Height="Auto"
AutomationProperties.AutomationId="AID_FavoritesCheck"
IsChecked="{Binding Path=IsChecked,
Mode=TwoWay}"
Visibility="{Binding IsFavoriteConfiguredAndInDA,
Converter={StaticResource boolToVisibility}}"
Checked="OnContentChanged"
Unchecked="OnContentChanged"/>
<Button Grid.Column="1"
Width="240"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
AutomationProperties.AutomationId="AID_BtnLaunchFavorite"
Command="{Binding Path=LaunchFavorite}"
Content="{Binding Path=ModuleDisplayName}"
Cursor="Hand"
FontSize="{StaticResource UxLevel_5}"
Padding="24 12 2 12"
ToolTip="{Binding Path=ModuleDisplayName}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
I have another button called "Launch". Its basic functionality is to launch the tasks as per the selection in the above listbox items so that multiple tasks can be launched.Button XAML:
<Button Name="btnLaunch"
Width="80"
Height="25"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="AUI_BtnLaunchForFavorite"
Command="{Binding LaunchCommand}"
Style="{StaticResource LaunchButtonStyle}"
Visibility="{Binding IsRIAMode,
Converter={StaticResource boolToVisibilityInverter}}">
Issue:
I want to enable/disable this launch button if atleast one item is selected and disable otherwise.How can i achieve? Pls help with some code snippets.
UPDATE:
Here is the launch command:
public ICommand LaunchCommand { get; private set; }
LaunchCommand = new DelegateCommand<object>(OnLaunch);
internal void OnLaunch(object sender)
{
var nFavItemToLaunchCount = Favorites.Count(favItem => favItem.IsChecked);
if (!IsSessionLimitReached(nFavItemToLaunchCount))
{
foreach (FavoriteItemViewModel favoriteItem in Favorites)
{
if (favoriteItem.IsChecked)
{
favoriteItem.LaunchFavorite.Execute(sender);
}
}
}
}
UPDATE 2:
I made the changes as per Krishna's comment:
LaunchCommand = new DelegateCommand<object>(OnLaunch,ToggleLaunch);
private bool ToggleLaunch(object obj)
{
if (Favorites.Count(i => i.IsChecked)!=0) //Favorites is the itemsource
{
return true;
}
return false;
}
Still the launch button is shown disabled always even when item is selected/checked.
UPDATE 3
After Krishna's further comment, i changed the implementation of the property IsChecked and also implemented INotifyPropertyChanged in the viewmodel.Still no luck!
public class FavoriteItemViewModel:INotifyPropertyChanged
{
public bool IsChecked
{
get
{
return m_IsChecked;
}
set
{
m_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
/// <summary>
/// Handler for property change
/// </summary>
/// <param name="propertyName">Name of property</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There is another viewmodel "FavoriteContainerViewModel" that holds the collection of "FavoriteItemViewModel" and also implements INotifyPropertyChanged. This container view model is the place where i added the code mentioned in UPDATE 2
UPDATE 4:
Implementation of favorites:
public class FavoriteContainerViewModel : ViewModelBase, IModuleView
{
private readonly ObservableCollection<FavoriteItemViewModel> m_Favorites = new ObservableCollection<FavoriteItemViewModel>();
public ObservableCollection<FavoriteItemViewModel> Favorites
{
get { return m_Favorites; }
}
public FavoriteContainerViewModel()
{
LaunchCommand = new DelegateCommand<object>(OnLaunch, ToggleLaunch);
OnLoad();
}
private bool ToggleLaunch(object obj)
{
if (Favorites.Count(i => i.IsChecked) != 0)
{
return true;
}
return false;
//return true;
}
}
Note:ViewModelBase implements INotifyPropertyChanged
Update 5:
Problem now resolved using event based model. Implemented following:
FavoriteViewModel.cs
public event EventHandler ItemChecked;
public bool IsChecked
{
get
{
return m_IsChecked;
}
set
{
m_IsChecked = value;
if (ItemChecked != null)
{
ItemChecked(this, new EventArgs());
}
}
}
FavoriteItemContainerViewModel.cs
private void SubscribeFavoriteItemEvents(FavoriteItemViewModel favorite)
{
favorite.ItemChecked += ToggleLaunchButton;
}
private bool m_IsLaunchEnabled;
public bool IsLaunchEnabled
{
get
{
return m_IsLaunchEnabled;
}
set
{
m_IsLaunchEnabled = value;
OnPropertyChanged("IsLaunchEnabled");
}
}
Property IsLaunchEnabled is binded to the button to enable/disable.
You can enable/disable the Button based on the CheckBox.IsChecked property:
<Button Name="btnLaunch"
Width="80"
Height="25"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="AUI_BtnLaunchForFavorite"
Command="{Binding LaunchCommand}"
Style="{StaticResource LaunchButtonStyle}"
Visibility="{Binding IsRIAMode,
Converter={StaticResource boolToVisibilityInverter}}"
IsEnabled="{Binding ElementName=CheckFavorite, Path=IsChecked}">
I am guessing the launch button is outside the listbox and user clicks it once the checkboxes are checked.
In that case, you need to add a condition in the 'CanExecute' part of your ICommand (LaunchCommand)
Lets assume your ICommand implementation in the constructor is something like
LaunchCommand = new RelayCommand(launchMethod);
private void launchMethod(object obj)
{
//do some work here
}
Now add canExecute part to your command by changing the initialisation
LaunchCommand = new DelegateCommand<object>(OnLaunch,checkCheckboxes);
and add this method
private bool checkCheckboxes(object obj)
{
//YourList is your itemsource
return YourList.Where(i=>i.IsChecked).Any();
}
Just change the above method to suit your needs
Update change your IsChecked property to below
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
if (checkbox1.Checked && !checkbox2.Checked)
{
button1.Enable = true
}
else if (!checkbox1.Checked && checkbox2.Checked)
{
button1.Enable = false
}

Categories

Resources