Prism regions make it easy to change the layout of views within an application. A region is a logical placeholder associated with a specific layout control. Displaying a view in a region causes the view to be added to the layout control. But because the region and the layout control are loosely coupled, you can easily swap the layout control for a different one without having to change the modules in the application. For example, you can change from a tabbed view to a single document view.
While Prism makes it easy to add views to regions, it isn’t obvious how to close and remove a view from a region, especially from the UI. A common scenario is when you have multiple views displayed in tab control and the user can close individual views using a close button on the tab header. You can see this tabbed view style of UI in Internet Explorer and in Visual Studio.
This sample shows how this can be done. It’s based on the quick start solution that’s provided by the Prism 4.0 Template Pack. There are really only a couple of minor changes to the quick start solution’s shell. All of the real work is done in a new re-usable Blend action class called CloseTabbedViewAction.
Before we dig into that class, let’s take a look at the changes in the shell’s view. The TabControl that is used to layout views in the Main region is declared as:
1: <sdk:TabControl prism:RegionManager.RegionName="MainRegion" ...
2: prism:TabControlRegionAdapter.ItemContainerStyle=
3: "{StaticResource TabHeaderStyle}">
4: ...
5: </sdk:TabControl>
1: <Style x:Key="TabHeaderStyle" TargetType="sdk:TabItem">
2: <Setter Property="HeaderTemplate">
3: <Setter.Value>
4: <DataTemplate>
5: <StackPanel Orientation="Horizontal">
6: <Image ... />
7: <TextBlock ... />
8: <Button Content="x" ToolTipService.ToolTip="Close this view." ...>
9: <ei:Interaction.Triggers>
10: <ei:EventTrigger EventName="Click">
11: <local:CloseTabbedViewAction />
12: </ei:EventTrigger>
13: </ei:Interaction.Triggers>
14: </Button>
15: </StackPanel>
16: </DataTemplate>
17: </Setter.Value>
18: </Setter>
19: </Style>
1: private T FindVisualParent<T>( DependencyObject node ) where T : DependencyObject
2: {
3: DependencyObject parent = VisualTreeHelper.GetParent( node );
4: if ( parent == null || parent is T ) return (T)parent;
5:
6: // Recurse up the visual tree.
7: return FindVisualParent<T>( parent );
8: }
1: public class CloseTabbedViewAction : TriggerAction<FrameworkElement>
3: protected override void Invoke( object parameter )
4: {
5: RoutedEventArgs args = parameter as RoutedEventArgs;
6: if ( args == null ) return;
7:
8: // Find the parent tab item that contains the view to remove.
9: TabItem tabItem = FindVisualParent<TabItem>( args.OriginalSource as DependencyObject );
10:
11: // Find the parent tab control that represents the region.
12: TabControl tabControl = FindVisualParent<TabControl>( tabItem );
13:
14: if ( tabControl != null && tabItem != null )
15: {
16: // Get the view.
17: object view = tabItem.Content;
18:
19: // Get the region associated with the tab control.
20: IRegion region = RegionManager.GetObservableRegion( tabControl ).Value;
21: if ( region != null )
22: {
23: region.Remove( view );
24: }
25: }
26: }
1: private T Implementor<T>( object content ) where T : class
3: T impl = content as T;
4: if ( impl == null )
5: {
6: FrameworkElement element = content as FrameworkElement;
7: if ( element != null ) impl = element.DataContext as T;
9: return impl;
10: }
1: private bool NotifyIfImplements<T>( object content,
2: Action<T> action ) where T : class
3: {
4: bool notified = false;
6: // Get the implementor of the specified interface -
7: // either the view or the view model.
8: T target = Implementor<T>( content );
9: if ( target != null )
10: {
11: action( target );
12: notified = true;
13: }
14: return notified;
15: }
1: NavigationContext context = new NavigationContext( region.NavigationService, null );
2:
3: // See if the view (or its view model) supports the INavigationAware interface.
4: // If so, call the OnNavigatedFrom method.
5: NotifyIfImplements<INavigationAware>( view, i => i.OnNavigatedFrom( context ) );
6:
1: // See if the view (or its view model) supports the
2: // IConfirmNavigation interface.
3: // If so, call the ConfirmNavigationRequest method.
4: // If not, just remove the view from the region.
5: if ( !NotifyIfImplements<IConfirmNavigationRequest>( view,
6: i => i.ConfirmNavigationRequest( context,
7: canNavigate => { if ( canNavigate ) if ( region != null )
8: region.Remove( view ); } ) ) )
9: {
10: // Remove the view.
11: region.Remove( view );
12: }
I belive there is something wrong with wpf+prism or I'm just too stupid to use it.
Hi David,
thanks for a great article.. i am trying to compile the attached solution but it seems that there is a file missing ("ConfirmNavigationAction.cs").. Could you please throw some light on that..
Doh! Yes sorry, there was a file missing :-( I fixed the sample so it should compile ok now...
Hey David,
I just found your site and there are some great examples. I am battling with learning Prism and I am finding there are not many "full fledged" enterprise application samples out there. I am re-architecting a MS Access/SQL Server Enterprise application at work and I have decided to use Prism, MVVM, etc. instead of the DomainDataSource examples that are so prevalently found every where. I am also the only developer at my small company so I do not have any other developers to bounce ideas off of (one blog I read said that many large corporations are using Prism with teams of devs, but they don't put their apps up for samples). Currently I am going through the Prism 4.0 Training Kit, went through Karl Shifflett's MVVM In-The-Box training (which was awesome) and found your PrismRiaServices sample which I hope to convert to VS2010 and Prism 4 (unless you update it first :)) to get my head around this stuff. Anyway, I just wanted to say I am glad I found your site, and if you have a chance, could you update your PrismRiaServices sample for VS2010, Prism 4 and possibly have a menu with nav, and some updates in it? I know I am asking a lot, but I think there is a need for some "real world" prism examples out there, not just the questionare, or stock trader samples that come with it.
Thanks for your site and your work. There needs to be more real world examples out there for developers to start using Prism!
CyclingFoodmanPA
First of all I would like to say thank you for the templates. They are brillant.
I was struggling to understand how it all worked in Prism. I had seen the StockTraiderRI demo and source code and other quick start tutorials but they were so long and had so much code that I got lost.
I have installed your templates and run the quickstart template and things have started to make sense the code is simple and short.
I have a couple of questions and would appreciate if you could clarify them to me:
1 - For me to add a new module, should I add a new Silverlight Project or class library?
2 - Do you have any simple sample code where you use MEF instead of unity?
I really would like to use the new MEF support in Prism but not know how.
3 - I have noticed that you are passing a string ID to the event aggregator. Would it be possible to pass a whole object to the event aggreagato like DataItem?
4 - Do you have produced any video tutorials (or know of any video tutorial) where they show how to use MEF with PRESM v4?
Cheers
C
Is it possible to use a View model Locator class with yout templates?
I do not get any design data when in VS 2010.
David. Excellent work and the templates will be very useful. I am trying to construct a UX framework for Silverlight 4 using Prism 4. and am keen to provide a TabCotrol region for the main work area. I want the contained veiw/viewmodels to supply the tabItem header name as in your example. I have used the TabHeaderStle style from your ShellView.xaml (I am not using an image so have commented that peice out). I have also copied your CloseTabbedViewAction.cs to handle Tab closing.
However, though the style is being applied to the TabItems, TabItems are created with the 'x' Button showing and closing TabItems when pressed the TextBlock text is not binding to the required property in the View's datacontext.
Oviously I am missing something, First how does the 'binding' in your example bind to the ViewModels property as you do not reference the View (the tabItems content) in the Xaml. I would have expected something like <TextBlock Text="{Binding Content.DataContext.ViewName}"...../>.
I am pretty new to Xaml and WPF/Silverlight not to mention Prism so any help would be much apreciated
Disregard the previous post. Completely self inflicted ignorance at work
Great post David! This is something that everybody is still missing from PRISM. I did the same with Avalon and it is good to have a blog post that explains exactly how it works!
Very nice example how can do the same thing with MEF and Jounce Framework
ref : http://jounce.codeplex.com/