Synchronize a bindable property with a command

33 views Asked by At

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"

1

There are 1 answers

5
BionicCode On BEST ANSWER

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");
}