MVVM Открытие окон, сохраняя вид и вид модели разъединенными - PullRequest
0 голосов
/ 16 апреля 2019

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

. Проблема в том, что мне нужно открыть несколько TradeView из моего TradeManagerViewModel, который нене имеет экземпляра DialogService.Я не могу создать другой экземпляр DialogService, потому что мне нужно зарегистрировать все сопоставления View / ViewModel для каждого создаваемого мной экземпляра.Я не могу использовать экземпляр DialogService из моего MainWindowViewModel, потому что мой TradeMangerViewModel не имеет ссылки на экземпляр моего 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 = dialogResult;
    }

    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.DialogResult;
            } 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);
    }
}

Ответы [ 3 ]

3 голосов
/ 16 апреля 2019

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

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

1 голос
/ 18 апреля 2019

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

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

1 голос
/ 18 апреля 2019

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

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

...