I am reading Josh Smith' WPF Apps With The Model-View-ViewModel Design Pattern tutorial
i don't understand what the below code is trying to do.
First, the syntax reminds me properties, but with add/remove instead.
But what is CommandManager.RequerySuggested?
It delegates the event subscription to
the CommandManager.RequerySuggested
event. This ensures that the WPF
commanding infrastructure asks all
RelayCommand objects if they can
execute whenever it asks the built-in
commands
//Figure 3 The RelayCommand Class
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute) : this(execute, null)
{ }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{ _execute(parameter); }
#endregion // ICommand Members }
Also, save command is configured with lambdas. 1st, there are 2 param variables.
Will they conflict? i cannot just do something like RelayCommand(this.Save(), this.CanSave) or is there no such syntax.
_saveCommand = new RelayCommand(param => this.Save(),
param => this.CanSave );
CommandManager.RequerySuggested += value means that if the function for CanExecute can resolve to both true and false depending on some conditions.
WPF will disable the Button/MenuItem (CommandButtonBase) if it evaluates to false and enable whenever the condition evaluates to true.
If you don't have those two lines, WPF will ask the command only once (when the Button/MenuItem is loaded and will not update after that unless you do it manually.
The two parameters (lambda-expressions) are of type Action<object> and a Predicate<object> respectively. So, they cannot, by definition, conflict (params is just a name - and as the two functions have different scope - they don't conflict).
If you have a method with the right signature, you can use that in the constructor
private void Save(object obj)
and
private bool CanSave(object obj)
respectively, but you shouldn't have the () at the end - so new RelayCommand(this.Save,this.CanSave) should work.
Related
I am working on a calculator. My view contains 16 Buttons and 1 TextBox:
TextBox's Text property is bound to the UserInput property in ViewModel:
<TextBox x:Name="InputTextBox"
Grid.Row="0"
Background="#ECDBBA"
Foreground="#191919"
FontSize="30"
Text="{Binding UserInput, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="True"
VerticalContentAlignment="Center"/>
public class ViewModel : INotifyPropertyChanged
{
public AddTextCommand AddTextCmnd { get; set; }
public ClearTextCommand ClearTextCmnd { get; set; }
public ShowResultCommand ShowResultCmnd { get; set; }
private string _userInput = "0";
public string UserInput
{
get { return _userInput; }
set
{
if (UserInput != value)
{
_userInput = value;
OnPropertyChanged("UserInput");
}
}
}
public ViewModel()
{
AddTextCmnd = new AddTextCommand(this);
ClearTextCmnd = new ClearTextCommand(this);
ShowResultCmnd = new ShowResultCommand(this);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have 3 commands: One to add text to TextBox, another to show the result, and a last one to clear the TextBox. AC and "=" buttons (see screenshot) are bound to ClearTextCommand and ShowResultCommand.
public class ShowResultCommand : ICommand
{
private ViewModel _viewModel;
public ShowResultCommand(ViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
if (_viewModel.UserInput != "0")
return true;
else
return false;
}
public void Execute(object parameter)
{
DataTable calculator = new DataTable();
_viewModel.UserInput = calculator.Compute(_viewModel.UserInput, null).ToString();
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
}
public class ClearTextCommand : ICommand
{
private ViewModel _viewModel;
public ClearTextCommand(ViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
if (_viewModel.UserInput != "0")
return true;
else
return false;
}
public void Execute(object parameter)
{
_viewModel.UserInput = "0";
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
The program works well, but 2 commands share a problem. I don't know if it is my ignorance or not, but I didn't find anything in Google. I thought that my CanExecute methods work only when UserInput changed (property changed), but I put a breakpoint on the CanExecute methods of my commands and saw that they work forever in debug, like once UserInput is changed, ShowResultCommand and ClearTextCommand's CanExecute methods work forever, they are called again and again in a loop. I am wondering if they should work this way? Shouldn't they be called just when UserInput is changed? This doesn't cause any error in my program, but I think there is something wrong with my commands.
So basically question is:
Should CanExecute work in a loop while my app is running or should it work when a property related to the method is changed? If it should work in a loop, then everything is fine, but if not what is wrong with my commands?
I thought that my CanExecute methods work only when UserInput changed(property changed).
Yes and no. Your commands delegate the CanExecuteChanged event to the RequerySuggested event of CommandManager. This is a common approach. The CommandManager is a WPF framework type responsible for:
Provides command related utility methods that register CommandBinding and InputBinding objects for class owners and commands, add and remove command event handlers, and provides services for querying the status of a command.
The RequerySuggested event is only raised under a few not well documented circumstances:
The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus.
As you can see this is very vage and there are situations, where the CommandManager simply cannot know:
In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.
In summary, yes, the RequerySuggested event is raised when user input changes and on other input related events e.g. in a TextBox or bound properties change, but not in all situations. From a different perspective, the CommandManager determines when it needs to raise the event only on general triggers, so often it will invalidate all can execute states, although most or even no command might even be affected. It is not targeted to the specific case, like a distinct property that you want to observe for changes, but like a watering pot. This can of course make a difference in performance, although negligible in the majority of applications.
I put breakpoint on CanExecute methods of my commands and saw that they work forever in debug
Yes and by now you know exactly why. In debug mode when a breakpoint is hit, the debugger or IDE is brought into foreground meaning the keyboard focus changes. When you switch back to your application being debugged, the keyboard focus changes again...and again...and again. Since the CommandManager raises the RequerySuggested event on keyboard focus, you will constantly trigger CanExecute and hit the breakpoint. The same may happen on window activation as well.
A Different Command Approach
There is another very common approach for notifying can execute changed. In your example, you rely on the CommandManager to do its best for all commands. However, you could also take responsibility yourself and explicitly invalidate can execute through another public method.
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute) : this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
Here you assume that you know exactly when to update your own command. Instead of asking "Dear command manager, please tell me when to update" you say "Update these specific commands now". For this you have to call RaiseCanExecuteChanged for each affected command in your properties.
public string UserInput
{
get { return _userInput; }
set
{
if (UserInput != value)
{
_userInput = value;
OnPropertyChanged("UserInput");
AddTextCmnd.RaiseCanExecuteChanged();
ClearTextCmnd.RaiseCanExecuteChanged();
ShowResultCmnd.RaiseCanExecuteChanged();
}
}
}
This mechanism has a few advantages over the RequerySuggested event.
You decide exactly when commands are updated.
Only commands that are really affected are updated.
It is much easier to comprehend the dependencies in your view model.
Potential performance benefit from reducing unnecessary updates.
No reliance on an external component and hidden magic or vage assumptions.
A Hint On DRY and Reusability
As of now, all of your commands contain duplicated logic, violating the Don't repeat yourself principle, in short DRY. This makes maintenance much harder. Instead you should at least extract a common, reusable command type as shown above to improve your code. You do not even have to implement one yourself, there are plenty of frameworks, libraries and NuGet packages available already, e.g. Microsoft.Toolkit.Mvvm, see its documentation here.
Here is an example how it would look like for your clear text command:
public ViewModel()
{
ClearTextCmnd = new RelayCommand(ExecuteClearText, CanExecuteClearText);
// ...other commands.
}
public ICommand ClearTextCmnd { get; set; }
public bool CanExecuteClearText()
{
return UserInput != "0";
}
public void ExecuteClearText()
{
UserInput = "0";
}
For simplicity I used a RelayCommand implementation without an extra parameter, as you do not use it currently. It is the same as the command above, only without the parameter for each method.
As asked in the comments, this would be the RelayCommand implementation without parameter.
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute) : this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
I have two methods that do almost the two things:
public static void ShowThing()
{
// code..
}
and
public static bool TryShowThing()
{
if(condition)
{
// same code above..
return true;
}
return false;
}
At the moment I'm binding a button's Command to the void method and it does what it should.
Problem is that now I'm cleaning up the code and to avoid coupling I wanted to bind the button to the bool method and that won't work.
Is Command={Binding BooleandReturningMedhod} even allowed in xaml?
Apparently nobody on the internet has ever had this problem before so I think I'm missing something here...
You cannot bind directly to a method.
What i think what you really wanna achieve is something like this
Code:
ShowThingCommand { get; } = new RelayCommand((o) => ShowThing(),(o) => condition)
RelayCommand:
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
XAML:
<Button Command={Binding ShowThingCommand } />
Important part is the CanExecute method, when it returns false your Button gets disabled
Continuing with my struggle with Static variables and XAML, I can't work around the command bindings greying out the button.
The code in View Model:
public static ICommand CancelCalender => _cancelCalender
?? (_cancelCalender = new CommandHandler(CancelCalender_Button, _canExecute));
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
The reference,
xmlns:viewModels="clr-namespace:StaffShiftManager.ViewModels"
And these are the ways I tried to bind the command variable:
Command="{x:Static viewModels:ViewModelBase.CancelCalender}"
And
Command="viewModels:ViewModelBase.CancelCalender"
And
Command="{Binding Source={x:Static viewModels:ViewModelBase.CancelCalender}}"
Is there something that I am missing? Any help would be greatly appreciated.
Basically in the ICommand CanExecute() method, this returns wether or not the Command is enabled \ can execute.
Returning false from the CanExecute() will disable (gray out) the button.
Now I have modified your code slightly to provide a Func<bool> as the CanExecute() handler. What will happen here is every time the Command Execution is re-queried it will execute your canExecute method.
public class CommandHandler : ICommand
{
public CommandHandler(Action execute)
:this(execute, null)
{
}
public CommandHandler(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException(nameof(execute));
_executeHandler = execute;
_canExecuteHandler = canExecute ?? (() => true);
}
Func<bool> _canExecuteHandler = () => true;
Action _executeHandler;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecuteHandler();
}
public void Execute(object parameter)
{
_executeHandler?.Invoke();
}
}
Not that the default implementation for the canExecute method is to return true. Even passing a null Func to the constructor will still result in true.
Just to add one more thing one of my favorite command binders (much more advanced than above) is using the DelegateCommand. I dont remeber where I found the original source (as I did not write it) but is much more advanced.
I'm using MVVM pattern to build UWP app. I have implemented ICommand interface as mentioned in book: "Microsoft Visual C# 2013 - Step-by-step".
ICommand implementation:
public class Command : ICommand
{
private Action _methodToExecute;
private Func<bool> _methodCanExecute;
public Command(Action methodToExecute) : this(methodToExecute, null)
{
}
public Command(Action methodToExecute, Func<bool> methodCanExecute)
{
_methodToExecute = methodToExecute;
_methodCanExecute = methodCanExecute;
var dt=new DispatcherTimer();
dt.Tick += (s, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
dt.Interval = new TimeSpan(0, 0, 1);
dt.Start();
}
public bool CanExecute(object parameter) => _methodCanExecute == null ? true : _methodCanExecute();
public void Execute(object parameter) => _methodToExecute();
public event EventHandler CanExecuteChanged;
}
App crashes with COM exception after every 3-4 mins of running.
System.Runtime.InteropServices.COMException was unhandled by user code
ErrorCode=-2147467259
HResult=-2147467259
Message=Error HRESULT E_FAIL has been returned from a call to a COM component.
Source=mscorlib
StackTrace:
at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
at System.Runtime.InteropServices.WindowsRuntime.ICommandAdapterHelpers.<>c__DisplayClass2.<CreateWrapperHandler>b__3(Object sender, EventArgs e)
at FavQuotesMain.ViewModels.Command.<.ctor>b__3_0(Object s, Object e)
InnerException:
This exception didn't occur while building Win 8.1 apps.
Please give suggestions to remove this exception.
dt.Tick += (s, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
That's most likely what's causing your issue. I'm not sure why you're spamming your CanExecuteChanged, but maybe you want to re-think your execution model.
Also we don't know what methods your delegate is calling. So there could be a plethora of reasons it's failing.
I guess your problem is the timer.
I always implement the ICommand like:
public class Command : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public Command(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public void Execute(object parameter)
{
execute(parameter);
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
#Water Solution is to manually raise CanExecuteChanged whenever we want View to know about Control's changed status.
Steps:
Wrap CanExecuteChanged into a method like OnCanExecuteChanged ()
Call this method form relevant ViewModel Property Setters.
I have the following implementation of an RelayCommand in my viewModel:
RelayCommand _resetCounter;
private void ResetCounterExecute()
{
_data.ResetCounter();
}
private bool CanResetCounterExecute()
{
if (_data.Counter > 0)
{
return true;
}
else
{
return false;
}
}
public ICommand ResetCounter
{
get
{
if (_resetCounter == null)
{
_resetCounter = new RelayCommand(this.ResetCounterExecute,this.CanResetCounterExecute);
}
return _resetCounter;
}
}
By calling _data.ResetCounter(); in the ResetCounterExecute method i reset the counter value to 0 in my model.
And this is the implementation of my RealyCommand Class that i use based on samples.
internal class RelayCommand : ICommand
{
readonly Action _execute;
readonly Func<bool> _canExecute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute();
}
}
In XAML i bind the comman to a button:
<Button Name="btnResetCount" Content="Reset" Command="{Binding Path=CounterViewModel.ResetCounter}" Click="btnResetCount_Click">
My Problem is that the button just gets enabled once i click on any control in the UI. But what i need is that the button gets enabled once my _data.Counter > 0 applies. So from my research it seems that i need to implement CommandManager.InvalidateRequerySuggested(); or use the RelayCommand.RaiseCanExecuteChanged().
I would like to know if this two ways are the only ways to notify the UI to refresh the bindings.
Also i would like to ask how i would have to implement the RelayCommand.RaiseCanExecuteChanged() in my current case. Where and how should i raise it to ensure that the UI changes the button state if the condition is given.
Thanks in advance.
when using CommandManager.RequerySuggested you can force CommandManager to invoke RequerySuggested event by calling CommandManager.InvalidateRequerySuggested()
or you may perhaps implement RaiseCanExecuteChanged. this may be more reliable method to trigger the same.
example
internal class RelayCommand : ICommand
{
...
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
...
}
and when you want to invalidate or _data.Counter changes, call
ResetCounter.RaiseCanExecuteChanged();
additionally you may also want to read How does CommandManager.RequerySuggested work?