All posts by Agustin Adami

Prism: InteractionRequest and PopupModalWindowAction for WPF applications

Hi everybody,

As you might already know the Prism Library does not provide UI implementations for WPF interaction requests as out of the box, it only provides examples of these implementations for Silverlight, which can be used as a basis for the development of your own triggers and actions.

Hence, I created this blog with the purpose of demonstrating a possible approach to achieve this and to show how creating similar implementations in WPF should be quite straightforward.

Therefore, I created a sample which benefits from InteractionRequestTriggers and a custom TriggerAction<FrameworkElement> similar to the default PopupChildWindowAction class, which can be used to display a pop-up modal windows in Silverlight.

Introduction

The Prism documentation provides useful information on how a view model can make interaction requests directly to the view itself via an InteractionRequest object coupled with a behavior in the view. I recommend reading the following section of the Prism documentation to have a better understanding on how this works:

As mentioned in the Prism documentation:  the standard EventTrigger provided by Expression Blend can be used to monitor an interaction request event by binding to the interaction request objects exposed by the view model. However, the Prism Library defines a custom EventTrigger, named InteractionRequestTrigger, which automatically connects to the appropriate Raised event of the IInteractionRequest interface. Once the event is raised, the InteractionRequestTrigger will invoke the specified action. As mentioned before, the library only provides the PopupChildWindowAction class for Silverlight, which displays a pop-up window to the user. By default, the specific type of pop-up window displayed by the PopupChildWindowAction class depends on the type of the context object (e.g for a Notification context object, a NotificationChildWindow is displayed, while for a Confirmation context object, a ConfirmationChildWindow is displayed). Unfortunately the ChildWindow class used by these objects returned to display as part of the trigger action is only available for Silverlight.

Hence, when I created a custom TriggerAction similar to the PopupChildWindowAction provided with Prism, I had to change the returned type of the object to be displayed. In mi case I defined it as Window. The problem was that as a result of this change, the displayed popups would not be modals like with the ChildWindows implementations. Therefore, I also changed the way the action invokes its child window, and instead of calling the Window.Show() method in my PopupModalWindowActionBase’s Invoke method I used the Window.ShowDialog() method, which allows showing a modal window instead.

Basically these were all the changes I needed to obtain a similar implementation to the PopupChildWindowAction. Take into account that this implementation may change depending on your personal preferences and the requirements of your scenario. Although I believe it could be considered as a starting point.

Sample Application

sample

You can download the sample in my SkyDrive account, under the name “ PopupModalWindowActionSample“.

Note that this code is provided “AS IS” with no warranties and confers no rights.

I hope you find it useful!

Updated 2012/05/15

Additional Considerations

After reviewing this approach we realize that some considerations should be taken for some scenarios. Particularly if passing a custom view using the PopupModalWindowAction‘s ChildWindow property. When doing so, you will find that you can’t show the dialog window more than once. This occurs as the view is of type window which won’t allow showing a windows once this is closed. Currently this could be achieved by creating a new custom window in the PopupModalWindowAction each time the InteractionRequest is raised, this is why the default windows implementation can be re open without problems.

Also if you want to keep the state of the view, this can be achieved without problems by passing the same instance of the Context object when raising the InteractionRequest.

Additionally, we have been thinking that a possible approach to reopen a custom window using the ChildWindow property may be possible passing a custom view to the ChildWindow property, and creating a new window each time the interaction is raised and use it to wrap the custom view as the content of this new window.

Note: Also for those interested in this subject, you could check the following related blogpost which portrays a different approach, and I believe it might be helpful when showing popupwindows with custom views also using interaction requests in WPF:

Prism: Using CompositeCommands, DelegateCommands and IActiveAware

Hi everybody,

Recently I found that these topics were often discussed in the Prism forum and, although there are samples already using these features provided by Prism as out of the box (e.g. Commanding Quickstart and Stock Trader Reference Implementation.) I thought that a small sample portraying all these functionalities together could become handy, especially one that shows the different behaviors of CompositeCommands (e.g. with and without the monitorCommandActivity parameter).

Introduction

As many of you might have read from the Prism documentation, CompositeCommands can be connected to several child commands so that when a CompositeCommand is invoked, its child commands are also invoked. Also, these commands support enablement, as they can listen to the CanExecuteChanged event of each one of its child commands. This way, when any call to the CanExecute method of the child commands returns false, the CompositeCommand’s CanExecute method will also return false and the corresponding invoker will be disabled.

Additionally, if its child commands implements the IActiveAware interface, the CompositeCommands provide the possibility to only execute and evaluate the corresponding Execute and CanExecute methods of the child commands that have its IsActive property set to true.

CompositeCommands can be configured to evaluate the active status of child commands (in addition to the CanExecute status) by specifying true to the monitorCommandActivity parameter in the constructor. In those cases DelegateCommands will become handy as they already implement the IActiveAware interface.

Basically, I created this sample with the purpose to highlight  the different possibilities that could be achieved when benefiting from these available functionalities.

Sample Application

pic

The Solution is a small application, that contains a shell project, one module and an infrastructure project.

There you can find that the module’s Initialize method registers two views in a region which is attached to a ListBox control in the shell. As their view model implements the IActiveAware interface, when one view is selected their active state is changed accordingly. Particularly, I decided to use a ListBox as the region placeholder, so it could be possible to appreciate the state of the different views displayed in the region simultaneously, although it could be easily replaced with any control that inherits from the Selector class.

Each of these views contain a pair of buttons which are bound to its corresponding DelegateCommand and they also contain a CheckBox which will allow to select the returned value of each CanExecute method in order to appreciate the different behaviors when those values are changed.

All these inner buttons’ DelegateCommands will be registered as child commands of the two CompositeCommands that are defined as static classes in the Infrastructure project so they could become globally available. One of them (GlobalCommands.MyGlobalCommand) sets its monitorCommandActivity parameter value to true and the other one (GlobalCommands.MyResetCommand) sets it to false.

You can download the sample application from my SkyDrive account, under the name “CompositeCommand&IActiveAware“.

Note that this code is provided “AS IS” with no warranties and confers no rights.

I hope you find it useful!

Prism Region Navigation and Scoped Regions

There have been some discussions (e.g. this one and this one) where people using Prism wonder how to achieve Region Navigation using Scoped Regions.

Therefore, we thought of an alternative approach to achieve the scoped regions scenario while keeping the benefits of navigation. The approach consists of obtaining the RegionManager returned by the call of the Region.Add method, through the NavigationResult passed in the navigation callback from the RequestNavigate method.

As the result you will be able to navigate with scoped regions, like in the following code snippet:

1 this.regionManager.RequestNavigate( 2 "MainRegion", 3 new Uri("HelloWorldView?createRegionManagerScope=true", UriKind.Relative), 4 (result) => 5 { 6 var myRegionManager = result.ExtractRegionManager(); 7 myRegionManager.RequestNavigate("NestedRegion", new Uri("View1", UriKind.Relative)); 8 });

Implementation details:

To achieve this scenario, the first thing we needed was a NavigationResult that allows us to pass the RegionManager instance. For this we created a CustomNavigationResult class that inherits from NavigationResult, but with another constructor which added an IRegionManager parameter, that sets its RegionManager property.

Next, to obtain the region manager returned by the call of the Region.Add method, we needed the LoadContent method in the RegionNavigationContentLoader to return a Tuple<object,IRegionManager> instead of only an object view. Therefore we created a CustomRegionNavigationContentLoader and its corresponding interface. Also we decided to pass a parameter (named: “createRegionManagerScope“) with the view when navigating to,  this way we can verify if its value is “true” to specify if a new region manager must be created (in case scoped regions are used).

This changes can be seen in the following code snippet:

1 public Tuple<object, IRegionManager> LoadContent(IRegion region, NavigationContext navigationContext) 2 { 3 (...) 4 if (view != null) 5 { 6 return new Tuple<object, IRegionManager>(view, region.RegionManager); 7 } 8 9 view = this.CreateNewRegionItem(candidateTargetContract); 10 11 bool createRegionManagerScope = navigationContext.Parameters["createRegionManagerScope"] == "true"; 12 13 var rm = region.Add(view, null, createRegionManagerScope); 14 15 return new Tuple<object, IRegionManager>(view, rm); 16 } 17 (...)

Also, as the ExecuteNavigation method in the RegionNavigationService is the one that calls the LoadContent method, we had to create a CustomRegionNavigationService, to let it receive the returned tuple. In this same method instead of passing the default NavigationResult to the navigationCallback we pass our CustomNavigationResult with the returned region manager as a parameter.

You can find this modifications in the following code snippet:

1 private void ExecuteNavigation(NavigationContext navigationContext, object[] activeViews, Action<NavigationResult> navigationCallback) 2 { 3 (...) 4 Tuple<object, IRegionManager> tuple = this.regionNavigationContentLoader.LoadContent(this.Region, navigationContext); 5 6 // Raise the navigating event just before activing the view. 7   this.RaiseNavigating(navigationContext); 8 9 this.Region.Activate(tuple.Item1); 10 11 // Update the navigation journal before notifying others of navigaton 12   IRegionNavigationJournalEntry journalEntry = this.serviceLocator.GetInstance<IRegionNavigationJournalEntry>(); 13 journalEntry.Uri = navigationContext.Uri; 14 this.journal.RecordNavigation(journalEntry); 15 16 // The view can be informed of navigation 17   InvokeOnNavigationAwareElement(tuple.Item1, (n) => n.OnNavigatedTo(navigationContext)); 18 19 navigationCallback(new CustomNavigationResult(navigationContext, true, tuple.Item2)); 20 (...) 21 }

Finally to avoid modifying the prism library we used the export attribute at the top of our custom classes (CustomRegionNavigationContentLoader and CustomRegionNavigationService) which allows the MefBootstrapper to provide these classes as a default implementation.

How to use:

If you apply this changes, then you will be able to call the RequestNavigate method with a delegate method as the navigation callback, which will receive a NavigationResult. The only problem is that you will have to cast the NavigationResult to our CustomNavigationResult class, in order to obtain the desired RegionManager. Hence we created an extension method called ExtractRegionManager in the NavigationResultExtension class, which will make things easier.

Additionally we added the possibility to retrieve the new RegionManager and pass it to the corresponding ViewModel using the RegionManagerAwareBehavior proposed by Damian Cherubini in his blog post about: Regions inside DataTemplates in Prism v4 using a region behavior

Sample Application:

For those interested, we prepared a sample application that portrays the aforementioned modifications. This sample shows two instances of a view being navigated to using the RequestNavigate method, inside a region in a TabControl. Each of these views has a “NestedRegion“, and because this region names will be duplicated they must be defined as scoped regions.
Also in the OnNavigatedTo method inside the HelloWorldViewModel, we added another view to the “NestedRegion” using the RegionManager obtained through the RegionManagerAwareBehavior.

You can find the application sample in my Skydrive account under the name NavigationWithScopedRegionSampleWithRMAware.

Let me know if you have used it.

Matching contract names and view names in Prism region navigation

I’ve seen some questions and issues in the Prism forum, mentioning that they receive exceptions such as “View already exists in region” when trying to navigate back to a view in a region. This error seems to happen when exporting a view with a contract name (string) different than the name of the view’s type.

Also, there have been some discussions where people confuse the contract type with the name a view is added to a region with, and so they try to retrieve it using the Region.GetView method, which misleadingly returns null.

In this post I’ll propose a possible solution in the form of a custom region navigation content loader, which might address both concerns.

Contract name vs type name

When navigating back to a view in a region, once the navigation request is confirmed, the RegionNavigationContentLoader.LoadContent method should return the view that is target of the navigation request, which should then be activated by the navigation service (this can be seen in the RegionNavigationService.ExecuteNavigation method; you can check more about the region navigation pipeline in the following blog post from Karl Shifflett).

To find the correct view, the content loader calls the GetCandidatesFromRegion method, which returns the set of candidates by comparing the requested contract name with the type Name or FullName of the views present in the region. I speculate this is because, for a given instance of an object, it’s not possible out of the box to obtain the contract name with which it has been registered in the container.

image

Hence, when registering views in the container with a contract name that differs from the view type Name or FullName, no matches will be found, and a new instance will be retrieved from the container. As a result it’s common to receive the “View already exists” exception (raising the NavigationFailed event) if your views were defined as singletons, or navigate to a new instance of the view if it weren’t; both of them are probably not the desired results.

1 protected virtual IEnumerable<object> GetCandidatesFromRegion(IRegion region, string candidateNavigationContract) 2 { 3 if (region == null) throw new ArgumentNullException("region"); 4 return region.Views.Where(v => 5 string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) || 6 string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal)); 7 }

Contract name vs view name in region

When you navigate to a new view (i.e. a view that is not already present in the region), an instance of the requested view will be retrieved from the container based on the supplied contract name (which is passed in the form of a uri). The result of this navigation request will be that the view will be added to the region. This is achieved by calling this overload of the Region.Add method (inside the RegionNavigationContentLoader.LoadContent method), which adds the view without specifying a name. Therefore if you later try to retrieve the view instance that was added to the region using a specific name through the Region.GetView method, the result will be null.

image

It is a common misconception to believe that, for a view that was navigated to, its name inside a region is equal to its contract name. These are in fact two separate concepts: the former is the name that identifies a view inside a region, where the latter is the name that identifies an object registration in the container.

1 public object LoadContent(IRegion region, NavigationContext navigationContext) 2 { 3 (...) 4 5 var view = acceptingCandidates.FirstOrDefault(); 6 7 if (view != null) 8 { 9 return view; 10 } 11 12 view = this.CreateNewRegionItem(candidateTargetContract); 13 14 region.Add(view); 15 16 return view; 17 }

Yet, in some cases, they’re both referring to a view. So why not make them the same?

Proposed modifications

Taking these problems into account, we realized that matching the view’s name inside a region with the contract name it was registered with might serve as a solution for both of them. This can be done by using a custom RegionNavigationContenLoader that replaces the default implementation provided by Prism.

As mentioned above, the LoadContent method will create a new view and add it to the region if none of the views in the region can be the target of the navigation request, hence we modified this method to add the view to the region also with a view name. This was implemented by calling this overload of the Region.Add method, which adds a view to a region with a name. We use the contract name (extracted from the Navigation Context) as the view name inside the region added.

While this is useful in case of adding only one instance of a view in a region through navigation, this might cause problems in scenarios where you need to navigate to multiple instances of a view with the same contract name (which would cause an error since there can’t be two views with the same name in a given region). So, to avoid this we decided to add an additional parameter named “matchname” to the Uri target argument, used in the RequestNavigate method, to decide whether we want to include a name for the view being added in the region. Also, we included two more parameters (“prefix” and “suffix”), which allow to add a prefix or suffix to the view name.

1 public object LoadContent(IRegion region, NavigationContext navigationContext) 2 { 3 bool matchName = navigationContext.Parameters["matchname"] == "true"; 4 5 (...) 6 7 string candidateTargetContract = this.GetContractFromNavigationContext(navigationContext); 8 string candidateTargetName = matchName ? (navigationContext.Parameters["prefix"] + candidateTargetContract + navigationContext.Parameters["suffix"]) : candidateTargetContract; 9 10 (...) 11 12 if (matchName) 13 { 14 region.Add(view, candidateTargetName); 15 } 16 else 17 { 18 region.Add(view); 19 } 20 21 return view; 22 } 23 (...)

Now we are able to retrieve views by their name using the same contract name (and a prefix/suffix, if any) we specified when navigating, by using the custom content loader we’re mentioning. This can be achieved for example with the following code:

1 (...) 2 var parameters = new UriQuery(); 3 parameters.Add("matchname", "true"); 4 parameters.Add("prefix", "MyPrefix"); 5 6 this.regionManager.Regions["MainRegion"].RequestNavigate("View1" + parameters.ToString()); 7 var myView = this.regionManager.Regions["MainRegion"].GetView("MyPrefixView1"); 8 (...)

Now taking this into account, we can additionally modify the GetCandidatesFromRegion method to also retrieve navigation candidates based on the name of a view in the region. This will make it possible to navigate back to a view in a region without the view necessarily being exported in the container with a contract name equal to its type name. The below code shows this modification:

1 protected virtual IEnumerable<object> GetCandidatesFromRegion(IRegion region, string candidateNavigationContract) 2 { 3 var candidates = new List<object>(); 4 var candidateBasedOnName = region.GetView(candidateNavigationContract); 5 6 if (candidateBasedOnName != null) 7 { 8 candidates.Add(candidateBasedOnName); 9 } 10 11 if (region == null) 12 { 13 throw new ArgumentNullException("region"); 14 } 15 16 candidates.AddRange(region.Views.Where(v => 17 string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) || 18 string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal))); 19 20 return candidates; 21 }

Looking at the code, it can be seen that we now add a candidate based on the view’s name in the region, before adding the views with a matching type Name or type FullName. To obtain the candidateBasedOnName we call the region.GetView method with the contract name (plus prefix/suffix, if any) as the parameter. This is possible due to the prior modifications, since they allow previously navigated to views to have a view name inside the region.

Now, this might sound a little confusing altogether. Let’s show it in a sample!

Sample application

We created a little sample that implements all these modifications in the ModifiedRegionNavigationContentLoader class. We exported this class in the container, which replaces the default implementation of IRegionNavigationContentLoader provided by Prism.

We then navigate to our main view, which allows you to navigate to another view on the press of a button. This navigation is done passing a Uri target that contains a prefix parameter, the “matchname” parameter set to true and the contract name of the view. Also the navigate command will retrieve the previously navigated view using the Region.GetView method, and will inform about this. Once in “View1” you will be able to navigate back using the navigation journal.

image

You can find the aforementioned sample in my SkyDrive account, under the name ModifiedRegionNavigationContentLoaderSample.

This code is provided “AS IS” with no warranties and confers no rights.

I hope you find this helpful.

Prism v4: RegionContext lost when removing a view from a region

There have been some discussions in the Prism forums (for example this one and this one), where it was reported that removing a view that shares a region context with other views in a region, causes the region context in all the views in that region to be lost. This is because in the BindRegionContextToDependencyObjectBehavior, when a view is removed from a region, the DetachNotifyChangeEvent method is called after the SetContextToViews method, which is responsible for setting the view’s RegionContext attached property to null. Hence, the subscription to the ViewRegionContext_OnPropertyChangedEvent is not removed when the null value is set, and so this value will be spread to all the view’s RegionContext in that region, thus losing the region context data.

In this work item, the user mstrobel suggested that, by inverting the order of these method calls, in the BindRegionContextToDependencyObjectBehavior class from the Prism library, this problem is solved. We’ve reproduced a similar scenario, applying this workaround, and found that it worked correctly. Additionally, we thought of a possible way to achieve this without modifying the Prism source code, and we ended in the following sample (you can find it in my skydrive account, under the name RegionContextLostSample.)

In this sample, the implementation we used to extend this functionality is contained  in the class BindRegionContextToDependencyObjectBehaviorWithFix, which is similar to the original behavior, but applies the aforementioned workaround:

1 (...) 2  private void Views_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 3 { 4 if (e.Action == NotifyCollectionChangedAction.Add) 5 { 6 SetContextToViews(e.NewItems, this.Region.Context); 7 this.AttachNotifyChangeEvent(e.NewItems); 8 } 9 10 else if (e.Action == NotifyCollectionChangedAction.Remove && this.Region.Context != null) 11 { 12 this.DetachNotifyChangeEvent(e.OldItems); 13 SetContextToViews(e.OldItems, null); 14 } 15 } 16 (...)

Please note that, in order for this approach to work, you need to override the ConfigureDefaultRegionBehaviors method in your bootstrapper in order to replace the default implementation of the BindRegionContextToDependencyObjectBehavior with the aforementioned BindRegionContextToDependencyObjectBehaviorWithFix. Here is an example:

1 ConfigureDefaultRegionBehaviors() 2 { 3 var defaultRegionBehaviorTypesDictionary = ServiceLocator.Current.GetInstance<IRegionBehaviorFactory>(); 4 5 defaultRegionBehaviorTypesDictionary.AddIfMissing(BindRegionContextToDependencyObjectBehaviorWithFix.BehaviorKey, typeof(BindRegionContextToDependencyObjectBehaviorWithFix)); 6 7 return base.ConfigureDefaultRegionBehaviors(); 8 } 9  

Let me know if you have used it.

Prism: How to provide a name to Views added with the view discovery approach

It is known that Prism doesn’t support a way to specify a name to a View added to a region with the RegisterViewWithRegion method (view discovery approach). Hence, views added to regions using this approach do not provide the possibility of, for example, being retrieved from the region using the GetView method.

We thought of a possible way to accomplish this without modifying the Prism source code, and we ended in the following sample (you can find it in my skydrive account, under the name RegisterViewWithRegionWithName.)

In this sample, two views of the same type are registered into a region using the RegisterViewWithRegion method. One uses the default RegisterViewWithRegion method, and the other one uses an extension method we created, which contains an additional parameter to set the specific name for the view to be registered.

Implementation details

The implementation we used to extend this functionality is contained in the following classes, which are placed in the Infrastructure project in the sample:

  • ViewWithName
  • AutoPopulateRegionBehaviorWithName
  • RegionViewRegistryExtensions

The ViewWithName class wraps the view and provides a name property to identify that view, as you can check in the code snippet below:


1 public class ViewWithName 2 { 3 public ViewWithName(object view, string name) 4 { 5 this.View = view; 6 this.Name = name; 7 } 8 9 public object View { get; set; } 10 11 public string Name { get; set; } 12 } 13

The RegionViewRegistryExtensions class contains the extension methods that provide a name parameter to the RegisterViewWithRegion method:

1 public static class RegionViewRegistryExtensions 2 { 3 public static void RegisterViewWithRegion(this IRegionViewRegistry registry, string regionName, Func<object> getContentDelegate, string viewName) 4 { 5 var view = getContentDelegate(); 6 var viewWithName = new ViewWithName(view, viewName); 7 registry.RegisterViewWithRegion(regionName, () => viewWithName); 8 } 9 10 public static void RegisterViewWithRegion(this IRegionViewRegistry registry, string regionName, Type viewType, string viewName) 11 { 12 var view = ServiceLocator.Current.GetInstance(viewType); 13 var viewWithName = new ViewWithName(view,viewName); 14 registry.RegisterViewWithRegion(regionName, () => viewWithName); 15 } 16 (…) 17 } 18

And finally, the AutoPopulateRegionBehaviorWithName class inherits from the AutoPopulateRegionBehavior class and overrides the AddViewIntoRegion method to support the scenario of adding a view specifying a name. This is achieved by attempting to cast the object provided as the view to be added to the ViewWithName class; if it can be casted (which means that the view has been registered using one of the extension methods defined above), then the view is added with a name, and if it can’t be casted, then the base implementation of the method is called. You can check the code that illustrates this below:


1 [Export] 2 public class AutoPopulateRegionBehaviorWithName : AutoPopulateRegionBehavior 3 { 4 [ImportingConstructor] 5 public AutoPopulateRegionBehaviorWithName(IRegionViewRegistry regionViewRegistry) 6 : base(regionViewRegistry) 7 { 8 9 } 10 11 protected override void AddViewIntoRegion(object viewToAdd) 12 { 13 var viewWithName = viewToAdd as ViewWithName; 14 15 if (viewWithName == null) 16 { 17 base.AddViewIntoRegion(viewToAdd); 18 } 19 else 20 { 21 this.Region.Add(viewWithName.View, viewWithName.Name); 22 } 23 } 24 } 25

Please note that, in order for this approach to work, you need to override the ConfigureDefaultRegionBehaviors method in your bootstrapper in order to replace the default implementation of the AutoPopulateRegionBehavior with the aforementioned AutoPopulateRegionBehaviorWithName. For example:


1 protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors() 2 { 3 ServiceLocator.Current.GetInstance<IRegionBehaviorFactory>().AddIfMissing(AutoPopulateRegionBehaviorWithName.BehaviorKey, typeof(AutoPopulateRegionBehaviorWithName)); 4 return base.ConfigureDefaultRegionBehaviors(); 5 } 6

This way, by using extension methods and benefiting from Prism’s extensibility features (e.g. region behaviors), you will be able to achieve the scenario of specifying a name to views added to regions using the view discovery approach without having to recompile the Prism Library.

Let me know if you have used it.