Prevent button command from executing from OnClick when a condition is met - c#

I have a RoutedUI Command that is bound as a Command property for a button and an OnClick event. Whenever I evaluate some condition from the OnClick I want to prevent the command from executing. I referred to this post but dosen't help much Prevent command execution. One quick fix is to get the sender of button on click and set its command to null. But I want to know if there is an other way. Please help.
<Button DockPanel.Dock="Right"
Name="StartRunButtonZ"
VerticalAlignment="Top"
Style="{StaticResource GreenGreyButtonStyle}"
Content="{StaticResource StartARun}"
Width="{StaticResource NormalEmbeddedButtonWidth}"
Click="StartRunButton_Click"
Command="{Binding StartRunCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl},AncestorLevel=2}}"
/>
Here is the code behind
private void StartRunButton_Click(object sender, RoutedEventArgs e)
{
if(SomeCondition){
//Prevent the Command from executing.
}
}

Assuming your StartRun() method follows the async / await pattern, then replace your ICommand implementation with the following. It will set CanExecute to false while the task is running, which will automatically disable the button. You don't need to mix commands and click event handlers.
public class perRelayCommandAsync : ViewModelBase, ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
public perRelayCommandAsync(Func<Task> execute) : this(execute, () => true) { }
public perRelayCommandAsync(Func<Task> execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
private bool _isExecuting;
public bool IsExecuting
{
get => _isExecuting;
set
{
if(Set(nameof(IsExecuting), ref _isExecuting, value))
RaiseCanExecuteChanged();
}
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => !IsExecuting
&& (_canExecute == null || _canExecute());
public async void Execute(object parameter)
{
if (!CanExecute(parameter))
return;
IsExecuting = true;
try
{
await _execute().ConfigureAwait(true);
}
finally
{
IsExecuting = false;
}
}
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
More details at my blog post.

This simple button extension could be useful for someone.
On button click at first it is invoked ConfirmationClick event. If you set in its callback e.IsConfirmed to true, then classic click event is invoked and command is executed.
Because of command binding you have button.IsEnabled property tied up to command.CanExecute.
public class ConfirmationButton : Button
{
public event EventHandler<ConfirmationEventArgs> ConfirmationClick;
protected override void OnClick()
{
ConfirmationEventArgs e = new ConfirmationEventArgs();
ConfirmationClick?.Invoke(this, e);
if (e.IsConfirmed == true)
{
base.OnClick();
}
}
}
public class ConfirmationEventArgs : EventArgs
{
public bool? IsConfirmed = false;
}
<model:ConfirmationButton x:Name="DeleteButton"
ConfirmationClick="DeleteButton_ConfirmationClick"
Command="{Binding DeleteCommand}"/>
private void DeleteButton_ConfirmationClick(object sender, ConfirmationEventArgs e)
{
var dialogWindow = new MyDialogWindow("Title..","Message.."); //example
e.IsConfirmed = dialogWindow.ShowDialog();
}

Related

How to Disable and Enable Button in C# WPF using MVVM

i have one view model name as "SettingsViewModel" and in that view model I am writing the function for button click ( bUpdate() )
namespace
{
class SettingsViewModel : Notifyable
{
public Settings settings
{
get => _settings;
set
{
_settings = value;
OnPropertyChanged();
}
}
private Settings _settings = Settings.Default;
private IWindowManager _windowManager;
public SettingsViewModel(IWindowManager windowManager)
{
_windowManager = windowManager;
}
protected override void OnClose()
{
base.OnClose();
settings.Save();
}
CopyFilesRecursively(serverDirectorty, localDirectory){
// DO SOMETHING
}
public void bUpdate()
{
CopyFilesRecursively(serverDirectorty, localDirectory);
}
}
}
I want to disable button click when copying of the files is start and when copying is done I want to re enabled the button click.
Below is my XML (SettingsView.xml) for the button
<Button Content="{x:Static p:Resources.update}" HorizontalAlignment="Right" Command= "{s:Action bUpdate }" />
How can i do that with the help of Binding?
Since you need the MVVM approach, the ideal way would be to set the DataContext of the View/UserControl to the instance of the ViewModel (tell me if you want how-to in comments further, I'll explain) and then bind to a property which is an instance of an ICommand implementation like this:-
View/UserControl:
<Button Content="{x:Static p:Resources.update}"
HorizontalAlignment="Right"
Command="{Binding Update}" />
ViewModel:
public ICommand Update => new RelayCommand(HandleUpdate, CanUpdate);
private bool _isRunning = false;
private void HandleUpdate()
{
_isRunning = true;
CommandManager.InvalidateRequerySuggested();
Task.Run(() =>
{
// Update Button click logic goes here
CopyFilesRecursively(serverDirectorty, localDirectory);
Application.Current.Dispatcher.Invoke(() =>
{
_isRunning = false;
CommandManager.InvalidateRequerySuggested();
});
});
}
private bool CanUpdate()
{
return !_isRunning;
}
The _isRunning flag just maintains the current running state information and the InvalidateRequerySuggested invocation on the CommandManager forces the View to force the CanExecuteChanged event on the ICommand.
The Task.Run ensures that your long-running process doesn't block the UI thread and the current dispatcher invocation is a guard against non-UI thread manipulating Xaml elements that could potentially cause an issue.
Here is a parameterless implementation of the ICommand interface:
public class RelayCommand : ICommand
{
readonly Func<Boolean> _canexecute;
readonly Action _execute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<Boolean> canexecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canexecute = canexecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canexecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canexecute != null)
CommandManager.RequerySuggested -= value;
}
}
public Boolean CanExecute(Object parameter)
{
return _canexecute == null ? true : _canexecute();
}
public void Execute(Object parameter)
{
_execute();
}
}
You could refactor the boolean flag and optimize your way but this is how we usually de-couple the viewmodel logic from the view code!
P.S.:
There are further ways to pass command parameters via the command binding as well, you could look into that when you need so or I could clarify in comments.
Also, there's no exception handling in the task run currently, do consider aggregate exception catching furthermore.
Well, I'm wondering a bit about your code example. Guess you will run into a "UI is blocked" issue soon. Anyhow, you can get around step by step.
Of course you can do that by binding. Note you can bind nearly any item property to a property in your VM. So for simplicity, you may do it like this
<Button IsEnabled={Binding MyButtonIsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="{x:Static p:Resources.update}" HorizontalAlignment="Right" Command= "{s:Action bUpdate }" />
For the VM side, I assume you are using some MVVM framework Nuget package, and/or have Fody enabled taking care about the plumbing of the INotifyPropertyChanged event. If not, ammend the MyButtonIsEnabled property with a backing field like your other VM properties:
public bool MyButtonIsEnabled {get; set;}
public void bUpdate()
{
MyButtonIsEnabled = false;
CopyFilesRecursively(serverDirectorty, localDirectory);
MyButtonIsEnabled = true;
}
So far, so nice - but won't work as expected, because the bUpdate function is a synchronous function. It will not return until work is done. Hence, your complete UI will not be responsive and the button won't get a time slice to disable and re-enable.
Rather you should work with an ICommands resp. IYourMVVMFrameworkCommand (I'm favoring Catel) like:
(view)
<Button Command="{Binding CopyMyFilesCommand}" Content="...whatever..."/>
(VM)
public ICatelCommand CopyMyFilesCommand { get; private set; }
MyVieModel() // constructor
{
...
CopyMyFilesCommand = new TaskCommand(OnCopyMyFilesCommand);
...
}
private async Task OnCopyMyFilesCommand()
{
await Task.Run(bUpdate).ConfigureAwait(false);
}
Using Catel, the TaskCommand constructor takes a second delegate parameter deciding if the ICommand can be executed. Wiring it as
CopyMyFilesCommand = new TaskCommand(OnCopyMyFilesCommand, () => MyButtonIsEnabled);
Will disable the command which in turn disables the button without the need of binding the IsEnabled property.

WPF Command binding with method returning bool

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

How do I prevent my command to run on Application Load

So I have this simple application that has a button, that's it.
And the button has a command property bound to a command.
This is the command it is bound to.
public class StartAsyncCommand : ICommand
{
private Task _execute;
public StartAsyncCommand(Task Execute)
{
_execute = Execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute.Start();
}
public event EventHandler CanExecuteChanged;
}
And in the ViewModel this is what I got.
public StartAsyncCommand StartCommand { get; }
public MoveMouseModel()
{
StartCommand = new StartAsyncCommand(MoveMove());
}
public async Task MoveMove()
{
MessageBox.Show("First Message..");
await Task.Delay(2000);
MessageBox.Show("Second Message..");
}
XAML
<Grid>
<Button Width="100"
Height="25"
Content="Async?"
Command="{Binding MoveMouseModel.StartCommand}"/>
</Grid>
When I start the application, that those messageboxes show even though I didnt click the button.
What can I do to prevent this?
You are passing the result of MoveMove to your StartAsyncCommand class, not the actual method
public MoveMouseModel()
{
StartCommand = new StartAsyncCommand(MoveMove()); //<-- MoveMove() is executed
}
Should work when you actually pass the method instead and call it inside of StartAsyncCommand instead
eg. public StartAsyncCommand(Func<Task> fnc) {...} as constructor and then just executing the func when the actual command is used
wondering why your visual studio is not displaying you some "this method is not awaited" info message though ..

RelayCommand change canExecute automatic

The current step of learning MVVM is RelayCommand for me.
So i came up with this RelayCommand class:
Relay Command class
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? (x => true);
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
}
View Code-Behind
To test if CanExecute is true or false, I created a Click Event which is calling the Command if CanExecute == true or Show an Error Message when CanExecute == false.
if (sender is Button button)
{
if (_viewModel.MyCommand.CanExecute(button.Tag)) // Also testet to set this parameter `null`
_viewModel.MyCommand.Execute(button.Tag);
else
ErrorMessage.Error("CanExecute = false");
}
ViewModel
In my ViewModel I created the Command and added a Thread.Sleep() to have time that canExecute can show me the ErrorMessage from the Code-Behind.
public ICommand MyCommand { get; set; }
public ViewModel()
{
MyCommand = new RelayCommand(MyCommandMethod);
}
public async void MyCommandMethod(object obj)
{
await Task.Run(() =>
{
Thread.Sleep(5000);
ErrorMessage.Error(obj as string);
});
}
The Problem now is, that if I click the Button 5 times for example, that MyCommandMetod() is used 5 times. So CanExecute will never change.
But why isn't it changing?
I understand RelayCommand as this:
1st - Button is clicked
2nd - canExecute = false (wait till process is finished)
3rd - canExecute = true
4th - Button can be executed again.
So that u can't spam Button clicks and crash the application if for example someone use SpeedClicker and clicks 1.000.000 times a seconds or so.
You have to pass some can-execute-logic to the command when creating it:
public ViewModel()
{
MyCommand = new RelayCommand(MyCommandMethod, MyCanExecutePredicate);
}
private bool MyCanExecutePredicate( object commandParameter )
{
// TODO: decide whether or not MyCommandMethod is allowed to execute right now
}
Example: if you want to allow only one command execution at a time, you could come up with something along these lines:
public async void MyCommandMethod(object obj)
{
_myCanExecute = false;
MyCommand.Refresh();
await Task.Run(() =>
{
Thread.Sleep(5000);
ErrorMessage.Error(obj as string);
});
_myCanExecute = true;
MyCommand.Refresh();
}
private bool MyCanExecutePredicate( object commandParameter )
{
return _myCanExecute;
}

Wiring up the code behind of the View with some Action in the ViewModel

In a Silverlight app that is written with MVVM I want to enable/disable my view based on some stuff.
In the constructor of the View class in code behind I can say something like this and it disables the form:
public MyForm1View()
{
InitializeComponent();
if(this.DataContext == null)
{
this.IsEnabled = False;
}
}
The issue is when there is no data to load, I am showing a gray overlay screen on top of my form to the user with a link on that gray overlay that says "Create a New Record"....now the problem is that if I disable my form like that above then How can I re-enable it when they click that CreateNewRecord link?
But how can I reenable it again from the view-model? Maybe I should have an Action on my ViewModel and when it's called on the ViewModel, it calls a method that's wired up in the code behind of the View ? But how to code this idea?
I would suggest few things:
simple wrapper for ICommand Interface:
public class DelegateCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
public DelegateCommand(Action execute, Func<bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (this.canExecute != null)
{
return this.canExecute();
}
else
{
return true;
}
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
execute();
}
public void RaiseExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged {
public void ViewModel() {
SwitchCommand = new DelegateCommand(() => this.IsEnabled = true, () => true);
}
public DelegateCommand SwitchCommand {get;set;}
private bool isEnabled;
public bool IsEnabled {
get {
return isEnabled;
}
set {
isEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
// here, InotifyPropertyChanged implementation, dozens of sample available
}
Xaml:
as example:
<Button Command={Binding SwitchCommand} /> bind command to click.
So, what's left is to set ViewModel to View, via view constructor, of IoC if you use it.
hope that help.

Categories