jueves, 30 de septiembre de 2010

Implementing a ChildWindow with MVVM in Silverlight (with DialogResult)

Hi! It is often asked which is the correct way to implement a childWindow and handle the DialogResult using the MVVM pattern. I've seen different approaches and I know that are several solutions to this, keeping the fundamentals of MVVM untouched. Well, this is my approach, that I've implemented in a couple of projects.

The main idea, is to use a view model that will handle the ChildWindow.xaml itself, and this view model will implement an interface. This way, in our view models where we want to use this child window, we will be referencing to an object of the type of the interface, making it mock-able and testeable. Let's take a look at the code.

First, we have the Child window, basically, the Xaml won't suffer any changes, but we could implement some logic in the code behind. This is ok (I guess) from the perspective of MVVM, because we are not putting there anything we want to test (I don't see to much utility in testing if the ok button works ;) ) and this code belongs to the child window only. This way, we can make a generic child window, and have in the code behind methods to change the icon, or the text in the ok/cancel buttons.

Now, lets create our interface, to ensure the testeability of our app.


   public interface IDialogWindowViewModel
    {
        
        void ShowConfirmWindow(string message, string title, Action dialogResultAction);
        void ShowMessageWindow(string message, string title, DialogTypes dialogType);
        void ShowConfirmWindow(string message, string title, string acceptButtonText, string cancelButtonText,
                               Action dialogResultAction);
        void ShowValidationResultsWindow(string title, List validationResult);


        void Close();
        DialogTypes DialogType { get; set; }
        bool DialogResult { get; set; }
        string Message { get; set; }
        string Title { get; set; }
        string AcceptButtonText { get; set; }
        string CancelButtonText { get; set; }
    }

I have this. As your needs change, you can put more methods/fields, but this is one example of how I did it. Then, we have the ViewModel that will implement that interface, and that actually will show the ChildWindow.


   public void ShowMessageWindow(string message, string title, DialogTypes dialogType)
        {
            _dialogChildWindow = new DialogChildWindow();
            _dialogChildWindow.DataContext = this;
            Message = message;
            Title = title;
            AcceptButtonText = "Aceptar";
            DialogType = dialogType;
            _dialogChildWindow.ChangeIcon(dialogType);
            _dialogChildWindow.Show();
        }


        public void ShowValidationResultsWindow(string title, List validationResults)
        {
            _dialogChildWindow = new DialogChildWindow();
            _dialogChildWindow.DataContext = this;
            Title = title;
            AcceptButtonText = "Aceptar";
            DialogType = DialogTypes.ErrorDialog;
            _dialogChildWindow.ChangeIcon(DialogType);
            _dialogChildWindow.ValidationSummaryMode(validationResults);
            _dialogChildWindow.Show();
        }


        public void ShowConfirmWindow(string message, string title, Action dialogResultAction)
        {
            _dialogChildWindow = new DialogChildWindow();
            _dialogChildWindow.DataContext = this;
            Message = message;
            Title = title;
            AcceptButtonText = "Aceptar";
            CancelButtonText = "Cancelar";
            DialogType = DialogTypes.ConfirmDialog;
            _dialogChildWindow.Closing += OnClosedDialogChildWindow;
            _dialogClosedAction = dialogResultAction;
            _dialogChildWindow.ChangeIcon(DialogTypes.ConfirmDialog);
            _dialogChildWindow.Show();
        }


        public void ShowConfirmWindow(string message, string title, string acceptButtonText, string cancelButtonText, Action dialogResultAction)
        {
            _dialogChildWindow = new DialogChildWindow();
            _dialogChildWindow.DataContext = this;
            Message = message;
            Title = title;
            AcceptButtonText = acceptButtonText;
            CancelButtonText = cancelButtonText;
            DialogType = DialogTypes.ConfirmDialog;
            _dialogChildWindow.Closing += OnClosedDialogChildWindow;
            _dialogClosedAction = dialogResultAction;
            _dialogChildWindow.ChangeIcon(DialogTypes.ConfirmDialog);
            _dialogChildWindow.Show();
        }


        private void OnClosedDialogChildWindow(object sender, EventArgs e)
        {
            var childWindow = sender as DialogChildWindow;
            if (childWindow != null)
            {
                if (childWindow.DialogResult.HasValue)
                    DialogResult = childWindow.DialogResult.Value;
            }
            _dialogChildWindow.Closing -= OnClosedDialogChildWindow;
            //_dialogChildWindow = null;
            _dialogClosedAction.Invoke(DialogResult);
        }


        public void Close()
        {
            if (_dialogChildWindow != null)
            {
                _dialogChildWindow.Close();
            }
        }

Then finally, we use this view model in another view model where we want to display the child window, here is an example of how it can be used.

We have the ViewModel instance:

 public IDialogWindowViewModel DialogWindowViewModel { get; set; }

and then we can safely call


 DialogWindowViewModel.ShowConfirmWindow(
                    string.Format("¿Do you want to delete item {0}?", SelectedItem.Id),
                    "Delete confirmation",
                    OnConfirmWindowClosed);

and the callback: OnConfirmWindowClosed, is something like

 private void OnConfirmWindowClosed(bool dialogResult)
        {
            if (dialogResult && SelectedItem != null)
            {
                SubmitOperationType = SubmitOperationType.Delete;
                _context.Clientes.Remove(SelectedItem);
                SubmitOperation so = null;
                so = _context.SubmitChanges(OnSubmitedChangesCompleted, so);
            }
        }

So we don't need to have multiple subscriptions. When we want to test our viewmodels, we'll just mock the method with another class implementing the interface, and everything will be fine :)

That is all. As I said, this is just one way of doing this, it has worked for me very well, and I think I am not breaking any patterns. You are welcome to make suggestions, and maybe show how you handle the child windows under MVVM. Hope that helps, and happy coding!

1 comentario: