Click here to Skip to main content
15,881,852 members
Articles / Desktop Programming / WPF

MessageBox using MVVM Patterns

Rate me:
Please Sign up or sign in to vote.
4.89/5 (7 votes)
8 Sep 2014CPOL5 min read 57.8K   3.1K   20   6
Displaying a Message Box (similar to the MessageBox) using MVVM patterns in WPF
 

Introduction

Displaying a notification or confirmation message box is a very basic thing in majority of the Windows applications. In .NET we have MessageBox class which we can use to display a message box in a single line of code. 

However, that's not what we want when we use MVVM patterns. This where this article will help to write a fully MVVM complaint Message Box.

Prerequisite

This articule assumes that you have basic understanding of following concepts:

  • C#
  • WPF
  • MVVM
  • Prism
  • MEF

Background

We can use same System.Windows.Forms.MessageBox class in our WPF applications. However, when we use MVVM patterns, we want a seperation of concerns and use of this class in such a case will violate the rules. Writing a unit test for such a View-Model will also become harder. Should we block the test and wait for the user to press a 'OK' or 'Cancel' button? Nop, that's not the solution.

We have couple of options in this case.

  1. Interaction Service
  2. Interaction Request Objects

 

Interaction Service

In this case the View-Model is dependent on the interaction service to display the message box to the user and get the response. Interaction services can be used to display both Modal and Modaless popup Windows. Once the View-Model have the reference to the interaction service its a very straight forward method.

Following code snippet (taken from here) shows how to display a modal popup message box:

var result =
    interactionService.ShowMessageBox(
        "Are you sure you want to cancel this operation?", 
        "Confirm", 
        MessageBoxButton.OK );
if (result == MessageBoxResult.Yes)
{
    CancelRequest();
}

And to display a modaless popup message box

interactionService.ShowMessageBox(
    "Are you sure you want to cancel this operation?",
    "Confirm",
    MessageBoxButton.OK,
    result =>
    {
        if (result == MessageBoxResult.Yes)
        {
            CancelRequest();
        }
    });

 

Interaction Request Objects

This approach is more consistent with MVVM patterns and Prism uses the same type approach. That's why we are going to use the same approach in this article to display a Message Box. 

Prism privides IInteractionRequest<T> class to achieve this task. This class implements IInteractionRequest interface. This class has two Raise methods which allow the View-Model to initiate interaction with the user. Signature of these two methods is given below:

public void Raise(T context);

and

public void Raise(T context, Action<T> callback)

The first method takes the context (Confirmation or Notification) as the parameter. Second method takes an additional parameter which is a callback delegate. This method is very useful when we want to do something in response to the user interaction.

To display a notification window following code is sufficient.

var notificationInteractionRequest = new InteractionRequest<Notification>();

C#
notificationInteractionRequest.Raise(
                    new Notification
                    {
                        Title = "Information",
                        Content = "Operation completed successfully!"                        
                    });

Similary to display a confirmation window we can call the raise method with some callback delegate and then in the callback method we can check the value of "Confirmed" property to see what the user response was.

C#
var confirmationInteractionRequest = new InteractionRequest<Confirmation>();

confirmationInteractionRequest.Raise(
new Confirmation
                {
                    Title = "Confirmation",
                    Content = "Do you want to continue?"
                }, OnWindowClosed);

private void OnWindowClosed(Confirmation confirmation)
        {
            if (confirmation.Confirmed)
            {
                //Do your stuff here.
            }

 

The above mentioned methods are all what we need to display a popup message box. However, we don't have full control over the displayed window like the layout, buttons and icons etc. Following are the default message boxes displayed using Prism.

Image 1  Image 2

To overcome this problem this article provides a very simple way to fully control the layout of the message boxes. Sample message boxes created in this article are shown below:

Image 3

Image 4

Understanding the code

The message box is implemented using MVVM patterns and the definition of the MessageBoxViewModel (which is a ViewModel for Message Box) is given hereunder:

C#
[Export(typeof(IMessageBoxViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MessageBoxViewModel : INotifyPropertyChanged, IMessageBoxViewModel
{
    private string _title;
    private string _message;
    private MessageBoxButtons _msgBoxButtons;
    private MessageBoxIcon _msgBoxIcon;
    private Confirmation _confirmation;
    
    public string Title 
    {
        get { return _title; }
        set
        {
            if (_title != value)
            {
                _title = value;
                RaisePropertyChanged(() => Title);
            }
        }
    }
    public string Message
    {
        get { return _message; }
        set
        {
            if (_message != value)
            {
                _message = value;
                RaisePropertyChanged(() => Message);
            }
        }
    }

    public MessageBoxButtons MessageBoxButton
    {
        get { return _msgBoxButtons; }
        set
        {
            if (_msgBoxButtons != value)
            {
                _msgBoxButtons = value;
                RaisePropertyChanged(() => MessageBoxButton);
            }
        }
    }

    public MessageBoxIcon MessageBoxImage
    {
        get { return _msgBoxIcon; }
        set
        {
            if (_msgBoxIcon != value)
            {
                _msgBoxIcon = value;
                RaisePropertyChanged(() => MessageBoxImage);
            }
        }
    }

    public string DisplayIcon
    {
        get
        {
            switch (MessageBoxImage)
            {
               case MessageBoxIcon.Information:
                    return @"../Images/Information_48.png";
                case MessageBoxIcon.Error:
                    return @"../Images/Error_48.png";
                case MessageBoxIcon.Question:
                    return @"../Images/Question_48.png";
                case MessageBoxIcon.Exclaimation:
                    return @"../Images/Exclaimation_48.png";
                case MessageBoxIcon.Stop:
                    return @"../Images/Stop_48.png";
                    case MessageBoxIcon.Warning:
                    return @"../Images/Exclaimation_48.png";
                default:
                    return @"../Images/Information_48.png";
            }
        }
    }

    public Confirmation Confirmation
    {
        get { return _confirmation; }
        set
        {
            if (_confirmation != value)
            {
                _confirmation = value;
                RaisePropertyChanged(() => Confirmation);
            }
        }
    }

    #region Protected Methods

    protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
        RaisePropertyChanged(propertyName);
    }

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    public event PropertyChangedEventHandler PropertyChanged;
}

As you can see that it implements some basic properties of the message box like title, message, buttons and icon. It also has a Confirmation property which is useful only when message box is displayed for confirmation.In other cases it will serve no purpose.

This ViewModel is implements IMessageBoxViewModel interface which is defined below:

C#
public interface IMessageBoxViewModel
{
    MessageBoxButtons MessageBoxButton { get; set; }
    MessageBoxIcon MessageBoxImage { get; set; }
    string Title { get; set; }
    string Message { get; set; }

}

MessageBoxButtons and MessageBoxIcon types in this ViewModel are simple enumerations 

C#
public enum MessageBoxButtons
{
    OK,
    OKCancel,
    YesNo,
    YesNoCancel
}

public enum MessageBoxIcon
{
    Information,
    Error,
    Warning,
    Exclaimation,
    Question,
    Stop
}

There are two different views ConfirmationWindow and NotificationWindow. They both look very similiar. The only difference is when the message box is displayed in confirmation mode, we set the value of Confirmed property of Confirmation class when user clicks a button on the message box Window.

Event Triggers are used in the view to handle certain events raised from the ViewModel. These triggers can invoke a specific action and they can also set some property values on a particular object. The definition of ConfirmationWindow view is given below:

C#
<Window x:Class="Com.Controls.MessageBox.Views.ConfirmationWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
        xmlns:converters="clr-namespace:Com.Controls.MessageBox.Converters"
        Title="{Binding Title}"
        Width="370"
        MinHeight="160"
        ResizeMode="NoResize"
        ShowInTaskbar="False"
        SizeToContent="Height"
        WindowStartupLocation="CenterOwner"
        x:Name="confirmationWindow">
    <Window.Resources>
        <converters:EnumToVisibilityConverter x:Key="EnumToVisibilityConverter"/>
    </Window.Resources>
    
    <Grid Margin="4">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <Image Source="{Binding DisplayIcon }" Margin="10,10,5,0" Grid.Row="0" Grid.Column="0" Stretch="None" VerticalAlignment="Top"/>
            <ContentPresenter Content="{Binding Message}" Margin="10,10,10,10" Grid.Row="0" Grid.Column="1">
                    <ContentPresenter.Resources>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="TextWrapping" Value="Wrap"/>
                        </Style>
                </ContentPresenter.Resources>
            </ContentPresenter>
        </Grid>

        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,8">
            <Button Content="Yes" Width="75" Height="23" Margin="0,0,10,0" 
                    Visibility="{Binding Path= MessageBoxButton, 
                                         Converter={StaticResource EnumToVisibilityConverter}, 
                                         ConverterParameter='YES'}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction PropertyName="Confirmed" TargetObject="{Binding Confirmation}" Value="True"/>
                        <ei:CallMethodAction TargetObject="{Binding ElementName=confirmationWindow}"
                                         MethodName="Close"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="No" Width="75" Height="23" Margin="0,0,10,0" 
                    Visibility="{Binding Path= MessageBoxButton, 
                                         Converter={StaticResource EnumToVisibilityConverter}, 
                                         ConverterParameter='NO'}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:CallMethodAction TargetObject="{Binding ElementName=confirmationWindow}"
                                         MethodName="Close"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="OK" Width="75" Height="23" Margin="0,0,10,0" 
                    Visibility="{Binding Path= MessageBoxButton, 
                                         Converter={StaticResource EnumToVisibilityConverter}, 
                                         ConverterParameter='OK'}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction PropertyName="Confirmed" TargetObject="{Binding Confirmation}" Value="True"/>
                        <ei:CallMethodAction TargetObject="{Binding ElementName=confirmationWindow}"
                                         MethodName="Close"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="Cancel" Width="75" Height="23" Margin="0,0,10,0" 
                    Visibility="{Binding Path= MessageBoxButton, 
                                         Converter={StaticResource EnumToVisibilityConverter}, 
                                         ConverterParameter='CANCEL'}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:CallMethodAction TargetObject="{Binding ElementName=confirmationWindow}"
                                         MethodName="Close"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>
        
    </Grid>
</Window>

As you can can see that under every <Button> tag we have <i:Interaction.Triggers> block where we listen for "Click" event and then we set the Confirmed property of Confirmation class and call the method "Close" for the view.

Similar approach has been used for the NotificationWindow view. The only major difference is that we don't set the value of property Confirmed.

There is a MessageContent helper class which we use to initialize the Confirmation.Content and Notification.Content properties.

C#
public class MessageBoxConent
{
    public string Message { get; set; }
    public MessageBoxButtons MessageBoxButton { get; set; }
    public MessageBoxIcon MessageBoxImage { get; set; }
    public bool IsModalWindow { get; set; }
    public Window ParentWindow { get; set; }
} 

The final thing we need is a class inherited from TriggerAction<FrameworkElement>.

We have two classes ConfirmationMessageAction and NotificationMessageAction which inherit from this class. 

These classes override Invoke method which is called in response to an EventTrigger action.

In the Invoke method passed arguments are retrieved and message box ViewModel (i.e MessageBoxViewModel) is initialized and assigned to the relevant view. Finally the view is displayed to the user.

Definition of the ConfirmationMessageAction is given below:

 

C#
public class ConfirmationMessageBoxAction : TriggerAction<FrameworkElement>
{
    protected override void Invoke(object parameter)
    {
        var args = parameter as InteractionRequestedEventArgs;
        if (args != null)
        {
            var confirmation = args.Context as Confirmation;
            if (confirmation != null)
            {
                var content = confirmation.Content as MessageBoxConent;
                var window = new ConfirmationWindow
                {
                    DataContext = new MessageBoxViewModel
                    {
                        Message = (content != null ? content.Message : ""),
                        MessageBoxButton = (content != null ? content.MessageBoxButton : MessageBoxButtons.OK),
                        MessageBoxImage = (content != null ? content.MessageBoxImage : MessageBoxIcon.Information),
                        Title = confirmation.Title,
                        Confirmation = confirmation
                    },
                    Owner = (content != null ? content.ParentWindow : null),
                    Icon = ((content != null && content.ParentWindow != null) ? content.ParentWindow.Icon : null)
                    
                };
                EventHandler closeHandler = null;
                closeHandler = (sender, e) =>
                {
                    window.Closed -= closeHandler;
                    args.Callback();
                };
                window.Closed += closeHandler;
                
                if (content != null && content.IsModalWindow)
                {
                    window.ShowDialog();
                    
                }
                else
                {
                    window.Show();
                }
            }
        }

    }
}

Using the code

Using the library code attached with this article is very simple and straight forward.

  • Add a reference to the Com.Controls.MessageBox library in your application.
  • In your ViewModel class declare and initialize the InteractionRequest variables as shown below.
  • C#
    //Delcare and initialize the InteractionRequest variables.
    var confirmationInteractionRequest = new InteractionRequest<Confirmation>();
    var notificationInteractionRequest = new InteractionRequest<Notification>();
    
    public IInteractionRequest ConfirmationInteractionRequest
    {
        get { return _confirmationInteractionRequest; }
    }
    
    
    public IInteractionRequest NotificationInteractionRequest
    {
        get { return _notificationInteractionRequest; }
    }
  • In the view (i.e. xaml) file add the following interaction triggers block.
  • C#
    <i:Interaction.Triggers>
        <!-- Trigger listening for the "Raised" event on the source object (of type IInteractionRequest) -->
        <i:EventTrigger EventName="Raised" SourceObject="{Binding ConfirmationInteractionRequest}">
            <i:EventTrigger.Actions>
                <actions:ConfirmationMessageBoxAction />
            </i:EventTrigger.Actions>
        </i:EventTrigger>
        <i:EventTrigger EventName="Raised" SourceObject="{Binding NotificationInteractionRequest}">
            <i:EventTrigger.Actions>
                <actions:NotificationMessageBoxAction />
            </i:EventTrigger.Actions>
        </i:EventTrigger>
    </i:Interaction.Triggers>
  • Finally when you want to display the popup message box  call the Raise method as shown below.
  • C#
    _confirmationInteractionRequest.Raise(
        new Confirmation
        {
            Title = "Question",
            Content = new MessageBoxConent
            {
                Message = "Do you want to continue?",
                MessageBoxButton = MessageBoxButtons.YesNo,
                MessageBoxImage = MessageBoxIcon.Question,
                IsModalWindow = true,
                ParentWindow = Application.Current.MainWindow
            }
        },
        delegate(Confirmation confirmation)
        {
            if (confirmation.Confirmed)
            {
               //User confirmed operation. Do your stuff here.
           }       
        });
        
    _notificationInteractionRequest.Raise(
        new Notification
        {
            Title = "Information",
            Content = new MessageBoxConent
            {
                Message = "Operation completed successfully!",
                MessageBoxButton = MessageBoxButtons.OK,
                MessageBoxImage = MessageBoxIcon.Information,
                IsModalWindow = true,
                ParentWindow = Application.Current.MainWindow
            }
        },
        delegate
        {
            //Once user closes the message box, do your stuff here (if you want).              
        });

However, there is problem with this technique. It requires redundant code in every ViewModel and View file where we want to display message box.

Other Option

Other option is to add the InteractionRequest variables (added in 2nd step) in the ViewModel of your Shell. Then add the xaml code (3rd step) in the View file of your Shell.

Use the EventAggregator to subscriber to some event say MessageBoxEvent in your shell class like below:

C#
EventAggregator.GetEvent<MessageBoxEvent>().Subscribe(ShowMessageBox, ThreadOption.UIThread, false);

Then in ShowMessageBox function handle the event and call the Raise method based on the event parameters like below:

 

C#
private void ShowMessageBox(MessageBoxEventArgs args)
{
    switch (args.MessageType)
    {
        case MessageType.INFO:
            _notificationInteractionRequest.Raise(
            new Notification
            {
                Title = args.Title,
                Content = new MessageBoxConent
                {
                    Message = args.Message,
                    MessageBoxButton = MessageBoxButtons.OK,
                    MessageBoxImage = MessageBoxIcon.Information,
                    IsModalWindow = args.IsModalMessage,
                    ParentWindow = Application.Current.MainWindow
                }
            },
            delegate
            {
                if (args.CallbackAction != null)
                {
                    args.CallbackAction(true);
                }
            });
            break;
        case MessageType.ERROR:
            _notificationInteractionRequest.Raise(
            new Notification
            {
                Title = args.Title,
                Content = new MessageBoxConent
                {
                    Message = args.Message,
                    MessageBoxButton = MessageBoxButtons.OK,
                    MessageBoxImage = MessageBoxIcon.Error,
                    IsModalWindow = args.IsModalMessage,
                    ParentWindow = Application.Current.MainWindow
                }
            },
            delegate
            {
                if (args.CallbackAction != null)
                {
                    args.CallbackAction(true);
                }
            });
            break;
        case MessageType.QUESTION:
            _confirmationInteractionRequest.Raise(
            new Confirmation
            {
                Title = args.Title,
                Content = new MessageBoxConent
                {
                    Message = args.Message,
                    MessageBoxButton = MessageBoxButtons.YesNo,
                    MessageBoxImage = MessageBoxIcon.Question,
                    IsModalWindow = args.IsModalMessage,
                    ParentWindow = Application.Current.MainWindow
                }
            },
            delegate(Confirmation confirmation)
            {
                if (args.CallbackAction != null)
                {
                    args.CallbackAction(confirmation.Confirmed);
                }
            });

            break;
        //Handle other cases like above
        default:
            break;
    }
}

 

Finally from your ViewModels simplay publish the event with right arguments like below:

 

C#
EventAggregator.GetEvent<MessageBoxEvent>().Publish(
    new MessageBoxEventArgs 
    {
        Title = "Question",
        CallbackAction = ResponseCallback,
        IsModalMessage = true,
        Message = "Do you want to continue?",
        MessageType = MessageType.QUESTION
    });

 

For a complete example see the sample application attached with the source code.

References

Magnus Montin's blog.

Prism Library 5.0

 

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)
United Kingdom United Kingdom
Ahmad Qarshi is an IT Consultant and have good expertise in different development technologies. He loves to code in .NET, JavaFX, Delphi, XSLT/Schematron. Above all very keen to learn new stuff Smile | :)

Comments and Discussions

 
PraiseVery Nice Article Pin
UdayKris13-Apr-17 4:02
UdayKris13-Apr-17 4:02 
QuestionSubscribeEvents and UnsubscribeEvents from BaseViewModel class Pin
Tukaram Thatikonda11-Apr-15 14:42
Tukaram Thatikonda11-Apr-15 14:42 
AnswerRe: SubscribeEvents and UnsubscribeEvents from BaseViewModel class Pin
ajalilqarshi13-May-15 1:24
ajalilqarshi13-May-15 1:24 
QuestionEvents Pin
Sami Abdelgadir9-Sep-14 23:02
Sami Abdelgadir9-Sep-14 23:02 
AnswerRe: Events Pin
ajalilqarshi10-Sep-14 1:20
ajalilqarshi10-Sep-14 1:20 
GeneralRe: Events Pin
Sami Abdelgadir10-Sep-14 1:33
Sami Abdelgadir10-Sep-14 1:33 

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.