Вопрос

Alright, let me try to explain.

I have an application where I can trigger some sort of scan process. This scan process launches (just a number) 10 background workers which do something. After (again, just a number) 5 seconds I would like to

  • Kill all the background workers (I'm using CancelAsync for that)
  • Do some calculation with the data I got from all of them
  • Update the UI so the data is displayed AND enable some buttons (these buttons are bound through a ViewModel Command called PerformUpdateCommand and they have a CanExecute property of IsPerformUpdateAllowed. The IsPerformUpdateAllowed dependency property is set when my tasks finish.

What I thought I would do:

  • After triggering the Background Workers, start a dispatcher timer with a 5 second interval.
  • When the DispatcherTimer "ticks" I calculate the data and set the property IsPerformUpdateAllowed to either true or false.

This basically works as expected (the UI stays responsive, ...) with just a minor hickup: The UI is not being updated (the button is not enabled). As soon as I put the window into the background and back into the foreground, the command is enabled, also the IsPerformUpdateAllowed property is set to true. Also, when I press the button (in disabled state) after pressing it, it will be enabled.

So, although I set the dependency property correctly, the UI does not react to this change.

Anybody knows why? Interestingly, I also set some text in the UI into a label - this text is updated correctly. It's just that the property which tells the command CanExecute does not trigger a UI update.

Code for the initialization of the timer.

        _scanTimer = new DispatcherTimer();
        _scanTimer.Interval = new TimeSpan(0, 0, 0, 3);
        _scanTimer.Tick += delegate
        {
            // After the timer has elapsed (some time passed), cancel all scans and update the result
            _scanTimer.Stop();
            UpdateScanResults();
            CancelNormalScans(false);
        };
        _scanTimer.Start();

Code how the Command is bound to the WPF element (the button is actually a Hyperlink):

            <Label Grid.Row="1" Grid.Column="1">
                <Hyperlink Command="{Binding ReadSettingsCommand}">
                    <TextBlock Text="{Binding Source={StaticResource Loc}, Path=Labels.ReadSettings}"></TextBlock>
                </Hyperlink>
            </Label>

Here is the code for the Command

    public RelayCommand ReadSettingsCommand
    {
        get
        {
            return _readSettingsCommand
                ?? (_readSettingsCommand = new RelayCommand(ExecuteReadSettings, () => IsScannedDeviceAvailable && !IsUpdateInProgress));
        }
    }

The code is actually dependent on two dependency properties IsScannedDeviceAvailable AND NOT IsUpdateInProgress. Both are dependency properties.

UPDATE: I just read that the binding to the CanExecute property is just one time. If you want it to revalidate you need to call RaiseCanExecuteChanged on the command. This works, but it is somewhat cumbersome, since now I need to call it manually every time one of the two properties change. Actually I wanted that to be handled automatically. Any ideas on how this can be done more easily? Isn't there some method to have kind of a one-way binding between CanExecute and the property?

Это было полезно?

Решение

Either on your VM call RaiseCanExecuteChanged whenever IsScannedDeviceAvailable/IsUpdateInProgress is set. OR My personal favorite create your own implementation of ICommand since its pretty simple anyway.

public class FooCommand : ICommand
{
    private bool _canExecute;
    private Action _delegate;
    public event EventHandler CanExecuteChanged;
    public new bool CanExecute
    {
        get
        {
            return _canExecute;
        }
        set
        {
            _canExecute = value;
            if(CanExecuteChanged != null)
                CanExecuteChanged();
        }
    }

    public void Execute(object parameter)
    {
        _delegate();
    }

    bool ICommand.CanExecute()
    {
        return CanExecute;
    }

    public FooCommand(Action action)
    {
        _delegate = action;
    }
}

Другие советы

If you are triggering a change in a Command.CanExecute() based on a property, you can also call CommandManager.InvalidateRequerySuggested() to force the CanExecute() to be re-evaluated.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top