Click here to Skip to main content
Click here to Skip to main content

WPF Modal Dialog

By , 17 Mar 2013
 

2009_4_WpfModalDialog

There is a newer article with improved functionality here on CodeProject: http://www.codeproject.com/Tips/563144/WPF-Dialog-MessageBox-Manager.

Introduction 

What can you do if you would like to display a modal / blocking dialog in WPF? By default, there is the possibility to use the Window.ShowDialog() method. This has the disadvantage that a completely new "Windows"-window will be created which is not part of our main application window (which again has several disadvantages that I don’t want to discuss here). I would like to present an alternative solution.

As shown in the image, we would like to have a half transparent overlay with some kind of content (here, just for example, we use a simple text message, but we could also display other controls).

The ModalDialog control

The functionality and design is outsourced in a User Control. The XAML of this control is quite simple: we have the half transparent border that we use as our overlay, the TextBlock to display our message, and two Buttons for OK / Cancel.

<Grid DataContext="{Binding ElementName=root}">
    <Border Background="#90000000" Visibility="{Binding Visibility}">
        <Border BorderBrush="Black" BorderThickness="1" Background="AliceBlue" 
                CornerRadius="10,0,10,0" VerticalAlignment="Center"
                HorizontalAlignment="Center">
            <Border.BitmapEffect>
                <DropShadowBitmapEffect Color="Black" 
                  Opacity="0.5" Direction="270" 
                  ShadowDepth="0.7" />
            </Border.BitmapEffect>
            <Grid Margin="10">
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock x:Name="MessageTextBlock" 
                    Text="{Binding Message}" 
                    TextWrapping="Wrap" Margin="5" />
                <UniformGrid Grid.Row="1" Margin="5" 
                          Columns="2" HorizontalAlignment="Center"
                          VerticalAlignment="Bottom">
                    <Button x:Name="OkButton" Click="OkButton_Click" 
                          Content="Ok" Margin="2"  />
                    <Button x:Name="CancelButton" 
                          Click="CancelButton_Click" 
                          Content="Cancel" Margin="2" />
                </UniformGrid>
            </Grid>
        </Border>
    </Border>
</Grid>

Of course, it is still not clear how the "modal" or "blocking" behavior of the dialog is achieved. But how is that done? That’s a little bit tricky (you could also say "hacky" :-)). We have the following problem: "blocking" or "modal" means that the instruction pointer of our execution should remain at its position as long as the dialog is shown, and when the dialog is closed, it should continue. To achieve this, we would have to suspend the calling thread (typically the UI thread). If it is suspended, an interaction with our dialog will also be impossible because it is suspended, too. This is because WPF relies on a single thread model, which means it is impossible to run UI elements in another thread different from the thread of our main window. So we have to use a little hack here (I’m really not a friend of hacks, but here, it’s absolutely necessary).

When the dialog is shown, we start a loop in which we suspend the calling thread (typically the UI thread) for a short time, and we also advise the WPF dispatcher to process the messages (mouse click, movements, keystrokes, repaint, etc.) that occur in the sleeping time. If we choose a sleep time that is small enough, for the user it seems that the application is running fluently. Attention: If you would like to display elements in our dialog that have a lot of "animation" stuff on it, we will run into problems. If we just use some simple elements, it’s OK.

Here is the code for our control:

public partial class ModalDialog : UserControl
{
    public ModalDialog()
    {
        InitializeComponent();
        Visibility = Visibility.Hidden;
    }

    private bool _hideRequest = false;
    private bool _result = false;
    private UIElement _parent;

    public void SetParent(UIElement parent)
    {
        _parent = parent;
    }

    #region Message

    public string Message
    {
        get { return (string)GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Message.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MessageProperty =
        DependencyProperty.Register(
            "Message", typeof(string), typeof(ModalDialog), 
            new UIPropertyMetadata(string.Empty));

    #endregion

    public bool ShowHandlerDialog(string message)
    {
        Message = message;
        Visibility = Visibility.Visible;

        _parent.IsEnabled = false;

        _hideRequest = false;
        while (!_hideRequest)
        {
            // HACK: Stop the thread if the application is about to close
            if (this.Dispatcher.HasShutdownStarted ||
                this.Dispatcher.HasShutdownFinished)
            {
                break;
            }

            // HACK: Simulate "DoEvents"
            this.Dispatcher.Invoke(
                DispatcherPriority.Background,
                new ThreadStart(delegate { }));
            Thread.Sleep(20);
        }

        return _result;
    }
    
    private void HideHandlerDialog()
    {
        _hideRequest = true;
        Visibility = Visibility.Hidden;
        _parent.IsEnabled = true;
    }

    private void OkButton_Click(object sender, RoutedEventArgs e)
    {
        _result = true;
        HideHandlerDialog();
    }

    private void CancelButton_Click(object sender, RoutedEventArgs e)
    {
        _result = false;
        HideHandlerDialog();
    }
}

How to use the ModalDialog control

The dialog is used in the main application window, and as we can see, it overlays the complete content of the window:

<Grid>
    <Grid x:Name="ModalDialogParent">
        <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
            <TextBox x:Name="MessageTextBox" 
              Text="Hello World!" Margin="3" />
            <Button x:Name="ShowModalDialog" 
              Click="ShowModalDialog_Click"
              Content="Show Modal Dialog" Margin="3" />
            <TextBlock x:Name="ResultText" />
        </StackPanel>
    </Grid>

    <controls:ModalDialog x:Name="ModalDialog" />
</Grid>

Notice that the dialog needs a reference to the element that "holds" the complete content of the window. In our case, this is the grid with the name "ModalDialogParent". This is important so that the dialog can disable it when it is shown (otherwise, the user could cycle with "tab" through the elements of the window).

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ModalDialog.SetParent(ModalDialogParent);
    }

    private void ShowModalDialog_Click(object sender, RoutedEventArgs e)
    {
        var res = ModalDialog.ShowHandlerDialog(MessageTextBox.Text);
        var resultMessagePrefix = "Result: ";
        if (res)
            ResultText.Text = resultMessagePrefix + "Ok";
        else
            ResultText.Text = resultMessagePrefix + "Cancel";
    }
}

Have fun!

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

Ronald Schlenker
Software Developer (Senior) www.technewlogic.de
Germany Germany
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalgood onememberaankur8117 Mar '13 - 19:50 
Nice For Begineers like me...
thank you
GeneralRe: good onememberRonald Schlenker17 Mar '13 - 20:27 
Thank you. You should have a look at my updated article with code here: WPF Dialog / MessageBox Manager[^]
GeneralRe: good onememberaankur8117 Mar '13 - 21:08 
Ya sure I will...
GeneralMy vote of 2memberIrina Pykhova17 Mar '13 - 7:30 
really bad implementation. If someone needs such functionality, search for better solution without hacks
GeneralMy vote of 1memberQuadiago17 Feb '13 - 21:47 
Please don't use this "solution"! It is shockingly incorrect. Found this used in our project and had to google "HACK: Simulate "DoEvents"" to find where it came from. I'll comment with links to PROPER ways to solve the problem.
Questioncontrol is not working in xbap applicationmemberS.Faizan.Ali9 Feb '13 - 0:34 
Any suggestions how to make it work.
Thanks.
GeneralMy vote of 2memberSergiy Tkachuk3 Aug '12 - 9:29 
because of hack
QuestionChild window also disablingmemberjeedigunta15 Jun '12 - 20:21 
I am using this this works fine but child window also disabling when parent property isEnabled false
 
May i know why this problem occures
Pls check out this...............
SatyaKishore

AnswerRe: Child window also disablingmemberJavid H. Hosseini14 Jan '13 - 21:59 
this is normal, when you open the modal it disables the parent and because of inheritance, your modal dialog (the element in the window) becomes disable.
 
if you consider the code provided by the OP you will see that the modal box is not a child of its called "parent" property!! bad naming you know made it confusing!!
 
hope it helps .. Big Grin | :-D
GeneralMy vote of 5memberAssil6 Feb '12 - 8:10 
That is nice article and it is smart and neat.. I loved it..
Even though it did not work on my case..
GeneralMy vote of 3memberjalalx4 Feb '12 - 9:47 
bad hack! using loops for this make me sick! :P
QuestionWhy the Thread.Sleep() method is required?memberNero.6664 Oct '11 - 20:27 
The question says it all. The User Control is being displayed under the main UI thread, while Dispatcher.Invoke() inside the loop keeps the dialog displaying as a synchronous operation. I just didn't get what purpose the Thread.Sleep() method is serving. Excuse my ignorance if there's something i'm missing, but otherwise can you please give a hint?
GeneralBlurry textmemberSue M Maurizio31 May '11 - 23:51 
This works fine for me, but the text inside the dialog looks blurry: I found out that this is somehow due to the BitmapEffect applied to one of the Border elements; if you remove the effect, the text will look fine.
QuestionWhy not use the Owner-Property of the ChildWindow [modified]memberroot20 Apr '11 - 2:00 
Hi,
 
what's the benefit of this solution? The problem with the "complete new window" can be avoided by using the Owner.Property.
 
private void ShowMyWindow()
{
   MyWindow wnd = new MyWindow()
   wnd.ShowInTaskbar = false;
   wnd.Owner = this;
   wnd.ShowDialog()
}
 
Regards
 
Florian

modified on Wednesday, April 20, 2011 8:36 AM

QuestionAdding new modal is NOT WORKING [modified]memberyrsk.aravind19 Mar '11 - 18:52 
Hi,
 
I downloaded your sample code, added new Usercontrol to be another modal dialog and coded in such a way that, clicking OK button on the Modal dialog will popup another modal dialog usercontrol. but its not working that way and i am not able to see that new usercontrol.
 
Below is the XAML:
<UserControl x:Class="Technewlogic.Samples.WpfModalDialog.UserControls.UserMessageDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <Grid>
        <Border Background="#90000000" Visibility="{Binding Visibility}">
            <Border BorderBrush="Black" BorderThickness="1" Background="AliceBlue" 
					CornerRadius="5,5,5,5" VerticalAlignment="Center"
					HorizontalAlignment="Center">
                <Border.BitmapEffect>
                    <DropShadowBitmapEffect Color="Black" Opacity="0.5" Direction="270" ShadowDepth="0.7" />
                </Border.BitmapEffect>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="18"  />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <DockPanel Width="Auto" Height="18" VerticalAlignment="Top" Grid.Row="0" Background="LightBlue" >
                        <TextBlock Text="Status" Margin="3,2,1,1" 
FontWeight="Bold" FontSize="11" DockPanel.Dock="Left" Background="Transparent" />
                        <Button Width="18" Height="18" DockPanel.Dock="Right" 
FontWeight="Bold" Foreground="DarkGray" HorizontalAlignment="Right" FontSize="10" 
VerticalAlignment="Center" Content="X" Click="OkButton_Click" Background="Transparent" BorderBrush="LightGray" />
                    </DockPanel>
                    <Grid Margin="1,5,1,1" Grid.Row="1">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <TextBlock Text="The OK button worked successfully." FontSize="11" Margin="3" />
                        <UniformGrid Grid.Row="1" Margin="1" Columns="1" HorizontalAlignment="Right"
								 VerticalAlignment="Bottom">
                        <Button x:Name="OkButton" Click="OkButton_Click" 
Background="DarkKhaki" Height="18" FontSize="10" FontFamily="Wingdings" Content="ü" Margin="2" HorizontalAlignment="Right" />
                        <!--<Button x:Name="CancelButton" Click="CancelButton_Click" Content="Cancel" Margin="2" />-->
                    </UniformGrid>
                    </Grid>
                </Grid>
            </Border>
        </Border>
    </Grid>
</UserControl>
 
 


And the code block for the same below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
using System.Threading;
using System.Windows.Threading;
 
namespace Technewlogic.Samples.WpfModalDialog.UserControls
{
    /// <summary>
    /// Interaction logic for UserMessageDialog.xaml
    /// </summary>
    public partial class UserMessageDialog : UserControl
    {
        public UserMessageDialog()
        {
            InitializeComponent();
            this.Visibility = Visibility.Hidden;
        }
 
        private bool _hideRequest = false;
        private bool _result = false;
        private UIElement _parent;
 
        public void SetParent(UIElement parent)
        {
            _parent = parent;
        }
 
        public bool ShowHandlerDialog()
        {
           
            this.Visibility = Visibility.Visible;
 
            _parent.IsEnabled = false;
 
            _hideRequest = false;
            while (!_hideRequest)
            {
                // HACK: Stop the thread if the application is about to close
                if (this.Dispatcher.HasShutdownStarted ||
                    this.Dispatcher.HasShutdownFinished)
                {
                    break;
                }
 
                // HACK: Simulate "DoEvents"
                this.Dispatcher.Invoke(
                    DispatcherPriority.Background,
                    new ThreadStart(delegate { }));
                Thread.Sleep(20);
            }
 
            return _result;
        }
 
        private void HideHandlerDialog()
        {
            _hideRequest = true;
            Visibility = Visibility.Hidden;
            _parent.IsEnabled = true;
        }
 
        private void OkButton_Click(object sender, RoutedEventArgs e)
        {
            _result = true;
            HideHandlerDialog();
        }
    }
}
Regards,
Aravind
modified on Sunday, March 20, 2011 10:36 AM

QuestionWhy is that Thread.Sleep() in dispatcher? [modified]memberyrsk.aravind18 Mar '11 - 10:00 
Hi, can you please explain why you used sleep() in that dispatcher object? I am trying to use or bring similar way of modal dialog approach. I definitely accept your 2nd point in ur reply that using ShowDialog() needs another Window type, but if we need to popup a modal window for an Usercontrol or inside the main window, we need to workaround as what you did.
 
I am just requesting your detailed explanation for my understanding... expecting somebody's reply for why sleep() is used there...
 
thanks,
Regards,
Aravind
modified on Sunday, March 20, 2011 10:37 AM

QuestionKeyUp Event thrown multiple timesmemberSCHRANKK19 Oct '10 - 9:27 
Hello,
 
first of all I have to say it's a nice sample.
 
In one case I got a strange behavior.
 
I replaced dialog buttons with Images. I handle the Image.MouseButtonUp event from the Images and execute the same code which was executed by the OkButton_Click handler before.
 
So far everything works fine.
 
The next thing I changed was to call the ShowHandlerDialog method from a TextBox.KeyDown eventhandler. The dialog pops up like i wished. But after a click on one of the "ImageButtons" the KeyUp event from the TextBox was fired again. The result is that the dialog is always shown 2 times.
 
Is there a way to solve this issue?
 
with best reguard
 
thx for any help
Generaldrag dropmemberdevvvy8 Aug '10 - 21:03 
hello
Looking for simplified version of this: http://blogs.msdn.com/b/jaimer/archive/2007/07/12/drag-drop-in-wpf-explained-end-to-end.aspx[^]
 
Need drag drop to move WPF popup but no time read the above...
 
Thanks
dev

GeneralMy vote of 1memberLefebvreOli25 Mar '10 - 1:41 
a
QuestionWPF modal operations solution?memberTri Q Tran3 Feb '10 - 14:48 
Not many know, heard or use Dispatcher Frames. This is a technique to lock WPF dispatcher process until some operation is completed.
 
private DispatcherFrame _blockingFrame;
private OperationResult _operationResult;
 
public OperationResult BlockingOperations()
{
   /// Setup operations
   
   _blockingFrame = new DispatcherFrame(true);
   Dispatcher.Push(_blockingFrame);
 
   return _operationResult;
}
 
public void OperationCompleted(OperationResult result)
{
    _operationResult = result;
    _blockingFrame.Continue = false;
}
 
Now in this short example, the Dispatcher will block the current calling thread until some trigger (ie, OperationCompleted) is called. This will cause our result to be updated and returned at the end of the BlockingOperation. This whole process will still allow UI thread to respond to clicks, and refresh. The Dispatcher will also comply to application shutdown request so there is not problems.
 
Happy coding.
AnswerRe: WPF modal operations solution?memberCallum16 May '10 - 11:24 
Very nice Smile | :)
Generaldisable x on modal wpf windowmemberTMags5 Jan '10 - 5:08 
Is there a way to disable the "x" (close window) on modal wpf window in C# code ?
Generalblocking methodmemberLoic Berthollet17 May '09 - 10:32 
Hie, Ronald.
 
To my mind, blocking the main UI thread is a very bad idea, and I dont' think that it's absolutely necessary. Try this :
 
-> in ModelDialog.xaml.cs, replace ShowHandlerDialog method with this code :
      public void ShowHandlerDialog( string message, Action<bool> completed )
      {
         ShowDialog(message);
         _completed = completed;
      }
 
      private void ShowDialog( string message )
      {
         Message = message;
         Visibility = Visibility.Visible;
         _parent.IsEnabled = false;
      }
 
thus, in MainDialog.xaml.cs, show the dialog like this :
         ModalDialog.ShowHandlerDialog(MessageTextBox.Text, result => 
{ ResultText.Text  = "Result: " + (result ? "Ok" : "Cancel"); });
         ResultText.Text = "...wait...";
 
With this code, the _hideRequest variable become useless.
 
Kind Regards.
 
Loïc Berthollet

GeneralRe: blocking methodmemberRonald Schlenker18 May '09 - 20:36 
Hi Loic,
 
I think you simply proposed a different solution. Your pattern is asynchronous (non-blocking), mine is synchronous (blocking). There are scenarios where asynchronous calls with a callback will not fit your requirements, unfortunately.
 
Blocking the UI thread - as you mentioned - might be a bad idea in the most cases (as usually the app runs in that thread and blocking the UI thread means blocking your whole application). But to generalize that it's _always_ a "very bad idea" is - from my point of view - not always correct: you have to check your requirements and figure out what you want to achieve. In this case here, blocking the UI thread (unfortunately in WPF we have only one UI thread) is the desired behavior.
 
Bye!
 
Ronald
GeneralRe: blocking methodmemberPhilipp Sumi30 May '09 - 23:44 
Ronald,
 
Loic is right - it *is* a bad idea to block the UI thread (you always have just one, that's not a specific WPF feature), as it prevents redrawing your UI. This will most likely lead to nasty side effects in a variety of use cases. If you are willing to introduce such a hack (your words Wink | ;) ) - would you care to elaborate the upside of blocking the UI's own thread?
 
As an alternative example of a blocking dialog, you might find this one an interesting read (progress dialog sample that also comes with a cancel button): http://www.hardcodet.net/2008/01/wpf-progress-dialog[^]
 
Maybe you could think of incorporating a similar pattern into your solution?
 
Cheers,
Philipp
 
NetDrives - Open Source Network Share Management Awesomeness

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 18 Mar 2013
Article Copyright 2009 by Ronald Schlenker
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid