Я пытаюсь использовать шаблон 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);
}
}