Reputation:
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?
Upvotes: 1
Views: 3004
Reputation: 56
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.
Upvotes: 2
Reputation: 191
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.
Upvotes: 1