WPF 从 ViewModel 打开一个新视图
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13671336/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF Open a new View from the ViewModel
提问by Guilherme Oliveira
This is my first WPF-MVVMapplication, and this is my structure:
这是我的第一个WPF-MVVM应用程序,这是我的结构:
- One project with my
app.xamlto open the application and override theOnStartupto resolve the MainWindow. (I did that due to the references); - One project for my Views;
- One project for my ViewModels;
- One project for my Model.
- 一个项目与我
app.xaml打开应用程序并覆盖OnStartup以解析 MainWindow。(我这样做是因为参考文献); - 我的观点的一个项目;
- 我的 ViewModel 的一个项目;
- 我的模型的一个项目。
And I have de following problem: I'm on the MainWindowViewand I click on a button to show another view. How am I supposed to do to open this another view from my MainWindowViewModelwhereas my View Projecthas reference with the ViewModel Project, and I can't reference the ViewModel Projectwith the View Project?
我有以下问题:我在MainWindowView,我点击一个按钮来显示另一个视图。我应该怎么做,从我打开这个另一种观点MainWindowViewModel,而我View Project与具有一定的参考ViewModel Project,我不能引用ViewModel Project与View Project?
By the way, I'm using Unityfor the dependency injection.
顺便说一句,我使用Unity的dependency injection。
So, could you help me?
那么,你能帮我吗?
回答by Arthur Nunes
There are several approaches to that.
有几种方法可以做到这一点。
You can define a dialog/navigation/window service interface, defined in the ViewModels project. You will need to decide how the ViewModels will express which window they want to open. I generally use an IDialogViewModel interface, which some of my ViewModels implement, and pass an instance of the ViewModel to the service, but you can use an enum, string, whatever you want, so your implementation can map to the real window which will be opened.
您可以定义一个对话框/导航/窗口服务接口,在 ViewModels 项目中定义。您需要决定 ViewModel 如何表达它们想要打开的窗口。我通常使用 IDialogViewModel 接口,我的一些 ViewModels 实现了这个接口,并将 ViewModel 的一个实例传递给服务,但是你可以使用枚举、字符串,任何你想要的,所以你的实现可以映射到真实的窗口,这将是打开。
For example:
例如:
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel);
}
ViewModels that want to open new Windows would receive an instance of that service and use it to express the intention of opening a Window. In your Views project, you would define a type which implements your service interface, with the real logic behind opening the Window.
想要打开新窗口的 ViewModel 将接收该服务的一个实例并使用它来表达打开窗口的意图。在您的视图项目中,您将定义一个实现您的服务接口的类型,以及打开窗口背后的真正逻辑。
Following the example:
按照示例:
public class DialogService : IDialogService
{
private Stack<Window> windowStack = new Stack<Window>();
public DialogService(Window root)
{
this.windowStack.Push(root);
}
public bool? ShowDialog(object dialogViewModel)
{
Window dialog = MapWindow(dialogViewModel);
dialog.DataContext = dialogViewModel;
dialog.Owner = this.windowStack.Peek();
this.windowStack.Push(dialog);
bool? result;
try
{
result = dialog.ShowDialog();
}
finally
{
this.windowStack.Pop();
}
return result;
}
}
Your main project will be responsible for creating and injecting the dialog service in the ViewModels who need it. In the example, the App would create a new dialog service instance passing the MainWindow to it.
您的主项目将负责在需要它的 ViewModel 中创建和注入对话服务。在示例中,应用程序将创建一个新的对话服务实例,将 MainWindow 传递给它。
A similar approach to do it would be using some form of the messaging pattern (link1link2). In addition, if you want something simple you can also make your ViewModels raise events when they want to open Windows and let the Views subscribe to them.
一种类似的方法是使用某种形式的消息传递模式(link1 link2)。此外,如果你想要一些简单的东西,你也可以让你的 ViewModels 在他们想要打开 Windows 时引发事件并让 Views 订阅它们。
EDIT
编辑
The complete solution that I use in my apps a generally a bit more complex, but the idea is basically that. I have a base DialogWindow, which expects a ViewModel which implements an IDialogViewModel interface as DataContext. This interface abstracts some functionalities you expect in dialog, like accept/cancel commands as well as a closed event so you can also close the window from the ViewModel. The DialogWindow consists basically in a ContentPresenter which Content property is bound to the DataContext and hooks the close event when the DataContext is changed (and a few other things).
我在我的应用程序中使用的完整解决方案通常有点复杂,但基本上就是这样。我有一个基本的 DialogWindow,它需要一个 ViewModel,它实现一个 IDialogViewModel 接口作为 DataContext。此接口抽象了您在对话框中期望的一些功能,例如接受/取消命令以及关闭事件,因此您还可以从 ViewModel 关闭窗口。DialogWindow 基本上包含一个 ContentPresenter,其中 Content 属性绑定到 DataContext 并在 DataContext 更改(以及其他一些事情)时挂钩关闭事件。
Each "dialog" consists in an IDialogViewModel and an associated View (UserControl). To map them, I just declare implicit DataTemplates in the resources of the App. In the code I've shown, the only difference would be there wouldn't be a method MapWindow, the window instance would always be a DialogWindow.
每个“对话框”都包含一个 IDialogViewModel 和一个关联的视图 (UserControl)。为了映射它们,我只是在应用程序的资源中声明隐式 DataTemplates。在我展示的代码中,唯一的区别是没有 MapWindow 方法,窗口实例始终是 DialogWindow。
I use an additional trick to reuse layout elements between dialogs. On approach is to include them in the DialogWindow (accept/cancel buttons, etc). I like to keep the DialogWindow clean (so I can use it event to "non-dialog" dialogs). I declare a template for a ContentControl with the common interface elements, and when I declare a View-ViewModel mapping template, I wrap the View with a ContentControl with my "dialog template" applied. You can then have as much "Master templates" for your DialogWindow as you want (like a "wizard like" one, for example).
我使用了一个额外的技巧来重用对话框之间的布局元素。一种方法是将它们包含在 DialogWindow 中(接受/取消按钮等)。我喜欢保持 DialogWindow 干净(所以我可以使用它事件来“非对话”对话框)。我为带有通用界面元素的 ContentControl 声明了一个模板,当我声明了一个 View-ViewModel 映射模板时,我用一个 ContentControl 包装了 View,并应用了我的“对话框模板”。然后,您可以根据需要为 DialogWindow 拥有尽可能多的“主模板”(例如,像“像向导一样”的模板)。
回答by Erik ?jebo
Straight forward approach
直截了当的方法
If I understand you correctly MainWindowView is resolved through Unity when the app starts, which resolves its dependency on MainWindowViewModel?
如果我理解正确,当应用程序启动时 MainWindowView 是通过 Unity 解决的,这解决了它对 MainWindowViewModel 的依赖?
If that is the flow you are using I would suggest continuing on the same path and letting the MainWindowView handle the opening of the new view via a simple click handler for the button. In this handler you can then resolve the new view, which would resolve that view's view model and then you're back in MVVM land for the new view as well.
如果这是您正在使用的流程,我建议继续沿相同的路径运行,并让 MainWindowView 通过按钮的简单单击处理程序来处理新视图的打开。在此处理程序中,您可以解析新视图,这将解析该视图的视图模型,然后您也将返回 MVVM 领域以获取新视图。
That solution is straight forward and would work perfectly fine for most smaller applications.
该解决方案很简单,对于大多数较小的应用程序都可以正常工作。
Heavier approach for more complex apps
更复杂的应用程序的更重的方法
If you don't want that kind of view-first flow I would suggest introducing some kind of controller/presenter that coordinates the views and view models. The presenter is responsible for making decisions about if/when to actually open/close views and so on.
如果您不想要那种视图优先的流程,我建议引入某种控制器/演示器来协调视图和视图模型。演示者负责决定是否/何时实际打开/关闭视图等。
This is a quite heavy abstraction though, which is more suitable for more complex applications, so make sure you actually get enough benefit out of it to justify the added abstraction/complexity.
不过,这是一个相当沉重的抽象,它更适合更复杂的应用程序,因此请确保您确实从中获得了足够的好处,以证明增加的抽象/复杂性是合理的。
Here is a code sample of what this approach might look like:
以下是这种方法可能是什么样子的代码示例:
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
var container = new UnityContainer();
container.RegisterType<IMainView, MainWindow>();
container.RegisterType<ISecondView, SecondWindow>();
container.RegisterType<IMainPresenter, MainPresenter>();
container.RegisterType<ISecondPresenter, SecondPresenter>();
var presenter = container.Resolve<IMainPresenter>();
presenter.ShowView();
}
}
public interface IMainPresenter
{
void ShowView();
void OpenSecondView();
}
public interface ISecondPresenter
{
void ShowView();
}
public interface ISecondView
{
void Show();
SecondViewModel ViewModel { get; set; }
}
public interface IMainView
{
void Show();
MainViewModel ViewModel { get; set; }
}
public class MainPresenter : IMainPresenter
{
private readonly IMainView _mainView;
private readonly ISecondPresenter _secondPresenter;
public MainPresenter(IMainView mainView, ISecondPresenter secondPresenter)
{
_mainView = mainView;
_secondPresenter = secondPresenter;
}
public void ShowView()
{
// Could be resolved through Unity just as well
_mainView.ViewModel = new MainViewModel(this);
_mainView.Show();
}
public void OpenSecondView()
{
_secondPresenter.ShowView();
}
}
public class SecondPresenter : ISecondPresenter
{
private readonly ISecondView _secondView;
public SecondPresenter(ISecondView secondView)
{
_secondView = secondView;
}
public void ShowView()
{
// Could be resolved through Unity just as well
_secondView.ViewModel = new SecondViewModel();
_secondView.Show();
}
}
public class MainViewModel
{
public MainViewModel(MainPresenter mainPresenter)
{
OpenSecondViewCommand = new DelegateCommand(mainPresenter.OpenSecondView);
}
public DelegateCommand OpenSecondViewCommand { get; set; }
}
public class SecondViewModel
{
}
<!-- MainWindow.xaml -->
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Command="{Binding OpenSecondViewCommand}" Content="Open second view" />
</Grid>
</Window>
<!-- SecondWindow.xaml -->
<Window x:Class="WpfApplication1.SecondWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SecondWindow" Height="350" Width="525">
<Grid>
<TextBlock>Second view</TextBlock>
</Grid>
</Window>
This articlepresents a similar solution to what I've used in production before.
本文提供了一个与我之前在生产中使用的解决方案类似的解决方案。
回答by nikola-miljkovic
To open new window from MainWindowView, you need to pass reference of Framecomponent or whole window to MainWindowViewModelobject(You can do this when binding command to transition button or something, pass them as object), there you can navigate to new page, however, if there is nothing of special that you need to do in ViewModelwhen transitioning, you can just use classic ButtonClickevent or w/e in MainWindowView.csthat will do navigation for you, that is ok for basic transitions.
要从 中打开新窗口MainWindowView,您需要将Frame组件或整个窗口的引用传递给MainWindowViewModel对象(您可以在将命令绑定到转换按钮或其他东西时执行此操作,将它们作为对象传递),您可以在那里导航到新页面,但是,如果有ViewModel转换时您不需要做任何特别的事情,您可以只使用经典ButtonClick事件或 w/e 来MainWindowView.cs为您导航,这对于基本转换来说是可以的。
P.S. I am not sure why you use different projects for ViewModels/Views/Models.
PS 我不知道你为什么为 ViewModels/Views/Models 使用不同的项目。

