A simple way to make a custom WPF message box
Close up packed or hidden image via Shutterstock
In his last piece A Simple Way to Make a WPF Chromeless Window, Steve Nadamast demonstrated how one could easily create a chromeless window using Microsoft’s “Windows Foundation Framework”, more commonly known as WPF. This time around he will show you how to make a customized WPF message box.
The reason for this is that the WPF default message box uses the same chrome that the default WPF window does. As a result, if you are going to develop a custom window you will also have to do the same with your message boxes or the contrasts between the two will not look symmetrical.
And being deficient in the graphic arts like many software engineers I found this out the hard way when my own commercial product was well reviewed except for the interface. Hence, my recent work to refine the interface to something much more aesthetically pleasing.
Limitations with your own message box we can work with
The first thing to realize is that developing your own message box will constrain you to a certain extent as to what you can provide with it as the default WPF message box comes with a number of configurable options that you can select to produce certain results. The only problem is that you cannot change how the default message box is displayed since it is reliant on the Win API.
Essentially, a message box will request a user to do one of two things, read the provided information and move on, or ask a question, which is in practically all cases a “yes” or “no” question.
Luckily, with the WPF modal dialogue, which is the type of window we will use for our message box, we can easily return a “true” or “false” value allowing us to handle the second scenario noted previously.
The XAML markup
Every display with WPF is made up of XAML markup and the same will be true for our own message box.
To begin with, we will add a new WPF window to our Visual Studio project with the following properties being defined…
Background="WhiteSmoke" WindowStartupLocation="CenterOwner" ResizeMode="NoResize" FontFamily="Arial" BorderThickness="1" BorderBrush="LightGray" AllowsTransparency="True" WindowStyle="None" SizeToContent="Height" Height="Auto" Width="600"
It should be noted that I made the Background color, “WhiteSmoke”, which is a nice, calming white instead of the bright default white color.
Since we will want our message box to be displayed in the center of an application’s master window, we set the property, WindowStartupLocation, to “CenterOwner”.
We don’t want to allow the user to resize our message box, so we will set this property, ResizeMode, to “No Resize”.
The FontFamily property can be set to the font to any one you prefer.
Like the window, which was described in my previous article, we will make the BorderThickness property equal to “1”, keeping it to a nice, thin outline. If you prefer, you can make the borders thicker by incrementing the size. In the same vein, to make the border unobtrusive, make the BorderBrush color “LightGray”.
The AllowsTransparency property must be set to “True” since we will be turning off the chrome with the next property, WindowStyle, which must then be set to “None”.
The SizeToContent property is set to “Height” so that the message window will resize automatically based on the content it is provided. This also follows that the Height property will be set to “Auto”.
Finally, we want to provide a standard width for our message window. In this case, we will make the Width property, “600”, though, you can make the width any size you prefer.
The window body — the dock panel
The content of our message box (window) is as expected, rather light-weight as it has to be able to display relatively limited information. The result is that we have to only put a few XAML controls within the default grid that is generated with each new WPF window and page.
To begin then we will first place a dock-panel inside our default grid so that we can anchor the subsequent content to the top of the window as shown below…
<DockPanel Name="dpTitleBar" VerticalAlignment="Top"> </DockPanel>
You do not have to apply a name for the dock-panel if you do not want to.
The window body — the upper textblock
Next, we add in a bar for the window so that we can provide a title-bar for our message box. This is accomplished by adding a text-block within the dock-panel…
<TextBlock DockPanel.Dock="Top" HorizontalAlignment="Stretch" VerticalAlignment="Top" Background="DarkSlateBlue" Foreground="WhiteSmoke" FontFamily="Arial" FontWeight="DemiBold" Height="24" Padding="5">Application Message</TextBlock>
Notice that we anchor the text-block to the dock-panel by using the object.property correlation to the dock-panel, DockPanel.Dock, setting it to “Top”.
Since we want our text-block to span the width of our message box (window) we set the property, HorizonalAlignment, to “Stretch”.
To style this part of the display we opt for a Background of “DarkSlateBlue” with a Foreground of “WhiteSmoke”.
Our FontFamily can be any type of your own choosing as with the colors. I like “Ariel”, so that is what this property is set to.
To keep the title-bar slim, we will set the Height to “24” pixels, while centering the title itself with a Padding of “5”.
The window body — the stack panel
After the text-block, which will act as our message box’s title\header bar, we will next add a stack-panel that will allow the message box to accommodate any message length required
<StackPanel Name="stkpnlMessageBody" DockPanel.Dock="Bottom" Background="WhiteSmoke" Height="Auto">
Notice that we set the object\property, DockPanel.Dock to “Bottom” so that whatever is included within the stack-panel will be anchored to the bottom of the message box’s window.
The window body — two new grids
Within our stack-panel we will now include two new grids. The first one will allow us to place a left-most image along with a text-block that will be left-aligned but stretched to the length of the remaining grid’s second right-most element.
Notice in the grid markup below how we define two columns; the left one being set to “Auto” so that it will only take up as much space as required by the content while the right-hand column is allowed the remainder of all space remaining. Thus, in the left-hand column we will place a 64×64 pixel image while in the right-hand one we will place our message text.
<Grid Background="WhiteSmoke" Height="Auto"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Image Name="imgMessageImage" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Top" Source="/SQLServer.SourceControl;component/Images/Exit_128x128.png" Height="64" Width="64" Margin="30,30,0,0"/> <TextBlock Name="txtbMessage" Text="" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Background="WhiteSmoke" Foreground="DarkSlateBlue" FontFamily="Arial" FontWeight="Normal" Height="Auto" Padding="5" Margin="0,30,25,0" TextWrapping="Wrap"> </TextBlock> </Grid>
Those of you who are familiar with WPF may ask why the “Margin” property is still being used in a grid that has defined columns when such a layout would afford automatic alignments. This may be true but the “Margin” properties being used are here to set the content more aesthetically within each column.
Such placement cannot be done with grid column\row definitions alone, though much can be done with them as is. However, if you still want to design your forms with more precise appearances, the use of the “Margin” property will still have to be considered.
Nonetheless, if you are fine with the default placements that grid column\row definitions provide then there is no need to use the “Margin” property at all. I use it also out of habit from my training whereby in earlier years of development, positional placement of display controls was quite normal and expected.
The second grid allows us to place the “Yes\No” buttons in a neat manner at the bottom of the message box and to the left…
<Grid Background="WhiteSmoke" VerticalAlignment="Bottom" Margin="5,5,5,5" Height="Auto"> <Button Name="btnYes" Content="Yes" HorizontalAlignment="Left" VerticalAlignment="Bottom" Background="DarkSlateBlue" Foreground="WhiteSmoke" FontFamily="Arial" FontWeight="Normal" Height="24" Width="75"/> <Button Name="btnNo" Content="No" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="80,0,0,0" Background="DarkSlateBlue" Foreground="WhiteSmoke" FontFamily="Arial" FontWeight="Normal" Height="24" Width="75"/> </Grid>
The resultant message box display from the previous markup is shown below…
Notice how the command buttons are somewhat lower than what would be expected from the markup shown previously? You will see why in the next section.
The source code in the parent window CodeBehind module
Similar to ASP.NET WebForms, WPF uses a similar construct to perform what ASP.NET uses to perform server-side processing but now on the desktop. With WebForms, the backend source code is implemented in what is called a CodeBehind module. Similarly, WPF uses the same construct to process requests from the XAML display.
In this case we will show the source code that launches our message box as a result of an action by the user of an application that requires a “yes/no” response. Where this response is requested is in what we call the parent window since the message box will be displayed as a “modal” form.
And it is here we can add some finishing touches to the subsequent message box display. The code for this is quite straightforward as shown below…
Private Function Display_MessageYesNoWindow_MessageLogClear() Dim lboolResult As Boolean = False Dim loMessageYesNoWindow As New MessageYesNoWindow() Dim loStringBuilder As New StringBuilder() Dim loStateProperties As New SQLServer.SourceControl.Library.StateManagement.StateProperties() loMessageYesNoWindow.IMAGE_SOURCE = "MessageWindowWait_128x128.png" loStringBuilder.Append("You have selected to delete all of the application message log data. Do you want to continue?") loStringBuilder.Append(Environment.NewLine + Environment.NewLine) loStringBuilder.Append(Environment.NewLine + Environment.NewLine) loStringBuilder.Append(Environment.NewLine + Environment.NewLine) loMessageYesNoWindow.MESSAGE = loStringBuilder.ToString() loMessageYesNoWindow.Owner = DirectCast(loStateProperties.MAIN_WINDOW,MainWindow) Return(loMessageYesNoWindow.ShowDialog()) End Function
In the code above we have two public properties that are part of the message box “CodeBehind” module; an image source property (IMAGE_SOURCE) and a message property (MESSAGE).
If you note the code for the “IMAGE_SOURCE” property you will see that we only provide the name of an image and its extension. This is because within this property we have also provided the default sub-directory path to the images in our application in WPF format…
Private ssImageSource As String = "/SQLServer.SourceControl;component/Images/" Public Property IMAGE_SOURCE() As String Get Return (ssImageSource) End Get Set(ByVal Value As String) ssImageSource = ssImageSource + Value End Set End Property
The “MESSAGE” property is merely a string that will contain a message as long as you require. However, note the code where the Environment.NewLine property is successively added to the message. It is the use of this property that allows us to move the message box’s command buttons down from the image and message being displayed. This is what provides a nicer symmetry to the display. If this property was not added to the message than the command buttons would align themselves right under the message box image. This is fine if this is what is desired and can also be spaced a little nicer with the addition of some extra padding. However, I chose to use the Environment.NewLine property so I would have slightly more control over this situation.
Returning a Boolean from the message box
The last major aspect of this code is the return of a Boolean when the user closes the message box by selecting one of the command buttons. This is a very simple process since modal WPF windows have the same attributes as a “dialogue” window and allow for the return of a “true\false” value. In the code above you will see that we return the display of our message box window as a Boolean result…
Within the message box “CodeBehind” module, returning the Boolean value is accomplished via the command button event fired by setting the DialogResult to “True” or “False”…
Me.DialogResult = True
Despite the length of this article, the message box modules are quite slim in terms of markup and code. You should then find it rather easy to modify it to your own preferences and implement in your applications.
In addition, changing or adding the capability of a simple “OK” result to the code is just as easy since you do not have to return a Boolean result in this case.
To make things easier in this respect, the complete WPF source code for this message box can be downloaded here.
If you have any questions or comments about this paper, you may contact me at this email address: [email protected]
This post was originally published on Tech Notes.