WPF: Replicating MDI Application Behaviour

I decided to use Prism, aka Microsoft Composite Application Guidance For WPF and Silverlight.

This is very much like using a supersonic jet fighter to pop down to the local shops but its effective, not very hard and does not require you to write reams of fragile custom code. You just need to spend a bit of time getting used to Prism. Now that’s not a trivial task, but you should have covered the fundamental concepts within a working week, assuming you have some prior knowledge of Dependency Injection.

The huge advantage to you is that Prism is written by people who are much better programmers than you or I are, so the code is robust, whereas writing a custom MDI Window Manager is out of the league of most Intermediate-level developers (most of us).

Very simply, Prism allows the development of Modular Applications in which the User Interface of an application is decomposed into loosely-coupled Modules, each of which occupies a specific area on the screen, known as a Region.

No window (known in Prism as a View) can move outside the Region in which it is contained. This replicates the MDI-like behaviour you’re looking for.

Prism is also a library of Best Practices – it’s produced by the Microsoft Patterns and Practices team – so you get heaps of built-ins for free. And there’s a helpful forum where others using Prism are happy to help us newbies.

You can also have multiple windows (views) open simultaneously if you want, but this will render as a tabbed interface which is not exactly the same as MDI.

I embarked upon this approach and have been successful in producing a MDI-like app. It wasn’t too hard and I learnt heaps along the way. Go for it.

Update 27-Jan-2010

Ahmed in comments asked me to post the code for my MDI sample.

All I did was use a WPF ContentControl for each Prism Region. A ContentControl can only display one View at any one time, so each time you activate a View the one currently in the Region becomes invisible and the new one you activated becomes visible.

This provides the illusion that there is only one window present at any one time. In fact, all the Views you activated are still there, just invisible until you explictly RegionManager.Remove the View from the Region. This is kind of good though, because the ‘invisible’ views are there in memory and so become visible again instantly you do RegionManager.Activate on them. Effectively this is caching the Views for performance.

So here’s my Shell Window markup:

Grid/
ContentControl Grid.Column="1" Grid.Row="1" cal:RegionManager.RegionName="MainRegion"
VerticalContentAlignment="Top"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"

ContentControl Grid.Column="1" Grid.Row="1" Name="AdminRegion"
cal:RegionManager.RegionName="AdminRegion"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Grid.ColumnSpan="2"

ContentControl Grid.Column="2" Grid.Row="1" cal:RegionManager.RegionName="ToDoListRegion"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"

/Grid

So you notice I have three ContentControls, one for each of my regions. I also have a Menu with a gazillion MenuItems (not shown).

Each Menu Item is bound to a Prism DelegateCommand like so:

'''
''' Initialise Commands On The Module Menu
'''
Private Sub InitialiseContentCommands()
Me.ContentShowLocalContentCommand = _
New DelegateCommand(Of Object)(AddressOf Me.ShowLocalContent)
Me.ContentShowModuleContentCommand = _
New DelegateCommand(Of Object)(AddressOf Me.ShowModuleContent)
Me.ContentShowModuleDetailsCommand = _
New DelegateCommand(Of Object)(AddressOf Me.ShowModuleDetails)
End Sub

And here’s the ViewModel code related to those DelegateCommands to show the View:

#Region "Module View Injection..."

'''
''' Show Module Content View
''' Update MainRegion with Module Content View
'''
Public Sub ShowModuleContent()
Dim mainRegion As Region = mainView._regionManager.Regions(RegionName.MainRegion)

Dim existingView As Object = mainRegion.GetView(ViewName.ModuleContentView)

If (existingView Is Nothing) Then

Dim moduleContentViewModel As IModuleContentViewModel = _
mainView._container.Resolve(Of IModuleContentViewModel)()
mainRegion.Add(moduleContentViewModel.View, ViewName.ModuleContentView)
mainRegion.Activate(moduleContentViewModel.View)
Else
mainRegion.Activate(existingView)
End If

End Sub

#End Region

Also in the Shell ViewModel I handle a Prism CompositeEvent that is published every time a View is closed in the MainRegion (i.e. by the User clicking on the Close Button for a View. This removes (destroys) the View as opposed to just letting it get overwritten by the next View that gers activated.

#Region "Event Handlers..."

'''
''' Handler for Composite CloseViewEvent
'''
Public Sub CloseViewEventHandler(ByVal closeViewInfo As CloseViewInfo)
Dim regionManager As RegionManager = _
mainView._container.Resolve(Of IRegionManager)()

'Find the region
Dim regionQuery = _
From regn In regionManager.Regions _
Where regn.Name = closeViewInfo.RegionName _
Select regn

' Find the View in the region
Dim region As Region = regionQuery.Single()
Dim view As Object = region.GetView(closeViewInfo.ViewName)

' Remove the view
If view IsNot Nothing Then
region.Remove(view)
End If

' Since ContentControl can only show one view at a time, if any one View is closed
' then no views are visible
Me.AllViewsClosed()
End Sub

I also have a CloseAll MenuItem which closes all the cached (invisible) Views in the Main Region, to give the users and the application the opportunity to clean up all those Views. This is done by having every View Register its CloseButton Command with the a Prism CompositeCommand called CloseAllCommand and having every View provides a handler which Publishes a Prism Event Aggregation event I defined called CloseView.

The Shell ViewModel subscribes to the Event Aggregation CloseView event and removes the calling View from the MainRegion. Since all Views do this, all Views are Removed

In XAML:

MenuItem Header=”_ Close Background Windows” Name=”mniCloseAll”
Command=”{x:Static inf:GlobalCommand.CloseAllCommand}” FontSize=”9″ /

In the ViewModel:

”’
”’ Subscribe to Event Aggregator CloseViewEvent
”’
”’
Private Sub SubscribeCloseViewEvent()

Dim ea As EventAggregator = mainView._container.Resolve(Of IEventAggregator)()

‘ Subscribe to CloseViewEvent
Dim closeViewevent As CloseViewEvent = ea.GetEvent(Of CloseViewEvent)()

If _closeViewSubscriptionToken IsNot Nothing Then
closeViewevent.Unsubscribe(_closeViewSubscriptionToken)
End If

‘ Perform the event on the UIThread
‘ keepSubscriberReference Alive is false. This means the event maintains a
‘ weak reference to the subscriber instance, allowing the garbage collector to
‘ dispose the subscriber instance when there are no other references to it.
‘ When the subscriber instance gets collected, the event automatically unsubscribes.
_closeViewSubscriptionToken = _
closeViewevent.Subscribe(AddressOf CloseViewEventHandler, ThreadOption.UIThread, False)
End Sub

So hopefully you can see that each region only has one View visible at any one time (kind of like a MDI application that I am porting) and because the Views are situated in a Region, they can’t move out of there, which is MDI-like behaviour. No letting your users drag windows all over the screen and losing them.

Careless users.

Advertisements

Tags: ,

6 Responses to “WPF: Replicating MDI Application Behaviour”

  1. Ahmed Says:

    Hi can you please provide me with a sample of doing an MDI application using the composite application guidance (WPF CAG)?

    Thanks in Advance
    Ahmed

    • baraholka1 Says:

      Hi Ahmed,

      Thanks for visiting BTWT.
      I’ll sketch it out in the next day or two.
      I just wanted to give a quick reply now so that you know its coming.

      All The Best,

      Barra

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: