Freigeben über


How to simulate modal windows inside a single WPF window using anonymous methods ?

Introduction

In our windows applications we are commonly using modal windows. Let's remind the idea. Using windows forms, once a window is created, we can choose to show it in a modal manner (form.ShowDialog()). The window then becomes THE front window between all the other windows of our application. Moreover, all the other windows seem to be disabled. To be exact, the other windows are not responding to any input event anymore (keyboard, mouse, etc), but the are still able to respond to other events like paint.
The user must close the modal window by validating or canceling to come back to the previous state. You can repeat this model and then have a stack of modal windows.

This mechanism also exists using Windows Presentation Foundation. Let's remind that WPF windows are "real" windows even if they are hosting a DirectX surface.

Therefore, WPF brings a bunch of new functionalities that are mainly taking advantage of the control tree (events, datacontext, datatemplates, styles, resources, commandbindings, etc). So it's quite interesting to stay in the same window having an unique control tree.

Moreover, the natural vectorial capabilities of WPF let us imagine a complete nested world inside a single window, recreating a workspace with its own internal windows logic, like we know in games interfaces.

Obviously, you cannot define a child control as being modal. In this article, I will try to offer a solution to simulate this behavior.

How to block controls ?

The first step is to disable all the child controls of a same container, excepted the front one.

Disabling the input interaction will be easily done using:

 

 control.IsEnabled = false;

Let's imagine a method that would add a UserControl on the top of a child control collection of a Grid control, disabling existing children:

 

 void NavigateTo(UserControl uc) 
{
    foreach (UIElement item in modalGrid.Children)
    item.IsEnabled = false;
    modalGrid.Children.Add(uc);
}

To "close" the UserControl as we could close a modal window, re-enabling the previous child control we could write:

 

 void GoBackward() 
{
    modalGrid.Children.RemoveAt(modalGrid.Children.Count - 1);
    UIElement element = modalGrid.Children[modalGrid.Children.Count - 1];
    element.IsEnabled = true;
}

This part is done. Those two methods allow to simulate a stack of graphic controls with a modal behavior. This solution supports pushing multiple controls.

That was the easy part. The next step is more complex.

How to block the calling code ?

Using Windows Forms, calling form.ShowDialog() is blocking.

This means that the following instructions will only be executed when the modal windows will close and return its modal value.

 

 if (f.ShowDialog() == DialogResult.Ok)
{
    //Action OK
}
else
{
    //Action not OK
}

The following actions will only be executed when the modal window is closed, creating a sequential execution, simple and comfortable for the developer.

Creating such a behavior in a single window using WPF is really too complex, almost impossible. Though we will try to simulate it.

We want to run an action at the moment when the modal control is closed. We will use a delegate to represent this action. This delegate will be invoked by the one that is closing the modal control. We will offer him a boolean to represent the modal result.

 

 public delegate void BackNavigationEventHandler(bool dialogReturn);

Thanks to anonymous methods, we will keep a very comparable syntax that we had with windows forms:

 

 NavigateTo(new UserControl1(), delegate(bool returnValue) {
    if (returnValue)
        MessageBox.Show("Return value == true");
    else
        MessageBox.Show("Return value == false");
});

The NavigateTo() method now accepts a second parameter we will have to store somewhere to call it later when closing the control.

As this method will have to support successive calls, an unique value will not be enough to store this delegate. We will use a stack to keep all these delegates:

 

 private Stack<BackNavigationEventHandler> _backFunctions
    = new Stack<BackNavigationEventHandler>();

The NavigateTo() implementation becomes:

 void NavigateTo(UserControl uc, BackNavigationEventHandler backFromDialog)
{
    foreach (UIElement item in modalGrid.Children)
        item.IsEnabled = false;
    modalGrid.Children.Add(uc);
    _backFunctions.Push(backFromDialog);
}

We now need to get the delegate back from the stack (Pop) when calling GoBackward().

 void GoBackward(bool dialogReturnValue)
{
    modalGrid.Children.RemoveAt(modalGrid.Children.Count - 1);
    UIElement element = modalGrid.Children[modalGrid.Children.Count - 1];
    element.IsEnabled = true;

    BackNavigationEventHandler handler = _backFunctions.Pop();
    if (handler != null)
        handler(dialogReturnValue);
}

The one that is closing the control just need to call GoBackward(true); or GoBackward(false);

Make the access global

Last step, it would be useful to provide a global access to these two methods across the application. Doing such, any UserControl could easily call NavigateTo() to push a control and GoBackward() to close it, without knowing the modal context.

Let's group these functionnalities into an interface:

 

 public interface IModalService
{
    void NavigateTo(UserControl uc, BackNavigationEventHandler backFromDialog);
    void GoBackward(bool dialogReturnValue);
}

In our sample, we will simply implement this interface in our main window "Window1". It's quite a natural choice since "modalGrid" is contained in Window1.

A public static scope will provide a global access to the interface:

 

 public class GlobalServices
{
    public static IModalService ModalService
    { 
        get
        {
            return (IModalService) Application.Current.MainWindow;
        } 
    } 
}

Here we are !

We can now call anywhere in our code:

 

 GlobalServices.ModalService.NavigateTo(new UserControl1(), delegate(bool returnValue) 
{ 
    if (returnValue) 
        MessageBox.Show("Return value == true"); 
    else 
        MessageBox.Show("Return value == false"); 
});

and 

 

 GlobalServices.ModalService.GoBackward(true);

Conclusion

Windows Presentation Foundation graphic possibilities are incredible. We now have to create smart ergonomic solutions to take advantage of this engine.
Anonymous methods are really surprising. Strange at first use, they are bringing new and incredible possibilities.

C# 3.0 is coming really soon…

WPFModal.zip

Comments

  • Anonymous
    May 28, 2007
    Excuse my English I speak Spanish I found the sample very good but I like to know how to  call a method or set or get a property no from the usercontrol but from the  container that hold the control I am developing a app  that can load many usercontrol but I like to know how I can acces from my app how to interop with the control Thank  you Reiner reinerra@hotmail.com

  • Anonymous
    May 31, 2007
    Congratulations, You have kept this very simple. How much more complex would it be to write a single method similar to ShowDialog() that doesn't return a value until the Control is closed? Then it could be called from a procedural program that would receive the input data at the end of the call before it proceeds to its next call. Using LOTS of modal Forms to sequentially enter data is not much more attractive than writing a console program. Console programs are pretty ugly, to say the least. I've tried threading without any success.

  • Anonymous
    June 03, 2007
    Hi William, It's very complex. Blocking the UI is very different from blocking a single call in a console program. In a console program, the UI is "static" and does not have to respond to any else than the user. A "windowed" application (WinForms or WPF) has to maintain UI interaction even if the window is "frozened" by a modal call. So you can't block the main thread as you could do with a console program. Maybe we could interact with the WPF dispatcher to simulate a modal behavior like winforms does with the windows message handler. I will try to have a look on such a solution.

  • Anonymous
    June 11, 2007
    So in the brave new world of WPF, you have to jump through hoops do something as routine as creating a modal dialog. This is progress?? I must be missing something...

  • Anonymous
    June 11, 2007
    Hi Marky, Imagine you are calling a method M() and you want M() to sleep for a while. During this time, you want your application to run normally except the caller of M() that must wait for M() to finish. All this with a single thread of course. This is not a WPF pb. I recommand you to read the ShowDialog() code from winforms or WPF (using reflector). Doing it between controls inside a single window is more complex.

  • Anonymous
    June 11, 2007
    Thanks for the sample Mitsu. Marky- Please realize that the WPF Window object has a .ShowDialog[1] method (as well as just a normal .Show) which gives you a modal window. I believe Mitsu's example is interesting in cases where you would like to embed a dialog inside an existing Window or Page.  For example, when running in an XBAP (a WPF app running in the browser), you can't show other Windows (popups!) so this is useful. Thanks, Rob Relyea | Program Manager, WPF & Xaml Language Team robrelyea.com | /blog | /wpf | /xaml [1] http://msdn2.microsoft.com/en-us/library/system.windows.window.showdialog.aspx

  • Anonymous
    April 29, 2008
    Thanks for great hint, Isn't any way to close dialog, if user clicked outside it, for example clicking outside a messagebox closes it?

  • Anonymous
    April 29, 2008
    It's not an usual use of modal window but you can do it easilly. you have to catch the click on the last 'frame' and add a common behavior to close the window (with a cancel information I can imagine). Note: if you want to get the click information on a particular area, this area must be painted, even with a transparent Brush (Color = Transparent). (no event if no content/no brush)

  • Anonymous
    July 02, 2008
    Good Work done, You also remove drawback of memory leak while calling showdialog method

  • Anonymous
    September 12, 2008
    Simulate Modal Windows Inside WPF Window Using Anonymous Methods You can read the original post in Mitsu&#39;s

  • Anonymous
    May 04, 2009
    The comment has been removed

  • Anonymous
    May 09, 2009
    Hi Mitsu It's just great what you have done simulating modal window in WPF Application. I was wondering.... do you think is it posible to create a sort of control that you as developer could drag and drop from toolbox to accelerate your control creation within a WPF form?

  • Anonymous
    February 22, 2010
    Hi mitsu. I apologize for my english, I am only a french guy ;) When I read your post, I find it really easy understanding, but when I had think about how to do it I did not imagine how to succeed. Than you so much for that post! Keep on trying to help guys like me... Bye little genius

  • Anonymous
    November 11, 2010
    The comment has been removed

  • Anonymous
    October 29, 2013
    aoa i used this in my project but on user control my all button ,textbox,image apear too large i have set the width height also but its not woking. mean how i make items(button,texbox) more precise and commertial look