Region information gets lost

Aug 26, 2012 at 11:01 PM
Edited Aug 27, 2012 at 7:52 AM

The region information I expect to find in the RegionManager instance is not there.

My Shell defines two regions: LeftRegion and RightRegion.

In LeftRegion I inject a view that shows a DataGrid with Customers. I define an event CustomerSelectedEvent that when fired, the event handler injects a view CustomerSummaryView that in turns defines a third region named DetailsRegion.

DetailsRegion has two views registered: CustomerDetailsView and CustomerOrdersView.

I'm trying to navigate from CustomerDetailsView to CustomerOrdersView and viceversa.

The problem is that RegionManager doesn't have the information of the DetailsRegion where the current view is in!.

Here's the project layout:

Here's ModuleCatalog.xaml:

<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                          xmlns:sys="clr-namespace:System;assembly=mscorlib"
                          xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
    <Modularity:ModuleInfoGroup>
        <Modularity:ModuleInfo Ref="CustomersModule.xap"
                           ModuleName="CustomersModule"
                           ModuleType="CustomersModule.CustomersModule, CustomersModule, 
                                        Version=1.0.0.0, Culture=neutral,
                PublicKeyToken=null">
        </Modularity:ModuleInfo>
        <Modularity:ModuleInfo Ref="CustomersDataModule.xap"
                           ModuleName="CustomersDataModule"
                           ModuleType="CustomersDataModule.CustomersDataModule, 
                               CustomersDataModule, Version=1.0.0.0, Culture=neutral,
                PublicKeyToken=null">
            <Modularity:ModuleInfo.DependsOn>
                <sys:String>CustomersModule</sys:String>
            </Modularity:ModuleInfo.DependsOn>
        </Modularity:ModuleInfo>
    </Modularity:ModuleInfoGroup>
</Modularity:ModuleCatalog>

Here's the Shell.xaml:

   <Grid x:Name="LayoutRoot" Background="White">
      <Grid.RowDefinitions>
         <RowDefinition Height="50" />
         <RowDefinition />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="500" />
         <ColumnDefinition Width="3*" />
      </Grid.ColumnDefinitions>
      <TextBlock Text="My Prism Solution"
         FontSize="36"
         TextAlignment="Left"
         HorizontalAlignment="Center"
         Grid.ColumnSpan="2" />

      <Border Grid.Column="0" Grid.Row="1">
         <ContentControl x:Name="LeftRegion"
               prism:RegionManager.RegionName="LeftRegion"
               VerticalContentAlignment="Stretch"
               HorizontalContentAlignment="Stretch" />
      </Border>

      <Border Grid.Column="1" Grid.Row="1">
         <ContentControl x:Name="RightRegion"
               prism:RegionManager.RegionName="RightRegion"
               VerticalContentAlignment="Stretch"
               HorizontalContentAlignment="Stretch">
         </ContentControl>
      </Border>
   </Grid>

Here's CustomersDataModule:

public class CustomersDataModule : IModule {
   private readonly IUnityContainer container;
   private readonly IRegionManager regionManager;
   private readonly IEventAggregator eventAggregator;

   public CustomersDataModule(IUnityContainer container, IRegionManager regionManager, 
                                    IEventAggregator eventAggregator) {
      this.container = container;
      this.regionManager = regionManager;
      this.eventAggregator = eventAggregator;
   }

   public void Initialize() {

      this.eventAggregator.GetEvent<CustomerSelectedEvent>().Subscribe(this.CustomerSelected, true);

      // Register two Views in DetailsRegion
      this.regionManager.RegisterViewWithRegion("DetailsRegion",
                                                   () => this.container.Resolve<CustomerDetailsView>());
      this.regionManager.RegisterViewWithRegion("DetailsRegion",
                                                   () => this.container.Resolve<CustomerOrdersView>());
   }

   private void CustomerSelected(Customer currCustomer) {
      if (currCustomer == null)
            return;

      IRegion rightRegion = this.regionManager.Regions["RightRegion"];
      if (rightRegion == null)
            return;

      // Inject CustomerSummaryView in RightRegion, CustomerSummaryView will define DetailsRegion
      CustomerSummaryView view = rightRegion.GetView("CustomerSummaryView") as CustomerSummaryView;
      if (view == null) {
            view = this.container.Resolve<CustomerSummaryView>();

            rightRegion.Add(view, "CustomerSummaryView", true);
      }
      else {
            rightRegion.Activate(view);
      }

      CustomerSummaryViewModel viewModel = view.DataContext as CustomerSummaryViewModel;
      if (viewModel != null) {
            viewModel.CurrentCustomer = currCustomer;
      }
   }
}

Here's CustomerSummaryView:

   <Grid x:Name="LayoutRoot">
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto" />
         <RowDefinition Height="85*" />
      </Grid.RowDefinitions>

      <TextBlock Grid.Row="0"
                  Text="Customer Summary View"
                  TextWrapping="Wrap"
                  FontSize="18"
                  Margin="8" />
      <Button Content="Change View" 
               Command="{Binding ChangeView}"
               Height="23" 
               HorizontalAlignment="Left" 
               Margin="276,12,0,0"
               Name="btnChangeView" 
               VerticalAlignment="Top" 
               Width="82" />
      <Border Grid.Column="0" Grid.Row="1">
         <ContentControl x:Name="DetailsRegion"
               prism:RegionManager.RegionName="DetailsRegion"
               prism:RegionManager.RegionContext="{Binding CurrentCustomer}"
               VerticalContentAlignment="Stretch"
               HorizontalContentAlignment="Stretch">
         </ContentControl>
      </Border>
</Grid>

CustomerSummaryViewModel.cs

public class CustomerSummaryViewModel : INotifyPropertyChanged, IConfirmNavigationRequest {

   IRegionManager _regionManager;
   string _nextView = "CustomerDetailsView";

   public CustomerSummaryViewModel(IRegionManager regionManager) {
      ChangeView = new DelegateCommand(OnChangeView, CanChangeView);

      this._regionManager = regionManager;
   }

   public DelegateCommand ChangeView {
      get;
      private set;
   }

   private bool CanChangeView() {
      return true;
   }

   private void OnChangeView() {

   // At this point DetailsRegion should already be created, but it isn't. WHY?

      _regionManager.RequestNavigate
      (
            "DetailsRegion",
            new Uri(_nextView, UriKind.Relative),
            PostNavigationCallback
      );

      switch (_nextView) {
            case "CustomerDetailsView":
               _nextView = "CustomerOrdersView";
               break;

            case "CustomerOrdersView":
               _nextView = "CustomerDetailsView";
               break;
      }
   }

   // If we don't want to use any method, we use "a => { }" instead of PostNavigationCallback
   void PostNavigationCallback(NavigationResult navigationResult) {
      if (navigationResult.Result == true)
            MessageBox.Show("Navigation Successful");
      else
            MessageBox.Show("Navigation Failed");
   }

   private Customer currentCustomer;

   public Customer CurrentCustomer {
      get {
            return this.currentCustomer;
      }
      set {
            this.currentCustomer = value;
            this.NotifyPropertyChanged("CurrentCustomer");
      }
   }

   #region INotifyPropertyChanged Members

   public event PropertyChangedEventHandler PropertyChanged;

   private void NotifyPropertyChanged(string propertyName) {
      if (this.PropertyChanged != null) {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }

   #endregion

   #region IConfirmNavigationRequest Members

   public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) {
      throw new NotImplementedException();
   }

   #endregion

   #region INavigationAware Members

   public bool IsNavigationTarget(NavigationContext navigationContext) {
      return true;
   }

   public void OnNavigatedFrom(NavigationContext navigationContext) {
      MessageBox.Show("We are within CustomerSummaryViewModel");
   }

   public void OnNavigatedTo(NavigationContext navigationContext) {
      MessageBox.Show("We are within CustomerSummaryViewModel");
   }

   #endregion
}

When executing OnChangeView, RegionManager doesn't have any information about DetailsRegion, that's why the RequestNavigate fails.

Here's the entire project:

https://skydrive.live.com/#cid=228822BB183339A9&id=228822BB183339A9%21234

Silverlight 5 and Prism 4.1.

Rafael

Developer
Aug 27, 2012 at 4:09 PM

Hi Rafael,

So far, I found that the cause of this problem could be that your "CustomerSummaryView" was added as a scoped region, and in the OnChangeView method, the main RegionManager is used to perform the request of the navigation. Hence, the "DetailsRegion" will not be present in this RegionManager when the navigation is performed, causing it to fail.

To avoid this, for example you could try removing the createRegionManagerScope parameter when adding the "CustomerSummaryView" in your "CustomerDataModule".

I hope you find this useful,

Agustin Adami
http://blogs.southworks.net/aadami

Aug 27, 2012 at 7:33 PM

Thank you very much Agustin!!!!