WPF MVVM data binding broken after page navigation
I have a Page within a WPF navigation-style application that displays search results. The page contains several data-bound controls. The page itself works fine; it performs the search and returns results. The data-bound CheckBox controls work properly.
But if I click a result link and then click the back button to return to the results list, all of my CheckBox.IsChecked
data bindings are broken. Other data bound controls (ComboBoxes, DatePickers, etc.) continue to work as expected. Binding to other properties on the CheckBox control, like IsEnabled
, works properly. But the IsChecked
binding remains broken until I refresh the page.
Here's the XAML used for one of my CheckBox controls开发者_运维知识库:
<CheckBox IsChecked="{Binding IncludeNote}" Content="Note" IsEnabled="{Binding IsBusy, Converter={StaticResource boolNot}}" />
As you can see, there's nothing fancy going on here. But after navigating the WPF app forward or backward to the page, the IsChecked
binding will be broken while the IsEnabled
property will continue to work.
What's going on here? Is this a bug?
UPDATE: After playing around with some alternatives, I discovered that this problem also affects the ToggleButton control, which CheckBox derives from.
UPDATE2: And it's also broken for the TextBox.Text property.
Is there a way to "refresh" the data bindings for these controls? Or should I take some other approach to troubleshooting this issue?
Apparently, it is a bug. Here's the bug report on Microsoft Connect: Binding does not work after back / forward navigation.
The user who reported the bug, RQDQ, also mentioned his approach to dealing with the problem:
The workaround I've found is to manually call BindingOperations.SetBinding for all bindings in the Page during the Loaded event. This appears to work whether navigating explicitly or via history (back / forward operations).
This is only an issue in WPF4. Data binding works as expected in .NET 3.5.
I hope Microsoft fixes this quickly. This is a serious problem for navigation-style WPF apps.
A simple workaround is to set KeepAlive to true and then to ensure no issues with the VIewModel having bad state from a previous page load, the DataContext is set to a new instance each time in the Loaded event (i.e don't bind to an instance of the ViewModel in your Page.Resources dictionary for example as it will be persisted).
A standard approach we use for binding the page to the view model is with a simple behavior attached to the page.
public sealed class PageViewModelBehavior : Behavior<Page>
{
public Type DataType { get; set; }
protected override void OnAttached()
{
this.AssociatedObject.KeepAlive = true;
this.AssociatedObject.Loaded += this.AssociatedObjectLoaded;
this.AssociatedObject.Unloaded += this.AssociatedObjectUnloaded;
base.OnAttached();
}
protected override void OnDetaching()
{
this.AssociatedObject.Unloaded -= this.AssociatedObjectUnloaded;
this.AssociatedObject.Loaded -= this.AssociatedObjectLoaded;
base.OnDetaching();
}
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
if (this.DataType == null || !typeof(IPageViewModel).IsAssignableFrom(this.DataType))
{
throw new InvalidOperationException("PageViewModelBehavior.DataType is not set. Page: " + this.AssociatedObject.GetType().Name);
}
this.AssociatedObject.DataContext = Activator.CreateInstance(this.DataType);
// TODO: Call load on your page view model etc.
}
private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
{
// TODO: Call unload on your page view model etc.
// Allow the throw-away view model to be GC'd
this.AssociatedObject.DataContext = null;
}
}
This ensures the page is bound again each time the user navigates back to the page. This also allows you to use your favourite IOC container to create the ViewModel.
精彩评论