Thursday, November 22, 2012

Simple MVVM MessageBox Alert

I am fairly new to the MVVM design pattern, but I thought I’d give it a shot for one of my current projects. However, I was baffled to realize there’s no straightforward way to show our coveted MessageBox alert from the ViewModel where all the logic is housed.

I did come across some examples of how to accomplish it but none were satisfactorily easy. I could probably have dissected them enough to figure it out, but creating interfaces or custom UserControls seemed overkill. I just want to use a MessageBox! But I want to do it from the View rather than the ViewModel in order to keep with the design pattern.

Here is a solution I didn’t see anywhere. I may have missed it, and in that case here is another example.

I used my base project code from HERE (The non-refactored original is HERE ). Special thanks to JR and Sean Feldman for the simple MVVM base code.

PROBLEM

The basic premise of MVVM is that the View controls all aspects of the presentation. This includes alerts or other various MessageBox-style popups. Somehow the ViewModel that controls business logic needs to inform the View that an alert should be popped up with certain data.

SOLUTION

To start, we create a class that will encapsulate all our Alert settings. This will include fields for Alert Message, Caption, type of button options, and finally an Action command to execute if the message response is affirmative.

    public class AlertSettings
    {
        public enum AlertType
        {
            YesNo,
            OkCancel,
            OkOnly
        }

        public string DialogMessage { get; private set; }
        public string HeaderText { get; private set; }
        public AlertType Alert_Type { get; private set; }

        private Action commandAction;
        private ICommand command;
        public ICommand CommandToExecute
        {
            get
            {
                if (command == null)
                {
                    command = new Command(commandAction);
                }
                return command;
            }
            private set
            {
                command = value;
            }
        }

        public AlertSettings(string dialogMessage, 
                             string headerText, 
                             AlertType alertType, 
                             Action delegateMethodToExecute)
        {
            this.DialogMessage = dialogMessage;
            this.HeaderText = headerText;
            this.Alert_Type = alertType;
            this.commandAction = delegateMethodToExecute;
        }
    }



Next we will wire up the View to hold a generic FrameworkElement for use as a listener object. This object will be notified of an incoming Alert request using its Tag to hold the Alert settings.

        private FrameworkElement alertListener;

        public InvoiceView()
        {
            InitializeComponent();

            alertListener = ConstructAlertListener();

            this.AddLogicalChild(alertListener);
        }

        /// 
        /// Creates the generic alert object with proper databinding on the Tag     property.
        /// NotifyOnTargetUpdated is used to trigger the alert display
        /// 
        /// 
        private FrameworkElement ConstructAlertListener()
        {
            // This for binding to the ViewModel on an AlertSettings object using
            //  INotifyPropertyChanged to trigger the update on the View
            Binding tagBinding = new Binding();
            tagBinding.Path = new PropertyPath("AlertSettings");
            tagBinding.Mode = BindingMode.OneWay;
            tagBinding.NotifyOnTargetUpdated = true;

            FrameworkElement element = new FrameworkElement();
            element.Visibility = Visibility.Hidden;
            element.SetBinding(TagProperty, tagBinding);
            element.TargetUpdated += alertListener_TargetUpdated;

            return element;
        }

        /// 
        /// Event handler for executing the alert message
        /// 
        /// 
        /// 
        private void alertListener_TargetUpdated(object sender, 
                                                 DataTransferEventArgs e)
        {
            if (alertListener.Tag != null)
            {
                ShowAlert();
            }
        }
The ShowAlert() method that is called in the TargetUpdated event handler will execute the actual showing of the Alert message in whatever way the View is designed to do. In this case, I’m just using our handy MessageBox.

        ///         /// Executes the alert using the settings saved in the alert object Tag property
        ///         private void ShowAlert()
        {
            if (alertListener.Tag == null)
            {
                return;
            }

            if (alertListener.Tag is AlertSettings)
            {
                AlertSettings settings = (AlertSettings)alertListener.Tag;
                ShowAlertByAlertType(settings);
            }
        }

        ///         /// Shows an alert based on alert settings.  This implementation uses MessageBox, but other
        /// implementations can use whatever is desired using the alert settings specified.
        ///         /// AlertSettings object to be used to form an alert message
        private void ShowAlertByAlertType(AlertSettings settings)
        {
            switch (settings.Alert_Type)
            {
                case AlertSettings.AlertType.OkOnly:
                    MessageBox.Show(settings.DialogMessage,
                                    settings.HeaderText,
                                    System.Windows.MessageBoxButton.OK);
                    break;

                case AlertSettings.AlertType.OkCancel:
                    if (MessageBox.Show(settings.DialogMessage,
                                        settings.HeaderText,
                                        MessageBoxButton.OKCancel)
                        == MessageBoxResult.OK)
                    {
                        settings.CommandToExecute.Execute(null);
                    }
                    break;

                case AlertSettings.AlertType.YesNo:
                    if (MessageBox.Show(settings.DialogMessage,
                                        settings.HeaderText,
                                        MessageBoxButton.YesNo)
                        == MessageBoxResult.Yes)
                    {
                        settings.CommandToExecute.Execute(null);
                    }
                    break;

                default:
                    break;
            }
        }
Last of all, we trigger the data in the ViewModel. This is simply done by creating an Alert settings property that will trigger the OnPropertyChanged action, then setting this property with a new instantiation of the settings class whenever an alert is desired.
    ///     /// This was created to set the alert data and then trigger to View using INotifyPropertyChanged
    ///     private AlertSettings alertSettings;
    public AlertSettings AlertSettings
    {
        get { return alertSettings; }
        set
        {
            alertSettings = value;
            OnPropertyChanged(PropertyOf.Resolve(x => x.AlertSettings));
        }
    }

    private void VerifyInvoiceCreationAction()
    {
        DisplayMessage = string.Format("Thanks for creating invoice: {0} {1}", Invoice.Id, Invoice.Receiver);
    }
The alert is triggered using this code:
      string alertMessage = string.Format("Are you sure you want to create invoice {0}?", Invoice.Id);
      AlertSettings = new AlertSettings(alertMessage, "Verify", AlertSettings.AlertType.YesNo, VerifyInvoiceCreationAction);

I’m interested to see if anyone has seen this approach before or can see any major flaws in the logic. Basically, I’m using a generic FrameworkElement Tag property and the FrameworkElement TargetUpdated event handler as a point of entry that triggers a specified action. It seems pretty straightforward.

Here is the full project: MvvmSimple2.zip

Enjoy!

No comments:

Post a Comment