Search code examples
c#wpfxamldata-bindingbinding

Synchronize a bindable property with a command


Is there a way to execute a command when property changes with specified binding delay?

As an example let's use CheckBox that has property IsChecked with Delay=1000 (1 sec) and Command that invokes when IsChecked property changes:

MainWindow.xaml:

<CheckBox Command="{Binding Command}"
          Content="Hello"
          IsChecked="{Binding IsChecked, Delay=1000}" />

MainWindow.xaml.cs:

private bool _isChecked;
public bool IsChecked
{
    get { return _isChecked; }
    set
    {
        if (_isChecked != value)
        {
            _isChecked = value;
            OnPropertyChanged();
            MessageBox.Show("Property Changed"); 
        }
    }
}

public ICommand Command { get; } = new RelayCommand(obj => MessageBox.Show("Command Invoked"));

When clicking on checkbox the MessageBox.Show("Command Invoked") invokes first and then MessageBox.Show("Property Changed");

Final output:

"Command Invoked" -\> after 1 sec delay -\> "Property Changed"


Solution

  • You can execute the operation from the property set(), depending on the delay of the Binding.

    Or you delay the operation explicitly using a timer or Task.Delay:

    public ICommand SomeCommand { get; } = new RelayCommand(ExecuteSomeCommandDelayedAsync);
    
    private async Task ExecuteSomeCommandDelayedAsync(object commandParameter)
    {
      await Task.Delay(TimeSpan.FromSeconds(1));
      MessageBox.Show("Command Invoked");
    }
    

    To replicate the delay behavior of the binding engine, you would have to use a timer and reset it on every invocation and honor the latest change/command parameter exclusively.

    The following example uses the System.Threading.Timer (if you need to access UI elements, or DispatcherObject instances in general, you should use the DispatcherTimer instead):

    public ICommand SomeCommand { get; } = new RelayCommand(ExecuteSomeCommand);
    
    // Important: this Timer implements IDisposable/IDisposableAsync.
    // It must be disposed if it is no longer needed!
    private System.Threading.Timer CommandDelayTimer { get; } 
    private object SomeCommandCommandParameter { get; set; }
    
    public Constructor()
      => this.CommandDelayTimer = new Timer(OnCommandDelayElapsed);
    
    private async Task ExecuteSomeCommand(object commandParameter)
    {
      // Capture latest parameter value and drop the previous
      this.SomeCommandCommandParameter = commandParameter;
    
      // Start/reset the timer.
      // This timer is configured to execute only once (period time = 0).
      _ = this.CommandDelayTimer.Change(TimeSpan.FromSeconds(1), TimeSpan.Zero);
    }
    
    private void OnCommandDelayElapsed(object? state)
    {
      SomeCommandOperation(state);
    }
    
    // The operation that the command is supposed to invoke after a delay
    private void SomeCommandOperation(object commandParameter)
    {  
      MessageBox.Show("Command Invoked");
    }