WPF - View model updating property via background worker, but view is not updating some elements until focused
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 LoadProject开发者_StackOverflow社区sLabel}"
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);
}
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.
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.
精彩评论