The problem:
The window control works nicely as a shell for your application, however, launching child windows is problematic because the window control cannot be defined in xaml unless as the root element.
So, if you wanted to launch child windows inside a parent window, you lose the ability to declare these in xaml. This loss means you will not be able to bind to properties in your ViewModel from the view for the purpose of opening/closing a window. Instead you end up doing this imperatively in code which means more code, more thought, more work.
Following is what you end up doing in the most minimalistic cases.
MyWindow window = new MyWindow(); window.ShowDialog();
Adding code such as the above means you are forced to make your ViewModel create instances of your Window and launch them when needed. Clearly something you will not appreciate during testing and since this will provide a tight coupling to a Window control in your ViewModel, it is useless to your tests and eventually breaks your pattern.
What would have been nice instead is if we could do the following :
<my:ModalDialogPopup IsOpen="{Binding FirstPopupIsOpen, Mode=TwoWay}"/>
If we could define our window declaratively in Xam as above, then we use the databinding capabilities in WPF and bind to an FirstPopupIsOpen property in our ViewModel, which is what we are after but sadly not currently possible.
The solution 1: A custom Control that behaved like a Window
Writing a custom control was pretty simple as we make use of the existing Popup control in Wpf. The popup control is pretty wild and requires taming but solves this problem.
Some reasons to design our solution around the existing Popup control:
- The popup control is designed to stay always ontop, which has it's pitfalls but it solves more problems than it brings. More specifically we will want the ability to hide the content under the popup while the popup is in view.
- The popup control can be positioned in so many ways, however what we are after is the ability to define a set of Left, Top coordinates and this is supported out of the box.
- The popup control supports a few animations out of the box. This means we can apply a nice sliding or a fade effect with zero effort.
- The popup control can contain child controls obviously. This is great and serves our purpose very well.
- Enjoy the beauty of an adorner masking the background beneath the ModalDialogPopup.
<my:ModalDialogPopup IsOpen="{Binding FirstPopupIsOpen, Mode=TwoWay}"> <my:ModalDialogPopup.HostedContent> <ContentControl> <Grid Height="200" Width="300"> <TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center" FontSize="20"> This is the first modal popup </TextBlock> </Grid> </ContentControl> </my:ModalDialogPopup.HostedContent> </my:ModalDialogPopup>
One inherent problem I had not considered while writing this control is that just like the Window control, even with a custom popup control, the limitation to not being able to nest a popup in another popup existed. That's because when showing the child, we are forced to hide the parent for technical reasons that exist in the popup control only(the popup control will always be the top most control).
That means if the parent is larger than the child in dimention, portions of controls in the parent will show and interaction with those pieces becomes possible. When a child popup is launched, we want it to behave as a modal window, so it shouldn't be able to interact with controls in popups beneat it. Sadly the adorner cannot help us here as it cannot cover the “Always ontop” popup.
Ofcourse, the solution to deal with this limitation is to hide the parent when the child is in view, which doesn't help the nesting because if the parent is hidden, then the child will be hidden too! I guess there is a good reason why the Window control in WPF does not allow nesting! Too bad I had to discover this at my own expense.
Even with this shortcomings, in most cases, you can workaround the nesting limitation by designing your solutions with this drawback in mind.
As you can note from the piece of xaml code in the previous code listing above, we have a control that can be defined in our view declaratively that takes content via the HostedContent template. We can also set a Title, and content in it's HostedContent template. In the previous code listing, everytime the property FirstPopupIsOpen evaluates to “true” in our ViewModel, the popup will open.
By default the custom popup provides an OK and Cancel button whose caption/visibility you can set. If you need more customizations you can very well customize its template by providing a custom style. Attemping to supply custom styling is quite simple because the default markup we use is plain and this is intentional since styling is subjective and a trival matter. This enables you to provide custom styling of your own without fighting your way through heavy use of xaml.
If you take a look at the code listing below, all we have is a 3 row grid, one holding the title, the second holding the content you define in the HostedContent template and the last row to hold the OK and Cancel buttons. Simply put, you are in control of the styling and the default style template is prive of any bloated styling markup to distract you.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfPopup.Controls"> <Style TargetType="{x:Type local:ModalDialogPopup}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate
TargetType="{x:Type local:ModalDialogPopup}"> <Popup x:Name="dialog" AllowsTransparency="True"> <Grid x:Name="content"> <Grid.RowDefinitions> <RowDefinition Height="20"/> <RowDefinition Height="*"/> <RowDefinition Height="20" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" x:Name="title" /> <!-- the hosted content --> <ContentPresenter x:Name="contentHost"
Grid.Row="1" Margin="5"/> <StackPanel Grid.Row="2" Orientation="Horizontal"
HorizontalAlignment="Right"> <Button Content="Ok" x:Name="buttonOK"
MinWidth="100" Margin="0,0,5,0" /> <Button Content="Cancel" x:Name="buttonCancel"
MinWidth="100" Margin="0,0,5,0" /> </StackPanel> </Grid> </Popup> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
And here it is when popped open in the example solution attached to this post :
It's actually quite a minimal control without any bells and whistles and leave the responsibility of styling it in your hands, any way you want. Notice the adorner masking the background and the convenient Ok/Cancel buttons. It's so much simpler to use and easy to test. The amount of work need to set this up compared to a window control (solution 2 below) is minimal.
Surely there is some value for such a control but it is most definitely not a resonable replacement to the Window control entirely, as far as launching child windows is concerned.
Fortunately, the needed work to decouple the existing Window control from the ViewModel could very well be done with a simple interface contract, which brings us to the next solution.
Solution 2 : Decoupling the Window control from the ViewModel
This solution relies on dependency injection to provide proper decoupling of the Window control from the viewmodel. So, we'll need a dependency injection container. You may use any at this point, I'll be using Microsofts Unity. This will also make it a breeze to inject a stub in place of the Window control when Unit testing.
First, let's look at what we're going to construct. It's going to be a series of dialogs. The main Window, and 2 child windows.
The main window :
A child window launched from the main window above :
And finally another child window launched from the child window above :
Ok, now that we know what we are building, lets start. The procedure is simple. We'll start by defining the common functionality we'd normally use from the Window Control in our ViewModel by throwing it into an interface.
public interface IChildWindow { void Close(); bool? ShowDialog(); void SetOwner(object window); bool? DialogResult { get; set; } }
We do this for each View that is a window control. The reason is simple, in order to decouple the window from the viewmodel, what we are going to do is use constructor injection. By injecting this dependency in the constructor we can ensure easy substitution with fake stubs during testing and everything will just work.
Let's also decouple the window we plan to launch by adding yet another interface :
public interface IChildWindowNested { void Close(); bool? ShowDialog(); void SetOwner(object window); bool? DialogResult { get; set; } }
And finally, the viewmodel:
public class ChildWindowViewModel { private readonly IChildWindow _childWindow; private readonly IChildWindowNested _childWindowNested; private ICommand _okCommand; private ICommand _openCommand; public ChildWindowViewModel(IChildWindow childWindow, IChildWindowNested childWindowNested) { _childWindow = childWindow; _childWindowNested = childWindowNested; } public ICommand OpenCommand { get { return _openCommand ?? (_openCommand = new DelegateCommand(OpenClick)); } } public ICommand OkCommand { get { return _okCommand ?? (_okCommand = new DelegateCommand(OkClick)); } } private void OpenClick() { _childWindowNested.SetOwner(_childWindow); _childWindowNested.ShowDialog(); } private void OkClick() { _childWindow.DialogResult = true; _childWindow.Close(); } }
As you can notice in the above class, we are passing 2 interfaces in the constructor. The first is the window using this viewmodel : ChildWindow.xaml and the second is the window it will launch : ChildWindowNested.xaml
The relationship between these two windows is a typical parent child relationship, ChildWindow.xaml is the parent, while ChildWindowNested.xaml is the child being launched.
The reason we pass IChildWindow, the parent window in this case is because :
- We need to tell the child Window we are launching who it's owner is. That way we can nicely position the child window relative to it's parent.
- As this is the viewmodel of ChildWindow.xaml, we will also want to handle closing this parent window when it's Ok and Cancel buttons are clicked.
- We want to launch this dialog based on an action in ChildWindow.xaml
- To decouple the window from the viewmodel, because we will be using dependency injection to set up this dependency on the window control.
Now the codebehind for ChildWindow.xaml :
public partial class ChildWindow : Window, IChildWindow { private readonly IUnityContainer _container; public ChildWindow() { InitializeComponent(); _container = UnityContainerResolver.Container; var childWindowNested =
_container.Resolve<IChildWindowNested>(); DataContext = new ChildWindowViewModel(this,
childWindowNested); Closing += ChildWindowClosing; } #region IChildWindow Members public void SetOwner(object window) { Owner = window as Window; } #endregion private void ChildWindowClosing(object sender, CancelEventArgs e) { e.Cancel = true; Visibility = Visibility.Hidden; } }
We can setup the viewmodel binding to the DataContext in several ways however doing this in the codebehind of the view as in the sample code above is the most flexible of all solutions especially if your moving beyond the typical blog post samples.
At the end of the day, it's only a matter of opinion and what's important is that there is no dependency impeding you from testing your application. And ofcourse that it does not break your pattern. In our case, it's both convenient as we want to pass a reference of the current window to the viewmodel and at the same time, we want to do it in a very decoupled way to help us test our viewmodel.
Also note that we are hooking into the closing handler and cancelling the default close behavior of the Window control. That's because once a window is closed, we cant reopen it and we'll need to create a new instance of the window.
Certainly this depends on your use case. For me, my requirements are such that I need to reuse the window being closed. This works nicely.
What about dependency injection and how is our Unity container setup ? It's quite simple in this case. For this sample code, I just created a singleton that registers all dependencies with the container :
public class UnityContainerResolver { private static IUnityContainer _container; private UnityContainerResolver() { } public static IUnityContainer Container { get { if (_container == null) { _container = new UnityContainer(); RegisterTypes(); } return _container; } } static void RegisterTypes() { _container.RegisterType<IMainWindow, MainWindow>(); _container.RegisterType<IChildWindow, ChildWindow>(); _container.RegisterType<IChildWindowNested,
ChildWindowNested>();
}
}
That's it. By registering dependencies with a dependency container, it becomes so easy to inject fakes in our viewmodel in place of the real object. In our case, when testing, we can make a different registration by mapping to our fake mock objects and this is relatively simple when using a dependency container :
static void RegisterTypes() { _container.RegisterType<IMainWindow, MockMainWindow>(); _container.RegisterType<IChildWindow, MockChildWindow>(); _container.RegisterType<IChildWindowNested,
MockChildWindowNested>();
}
And MockChildWindow having enough code to satisfy the contract of IChildWindow.
public class MockChildWindow : IChildWindow { public object Owner { get; set; } public void Close() { // } public bool? ShowDialog() { return true; } public void SetOwner(object window) { Owner = window; } public bool? DialogResult { get; set; } }
The viewmodel itself only uses methods exposed by the contract so we are safe to use a fake object as above for testing.
I have added a test app containing the custom popup and all code discussed here. Be sure to check it out!
Download sample application
It is really cool! Thanks!!!!!
ReplyDeleteThat's awesome ! Thanks alot !
ReplyDeleteexcellent!!
ReplyDeleteWhat if I want to host this popup in the browser?
ReplyDeleteYou can't. It's a WPF control. If you are asking in relation to xbap then it's not something i've really explored.
ReplyDeleteOk, I handled that issue by myself.
ReplyDeleteBut, Would you please let me know, How to bind a command from my ViewModel to the OK Button in your Popup?
Dear Alessandro,
ReplyDeleteI Actually figured that out.
Sorry for my steady posts.
And your Custom Popup is brilliant.
Thank you very much.
Hi,
ReplyDeleteThanks a lot, this is some wonderful stuff here. I employed your ModalDialogPopup and it works vey well for me. I however need to do more stuff with it and have running into two issues. I would be very grateful if you could help me with it.
1) The popup grey's out (draws a rect over) the whole window. Now my App has 3 regions in the window, I want it to grey out just two out of these three windows. Any suggestions on how I should go about it?
2) The region which launches the popup is a region which can hold tabbed views. Now for me when I switch between the tabs I need to make sure the proper region is greyed and ungreyed on such transition. Now I can think of one possible way to do that but the bigger issue here is that as soon and I switch to another tab the popup dissappears, is there any way to make the popup persist?
I would be really thankful if you could help me on that.
Thanks a lot for this post,the ModalPopup was exactly what I was looking for!... But: I need to bind the Command of the OK button to my viewModel, how can I get this done?
ReplyDelete@rainclick: you had the same question and then said you figured it out, could you please help?
Thanks!
Thank you for the post.
ReplyDeleteI tried it out, but when I open a child window and close that by "Ok", the dialog result is still "false"?! It should be "true".
Thanks for sharing your info. I really appreciate your efforts and I will be waiting for your further write ups thanks once again.
ReplyDeleteVery good explanation.
ReplyDeleteHow can I get and use data from the MainWindow in the child Window.
I have a datagrid with multi selection enable in the main window + ContextMenu with commands. I'd like to use the selected items in the child windows. Should I use Event or there's I other way to do that?
Thanks a lot
Jérôme
Hi there--I'm really liking where you are going with this. One limitation we're finding with any 'shadow-box' style child window, like the one you've demonstrated with the embedded pop-ups, is that keyboard focus can still be used to access elements on the parent window. Any ideas on how to limit any focus, general & keyboard, to JUST the pop-up?
ReplyDeleteExcellent!
ReplyDeleteOnly had to make a small adjustment to find the Window (child) using the VisualTreeHelper. This because I used the popup within a bound TabControl.
Thanks very much!
WOW! Thank you for that! You help me so much!
ReplyDeleteIs the ModalDialogPopup.cs really just an alternate ViewModel for the modal view that is constructed in the MainWindow.xaml and through the application of different resource dictionaries? I am looking for a way to completely draft a view in the Designer and wire it up with its own view model.
ReplyDeleteFinally! A modal dialog control that allows me to still render XAML within the popup. Loving it.
ReplyDeleteOne thing I noticed though - WPF's validation system no longer works properly. When an input field has an invalid value, WPF adds an adorner to the adorner layer as well. But it's not visible when I use this control. Any tips on how to solve this?
Excellent control you posted here, big thanks. Made one small adjustment - I noticed the validation system of WPF didn't work anymore (also places a control on the adorner layer), due to this control hiding the adorner layer. I changed it to hiding just the Shader, not the complete layer. I also used Visibility.Collapsed instead of hidden.
ReplyDeleteBut those are minor tweaks to a fantastic control which took me a while to find online. Big thanks from The Netherlands.
Came across this via google. The downloadable project has helped me a great deal, but I'm having trouble understanding how you might do one thing in particular in this paradigm: If your MainWindowViewModel has a some settings, and the child window is opened to allow the user to modify those settings, how can you pass the settings from the MainWindowViewModel to the ChildWindowViewModel, so that the ChildWindowViewModel can copy those settings (for display) before the user modifies them? Would you stick an extra function in the IChildWindow interface? Would you use a different view model?
ReplyDeleteHow do i open multiple windows from the root parent window. Let me know what changes i need to make on the existing code to handle this. Actually the parent window will have a grid, on clicking each item, i need to open the details of the each item in multiple windows.
ReplyDeleteHey I found this very useful. Thank you very much for this article. I use it all the time but have run into a few issues. One I cannot figure how out how to bind an action to the ok or cancel button from the view model. And even more problematic is when i have multiple view models in an application. Clicking on button to go to another view model and then clicking back to previous view model the popup stops working....Any help is appreciated
ReplyDeleteMassive over-engineering for such a simple task.
ReplyDeletecare to point to a simpler/better solution?
DeleteHow do you go about when the dialog actually needs to return something? Let's say the dialog contains a listview to select an item from. The VM needs to know about the dialog.
ReplyDelete