我第一次尝试使用MVVM模式,但是我在打开视图的同时仍在努力使它们与视图模型分离。我正在使用DialogService
类(下面的IDialog.cs),该类是YouTube上MVVM教程的一部分。只要从具有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);
}
}
答案 0 :(得分:3)
通常,解耦的解决方案是使用依赖注入/控制反转。您可以使用任何DI容器(作为Unity)。
此外,您可以使用Prism之类的MVVM框架,它可以帮助您创建松散耦合且可维护的整个应用程序。
答案 1 :(得分:1)
您会从其他人的建议中受益于IoC容器,但我不认为您应该从Prism开始。从小开始,使用MVVM Light中的IoC容器,有许多示例向您展示如何使用该库编写应用程序。
您还可以看一下MVVM Dialogs的示例,其中有很多示例都可以在IoC容器中设置对话框服务。
答案 2 :(得分:1)
但是我也阅读了有关单例类的许多不同观点,其中大多数表明您根本不需要使用它们。
那是完全错误的。实际上,单例确实有助于使彼此之间不认识的实例进行通信。我会使用only make those classes a singleton that need to be one
之类的弱势语句,但是完全没有理由完全避免单例。