MVVM открывает окна, сохраняя разделенными представление и модель представления

#c# #mvvm #prism

#c# #mvvm #призма

Вопрос:

Я пытаюсь впервые использовать шаблон MVVM, но мне трудно открывать представления, сохраняя их отделенными от моделей представления. Я использую DialogService класс (IDialog.cs ниже), который был частью руководства по MVVM на YouTube. DialogService Работает нормально, пока к нему осуществляется доступ из MainWindow, в котором есть экземпляр DialogService .

Проблема в том, что мне нужно открыть несколько TradeView из моего TradeManagerViewModel , у которого нет экземпляра DialogService . Я не могу создать другой экземпляр DialogService , потому что мне нужно будет зарегистрировать все сопоставления View / ViewModel для каждого создаваемого мной экземпляра. Я не могу использовать DialogService экземпляр из my MainWindowViewModel , потому что у my TradeMangerViewModel нет ссылки на экземпляр my MainWindowViewModel . В модели представления главного окна я не могу сделать public readonly IDialogService dialogService; статическую, потому что тогда я не могу назначить dialogService параметр, переданный в MainWindowViewModel конструкторе.

Единственный другой способ, который я могу придумать, — это создать отдельный одноэлементный класс, который содержит экземпляр DialogService , чтобы к одному и тому же экземпляру можно было получить доступ из обеих моделей представления (и будущих, которые я еще не написал). Но я также прочитал много разных мнений об одноэлементных классах, и большинство из них предполагают, что вам никогда не понадобится их использовать. Итак, я нашел исключение из этого мнения? или есть другой способ, которым я могу / должен это сделать?

App.xaml.cs (Изменения здесь также были взяты из видео на YouTube)

 public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IDialogService dialogService = new DialogService(MainWindow);

        dialogService.Register<TradeViewModel, TradeView>();
        dialogService.Register<TradeManagerViewModel, TradeManager>();

        var viewModel = new MainWindowViewModel(dialogService);

        base.OnStartup(e);
    }
}
 

IDialog.cs

 /// <summary>
/// Allows Windows/Dialogs to be opened and closed without coupling the View to the ViewModel
/// </summary>
public interface IDialog
{
    object DataContext { get; set; }
    bool? DialogResult { get; set; }
    Window Owner { get; set; }
    void Close();
    bool? ShowDialog();
}

/// <summary>
/// Registers a dictionary of View Models to the the correct Views allowing the correct View to be displayed when an instance of a View Model is instantiated
/// </summary>
public interface IDialogService
{
    void Register<TViewModel, TView>() where TViewModel : IDialogRequestClose
                                       where TView : IDialog;

    bool? ShowDailog<TViewModel>(TViewModel viewModel) where TViewModel : IDialogRequestClose;

}

/// <summary>
/// Creates an Event Handler which handles close requests for the dialog
/// </summary>
public interface IDialogRequestClose
{
    event EventHandler<DialogCloseRequestedEventArgs> CloseRequested;
}

public class DialogCloseRequestedEventArgs : EventArgs
{
    public DialogCloseRequestedEventArgs(bool? dialogResult)
    {
        DialogResult = dialogResu<
    }

    public bool? DialogResult { get; }
}

public class DialogService : IDialogService
{
    private readonly Window owner;

    /// <summary>
    /// Initialises the DialogService and sets its owner
    /// </summary>
    /// <param name="owner">The Window which will own the DialogService. The main window of the application will probably be the best owner.</param>
    public DialogService(Window owner)
    {
        this.owner = owner;
        Mappings = new Dictionary<Type, Type>();
    }

    public IDictionary<Type, Type> Mappings { get; } //Used to store which type of View should be used with each ViewModel

    /// <summary>
    /// Register which View should be used with a ViewModel
    /// </summary>
    /// <typeparam name="TViewModel">Type of ViewModel</typeparam>
    /// <typeparam name="TView">Type of View</typeparam>
    public void Register<TViewModel, TView>()
        where TViewModel : IDialogRequestClose
        where TView : IDialog
    {
        if (Mappings.ContainsKey(typeof(TViewModel))) //If a mapping already exists for this type of ViewModel
        {
            throw new ArgumentException($"Type {typeof(TViewModel)} is already mapped to type {typeof(TView)}");
        }

        Mappings.Add(typeof(TViewModel), typeof(TView)); //Otherwise create a new mapping
    }

    /// <summary>
    /// Shows the correct View for the given ViewModel and subscribes to the close request handler
    /// </summary>
    /// <typeparam name="TViewModel"></typeparam>
    /// <param name="viewModel">ViewModel which you want to open the mapped View for</param>
    /// <returns>Returns bool dialog result</returns>
    public bool? ShowDailog<TViewModel>(TViewModel viewModel) where TViewModel : IDialogRequestClose
    {
        Type viewType = Mappings[typeof(TViewModel)]; //Get the type of View associated with this type of ViewModel from the Mappings Dictionary

        IDialog dialog = (IDialog)Activator.CreateInstance(viewType); //Create an instance of the mapped view

        EventHandler<DialogCloseRequestedEventArgs> handler = null;

        // When the handler is called, unsubscribe from the event as we no longer need to listen to it once the View has been closed
        handler = (sender, e) =>
        {
            viewModel.CloseRequested -= handler;

            if (e.DialogResult.HasValue)
            {
                dialog.DialogResult = e.DialogResu<
            } else
            {
                dialog.Close();
            }
        };

        //Subscribe to the CloseRequested event
        viewModel.CloseRequested  = handler;

        dialog.DataContext = viewModel;
        dialog.Owner = owner;

        return dialog.ShowDialog();
    }
}
 

MainWindowViewModel.cs

 internal class MainWindowViewModel
{

    public readonly IDialogService dialogService;

    public MainWindowViewModel(IDialogService dialogService)
    {
        this.dialogService = dialogService;

        //Load settings etc. removed.

        //This works here, but dialogService isn't accessible in TradeManagerViewModel:
        var tradeManagerViewModel = new TradeManagerViewModel(filePath);
        bool? result = this.dialogService.ShowDialog(tradeManagerViewModel);
    }
}
 

Комментарии:

1. ViewModels не должны напрямую общаться / знать друг о друге. Посмотрите на реализацию посредника, чтобы справиться с этим. Джош Смит — прототип посредника

2. Спасибо, я рассмотрю посредников. Но если ViewModels не должны знать друг о друге, то не будет ли отдельный одноэлементный класс со ссылкой на DialogService более простым решением в этом случае?

3. Да, я бы согласился.

Ответ №1:

Решение для развязки, как правило, заключается в использовании внедрения зависимостей / инверсии управления. Вы можете использовать любой контейнер DI (как Unity).

Кроме того, вы можете использовать фреймворк MVVM, такой как Prism, который может помочь вам создать все приложение, слабо связанное и поддерживаемое.

Комментарии:

1. Спасибо за информацию. Я смотрел на Prism, но пока все приведенные примеры, которые я рассмотрел для версии 7, показывают, как перемещаться с использованием областей в главном окне. Запросы на уведомления открывают новое окно, но пример пользовательского уведомления просто передает пользовательский пользовательский элемент управления в предварительно созданное окно. Мне нужно открыть пользовательское окно, в котором нет строки заголовка и блока управления (т.Е. Кнопок минимизации, максимизации и закрытия). Можете ли вы указать мне правильное направление, пожалуйста, поскольку я изо всех сил пытаюсь понять, как это сделать?

Ответ №2:

Вам бы пригодился контейнер IoC, как предлагали другие, но я не думаю, что вам следует начинать с Prism. Начните с малого, используйте контейнер IoC в MVVM Light, есть много примеров, показывающих, как писать приложения с использованием этой библиотеки.

Вы также можете взглянуть на примеры диалоговых окон MVVM, есть множество примеров, когда можно настроить службу диалогового окна в контейнере IoC.

Ответ №3:

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

Это просто неправильно. На самом деле, синглтоны действительно полезны для обмена данными между экземплярами, которые не знают друг о друге. Я бы выбрал оператор weakend, подобный only make those classes a singleton that need to be one , но нет никаких причин полностью избегать одиночек.