Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Customizable WPF MessageBox

Rate me:
Please Sign up or sign in to vote.
4.95/5 (56 votes)
21 Jan 2021CPOL29 min read 74.2K   2.6K   58   140
Finally, a customizable MessageBox for WPF applications
Describes a variant of the MessageBox that allows some customization.

Introduction

I'm old. Really old. And as a result of increasing age (or maybe just because of bad choices made in my intemperate youth), my eyesight is beginning to fail me. Everything is out of focus, I can't find quite the right distance at which to hold a book, and if it weren't so annoying, I might find the kaleidoscopic color halos that seemingly surround everything somewhat pretty.

A particular annoyance related to failing eyesight is the infinitesimally small text that Windows uses unless you increase the font size in 10% increments. The problem with this is that even at 110%, the text is WAY bigger than it really needs to be. In any case, this tiny text is used in the standard WPF message box, and it cannot be changed. In fact, NOTHING about the standard WPF message box can be changed. This realization finally got the better of me, and I was forced to come up this code.

But First, Complaints

When I started researching the standard WPF message box, I was surprised and appalled that it wasn't a WPF-specific object. It turns out that it's entirely implemented by the Win32.Interop functionality, and doesn't have a single drop of WPF goodness thrown in. I suppose a Microsoft apologist could try making a case about how their approach probably reduced the overall .Net code footprint by a few thousand bytes, but I ain't buying it. There was a true opportunity here, and Microsoft squandered it. By the way, this is the reason that it's not configurable in any way - it uses Windows system properties and that's that. You're not allowed to stray from those parameters. BAD PROGRAMMER! BAD! BAD!

What I Wanted to Achieve

My primary goal was to develop a replacement for the standard message box that was reasonably configurable, while being totally WPF-based, as well as presenting an interface that was as close as possible to the original message box. Through some static wizardry, I was able to accomplish this without too much heartburn.

Emulating Standard WPF Message Box Functionality

My first task was to emulate the standard WPF MessageBox. Originally (in the previous iterations of the code), I provided an overload of the Show() method for every possible parameter combination, but the sheer number of overloads was becoming untenable. What really pushed me over the edge was providing functionality to specify the default button - it was simply a bridge too far, so I decided to do this:

C#
MessageBoxEx.Show(string msg, params object[] parameters)

This allows the user to specify the message text, and any other parameter he desires. Yes, using this strategy allows the dev to specify more than one of any parameter, but the method has some protection against stupid developer tricks. The method starts out with variables set to reasonable defaults. As the parameters are processed, a given variable is set until its value is no longer equal to the default value. Granted, this is fairly minimal in nature, and could be taken to the extreme of throwing an exception if a given variable is set more than once, but I prefer not to interrupt processing unless something truly catastrophic has happened, and allowing the programmer to be a moron isn't a catastrophe as much as it is a comment on the elasticity of today's already-loose moral standards.

In short, be an idiot if you want to, because the method will use what it can, and throw away the rest. Anything more than the parametersthat are expected will result in a waste of (and excessive) processing time, which has been proven to adversely affect the already tenuous fabric of space and time. The true benefit of this approach is to me (which is admittedly all that really matters), and that benefit is that there is only ONE Show() method to maintain, and the code becomes MUCH more simple. This method can optionally accept the title, desired buttons, the icon image, and the default button, and those parameters can be specified in any order. Any other parameter is not supported, and will be ignored.

The standard Wpf MessageBox (and MessageBoxEx) accepts the following objects:

  • MessageBoxButton.OK
  • MessageBoxButton.OKCancel
  • MessageBoxButton.YesNo
  • MessageBoxButton.YesNoCancel
  • MessageBoxImage.Information (can also use MessageBoxImage.Asterisk)
  • MessageBoxImage.Question
  • MessageBoxImage.Warning (can also use MessageBoxImage.Exclamation)
  • MessageBoxImage.Error (can also use MessageBoxImage.Hand or MessageBoxImage.Stop)

Note - If you specify an icon, the system sound associated with that icon is also played. However, you can optionally turn off the playing of the sound via a configuration method.

While the Wpf MessageBox uses the MessageBoxDefaultResult enumerator to specify the default button, it's fairly limited in scope, so MessageBoxEx provides a new MessageBoxButtonDefault enumerator that expands the support of default buttons. You can specify a specific button, the button's ordinal left-to-right position, the default used by Windows.Forms.MessageBox, the most/least positive button in the specified buttons, or even no default at all.

Added Features

Beyond the standard features, I also added support for altering the following items. This is the primary reason for writing this code in the first place.

  • Font family
  • Font size
  • Message area text and background color
  • Button panel background color
  • Custom button template support
  • Enhanced default button support
  • Set maximum possible message box width

Notable Missing Features

If you've ever written code for Windows.Forms, you've probably had reason to use the Windows.Forms.MessageBox, and you may have noticed that Microsoft decided not to support MessageBoxButton.AbortRetryIgnore or MessageBoxButton.RetryCancel. This also means these buttons aren't available for the default button setting either. MessageBoxEx adds these buttons (and the associated defaults) back to the playing field - see the next article section. Huzzah!

Extended Functionality

When creating the MessageBoxEx class, I decided I wanted to add functionality not seen in the standard WPF message box. This included the afore-mentioned missing buttons, a checkbox, a clickable URL, a clickable icon (to execute external code), and more default button options. To delineate the standard stuff from the extended functionality, you have the MessageBoxEx.ShowEx() method, which adds support for the MessageBoxButtonEx enumerator, and the MsgBoxExtendedFunctionality class, which allows you to add the checkbox, url, and clickable icon.

The following buttons are available:

  • MessageBoxButtonEx.OK
  • MessageBoxButtonEx.OKCancel
  • MessageBoxButtonEx.YesNo
  • MessageBoxButtonEx.YesNoCancel
  • MessageBoxButtonEx.AbortRetryIgnore
  • MessageBoxButtonEx.RetryCancel

The following extended functionality has also been implemented:

  • Clickable icon - When specified, the icon becomes clickable, and allows you to perform an action defined in what I call an "error delegate" object. This allows the message box to perform some action when the icon is clicked. This feature is available for all image icons. When the icon is clickable, the cursor will change to a hand, and the associated tooltip (if specified) will be displayed. This feature idea was spurred by a discussion in the CodeProject lounge regarding screen shot generation for emailing to the support team.
     
  • Details - The message box can display both a message, and "details". This could be useful for those times when you want to display an Exception.Message, as well as an Exception.StackTrace, but you don't want to have a huge message box. The details panel is an expander, and is initially collapsed when the message box is displayed.
     
  • Checkbox - this displays a CheckBox with a prompt (content) that you specify. Some possible uses for this would be to suppress the display of the message box in certain instances, or affect some other functionality in your application. It is up to the developer as far as handling the checkbox value. The messagebox does not care beyond presenting it. See the BtnClickme3c_Click() method in the sample application for an example of how this feature might be used.
     
  • Clickable URL - this displays a clickable URL that will automatically open the default browser when you click it. When you hover over the link, the cursor will change to the System "hand", and a tooltip is displayed with the actual URL to which the user will be browsing. I couldn't find a screen capture tool that would capture both the cursor AND the tooltip (I tried the snipping tool and Irfanview, so my efforts were substantially less than "exhaustive"), so the screenshot below doesn't show the tooltip..
     
  • AbortRetryIgnore and RetryCancel buttons - WPF MessageBox does not support the Windows Forms buttons AbortRetryIgnore or RetryCancel. The MessageBoxEx gives you the opportunity to add them back (via the extended functionality object). It baffles me as to why Microsoft would half-ass something like this.
     
  • Default Button - you can specify any button as the default by one of a number of ways - the specific button, the button according to its left-to-right ordinal position, the standard Windows Forms default (depending on buttons used), the most/least positive, or none at all.

NOTE: The checkbox and the URL are not included in the scrollable message area. This keeps them in view at all times.

The Code

The amount of code needed to implement this message box started out surprisingly small. So small that it only took me a few hours to implement it, and then move it to a sample project for this article.

NOTE: The original version of this article included several code dumps, but with the frequency of change, it became tedious to try to keep them synced up with what's really going on in the code, so I deleted them from the article. If you want to see what I did, either browse the code here on CodeProject, or just download the ZIP file and check it out (this is a lot more fun).

Basic Design Goals

As you know, the standard MessageBox is a static object that doesn't need to be instantiated to use. This new MessageBoxEx is static as well, or at least "static-ish". In order to implement it, I created a standard WPF Window that contains both static and non-static properties and methods. The idea is that the programmer would call one of the static MessageBoxEx.Show...() methods, which would then instantiate a non-static version of the window. I also wanted to make the outward-facing experience as identical as possible to the standard MessageBox, so the return values are the same as what you get from the standard message box.

Implementation - XAML

The XAML for this form is fairly limited because we're not really doing anything fancy. I think the comments in the XAML provide sufficient enough documentation as to not require an accompanying narrative.

Clickable Icon - Since images aren't "clickable", I had to resort to handling the MouseLeftButtonUp event. On top of that, I wanted to make the visual appearance of the icon change when the mouse hovered over it, so I set the default opacity to 0.85, and when the message box is displaying an error, I add a custom style (in the code behind) to the image that reacts to mouse hover, which changes the opacity to 1.0 and changes the cursor to a hand. I also use the code behind to add a tooltip to the image (actually the image's parent grid because I could not get a bound tooltip to work on the image element), so the user gets some sort of indication that the icon is indeed clickable.

Clickable URL - The clickable URL is pretty much the same. The underlying control is a TextBlock and doesn't support the Click event, so once again, I had to handle the MouseLeftButtonUp event instead. I also created a custom style that triggers on MouseEnter so I could change the cursor to a hand. On hind-soght, I suppose I could have used a RichTextBox to display the URL, but I considered it too late, and besides, I didn't consider the juice to be worth the squeeze.

Implementation - C#

The code-behind really isn't anything special or technically difficult to understand after a cursory inspection. As stated earlier, the code is essentially broken up into static, and non-static fields, properties, and methods. The message box itself needs to be instantiated, but the class has static properties and methods that can be used for easy configuration and rendering of the message box. Just like MessageBox, MessageBoxEx provides several overloads of the Show() method (and its variants) to display itself.

As far as code organization is concerned, I did something I don't normally do, but it was done in the interest of making it easy to copy the files into your own code. First, all of the classes related to the MessageBoxEx class are located in the MessageBoxEx.xaml.cs file. Second, I took advantage of the fact that the class is a partial class, and separated the static parts from the non-static parts. It might seem weird, but it helps me, and that's the most important thing.

To make the form size itself appropriately, I utilized the window SizeToContent property (in the XAML). This property allows the window to grow/shrink to fit its content, allowing the window to only be as big as it needs to be, given the minimum and maximum width/height limitations specified in the XAML. A curious side-effect of the SizeToContent property is that it takes affect AFTER the window has been positioned on the screen. This means that if the window needs to display more than a single line message, the window won't be "centered" on the screen as intended. To mitigate this behavior, I had handle the window's SizeChanged event, and manually re-center the form on the screen. The code was simple enough, but I figured I'd cite it here.

Extended Functionality

JAN 08 - See update notice at the end of this section!

The extended functionality required different method names (and requisite overloads) because parameter collisions were induced. Beyond that, some additional classes were required to make implementation and use as seamless as possible. All of the extended features described below can be combined into a single message box.

CheckBox

The checkbox allows you to present a checkbox on the message box. The content of the checkbox is specified by you, and can really be anything that you find appropriate. This feature requires that you instantiate an MsgBoxExCheckBoxData object in order to both specify the checkbox content, and provide property to determine the "checked" state of the checkbox. The MsgBoxExCheckBoxData class can be found near the bottom of the MessageBoxEx.xaml.cs. It looks like this:

C#
/// <summary>
/// Reresents the object that allows the checkbox state to be discoevered externally of the 
/// messagebox.
/// </summary>
public class MsgBoxExCheckBoxData : INotifyPropertyChanged
{
#region INotifyPropertyChanged

private bool isModified = false;
public bool IsModified { get { return this.isModified; } set { if (value != this.isModified) { this.isModified = true; this.NotifyPropertyChanged(); } } }
public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        if (propertyName != "IsModified")
        {
            this.IsModified = true;
        }
    }
}
    
#endregion INotifyPropertyChanged

private string checkBoxText;
private bool   checkBoxIsChecked;

/// <summary>
/// Get/set the text content of the checkbox
/// </summary>
public string CheckBoxText      
{ 
    get { return this.checkBoxText;} 
    set 
    { 
        if (value != this.checkBoxText) 
        { 
            this.checkBoxText = value; 
            this.NotifyPropertyChanged(); 
        } 
    } 
}

/// <summary>
/// Get/set the flag that indicates whether the checkbox is checked
/// </summary>
public bool CheckBoxIsChecked 
{ 
    get { return this.checkBoxIsChecked; } 
    set 
    { 
        if (value != this.checkBoxIsChecked) 
        { 
            this.checkBoxIsChecked = value; 
            this.NotifyPropertyChanged(); 
        } 
    } 
}
}

To use implement this feature in your own code, you need to instantiate the checkbox data object. The code shown below was pulled from the sample application.

C#
MsgBoxExCheckBoxData checkboxData = new MsgBoxExCheckBoxData()
{ 
// I suspect you're sometimes going to want to persist this value (app 
// settings, etc), so how you set this value is completely up to you.
CheckBoxIsChecked = false, 
CheckBoxText = "Don't show this message anymore"
};

Once you have your checkbox data object instantiated, you need to do something like the following.

C#
// In this particular example, handling the decision to show/not show a messagebox is 
// handled by checking the status of the checkbox data. The mesagebox itself doesn't care.
if (this.checkboxData != null && !this.checkboxData.CheckBoxIsChecked)
{
// show box with a checkbox
MessageBoxEx.ShowWithCheckBox("This is possibly pointless and can be permenantly "+
                                "dismissed by clicking the checkbox below."
                                , this.checkboxData, "Checkbox Sample");
}
else
{
MessageBoxEx.Show("But you said not to show the message anymore. Make up your mind.","Hypocrite Notice");
}

In essence, the messagebox doesn't care what the checkbox is for, and it's on the developer to handle all the associated persistence issues that might be associated with the checkbox's intended functionality, as well as the scoping of the data object's implementation. In the example shown above, the data object is declared in the MainWindow class so that subsequent attempts to display that message box can be controlled by the last value of the CheckBoxIsChecked property.

Details

This feature allows you to display large amounts of text in a shorter message box, and is handy for separating augmenting information from the actual message. An example would be specifying an Exception.Message as the message text, and then specifying th Exception.StackTrace as the "details". Useage requires that you use one of the ShowWithDetails method overloads.

C#
MessageBoxEx.ShowWithDetails("Here's some source code. Click the Details expander below."
                            , Testmsg()
                            , "Really Long Message, With Deatils"
                            , MessageBoxButton.OK
                            , MessageBoxImage.Information);

The Testmsg() method returns a long block of source code to serve as content for the details pane.

Clickable Error Icon

This feature allows you to make the icon clickable, ostensibly to perform some ancillary action. It's limited to the error icon, because I figured that's really the only place you would need something like that. I envision it being used to send an email to your support team, or maybe to correct some setting that may have adversely affected execution in some way. In any case, if you want it available for every icon, modifying the code to do so should be fairly trivial.

The first thing you need to do is create a class that inherits from MsgBoxExDelegate. That class provides everything you need to react to a clicked icon, but requires you to override the PerformAction method.

C#
public abstract class MsgBoxExDelegate
{
/// <summary>
/// Get/set the message text from the calling message box
/// </summary>
public string   Message     { get; set; }
/// <summary>
/// Get/set the details text (if it was specified in the messagebox)
/// </summary>
public string   Details     { get; set; }
/// <summary>
/// Get/set the message datetime at which this object was created
/// </summary>
public DateTime MessageDate { get; set; }

/// <summary>
/// Performs the desired action, and returns the result. MUST BE OVERIDDEN IN INHERITING CLASS. 
/// </summary>
/// <returns></returns>
public virtual MessageBoxResult PerformAction(string message, string details=null)
{ 
    throw new NotImplementedException(); 
}
}

An inheriting class may look like this:

C#
public class ErrorMsgDelegate : MsgBoxExDelegate
{
public override MessageBoxResult PerformAction(string message, string details=null)
{
    this.Message = message;
    this.Details = details;
    this.MessageDate = DateTime.Now;

    // for this sample, we're just showing another messagebox
    MessageBoxResult result = MessageBoxEx.Show("You're about to do something because this is an "+
                                                "error message (clicking yes with play a beep sound). "+
                                                "Are you sure?", 
                                                "Send Error Message To Support", 
                                                MessageBoxButton.YesNo, 
                                                MessageBoxImage.Question);
    if (result == MessageBoxResult.Yes)
    {
        //indicate that they clicked yes
        SystemSounds.Beep.Play();
    }
    return result;
}
}

The extent of the code that is contained in the PerformAction methods depends entirely on the architecture of the rest of your application. The sample above merely exercises the feature within the context of the sample application.

When the messagebox closes, the reference to the delegate object in the message box is set to null to avoid inadvertently triggering the clickable icon in subsequent calls to the message box.

To use this feature, do the following:

C#
MessageBoxEx.SetErrorDelegate(this.errorDelegate);

// if you want the message box to be closed when the action is completed, do this:
MessageBoxEx.SetExitAfterErrorAction(true);

MessageBoxEx.Show("Show an error message. The icon is clickable.", "Error"
                , MessageBoxButton.OK, MessageBoxImage.Error);

After writing this part of the article, it occurred to me that I could have combined the two "Set..." methods into one since they're associated. Ahh well, it's easy enough to change for even a lightly experienced dev, so have at it if you feel the need.

JAN 08 UPDATE!

While I was implementing the new URL code, it became obvious that the extended parameters were getting out of hand (the URL was the fourth extended functionality to be added), so I created a single additional object that is passed into the ShowExt method (along with the message, titlem, buttons, and image). This object is called MsgBoxExtendedFunctionality, and in it, you place one or more of the extended functionalities that you want to use in a given messagebox. This serves a couple of purposes - a) reduce the number of "special" versions of the Show method (with each one having eight overloads), and b) reduced maintenance time if I felt like adding more extended functionality. The old methods (ShowWithCheckBox, ShowWithDetails, and ShowWithCheBoxAndDetails) are still there, but they've been modified to call the new ShowExt methods, and the intellisense has been updated to show that they are deprecated.

To see examples of its usage, look for the ClickMe_Nc methods (they handle the button clicks for the buttons on the right side of the sample app window).

Useage

Somewhere in your app startup (I recommend using the constructor in the MainWindow because at that point you can specify the parent window from which to inherit the appropriate font properties. In the sample app, I created a method called InitMessageBox() containing the following statements.

C#
MessageBoxEx.SetParentWindow(this);
MessageBoxEx.SetMessageForeground(Colors.White);
MessageBoxEx.SetMessageBackground(Colors.Black);
MessageBoxEx.SetButtonBackground(MessageBoxEx.ColorFromString("#333333"));
MessageBoxEx.SetButtonTemplateName("AefCustomButton");
MessageBoxEx.SetMaxFormWidth(500);

After you've done this once in the application, using MessageBoxEx is identical to using the original MessageBox.

C#
MessageBoxResult result = MessageBoxEx.Show("Your message. Continue?", "Caption", MessageBoxButton.YesNoCancel);

Of course, the button specification defaults to MessageBoxButton.OK in no button group is specified.

The Sample Application

MainWindow

The sample application is simple in and of itself, providing a series of buttons intended to exercise certain aspects of the message box. None of the following screen shots really line up with buttons on the main window, but they do illustrate several of the different combinations of message length, icons, and buttons, as well as illustrate the icon alignment in various conditions. You should play with the sample app to get a more current feel for things. Each screen shot is preceded by the standard MessageBox version.

Small message (standard MessageBox)
Small message (MessageBoxEx)

Medium message (standard MessageBox)
Medium message (MessageBoxEx)

Longer message (standard MessageBox)
Longer message (MessageBoxEx)

Huge message (standard MessageBox)
Huge message (MessageBoxEx)

details collapsed
details expanded

URL example

If you don't hear a sound, it's because you haven't specified an associated system sound in Windows. This will most likely happen on the "question" button (that's where it happened to me, and setting a sound caused both the standard message box and the extended one to play the appropriate sound).

MessageBoxEx Class Documentation

The following is a method by method description of the class. Since all of the configuration is handled via static methods, those are the methods I will be discussing here. There will be few, if any, code dumps.

The Show...() Methods

The Show() method presents a message box that represents a feature-similar version of the standard WPF MessageBox. This method accepts the following parameters.

string msg This is the text of the message to be displayed. All desired formatting must be performed in the calling method. At the very least, this parameter MUST be specified.
params object[] parameters This parameter allows the developer to add whatever parameters are applicable to the given message box instance. This is done to eliminate the dozens of overloads to the Show method that would otherwise be required. The following parameters will be evaluated by this method.
  • string - evaluated as the intended title of the message box window. If not specified, the window title is determined by the icon (if specified), or the default caption.
     
  • MessageBoxButton - evaluated as the intended buttons to display. If not specified, the default value of MessageBoxButton.OK is used. The following buttons are available:
    • OK
    • OKCancel
    • YesNo
    • YesNoCancel
  • MessageBoxImage - evaluated as the intended icon to display. If not specified, the default value of MessageBoxImage.None is used. The following images are available:
    • Information (same icon for Asterisk)
    • Question
    • Warning (same icon for Exclamation)
    • Error (same icon for Stop or Hand)
  • MessageBoxButtonDefault - evaluated as the intended default button. If not specified, the default value of MessageBoxButtonDefault.Forms is used (uses the same deafult button strategy as the Windows.Forms.MessageBox. The following defaulst are available:
    • OK - specific button
    • Cancel - specific button
    • Yes - specific button
    • No - specific button
    • Button1 - 1st button by ordinal left-to-right position
    • Button2 - 2nd button by ordinal left-to-right position
    • Button3 - 3rd button by ordinal left-to-right position
    • Forms - the same as the Windows.Forms.MessageBox defauilt
    • MostPositive - the button that indicates the most positive result
    • LeastPositive - the button that indicates the least positive result
    • None - no default button
  • Window - evaluated as the owner window. If not specified, the applications main window will be used.
     

While it's possible to specify more than one of any of the parameters, the Show method will stop evaluating parameters after it encounters one that does not use the default value of the given parameter type. For instance, if your parameter list looks like this:

MessageBoxEx.Show("My message text", MessageBoxButton.YesNo, MessageBoxButton.OK);

...the message box will display the Yes/No buttons, and the OK button will.be ignored. Conversely, if your parameter list looks like this:

MessageBoxEx.Show("My message text", MessageBoxButton.OK, MessageBoxButton.YesNo);

...the message box will again display the Yes/No buttons, because the OK button is the default. Finally, if you do this:

MessageBoxEx.Show("My message text", MessageBoxButton.OkayCancel, MessageBoxButton.YesNo);

...the message box will display the Okay/Cancel buttons because that value was the first value encountered that was not the default.

I certainly can't control what you do, but it's pointless and stupid to specify multiple instances of the same parameter, and it just forces the Show method to handle useless parameters, and takes more time to decide what to do

The Show() method returns a MessageBoxResult value. This is used to indicated which button was clicked to dismiss the message box window. If the user clicks the window's close button, the return value will be selected based on the following:

  • For MessageBoxButton.OK, the result will be MessageBoxButton.OK
  • For MessageBoxButton.OKCancel, the result will be MessageBoxButton.Cancel
  • For MessageBoxButton.YesNo, the result will be MessageBoxButton.No
  • For MessageBoxButton.YesNoCancel, the result will be MessageBoxButton.Cancel

The ShowEx() method is similar to the Show() method, except that it presents a message box that implements extended functionality. This method accepts the following parameters.

string msg This is the text of the message to be displayed. All desired formatting must be performed in the calling method. At the very least, this parameter MUST be specified.
params object[] parameters This parameter allows the developer to add whatever parameters are applicable to the given message box instance. This is done to eliminate the dozens of overloads to the Show method that would otherwise be required. The following parameters will be evaluated by this method.
  • string - evaluated as the intended title of the message box window. If not specified, the window title is determined by the icon (if specified), or the default caption.
     
  • MessageBoxButtonEx - evaluated as the intended buttons to display. If not specified, the default value of MessageBoxButtonEx.OK is used. The following buttons are available:
    • OK
    • OKCancel
    • YesNo
    • YesNoCancel
    • AbortRetryIgnore
    • RetryCancel
  • MessageBoxImage - evaluated as the intended icon to display. If not specified, the default value of MessageBoxImage.None is used. The following images are available:
    • Information (same icon for Asterisk)
    • Question
    • Warning (same icon for Exclamation)
    • Error (same icon for Stop or Hand)
  • MessageBoxButtonDefault - evaluated as the intended default button. If not specified, the default value of MessageBoxButtonDefault.Forms is used (uses the same default button strategy as the Windows.Forms.MessageBox. The following defaults are available:
    • OK - specific button
    • Cancel - specific button
    • Yes - specific button
    • No - specific button
    • Abort - specific button
    • Retry - specific button
    • Ignore - specific button
    • Button1 - 1st button by ordinal left-to-right position
    • Button2 - 2nd button by ordinal left-to-right position
    • Button3 - 3rd button by ordinal left-to-right position
    • Forms - the same as the Windows.Forms.MessageBox defauilt
    • MostPositive - the button that indicates the most positive result
    • LeastPositive - the button that indicates the least positive result
    • None - no default button
  • Window - evaluated as the owner window. If not specified, the applications main window will be used.
     

While it's possible to specify more than one of any of the parameters, the Show method will stop evaluating parameters after it encounters one that does not use the default value of the given parameter type. For instance, if your parameter list looks like this:

C#
MessageBoxEx.Show("My message text", MessageBoxButtonEx.YesNo, MessageBoxButtonEx.OK)

...the message box will display the Yes/No buttons, and the OK button will.be ignored. Conversely, if your parameter list looks like this:

C#
MessageBoxEx.Show("My message text", MessageBoxButtonEx.OK, MessageBoxButtonEx.YesNo)

...the message box will again display the Yes/No buttons, because the OK button is the default. Finally, if you do this:

C#
MessageBoxEx.Show("My message text", MessageBoxButtonEx.OkayCancel, MessageBoxButtonEx.YesNo)

...the message box will display the Okay/Cancel buttons because that value was the first value encountered that was not the default.

I certainly can't control what you do, but it's pointless and stupid to specify multiple instances of the same parameter, and it just forces the Show method to handle useless parameters, and takes more time to decide what to do

The MsgBoxExtendedFunctionality Class

The extended functionality class (MsgBoxExtendedFunctionality) provides a sort of one-stop-shop for any/all of the available/future extensions to the standard message box. It's simply a container for all of the extended functionality objects, and makes using the extended functionality much simpler. The idea is that the user will populate this object with whatever is required in a given situation. Indeed, only the objects that are needed for a given instance of the message box are necessary to be included. The messagebox will simply use its current property values if they are possible affected by the unpopulated objects.

string DetailsText Get/set the text that will be displayed in the details expander. If this property is populated (not null/empty), the details expander will be displayed automatically. This property is not associated with any of the other extended functionalities.
MsgBoxExCheckBoxData CheckBoxData Get/set the CheckBoxData object. This property is not associated with any of the other extended functionalities.
MsgBoxExDelegate MessageDelegate Get/set the message delegate object. This object allows the message box icon to be clickable, and provides a mechanism for executing code in response to the click.
bool ExitAfterAction Get/set the flag that indicates that the message box should be dismissed after running the message delegate action.
string DelegateToolTip Get/set the tooltip displayed when the user hovers the mouse over the message box icon
MsgBoxUrl URL/b> Get/set the message box URL object, which causes a clickable URL to be displayed in the message box.
MessageBoxButtonDefault ButtonDefault Get/set the default button to use on the message box. If no button is specified, it uses the button that is typically used in a Windows.Forms.MessageBox.

Using this class is fairly straightforward, in that you use object initializers to populate it, like so:

C#
MsgBoxExtendedFunctionality ext = new MsgBoxExtendedFunctionality()
{
// Default button - you only need to set this if you want to use something 
// other than the standard Windows.Forms.MessageBox default button 
staticButtonDefault = this.ButtonDefault;

// clickable icon
MessageDelegate  = this.errorDelegate,
ExitAfterAction  = true,
// you can also set the tooltip text, but for the purposes of this demo, we're 
// using the default value
//,DelegateToolTip = null

// checkbox
CheckBoxData     = this.checkboxData,

// details
DetailsText      = this.Testmsg(),

// url
    
URL              = new MsgBoxUrl()
{
    URL          = new Uri("http://www.google.com"),
    DisplayName  = "Google",
    Foreground   = Colors.LightBlue
}
};

When you look at the constructor, you can see that everything is set to some default value to make sure each call to MessageBoxEx.ShowEx() uses its own settings.

C#
public MsgBoxExtendedFunctionality()
{
this.ButtonDefault   = MessageBoxButtonDefault.Forms;
this.DetailsText     = null;
this.CheckBoxData    = null;
this.MessageDelegate = null;
this.URL             = null;
this.DelegateToolTip = "Click this icon for additional info/actions.";
}

If you want to add new extended functionality, create your extended functionality object, and add it to this class as a property. You'll probably want to add more static configuration methods inside MessageBoxEx to handle settings for the new functionality as well.

The MsgBoxExCheckBoxData Class

This object allows you to create a bindable object that can be evaluated externally of the message box (in the code that showed the message box), and is most likely going to be used to allow the app's user to elect not to see a given message box.

C#
public class MsgBoxExCheckBoxData : INotifyPropertyChanged
{
#region INotifyPropertyChanged

private bool isModified = false;
public bool IsModified { get { return this.isModified; } set { if (value != this.isModified) { this.isModified = true; this.NotifyPropertyChanged(); } } }
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Notifies that the property changed, and sets IsModified to true.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        if (propertyName != "IsModified")
        {
            this.IsModified = true;
        }
    }
}
    
#endregion INotifyPropertyChanged

private string checkBoxText;
private bool   checkBoxIsChecked;

/// <summary>
/// Get/set the text content of the checkbox
/// </summary>
public string CheckBoxText      
{ 
    get { return this.checkBoxText; } 
    set 
    { 
        if (value != this.checkBoxText) 
        { 
            this.checkBoxText = value; 
            this.NotifyPropertyChanged(); 
        } 
    } 
}
/// <summary>
/// Get/set the flag that indicates whether the checkbox is checked
/// </summary>
public bool CheckBoxIsChecked 
{ 
    get { return this.checkBoxIsChecked; } 
    set 
    { 
        if (value != this.checkBoxIsChecked) 
        { 
            this.checkBoxIsChecked = value; 
            this.NotifyPropertyChanged(); 
        } 
    } 
}
}

A possible usage would something like this:

MsgBoxExCheckBoxData checkboxData = new MsgBoxExCheckBoxData()
{ 
CheckBoxIsChecked = false, 
CheckBoxText = "Don't show this message any more"
};
MsgBoxExtendedFunctionality ext = new MsgBoxExtendedFunctionality()
{
CheckBoxData = this.checkboxData
};
MessageBoxEx.ShowEx("Message text", ext);

When the message box returns, the ext.CheckBoxData object contains the current checked state of the checkbox that was set in the message box. How you persist this value is entirely up to you.

The MsgBoxExDelegate Class

 

C#
public abstract class MsgBoxExDelegate
{
/// <summary>
/// Get/set the message text from the calling message box
/// </summary>
public string   Message     { get; set; }
/// <summary>
/// Get/set the details text (if it was specified in the messagebox)
/// </summary>
public string   Details     { get; set; }
/// <summary>
/// Get/set the message datetime at which this object was created
/// </summary>
public DateTime MessageDate { get; set; }

/// <summary>
/// Performs the desired action, and returns the result. MUST BE OVERIDDEN IN INHERITING CLASS. 
/// </summary>
/// <returns></returns>
public virtual MessageBoxResult PerformAction(string message, string details = null)
{
    throw new NotImplementedException();
}
}

This class allows you make the message box icon clickable, and when it's clicked, executes the code contained in this object (the PerformAction method). This (abstract) class MUST be inherited, and the PerformAction method is executed when the icon is clicked.

The MsgBoxUrl Class

C#
public class MsgBoxUrl
{
/// <summary>
/// Get/set the web link. Any Uri type other than "http" is ignored. The URL is also used for the tooltip.
/// </summary>
public Uri                        URL         { get; set; }
/// <summary>
/// Get/set the optional display name for the web link
/// </summary>
public string                     DisplayName { get; set; }
/// <summary>
/// Get/set the foreground color for the web link
/// </summary>
public System.Windows.Media.Color Foreground  { get; set; }

public MsgBoxUrl()
{
    // default color
    this.Foreground = MessageBoxEx.DefaultUrlForegroundColor;
}

This class allows you to specify a url in the message box that the user can click.

Public Static Configuration Methods

All of the ShowEx() methods described in this section are intended to provide extended functionality over and above what the standard message box offers. The same overloads are available as for the standard Show() methods. These methods require that you specify an extended functionality object as the first parameter.

SetMessageBackground(System.Windows.Media.Color color) Sets the message background color.
SetMessageForeground(System.Windows.Media.Color color) Sets the message foreground color.
SetButtonBackground(System.Windows.Media.Color color) Sets the button panel background color.
SetFont() ASets the font family/size based on the application main window
SetFont(ContentControl parent) Sets the font family/sizebased on the specified content control
SetFont(string familyName, double size) Sets the font family/size.
SetButtonTemplateName(string name) Specify the desired button template name.
SetMaxFormWidth(double value) Sets the maximum window width.
ResetToDefaults() Resets all configuration items to their default values.
SetAsSilent(bool quiet) Toggles system sounds (associated with icons) on/off.
SetDefaultButton( Sets the button default.

There are other public configuration and Show...() methods, but they are all deprecated, and shouldn't be used.

string DetailsText Get/set the text string that represents the message details.
MsgBoxExCheckBoxData CheckBoxData Get/set the object that enables the display of a checkbox.
MsgBoxExDelegate MessageDelegate Get/set the object that makes the message box icon clickable and provides an action to be performed hen the icon is clicked.
bool ExitAfterAction Get/set the flag that indicates that the message box should be dismissed after the message box delegate action has been executed.
string DelegateToolTip Get/set the clickable icon tooltip text.
MsgBoxUrl URL Get/set the object that allows a clickable URL to be displayed.

History

  • 2021.01.15 - Change list:
    Bugs Fixed
    • Fixed the issue where the LargestButtonWidth was "not working". The actual problem was that I was calling this method BEFORE setting the font family/size.
       
    • Fixed the issue that caused an exception if you tried to show a message box from a window's constructor.
       
    • Fixed an issue where no buttons will display if you specify a custom button template name that does not exist. 
    Added Features
    • Added a ResetToDefault() method to reset all MessageBoxEx properties to their default values.
       
    • Added an executable intended to be used from Powershell. See this article -
       
    • When I was working on the previous item, I discovered that the WPF message box is NOT the same as the Windows.Forms version. I added buttons missing from the WPF version of message box (that the Windows.Forms version had) - AbortRetryIgnore, and RetryCancel. This applies to both the Powershell message box app and the regular MessageBoxEx code.
       
    • Added the ability to specify the default button. 
    Maintenance Stuff
    • Moved the MessageBoxEx class into a DLL. This allows you to include it in your app without having to add references that your app would not normally include, as well as allowing me to separate the various classes into their own files.
       
    • Deleted the deprecated ShowWithCheckBox, ShowWithDetails, and ShowWithCheckBoxAndDetails methods, so if you're still using them, change over to the ShowEx methods.
       
    • I put a compiler definition around all of the old Show() and ShowEx() overloads (see article content for details).
    • Added code to validate the specified font family name (if you use the SetFont(familyName, size) overload).
       
    • Added a default color (Blue) for the clickable URL.
       
    • Removed unused using statements in all files.
       
    • Modified the way the maxFormWidth is handled. The default value is now the width of the primary screen work area minus 100 pixels.
       
    • Added a class reference section to the article so you can see what methods are available. 
    • Moved the project into VS2019 because VS2107 doesn't allow linebreaks in intellisense comments, and I had a lot of important stuff to say in the comments. Since the code is using .Net Framework 4.72, you can still load the solution in VS2017 without issue. 
    Article Stuff
    • Added a class reference section to the article so you can see what methods are available. 
    • Fixed a bunch of spelling errors. 

     
  • 2021.01.08 - Added the ability to present a clickable URL, removed the arbitrary error-only restriction on the message delegate functionality, and refactored the status Show method variants to simplify maintenance as well as ease adoption of the new functionality.
     
  • 2021.01.07 - Edited the article to include more info about the checkbox, details, and clickable error icon. The download ZIP file did not change.
     
  • 2021.01.06 - Added details, checkbox, and clickable Error icon, and removed code dumps from article body. There is, of course, a new downloadable source ZIP file. Finally, I fixed the return value when the user closes the message box with the close button in the title bar.
     
  • 2021.01.04 - Made the MessageBoxIcon alignment the same as the standard MessageBox. I also took a stab at fixing the issue that caused the message box to be replaced as the top-most window of an application. Finally, I updated the screen shots and included screen shots from the standard MessageBox for a more immediate visual comparison within the article. Once again, I did NOT update the source code displayed in the article, but there is a new ZIP file download.
     
  • 2021.01.03 - Added support for MessageBoxIcon and system sounds, changed to using a TextBox for the message display so you can copy the message to the clipboard, and changed some control alignment to make it "better". I did NOT update the source code displayed in the article.
     
  • 2020.12.25 - Initial publication
     

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
QuestionHorrible Realization Pin
#realJSOP15-Jan-21 6:09
mve#realJSOP15-Jan-21 6:09 
AnswerRe: Horrible Realization Pin
michaelbarb16-Jan-21 7:55
michaelbarb16-Jan-21 7:55 
GeneralRe: Horrible Realization Pin
#realJSOP16-Jan-21 8:41
mve#realJSOP16-Jan-21 8:41 
QuestionSomeone mentioned getting no buttons Pin
#realJSOP15-Jan-21 2:17
mve#realJSOP15-Jan-21 2:17 
SuggestionDefault Button Pin
michaelbarb13-Jan-21 12:51
michaelbarb13-Jan-21 12:51 
GeneralRe: Default Button Pin
#realJSOP14-Jan-21 8:23
mve#realJSOP14-Jan-21 8:23 
GeneralRe: Default Button Pin
michaelbarb14-Jan-21 8:58
michaelbarb14-Jan-21 8:58 
GeneralMy vote of 5 Pin
Slacker00711-Jan-21 12:10
professionalSlacker00711-Jan-21 12:10 
QuestionMy vote is 5 Pin
AchLog11-Jan-21 4:32
AchLog11-Jan-21 4:32 
AnswerRe: My vote is 5 Pin
#realJSOP11-Jan-21 6:34
mve#realJSOP11-Jan-21 6:34 
QuestionEmulating MessageBoxEX in powershell Pin
strunker11-Jan-21 4:26
strunker11-Jan-21 4:26 
AnswerRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 6:33
mve#realJSOP11-Jan-21 6:33 
AnswerRe: Emulating MessageBoxEX in powershell Pin
strunker11-Jan-21 7:25
strunker11-Jan-21 7:25 
GeneralRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 8:04
mve#realJSOP11-Jan-21 8:04 
GeneralRe: Emulating MessageBoxEX in powershell Pin
strunker11-Jan-21 8:09
strunker11-Jan-21 8:09 
GeneralRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 8:15
mve#realJSOP11-Jan-21 8:15 
GeneralRe: Emulating MessageBoxEX in powershell Pin
strunker11-Jan-21 8:22
strunker11-Jan-21 8:22 
GeneralRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 9:01
mve#realJSOP11-Jan-21 9:01 
GeneralRe: Emulating MessageBoxEX in powershell Pin
strunker11-Jan-21 10:19
strunker11-Jan-21 10:19 
GeneralRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 9:58
mve#realJSOP11-Jan-21 9:58 
GeneralRe: Emulating MessageBoxEX in powershell Pin
strunker11-Jan-21 10:29
strunker11-Jan-21 10:29 
GeneralRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 11:00
mve#realJSOP11-Jan-21 11:00 
AnswerRe: Emulating MessageBoxEX in powershell Pin
#realJSOP11-Jan-21 13:30
mve#realJSOP11-Jan-21 13:30 
GeneralRe: Emulating MessageBoxEX in powershell Pin
strunker12-Jan-21 5:21
strunker12-Jan-21 5:21 
GeneralRe: Emulating MessageBoxEX in powershell Pin
#realJSOP12-Jan-21 5:41
mve#realJSOP12-Jan-21 5:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.