Displaying modal content in WPF

A common question asked by WPF developers is “How do I display modal content in a windows presentation foundation application?”.

Usually this question comes with requirements such as:

  1. The ability to display arbitrary content.
  2. Making the modal content modal to a particular view rather than the entire application.
  3. Stopping the user interacting with the content that is behind the modal content.
  4. making this work in an MVVM compliant way.

There are a number of questions on stackoverflow which are a variation on this theme. The answer usually involves interfaces and a lot of architecture to decouple the view from the viewModel. All of this seemed overly complicated to me and something just didn’t seem right with controlling this explicitly in the viewModel (after all, modality is a feature of the view not the viewModel; can anyone explain to me what modal data is exactly?)

The solution

One solution I found was to create a custom UserControl that hosts two pieces of content and uses a property to show and hide the modal content. This stackoverflow post provides an example of this solution. I managed to hack together a similar solution that derived from the ContentControl class based on the code provided but as I developed it I noticed a couple of issues.

First of all, the content behind the modal popup was disabled. This isn’t a major problem but I have noticed that whenever this question is asked people do not want this to happen. I can also see this being a problem if an application is using custom themes as it may not have disabled states defined for all controls (a control may not need to be disabled under any other circumstances except when used by this control).

Second of all, the solution derived from UserControl which is not right. This problem isn’t about visual representation; it is about application behaviour. I also noticed that the control was very dependant on the items that were contained in the user controls xaml; If the controls or the grid contained in the user control were changed or moved about then the control would break. I had the same problem even after I updated my version of the control to derive from ContentControl because the user could, in theory, re-template the control causing it to break. The control was very brittle.

The fact that this solution derived from ContentControl just didn’t seem right to me so I decided to develop this into a custom FrameworkElement and in doing so hopefully fix some of these flaws.

How it works

The custom type will derive from the FrameworkElement class and will work by:

  • Providing two properties that accept arbitrary content. Each piece of content will be hosted in a ContentPresenter.
  • The modal content will always be positioned in front of the primary content but will initially be hidden.
  • The visibility of the modal content will be controlled by a property so that it can support data binding.
  • When the modal content is displayed, the primary content will be dimmed and made inaccessible.

The content properties

The control will store the content using a pair of dependency properties called PrimaryContent and ModalContent, respectively. These properties will also be accessible via standard clr property wrappers.


public static readonly DependencyProperty ContentProperty =
    DependencyProperty.Register("Content",typeof(object), 
    typeof(ModalContentPresenter), 
    new UIPropertyMetadata(null, OnContentChanged));

public static readonly DependencyProperty ModalContentProperty =
    DependencyProperty.Register("ModalContent", typeof(object), 
    typeof(ModalContentPresenter),
    new UIPropertyMetadata(null, OnModalContentChanged));

public object Content
{
    get { return (object)GetValue(ContentProperty); }
    set { SetValue(ContentProperty, value); }
}

public object ModalContent
{
    get { return (object)GetValue(ModalContentProperty); }
    set { SetValue(ModalContentProperty, value); }
}

When these properties are set, the object will be passed to the respective ContentPresenter.Content property and the display of the ‘content’ will be managed automatically (this is what allows the control to host arbitrary content).

Positioning the content

What I wanted was to have the modal content placed in front of the primary content but also take up the same area (remember the modal content is initially hidden). A Grid with no columns or rows actually behaves like this so this is the layout panel of choice; each ContentPresenter can be hosted in the grid and the ‘content’ will be placed in the correct position.

Note: technically, the modal content is contained in a Border which is used as the overlay and it is this that is placed above the primary content. This is an implementation detail that is required so the overlay, when shown, only obscures the primary content (the reason which will be explained later).

Hiding and showing the content

Displaying and hiding the modal content is controlled in two ways; by a boolean property and by methods. Again, the property is simply a DependencyProperty, defined like this:


public static readonly DependencyProperty IsModalProperty =
    DependencyProperty.Register("IsModal", typeof(bool), 
    typeof(ModalContentPresenter),
    new FrameworkPropertyMetadata(false,
        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        OnIsModalChanged));

And the method simply wraps a call to the clr property wrapper:


public void ShowModalContent()
{
    if(!IsModal)
        IsModal= true;
}

public void HideModalContent()
{
    if (IsModal)
        IsModal = false;
}

All of the logic that controls the display of the modal content happens inside the property changed callback for the IsModal property.


private static void OnIsModalChanged(DependencyObject d,
                                         DependencyPropertyChangedEventArgs e)
{
    ModalContentPresenter control = (ModalContentPresenter)d;

    if ((bool)e.NewValue == true)
    {
        // Show modal content.  
    }
    else
    {
        // Hide modal content.
    }
}

Overlaying the primary content

Remember earlier when I said that the ContentPresenter that is used to display the modal content is actually hosted inside a Border? Well the reason for this is that when the modal content is displayed I want the primary content to be ‘dimmed’. This is achieved by simply setting the borders background property to DarkGray with 80% opacity.

This actually had the side effect of stopping the user from selecting the primary content with the mouse which means it no longer has to be disabled when the modal content is displayed.

The final step was to stop the user from navigating to the primary content using the keyboard whilst the modal content was being displayed. This is how I achieved this:

  • When the modal content is displayed, cache the keyboard navigation mode of the primary content.
  • Set the primary contents keyboard navigation mode to ‘None’.
  • When the modal content is hidden set the primary contents keyboard navigation mode back to the cached value.
// Called when showing the modal content
control.cachedKeyboardNavigationMode =
                    KeyboardNavigation.GetTabNavigation(control.primaryContent);
                KeyboardNavigation.SetTabNavigation(
                    control.primaryContent, KeyboardNavigationMode.None);

// Called when hiding the modal content
KeyboardNavigation.SetTabNavigation(
                    control.primaryContent, control.cachedKeyboardNavigationMode);
                control.primaryContent.MoveFocus(traversalDirection);

Finishing touches

Because I have derived this control from FrameworkElement, there are a number of additional steps that need to be taken in order to get this to work properly. Explanation of this is outside the scope of this post so I plan on explaining these details in a follow up post. For now, see the source code for the additional details.

Below is an example of how you would use this new element in a WPF application.

<Window x:Class="ModalContentTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:c="ModalContentTest.Controls"
        Title="Modal content test"
        Height="350"
        Width="525">
    <c:ModalContentPresenter Name="modalPresenter">
        <TabControl Margin="5">
            <TabItem Header="Tab one">
                <Button Margin="55"
                        Padding="10"
                        Click="ShowModalContent">
                    _Show modal content
                </Button>
            </TabItem>
        </TabControl>

        <c:ModalContentPresenter.ModalContent>
            <StackPanel Orientation="Vertical"
                        VerticalAlignment="Center">
                <Button Margin="75"
                        Padding="50"
                        Click="HideModalContent">
                    Hide modal content
                </Button>
            </StackPanel>
        </c:ModalContentPresenter.ModalContent>

    </c:ModalContentPresenter>
</Window>

The code-behind for the main window looks like this:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ShowModalContent(object sender, RoutedEventArgs e)
    {
        modalPresenter.ShowModalContent();
    }

    private void HideModalContent(object sender, RoutedEventArgs e)
    {
        modalPresenter.HideModalContent();
    }
}

Note: This example uses code-behind but this control can be used in an MVVM friendly way by binding to the IsModal property. The example was shown this way for simplicity.

Using the custom element

When the application is first run, the main window will only show the primary content:

The ModalContentPresenter showing only its primary content

After the button is pressed, the modal content will be displayed, like so:

The ModalContentPresenter showing its modal content over the primary content

The user is unable to access the content behind the modal popup until it has been closed; the content cannot be accessed with the mouse, presses of the tab key, or the use of an access key (note the first button can be invoked using alt-s).

The modal content is only modal to the content it is covering and not the entire application. This is easier to show with an example:

Modal content is only modal to the content it covers

This shows a window that contains a grid that contains two columns. The first column contains a StackPanel which contains six buttons. The second column contains the ModalContentPresenter.

When the modal content is displayed, the user can still select the buttons contained in the stackpanel but will be unable to access any of the content that is behind the modal content whilst it is being shown.

Summary

This custom element provides the following features:

  • Modal display of arbitrary content.
  • Does not disable the primary content whilst the modal content is being displayed.
  • Disables mouse and keyboard access to the primary content whilst the modal content is displayed.
  • Is only modal to the content it is covering, not the entire application.
  • Can be used in an MVVM friendly way by binding to the IsModal property.

Update 11-04-14: I have now uploaded this project to GitHub under the Apache 2.0 open source license. Hopefully this will encourage more people to use the control and to contribute bug fixes and features.

The project page is here.

About these ads

25 thoughts on “Displaying modal content in WPF

    1. benjaminiangale Post author

      Hi Andrew.

      Thank you for the feedback. Could you provide me with some more information so that I can fix the problem?
      E.G. What happens when you click on the link? Does it take you to the download page?

      Regards,
      Benjamin

      Reply
      1. Andrew Fraser

        Benjamin,

        I clicked on the Download button on the MediaFire website and nothing happened. Tried a few times and left it for several minutes but nothing.
        However, have just tried again and it worked.

        Thanks for the article :-)

        Andy

      2. benjaminiangale Post author

        That’s great. Please let me know if there are any issues using the code you have downloaded and of course if you spot any bugs please let me know and I’ll have the sample updated.

  1. alternator

    This works very well thanks, I’m using it with templated vm’s getting swapped in and out for the modal property and haven’t seen any hiccups thus far (and actually it is on templated vm’s which can be swapping in and out of the visual tree freely as well).

    Reply
  2. javier

    Works very well.
    I have a question concerning the MVVM way. I’m binding to the IsModal property and this shows up perfectly. I changed the code a bit, so that all my views inherit from the ModalContentPresenter, so I can get the overlay anywhere where I want, over the complete view.
    The problem that i’m facing now, is instead of a border with a child of contentpresenter I want to use a view and viewmodel, which i can give a custom layout, I can add the view to the _layoutroot but that doesn’t give me access to my viewmodel.

    Any thoughts on how to do this?

    Reply
  3. Benjamin Post author

    My advice would be to not inherit from the ModalContentPresenter as it is designed to be composed like the other WPF controls.

    The control is designed to be able to display any content which is why it uses a ContentPresenter to display both pieces of content. In the example above I host a StackPanel that contains buttons but this could be anything you want such as a custom user control.

    If you want the embedded user control to use a specific view and view model I would make my primary view model contain nested view models which are exposed as properties. I would then bind the primary and modal content properties to the sub-view models like this:

     <c:ModalContentPresenter Name="modalPresenter"
                              Content={Binding DataContext.PrimaryViewModel}
                              ModalContent={Binding DataContext.SecondaryViewModel} />
    

    You can then use DataTemplates to determine which ‘view’ should be displayed:

    Reply
  4. javier

    Thank you very much.
    This pointed me into the right direction, I’ve bound the ModalContent to my viewmodel with success.

    Reply
  5. Josh

    This looks promising. I am still researching MVVM frameworks before we make the big jump from Win Forms. Control-modal dialogs (especially ones that can’t be bypassed by tab or shortcut keys) seems to be a place where most solutions just fall short, even some of the big 3rd party control vendors. So I will definitely be trying your solution.

    I’m sorry if any of these answers are obvious. I haven’t had a chance to download and play with your solution yet.

    1. Can these dialogs be nested? I suspect they can but that I will need to keep my margins small to avoid tiny nested controls? For example, maybe I have a customer view which uses a modal ‘find customer’ dialog. But that ‘find customer’ dialog sometimes filters by customers who have ordered a product that is selected by a nested ‘find product’ dialog? So the find customer dialog has its own child find product dialog?

    2. How would you go about building a view which uses more than one modal dialog (obviously not at the same time). It appears your solution only defines 1 dialog per primary content area. For example, a customer view might need a find customer dialog and maybe another find dialog for one of its properties or some confirm dialog. Would you suggest a different approach altogether, swapping out the main view’s dialog content on the fly, or…?

    Thanks in advance.

    Reply
  6. Benjamin Post author

    Hi Josh,

    thanks for your interest in the control.

    I haven’t tried nesting the control but I cannot see any technical reason why this would be a problem. My advice, however, would be to think in terms of user interface guidelines rather than what the control lets you do; would having multiple nested modal dialogs actually make for a compelling user experience (my opinion is that it wouldn’t). So although the control can be nested multiple times does not mean that it should be used this way (WPF lets you nest a button within a button within a button within but I wouldn’t advise trying that). Of course if you can justify using the control this way and still provide a good user experience then it is good that the control can be used in this way.

    The way I would use the control is to have a ModalContentPresenter pretty high up in the view hierarchy so that the modal content is modal to the application rather than a particular region. This would allow me to provide application wide modal behaviour. I may then have further ModalContentPresenters in other regions of the user interface to provide view specific behaviour but this would be a last resort based on my comments above.

    Using the control this way also helps with your second question; the control is then responsible for displaying modal ‘content’ but this can be determined at runtime using DataTemplates to switch the modal content before displaying it (see my answer above for Javier).

    hopefully this will point you in the right direction but if not please leave another comment and I will help where I can.

    Regards,

    Ben

    Reply
  7. Chris

    Many thanks for sharing. I have a question. When using in an MVVM scenario how do you set the IsModal flag back to false ?

    In my view I bind the IsModal property to a property in ViewModel “A”
    My “modal” dialog is showing a usercontrol which has its own ViewModel – ViewModel “B”. To keep everything loosely coupled I have avoided passing a reference of VM A to VM B . The challenge then is when I am done in my modal view how can I set IsModal to false and get back to my original view ?

    Right now I use the MVVM Light messenger to do this – which works well, but I’m curious to know if you have used a different technique.

    thanks
    Chris

    Reply
  8. Benjamin Post author

    Hi Chris,

    The method you are using sounds fine and is similar to the method I have used in the past. I usually make all viewModels that represent the modal views implement an interface that has an event to communicate to the viewModel that is controlling the modal content.

    Regards,

    Ben.

    Reply
  9. Pingback: ModalContentPresenter with Caliburn.Micro | BlogoSfera

  10. Benjamin Post author

    The sample only contains the first simple example and the control itself.

    Luckily, the second example is very easy to set up. Just place the following xaml in your Window:

    <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
    
            <StackPanel Orientation="Vertical">
                <Button Content="Button 1"/>
                <Button Content="Button 2"/>
                <Button Content="Button 3"/>
                <Button Content="Button 4"/>
                <Button Content="Button 5"/>
                <Button Content="Button 6"/>
            </StackPanel>
    
            <c:ModalContentPresenter x:Name="modalPresenter"
                                     Grid.Column="1">
                <TabControl Margin="5">
                    <TabItem Header="Tab one">
                        <Button Margin="55"
                            Padding="10"
                            Click="ShowModalContent">
                            Show modal content
                        </Button>
                    </TabItem>
                </TabControl>
                
                <c:ModalContentPresenter.ModalContent>
                    <StackPanel Orientation="Vertical"
                            VerticalAlignment="Center">
                        <Button Margin="75"
                            Padding="50"
                            Click="HideModalContent">
                            Hide modal content
                        </Button>
                    </StackPanel>
                </c:ModalContentPresenter.ModalContent>
    
            </c:ModalContentPresenter>
        </Grid>
    
    Reply
  11. Oskar

    Hi Benjamin.

    Thanks for sharing! I have a question. I’ve designed a numpad dialog with buttons 0-9, Ok, abort and delete and would like to add keybinding support to it. It seems that i doesn’t work. Do you have any idea why keybinding shouldn’t work?

    Reply
    1. Benjamin Post author

      Hi Oskar,

      I am assuming you are using your keypad control with the ModalContentPresenter. Can you supply some sample XAML? Without this I am not going to be able to offer any help. I’m not an expert with KeyBindings and it’s been a while since I’ve done any WPF so would need some code or XAML to get me started.

      Reply
  12. Richard Yates

    Hi Benjamin,

    I am relatively new to WPF having used WinForms for many years. I am trying to use the ModalContentPresenter to display different modal content depending on whats going on in the application behind. As such the XAML for the main window only conatins the primary view within the ModalContentPresenter and the modal content is set programatically. As a trial I have created a particular UserControl that I want to display as the modal content. When the application is run however, you can now longer see the disabled primary content behind. The user control I created to shown greyed out and full screen within the modal presenter.

    Any help or suggestions would be greatly appreciated.

    Thanks,

    Richard

    Reply
  13. Clay Anderson

    Benjamin,
    This is very helpful for the application I’m building. I made a couple of enhancements.

    1. Cache and restore Directional navigation. This fixes a bug where you can use the arrow keys to focus on controls in the primary content:

    2. Cache and restore focus to the original focused element.

    //…OnIsModalChanged…

    control.cachedDirectionalNavigationMode =
    KeyboardNavigation.GetDirectionalNavigation(control.primaryContent);
    control.cachedFocusedElement = Keyboard.FocusedElement;
    //…
    KeyboardNavigation.SetDirectionalNavigation(
    control.primaryContent, control.cachedDirectionalNavigationMode);
    Keyboard.Focus(control.cachedFocusedElement);

    Reply
  14. dan

    hi, im using prism, and this modal windows works in my situation, however i would like to know how to make the modal window which comes from nested region to take over the parent region and become a size of the parent region, because the row code of your would make only the

    Reply
  15. Daniel

    Hi,
    Could you explain or give an example what are these four RoutedEvents for in your ModalContentPresenter class “PreviewModalContentHidden”, “ModalContentShown”, “PreviewModalContentHidden”, “ModalContentHidden”. A small example of how to use it would be really appreciated. I am assuming that they can make some kind of the modal window preview, I just don’t know how to use them properly.

    Kind Regards
    Daniel

    Reply
    1. Benjamin Post author

      Hi Daniel,

      These are just standard routed events that are fired when the modal content is hidden or shown. They can’t be used to change the state of the control (because they are events) but they allow you to react to content being shown or displayed in your code behind.

      If you need to know more about routed events then please refer to the Microsoft documentation.

      Reply
  16. Daniel

    Hi Benjamin,

    Thank you for your answer, I’ll have a good read to understand it better. I’m still new to WPF just started about a month ago.
    I have one more question if you don’t mind. I put your modal window in a modal window and, again a modal window on top of it (3 windows on top of each other), it works like a charm.
    But now I would like to make the modal window moveable, so I can move that top modal window around the screen, but I don’t want to allow this moveable window to go outside of parent window boundaries, and stay attached to it?

    Hopefully this makes sense.

    Thank you for your code I really enjoy it, it was hard to find something decent on the internet that is not commercial.

    Regards
    Daniel

    Reply

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