3

View model is loading data asynchronously using background worker thread in model. All properties in the model and view model raise the property changed events, and all properties are being updated in the view as expected, except 2 buttons whose IsEnabled state depends on the outcome of some properties that are loaded.

The frustrating part is that as soon as I focus on any part of the view, or set a breakpoint after the properties are updated (create a delay), then the buttons IsEnabled state is updated as expected. So it appears to be a timing issue.

Any clues as to how to the best way to solve this? I'm using mvvm-light framework, but that shouldn't matter.

I've tried binding IsEnabled to the button instead of just relying on the Command property, but that made no difference. I've confirmed via logging that view model properties are set and the PropertyChanged event is being raised for the properties associated with the buttons.

Considering sending a message using mvvm-light messenger from the view model to the view on the async completed event and then somehow? triggering a view refresh, but that seems like a kludge.

Update

Thanks to blindmeis' answer, I tested the button behaviour without the Command binding set, i.e. just binding IsEnabled property, and it works as expected!

<Button 
    Grid.Column="2" Content="{Binding LoadProjectsLabel}"
    VerticalAlignment="Top" HorizontalAlignment="Right" 
    IsEnabled="{Binding CanLoadProjects}" />

Obviously it's not great because I can no longer execute the command :) but as soon as I add the command back, it stops behaving:

<Button 
    Grid.Column="2" Content="{Binding LoadProjectsLabel}"
    VerticalAlignment="Top" HorizontalAlignment="Right" 
    Command="{Binding LoadProjectsCommand}" />

Leaving IsEnabled binding doesn't solve the problem, but that seems like a good clue.

The view model command code:

public ICommand LoadProjectsCommand
{
    get
    {
        if (_loadProjectsCommand == null)
        {
            _loadProjectsCommand = new RelayCommand(loadProjects, () => CanLoadProjects);
        }
        return _loadProjectsCommand;
    }            
}

Workaround

Wire up the Click event and avoid Command. Would be nice to solve it from the view model, but this works:

<Button 
    Grid.Column="2" Content="{Binding LoadProjectsLabel}"
    VerticalAlignment="Top" HorizontalAlignment="Right" 
    IsEnabled="{Binding CanLoadProjects}" 
    Click="loadProjects_Click"/>

Code behind:

void loadProjects_Click(object sender, RoutedEventArgs e)
{
    SettingsViewModel vm = (SettingsViewModel)DataContext;
    vm.LoadProjectsCommand.Execute(null);
}
si618
  • 16,580
  • 12
  • 67
  • 84
  • What type of ICommand are you using? Is it firing its CanExecuteChanged method when your properties change, or does its CanExecuteChanged just hook into RequerySuggested? – Joe White May 18 '11 at 06:58
  • Hi Joe, it's the RelayCommand in MVVM-Light: http://geekswithblogs.net/lbugnion/archive/2009/09/26/using-relaycommands-in-silverlight-and-wpf.aspx I'm not sure about your other question: http://mvvmlight.codeplex.com/discussions/212419 – si618 May 18 '11 at 07:39

2 Answers2

4

Answer from other thread:

When your BackgroundWorker completes, call CommandManager.InvalidateRequerySuggested();

By default, Commands are only requeried occasionally by WPF. Otherwise, there would be a huge amount of overhead in constantly calling "CanExecute" on every ICommand implementation. Calling the above method forces the CommandManager to update immediately.

This will force the Commands to re-enable/disable appropriately.

EDIT:

i use a simpler but not so beautiful workaround. i simply call OnPropertyChanged("MyICommand") for my commands in my BackgroundWorker Completed Event.

EDIT:

here is another nice solution.

Community
  • 1
  • 1
blindmeis
  • 22,175
  • 7
  • 55
  • 74
  • Thanks blindmeis, that looks like it should solve the problem, but that didn't work either :( I also tried binding the view model property to the `IsEnabled` property of the button i.e. avoid using the Command CanExecute via mvvm-light RelayCommand, and alas that didn't work either. But your answer has provided a clue, question updated :) – si618 May 17 '11 at 06:30
  • +1 for allowing me to see how to implement a workaround. I will wait a while to see if someone else can solve it w.r.t. `Command`. The thing is your answer *looks* like it should solve the problem :-/ – si618 May 17 '11 at 06:46
  • Unfortunately, mvvm-light throws an exception when you raise property changed event for a property that doesn't exist. – si618 May 17 '11 at 07:15
  • ? "MyICommand" is just a placeholder for your ICommand property in your ViewModel. you have to use your propertyname of course :) – blindmeis May 17 '11 at 07:20
  • Hey! I'm not that silly ;-) Well actually, I had a typo in the property name, so maybe I am :D Anyhow, that workaround reverted back to the old (non-working) behaviour, where the button `IsEnabled` state (or Command.CanExecute) is not updated until some element in the window is focused. – si618 May 17 '11 at 07:28
  • ok :) i use mvvm with delegatecommand, and calling OnPropertyChanged("ICommand") in the completed event solve this issue. i just use the Button Command Binding, no click event or IsEnabled stuff – blindmeis May 17 '11 at 10:43
  • Bingo! Thanks for this answer! – Ryan Oct 08 '15 at 19:48
0

You should bind command parameter property to any updatable property on viewmodel and can execute must use this command parameter to enable button. If command parameter's target is updated, binding will enable/disable based on return value of can excute.

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • Correct, the command parameter property is a property on the view model. It's not updateable because it's a logical operation set by logic in the model, but the model property is raising an event which flows through to PropertyChanged event for the property on the view model. Like I said, it is working, and it doesn't matter whether I make the property settable/updatable (I have tried), it's only *after* I focus (e.g. mouse click in form) on the view that the update occurs. It's like the operation is happening before the view has a chance to refresh itself. – si618 May 17 '11 at 05:22