Vista-style message box for Silverlight


Background

Silverlight seems to be missing many controls which is surprising given its 2.0 designation.  Among the facilities I want to use is a modal message box but the only built-in options are to use the alert method or MessageBox.Show.  Neither of these is appealling and both seem to go against my perception of the grain Silverlight.  So time to roll my own.  You can try here… which is much easier than trying to explain it. Requires Silverlight!

Silverlight message box

Download

Binaries – 119 Kb; Source – 172 Kb

Credits

Fortunately to get the job done I’ve been able to lean on three existing projects:

WPF Common TaskDialog for Vista and XP by Karl Shifflett
This WPF control is the style I wanted and have begun to use in WPF projects.

Draggable, resizable, popup window for Silverlight
The Popup window available in the primitives namespace could be a basis for the control however its not draggable. Instead I came across this discussion where sladapter and others created the bones of a draggble and resizeable window that can also be modal. Modal in Windows means taking control of the message pump. As I discovered through this discussion and associated code, in Silverlight modal means preventing other controls from being clicked.

Microsoft Silverlight Control Toolkit
It’s great that the Microsoft Silverlight control toolkit is available because it implements some of the controls that are obviously missing from the commercial release. I’ve used the themeing support and the Expander control from this project.

Caveats

This is only my second Silverlight project. I’ve done WPF work before and lots of web stuff so Silverlight just brings the two together, right? Kind of, but it’s got lots of its own quirks that made the learning curve much steeper that I expected. Because I had the head start of the three projects lists, my expectation (hope) was that I might be able to get the control created in a day, maybe two. In reality its been more like four days. Much of the difference between expectation and reality was the time needed to debug. I’ve become used to the Edit-and-continue functionality in C# but this is not an option when developing for Silverlight because the assemblies are in a .xap (a zip) file so cannot be recompiled on the fly. Also error messages are often not helpful. A common error is “Value not valid key already used”. Great. What value, which key? This is always a markup issue but where? Or AG_UNKNOW_ERROR which I now know will occur when an attempt to use a control for which there is no control template in generic.xaml (usually after I’d refactored something). In general, debugging in Silverlight is not fun.

This is the first Silverlight control I’ve authored. I’m not familiar with the Parts and States model now being used to improve the design experience in Blend. I’ve tried to play nice and create part templates but not being a designer I don’t really know Blend so this is just a best effort adornment. Maybe someone with more experience will pick it apart and correct it.

Using the control

Here’s a simple example using the control in Xaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<UserControl x:Class="SilverlightWebApplication.SimpleExample"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:controls="clr-namespace:Lyquidity.Silverlight.Controls;assembly=Lyquidity.Silverlight.Controls"
     Width="Auto" Height="Auto">
     <Grid x:Name="LayoutRoot" Background="White">
    <controls:CommonDialogWindow
        x:Name="CommonDialogWindow"
        Caption="Drag this window by clicking and dragging this caption area"
        FooterText="Click 'See details' to see more details"
        FooterIcon="Question"
        InstructionIcon="Stop"
        InstructionHeading="Example Message Box Dialog"
        InstructionText="This is where some text will go."
        AdditionalDetailsText="This control uses the Whistler Blue theme for buttons and other controls"
        Buttons="YesNoCancel"
        IsModal="False"
        Grid.Row="2"
    />
     </Grid>
</UserControl>
<UserControl x:Class="SilverlightWebApplication.SimpleExample"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:controls="clr-namespace:Lyquidity.Silverlight.Controls;assembly=Lyquidity.Silverlight.Controls"
     Width="Auto" Height="Auto">
     <Grid x:Name="LayoutRoot" Background="White">
	<controls:CommonDialogWindow
		x:Name="CommonDialogWindow"
		Caption="Drag this window by clicking and dragging this caption area"
		FooterText="Click 'See details' to see more details"
		FooterIcon="Question"
		InstructionIcon="Stop"
		InstructionHeading="Example Message Box Dialog"
		InstructionText="This is where some text will go."
		AdditionalDetailsText="This control uses the Whistler Blue theme for buttons and other controls"
		Buttons="YesNoCancel"
		IsModal="False"
		Grid.Row="2"
	/>
     </Grid>
</UserControl>

Although the control can be used this way (in Xaml) it’s really intended to be used from code to display some information to a user in response to some event like a condition or an error. That is, it is likely to be necessary to create the control in code. Here’s how you instantiate the message box from code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void ShowMessagBox(bool modal)
{
    CommonDialogWindow cdw = new CommonDialogWindow()
    { 
        AdditionalDetailsText = "Some additional text", 
        ButtonsDisabledDelay = 5, 
        Caption = "My window caption", 
        DefaultButton = CustomDialogResults.OK, 
        FooterIcon = CustomDialogIcons.Shield, 
        FooterText = "Some footer message", 
        InstructionHeading = "Some instruction heading", 
        InstructionText = "Some instruction text.  " + 
            "This has to be very long because I want it to wrap.  "+
            "Also going to add new lines\n\n\nto create more bulk.", 
        Buttons = CustomDialogButtons.YesNoCancel, 
        InstructionIcon = CustomDialogIcons.Information, 
        ModalMaskOpacity = 0, 
        ModalMaskBrush = new SolidColorBrush(Colors.LightGray) 
    }; 
 
    cdw.CreateDefaultMaskStoryBoard(0, 0.7, 1.5, null /* Not using the Storyboard's complete event */); 
    cdw.ClosedEvent += new PopupWindowBase.ClosedEventHandler(cdw_ClosedEvent); 
    cdw.ShowDialog("MainOuter"); 
 
    // or if you have a code reference to the root element...
    cdw.ShowDialog(this.MainOuter); 
}
void ShowMessagBox(bool modal)
{
	CommonDialogWindow cdw = new CommonDialogWindow()
	{ 
		AdditionalDetailsText = "Some additional text", 
		ButtonsDisabledDelay = 5, 
		Caption = "My window caption", 
		DefaultButton = CustomDialogResults.OK, 
		FooterIcon = CustomDialogIcons.Shield, 
		FooterText = "Some footer message", 
		InstructionHeading = "Some instruction heading", 
		InstructionText = "Some instruction text.  " + 
			"This has to be very long because I want it to wrap.  "+
			"Also going to add new lines\n\n\nto create more bulk.", 
		Buttons = CustomDialogButtons.YesNoCancel, 
		InstructionIcon = CustomDialogIcons.Information, 
		ModalMaskOpacity = 0, 
	 	ModalMaskBrush = new SolidColorBrush(Colors.LightGray) 
	}; 

	cdw.CreateDefaultMaskStoryBoard(0, 0.7, 1.5, null /* Not using the Storyboard's complete event */); 
	cdw.ClosedEvent += new PopupWindowBase.ClosedEventHandler(cdw_ClosedEvent); 
	cdw.ShowDialog("MainOuter"); 

	// or if you have a code reference to the root element...
	cdw.ShowDialog(this.MainOuter); 
}

I’ll explain the reason for the “MainOuter” parameter to the ShowDialog() method in “Things you need to know” below.

In this case, I’m going to respond to the Closed event to retrieve the button the user presses. In Silverlight its not possible to hang around and wait for a return value from the Show() method because this implies the control will block on the main (UI) thread and this is a no-no. So instead you will respond to the Closed (or Closing) event and continue processing from there. This inability to block on Silverlight’s UI thread is one of the gotcha’s that like many other’s I’ve had to come to terms with: inherently Silverlight application code has to assume multi-threading.

By default the mask will be displayed straight away. However I wanted to learn about creating Storyboards in code and thought the ability to use a Storyboard to display the mask gradually by changing it’s opaque property might be interesting. You could create a custom story board to, for example, change the brush colors or the mask as it is presented.

Things you NEED to know

To use the control in your own project you can copy the sources or add a reference to the Lyquidity.Silverlight.Controls.dll or copy the source into your own project. However, the message box dialog uses the Microsoft toolkit controls and themes so whichever route you take, you need to include two Microsoft control toolkit assemblies in your project:

Microsoft.Windows.Controls.dll

Microsoft.Windows.Controls.Theming.dll

I include copies of these assemblies in the source zip file but you might want to download your own copies from the toolkit project on the Codeplex site.

The concept of modality does not exist in Silverlight. In Windows you can use ShowDialog() to show a modal form but that option does not exist in Silverlight. To emulate a modal Window the control will insert a “mask” (a transparent or semi-opaque Rectangle as it turns out) into the visual tree. This mask is set to cover any existing controls and is set as the top most element (by controlling the z-order). Finally an instance of the message box control is inserted into the visual tree on top of the mask. In this way a user can interact with the controls of the message box and can see the other controls but cannot interact with them.

However…

The mask and control need to be inserted some place into the visual tree. I thought this would be easy. The Application.Current property provides access to the root visual (usually the main page defined in Page.xaml) so, I naively thought, I’d be able to stick the mask and control as a child of the root visual. But no, if the root visual is a default Page (for example page.xaml) then its defined as a UserControl and, for reasons I cannot fathom, a UserControl does not expose a Child or Children property so there’s no handy parent into which to put the mask and control.

Instead you are required to provide a root element inside the UserControl. The element you use must be a Panel or one of its descendants such as a Grid. Fortunately when you create a new page using VS it creates this structure anyway. You must name this root element then provide the name of the element when calling the Show() or ShowDialog() methods. The control uses FindName(<Your root element's name>) on the root visual to find the root element and inserts the mask and control into it dynamically.

Bloat

The .xap file generated by this project is 168K. Much of this size is down to the use of the Microsoft control toolkit library (75K), the implicit theme manager (17K) and the Whistler Blue theme (19K). So it maybe possible to trim the size of the .xap by as much as 100K by not using theme support and including only the Expander control from the tool kit (as it is the only control from that toolkit that’s being used). However, I don’t care too much about wasting 100K in band width as I want the convenience of the toolkit, like the Whistler Blue theme and want the theme support. You might take a different view and you can use this information to assess when the aggrevation of slimming is worth it.

History

2009-02-06 First release
2009-02-17 Updated to support being embedded in a control within a page (thanks to Sarah Boys for her example) and to use Embedded resources rather than the Resource packaging option because its doesn’t work well in an embedded control.

Information and Links

Join the fray by commenting, tracking what others have to say, or linking to it from your blog.


Other Posts

Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Nice post,
good to have nice message box afterall these time
Anyway, thanks for the post