#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
, но нет никаких причин полностью избегать одиночек.