NOTE: Version 2
This article is an update of the original published in September 2008. After the original article was published, it was noted that there are problems with hosting controls like ComboBox
and DataGrid
on the modal window. The problem was with the Popup
class not being added to the visual tree.
As a solution, I have added a static
property that needs to be set only once in the application and ideally it should be initialised to the VisualRoot of the main page and it must be of a Grid class.
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
ModalControl.ParentHost = LayoutRoot;
}
}
After the initialisation of the ModalControl.ParentHost
, you may use modal windows to host ComboBoxes
and DataGrids
.
The demo project shows how to use the ModalControl
and updated MessageBox
.
Introduction
This article shows how to implement a class that enables a modal display of any UserControl
derived control.
Background
My first implementation of this class required that you derive your controls from it in order for them to be displayed modally.
That works very well, and it is the most complete and easy to use implementation. But, the Expression Blend designer window does not display controls that are not derived directly from UserControl
, so we lose all the nice design features of Blend.
That made me think how else I could achieve the same behaviour without losing designing with Blend. After some thinking, I realised that aggregation must be the solution.
Features of the ModalControl Class
- Modal display of any
UserControl
derived control
- Dragging of the hosted control
- Centering the hosted control when the browser is resized
- Hosted control is prevented from being dragged outside the browser's window
The Project
This project builds on the Silverlight Wizard project by showing how to show the Wizard modally, and I have also built a MessageBox
that demonstrates how the ModalControl
class can be wrapped up to create a self contained modal window.
What It Looks Like
Here, we can see the Silverlight Wizard project main page changed so a button shows the wizard modally. I have also added a Show Dialog button that will popup a modal dialog window that I have implemented by aggregating ModalControl
.
Here, we see the Silverlight Wizard shown modally without making any changes to it. We use the ModalControl
class externally.
And, here is the modal dialog:
Using the Code
You may employ services provided by the ModalControl
class in two ways:
- Use as a helper class and pass in the
ShowModal
function of the UserControl
you want to show modally.
- Wrap the
ModalClass
into your control, the MessageBox
shows how to use this technique.
ModalControl as a Helper
Notice in the code below that we only will need to define one instance of the ModalControl
; it is important that the ShowModal
takes the control to display as a parameter and the HideModal
returns the UserControl
.
ModalControl _oModalCtrl = new ModalControl();
void OnShowWizardClick(object sender, RoutedEventArgs e)
{
Wizard oWizard = new Wizard();
oWizard.DataContext = LayoutRoot.DataContext;
oWizard.Pages.Add(new WizardPage1());
oWizard.Pages.Add(new WizardPage2());
oWizard.Pages.Add(new WizardPage3());
oWizard.PageEvent += new WizardPageEvent(OnPageEvent);
_oModalCtrl.ShowModal(oWizard);
}
void OnPageEvent(Wizard sender, WizardEventArgs e)
{
_txtMsg.Text = string.Format("Action: {0}, Current: {1}, New: {2}",
e.Action, e.CurrentPageIndex, e.NewPageIndex);
if(e.Action == WizardAction.Finish)
{
Wizard oWizard = (Wizard)_oModalCtrl.HideModal();
}
}
Wrapping the ModalControl
Encapsulating ModalControl
within your own class is just as simple, it is fully demonstrated in the MessageBox
class in the ModalWizard
project here. The code here is a very simplified version of the actual implementation of the MessageBox
, but it shows the important bits.
public class MessageBox : UserControl
{
public static MessageBox Show(string Title, string Message)
{
MessageBox oBox = new MessageBox(Title, Message);
oBox.ModalHost.ShowModal(oBox);
return oBox;
}
ModalControl ModalHost;
private MessageBox(string Title, string Message)
{
InitializeComponent();
txtTitle.Text = Title;
txtMessage.Text = Message;
ModalHost = new ModalControl();
btClose.Click += OnCloseClick;
}
private void OnCloseClick(object sender, RoutedEventArgs e)
{
ModalHost.HideModal();
}
}
Using the MessageBox Class/Control
The following code section shows how to use the MessageBox
control; you must remember that the ModalControl
does not show the child/hosted control modally, it only gives the illusion of modality. In this version of the MessageBox
, we don't need to maintain a reference
to the MessageBox
as I have assumed that we will display one MessageBox
at a time ever. This assumption allows me to have a single static
reference to the MessageBox
and therefore makes the MessageBox
look more like the WinForms MessageBox
.
void OnShowDialogClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("Some modal title", "This is some message!",
MessageBox.Buttons.YesNo,
MessageBox.Icons.Information, OnDialogClosed);
}
bool OnDialogClosed(object sender, ExitCode e)
{
Debug.Print("Dialog Closed with code: " + e);
return true;
}
Points of Interest
It is very important to remember that the modal windows are not actually modal, there is a semi-transparent canvas placed in between the current view and the modal control. If when the modal window is closed an exit code or any other detail is required, you must implement
some callback or an event handler. The MessageBox
and ModalWizard
classes in this project demonstrate all of these points.
Special Thanks
Reader Predrag Tomasevic for identifying the problem with Popup
class not being added to the visual tree.
History
- 28th September, 2008: Initial post
- 11th March, 2009: Article updated