1

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?

8
  • Have you tried assigning a Name to the button like btnUpdate and then do btnUpdate.IsEnabled = false; Commented Jan 5, 2022 at 13:51
  • Yes but it is not working like that i have to do it with Binding because with name is it not accessible Commented Jan 5, 2022 at 14:01
  • The name is accessible only on xaml.cs. If you want to access it from ViewModel you need to to public static Button btnUpdate = new Button();. And in initialisation you need to assign this btnUpdate to the actual Button name. Then access it from ViewModel like NameOfView.btnUpdate.IsEnabled = false. Let me know if this works. Commented Jan 5, 2022 at 14:05
  • could you show your viewmodel code? Commented Jan 5, 2022 at 14:12
  • @stersym no it is not working like that Commented Jan 5, 2022 at 14:15

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I followed your answer. i copied the button xml and replaced it with my current one. i made the new class and paste all the code of RelayCommand in the class (RelayCommand ) and posted the ViewModel code in my (SettingsViewModelCode) which i posted above but it is not working nothing happening button is not disabling and even button click is not working :(
Did you set the DataContext of the SettingsView.xaml in the code-behind constructor like this? this.DataContext = new SettingsViewModel();
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.