WPF MVVM — простое использование MessageBox.Show с помощью делегатов Action и Func

Автор: Дин (Dean)

В мире MVVM такие вещи, как окна сообщений (MessageBox.Show) и диалоговые окна (открыть файл, сохранить файл и т. д.), выглядят неестественно.

Эти всплывающие окна тесно связаны с частью View в MVVM, но фактически они могут быть вызваны только из ViewModel, что разрушает четкое разделение в MVVM.

Если поискать сведения об этой проблеме, будет найдено множество искусных решений, многие из которых сами по себе являются значительными инженерными проектами.

Я большой поклонник реализации максимально простых решений, так как многословный код является главным виновником появления проектов, которые нельзя поддерживать. Поэтому я занялся поиском решения, которое будет простым, надежным, элегантным и не разрушит модель MVVM.

Решение, к которому я пришел — использовать делегаты Action и Func.

А чтобы проиллюстрировать свое решение, я создал новый проект, используя пакет "WPF Model-View-ViewModel Toolkit", (http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit), устанавливающий шаблон проекта в VS2008

Вот как выглядит мой измененный класс MainViewModel.cs.

public class MainViewModel : ViewModelBase
{
    private DelegateCommand exitCommand;
    private Action<string> popup;
    private Func<string, string, bool> confirm;
 
    public MainViewModel(Action<string> popup, Func<string, string, bool> confirm)
    {
        this.popup = popup;
        this.confirm = confirm;
    }
 
    public ICommand ExitCommand
    {
        get
        {
            if (exitCommand == null)
                exitCommand = new DelegateCommand(Exit);
            return exitCommand;
        }
    }
 
    private void Exit()
    {
        if (confirm("Are you sure you want to exit", "confirm exit"))
            Application.Current.Shutdown();
    }
}

Как можно видеть, конструктор MainViewModel принимает 2 делегата, 1 для всплывающего окна и 1 для подтверждения.

Теперь взгляните на App.xaml.cs, в котором создаются экземпляры View и ViewModel.

private void OnStartup(object sender, StartupEventArgs args)
{
    // messagebox
    var popup = (Action<string>)(msg => MessageBox.Show(msg));
 
    // confirm box
    var confirm = (Func<string, string, bool>)((msg, capt) =>
        MessageBox.Show(msg, capt, MessageBoxButton.YesNo) == MessageBoxResult.Yes);
 
    Views.MainView view = new Views.MainView();
    view.DataContext = new ViewModels.MainViewModel(popup,confirm);
    view.Show();
}

При тщательном анализе видно, что мои делегаты фактически отображаются на методы статического класса MessageBox, который и даст нам нужные всплывающие окна. Делегат всплывающего окна popup создаст экземпляр простого всплывающего окна сообщения, а делегат окна подтверждения confirm создаст экземпляр всплывающего окна сообщения с кнопками подтверждения.

И именно это происходит при выборе пункта меню Exit (выход). (Примечание. Это меню создается по умолчанию при создании нового проекта с помощью пакета.)

Теперь, когда нужно запустить тесты для нашей модели ViewModel, можно просто передать в нее делегаты-заглушки.

[TestMethod()]
public void MainViewModelConstructorTest()
{
    var dummyPopup = (Action<string>)((a) => {return;});
    var dummyConfirm = (Func<string,string,bool>)((a,b) => {return true;});
    ViewModels.MainViewModel target = new ViewModels.MainViewModel(dummyPopup, dummyConfirm);
    Assert.Inconclusive("TODO: Implement code to verify target");
}

Дин (Dean)