我有Button,我已将此按钮绑定到ViewModel中的命令说OpenWindowCommand。当我点击按钮时,我想打开新窗口。但是创建窗口实例并从视图模型显示窗口违反了MVVM。我创建了像
这样的界面interface IWindowService
{
void showWindow(object dataContext);
}
和WindowService实现此接口,如
class WindowService:IWindowService
{
public void showWindow(object dataContext)
{
ChildWindow window=new ChildWindow();
window.DataContext=dataContext;
window.Show();
}
}
在这个课程中,我指定了ChildWindow。因此,这个类与显示ChildWindow紧密结合。当我想要显示另一个窗口时,我必须实现具有相同接口和逻辑的另一个类。如何使这个类通用,以便我可以传递任何窗口的实例,并且类将能够打开任何窗口? 我没有使用任何构建的MVVM框架。我已经阅读了很多关于StackOverflow的文章,但我找不到任何解决方案。
答案 0 :(得分:41)
你说“创建窗口实例并从视图模型显示窗口是违反MVVM”。这是正确的。
您现在正在尝试创建一个接口,该接口采用VM指定的视图类型。这也是一种违规行为。您可能已经抽象出了界面背后的创建逻辑,但您仍然在虚拟机内请求查看创建。
VM应该只关心创建VM。如果您确实需要一个新窗口来托管新VM,那么请提供一个已完成的界面,但不提供视图的界面。你为什么需要这个观点?大多数(VM优先)MVVM项目使用隐式数据窗口将视图与特定VM相关联。 VM对它们一无所知。
像这样:
class WindowService:IWindowService
{
public void ShowWindow(object viewModel)
{
var win = new Window();
win.Content = viewModel;
win.Show();
}
}
显然,您需要确保在app.xaml中设置了VM-> View隐式模板才能实现此功能。这只是标准的VM第一MVVM。
例如:
<Application x:Class="My.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:My.App.ViewModels"
xmlns:vw="clr-namespace:My.App.Views"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate DataType="{x:Type vm:MyVM}">
<vw:MyView/>
</DataTemplate>
</Application.Resources>
</Application>
答案 1 :(得分:6)
一种可能性就是:
class WindowService:IWindowService
{
public void showWindow<T>(object DataContext) where T: Window, new()
{
ChildWindow window=new T();
window.Datacontext=DataContext;
window.show();
}
}
然后你可以这样:
windowService.showWindow<Window3>(windowThreeDataContext);
有关新约束的更多信息,请参阅http://msdn.microsoft.com/en-gb/library/sd2w2ew5.aspx
注意:new() constraint
仅适用于窗口具有无参数构造函数的情况(但我认为在这种情况下这不应该成为问题!)在更一般的情况下,请参阅{{3}为了可能性。
答案 2 :(得分:3)
你可以写一个这样的函数:
class ViewManager
{
void ShowView<T>(ViewModelBase viewModel)
where T : ViewBase, new()
{
T view = new T();
view.DataContext = viewModel;
view.Show(); // or something similar
}
}
abstract class ViewModelBase
{
public void ShowView(string viewName, object viewModel)
{
MessageBus.Post(
new Message
{
Action = "ShowView",
ViewName = viewName,
ViewModel = viewModel
});
}
}
确保ViewBase具有DataContext属性。 (您可以继承UserControl)
一般情况下,我会制作某种消息总线并让ViewManager监听要求查看的消息。 ViewModels将发送一条消息,要求显示视图和要显示的数据。然后ViewManager将使用上面的代码。
为了防止调用ViewModel了解View类型,您可以将视图的字符串/逻辑名称传递给ViewManager,并让ViewManager将逻辑名称转换为类型。
答案 3 :(得分:3)
在Window中使用contentpresenter,将DataConext绑定到。 然后为您的DataContext定义一个Datatemplate,以便wpf可以呈现您的DataContext。类似于我的DialogWindow Service
所以你只需要一个带有ContentPresenter的ChildWindow:
<Window x:Class="ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter Content="{Binding .}">
</ContentPresenter>
</Window>
答案 4 :(得分:1)
我发现接受的解决方案非常有用,但在实际尝试时,我发现它无法在托管窗口内建立UserControl(由VM生成的视图 - &gt;视图映射),以便占据了它提供的整个区域。所以我扩展了解决方案以包含这个能力:
public Window CreateWindowHostingViewModel(object viewModel, bool sizeToContent)
{
ContentControl contentUI = new ContentControl();
contentUI.Content = viewModel;
DockPanel dockPanel = new DockPanel();
dockPanel.Children.Add(contentUI);
Window hostWindow = new Window();
hostWindow.Content = dockPanel;
if (sizeToContent)
hostWindow.SizeToContent = SizeToContent.WidthAndHeight;
return hostWindow;
}
这里的技巧是使用DockPanel来托管从VM转换的视图。
如果您希望窗口的大小与其内容的大小相匹配,则使用前面的方法如下:
var win = CreateWindowHostingViewModel(true, viewModel)
win.Title = "Window Title";
win.Show();
或如果您有一个固定的窗口大小,如下所示:
var win = CreateWindowHostingViewModel(false, viewModel)
win.Title = "Window Title";
win.Width = 500;
win.Height = 300;
win.Show();
答案 5 :(得分:0)
也许你可以传递窗口类型。
尝试使用Activator.CreateInstance()
。
请参阅以下问题: Instantiate an object with a runtime-determined type
chakrit的解决方案:
// determine type here
var type = typeof(MyClass);
// create an object of the type
var obj = (MyClass)Activator.CreateInstance(type);