Search code examples
wpfbindingmefregion

MEF: How can I disable reevaluation of bindings when leaving a view


I encountered an interesting situation in my MEF application. The main workspace is a region registered on a ContentControl with only one active view at a time. Setup and navigation is working just fine. Now what I observed is that when I'm changing the view on this MainRegion all my bindings to the viewmodel are evaluated again.

To check this, I added a counter on my view model to see how often it is loaded. When I'm changing View A -> View B -> View A, then the counter will be 3:

  • entering View A
  • leaving View A
  • entering View A

I'm using the normal call to activate a region:

region.Activate(view);

When debugging this issue I saw that when activating a region, the old one gets deactivated, eventually setting the

ContentControl.Content = null;

This seems to modify the visual tree and reevaluates all bindings on the old view.

It seems to be a mixture of a MEF and WPF problem. Is there any way to prevent the evaluation of bindings when activating a new region or on the WPF side prevent re-evaluation of bindings when ContentControl.Content becomes null?

I found a similar question but without an answer here: WPF: disable bindings update on detach


Solution

  • This issue seems to be a problem of the ContentControl itself, see this post for further information: http://blogs.microsoft.co.il/tomershamam/2009/09/11/wpf-performance-sweets-contentcontrolcontent-null/

    To avoid performance issues while switching between views in a regions you can keep the view in the visual tree by using another control for the region. Replace the ContentControl by a modified ItemsControl. I find a solution in this post: https://vslepakov.blogspot.de/2014/09/navigate-faster-with-prism-and-wpf.html. It works with hiding old views and show new views.

    I modified this example like this:

    public class RegionItemsControl : ItemsControl
    {
       protected override bool IsItemItsOwnContainerOverride(object item)
       {
          return false;
       }
    
       protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
       {
          base.PrepareContainerForItemOverride(element, item);
          ((ContentPresenter)element).ContentTemplate = ItemTemplate;
       }
    }
    

    Add it to your shell and mark it as region:

        <controls:RegionItemsControl 
           prism:RegionManager.RegionName="MainRegion">
           <controls:RegionItemsControl.ItemsPanel>
               <ItemsPanelTemplate>
                   <Grid />
               </ItemsPanelTemplate>
           </controls:RegionItemsControl.ItemsPanel>
           <controls:RegionItemsControl.ItemTemplate>
               <DataTemplate>
                   <ContentControl Content="{Binding}"/>
               </DataTemplate>
           </controls:RegionItemsControl.ItemTemplate>
       </controls:RegionItemsControl>
    

    The navigation is handled by an event, which triggers the following code:

    private FrameworkElement _lastView = null;
    
    private bool LoadAndActivateWorkspaceAreaView(Type requestedViewType, IRegion region)
    {
        var viewToActivate = region.Views.FirstOrDefault(viewItem => viewItem.GetType() == requestedViewType) as FrameworkElement;
        if (viewToActivate == null)
        {
            viewToActivate = MefContainer.GetExportedValue(requestedViewType) as FrameworkElement;
            if (viewToActivate == null)
                throw new InvalidOperationException("view not found!");
    
            viewToActivate.Visibility = Visibility.Collapsed;
    
            region.Add(viewToActivate); // Adds new view to RegionItemsControl
        }
    
        if (_lastView != null)
            _lastView.Visibility = Visibility.Collapsed;
    
        _lastView = viewToActivate;
        _lastView.Visibility = Visibility.Visible;
    }
    

    One issue of this solution is, that bindings of "old" views which are stored in RegionItemsControl are re-evaluated once on adding a new view to the region. This seems to be an issue of the ItemsControl I think.