I try not to post questions like this, but i\'ve really been struggling to find an answer or similar example. I have what I think is a really simple example I\'d like to setup.
Well, MVVM is just a pattern or philosophy, so I think your desire to avoid using a framework might be a little misguided. Even if you're not using one of those frameworks, you will essentially be writing your own framework in order to implement the MVVM pattern.
That being said, probably what you want to use is a DelegateCommand
or one of the similar implementations. See: http://www.wpftutorial.net/DelegateCommand.html. The important part that I think you are looking for is that the command that the WPF button is binding to must raise the CanExecuteChanged
event whenever there is a change made in the view model which affects whether the command can or cannot be executed.
So in your case, for example, you would want to add a call to the CanExecuteChanged
of your AddPersonDelegateCommand
to your OnPropertyChanged
method (possibly filtered by the name of the property that was changed). This tells anything bound to the command to call CanExecute
on the command, and then you would have your logic in that CanExecute
that actually determines if a person with the entered name already exists.
So to add some sample code, it might look something like this:
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
People = new ObservableCollection<Person>();
People.Add( new Person {Name = "jimmy"});
AddPersonDelegateCommand = new DelegateCommand(AddPerson, CanAddPerson);
}
// Your existing code here
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if(propertyName == "NewNameTextBox") AddPersonDelegateCommand.RaiseCanExecuteChanged();
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public DelegateCommand AddPersonDelegateCommand { get; set; }
public void AddPerson()
{
// Code to add a person to the collection
}
public bool CanAddPerson()
{
return !People.Any(p=>p.Name == NewNameTextBox);
}
public string NewNameTextBox
{
get { return _newNameTextBox; }
set
{
_newNameTextBox = value;
OnPropertyChanged();
}
}
}
*Note: In this sample your <TextBox></TextBox>
would need to be bound to the NewNameTextBox
property on the view model.
how would I implement commanding so that it uses the CanExecute to disable the Add button if the name is already there or the Name field is empty
I will show how to do the add, the remove is similar and I leave that for you to figure out. First I will show the xaml changes with the button using an AddPerson
command:
<TextBox Text="{Binding CurrentPerson,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged
}"/>
<Button Command="{Binding AddPerson}">Add</Button>
We have bound the current edited text to a new property on the View Model named CurrentPerson
. This is done because we want to access what the person enters, but also we need the binding updated as the user types. To accomplish that updating, we specify that the binding updates by setting the UpdateSourceTrigger
attribute to be PropertyChanged
. Otherwise our the CurrentPerson
string and ultimately the command Can
operation would only fire when the edit text box lost focus.
ViewModel
The viewmodel will subscribe to the AddPerson
command. Execution of that will add the user, but also check a can
method which returns a boolean whether to enable the button or not. The can will be excecuted when the CurrentPerson
property changes where we ultimately call RaiseCanExecuteChanged
on the commanding class to have the button check the can
method.
(This VM is abbreviated for the example and based on your full VM)
public OperationCommand AddPerson { get; set; }
public string _currentPerson;
public MainViewModel()
{
People = new ObservableCollection<Person>();
People.Add(new Person { Name = "jimmy" });
// First Lamda is where we execute the command to add,
// The second lamda is the `Can` method to enable the button.
AddPerson = new OperationCommand((o) => People.Add(new Person { Name = CurrentPerson }),
(o) => (!string.IsNullOrWhiteSpace(CurrentPerson) &&
!People.Any(per => per.Name == CurrentPerson)));
// When the edit box text changes force a `Can` check.
this.PropertyChanged += MainViewModel_PropertyChanged ;
}
void MainViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentPerson")
AddPerson.RaiseCanExecuteChanged();
}
Finally here is the commanding class used which is based on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding.:
public class OperationCommand : ICommand
{
#region Variables
Func<object, bool> canExecute;
Action<object> executeAction;
public event EventHandler CanExecuteChanged;
#endregion
#region Properties
#endregion
#region Construction/Initialization
public OperationCommand(Action<object> executeAction)
: this(executeAction, null)
{
}
public OperationCommand(Action<object> executeAction, Func<object, bool> canExecute)
{
if (executeAction == null)
{
throw new ArgumentNullException("Execute Action was null for ICommanding Operation.");
}
this.executeAction = executeAction;
this.canExecute = canExecute;
}
#endregion
#region Methods
public bool CanExecute(object parameter)
{
bool result = true;
Func<object, bool> canExecuteHandler = this.canExecute;
if (canExecuteHandler != null)
{
result = canExecuteHandler(parameter);
}
return result;
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
public void Execute(object parameter)
{
this.executeAction(parameter);
}
#endregion
}