在MVVM WPF中打开新窗口

iugsix8n  于 2023-05-19  发布在  其他
关注(0)|答案(7)|浏览(226)

我有一个按钮,我把这个按钮绑定到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的文章,但我找不到任何解决方案。

7cwmlq89

7cwmlq891#

你说“创建窗口示例并从视图模型显示窗口违反了MVVM”。这是正确的。
您现在正在尝试创建一个接口,该接口采用VM指定的视图类型。这同样是一种侵犯。您可能已经抽象出了接口背后的创建逻辑,但您仍然在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-> Viewimplicittemplates,这样才能正常工作。这只是标准的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>
jgovgodb

jgovgodb2#

一种可能性是具有以下内容:

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只在窗口有无参数构造函数的情况下工作(但我想在这种情况下这应该不是问题!)在更一般的情况下,请参阅Create instance of generic type?了解可能性。

egdjgwm8

egdjgwm83#

你可以这样写一个函数:

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来了解视图类型,您可以将视图的字符串/逻辑名称传递给ViewManager,并让ViewManager将逻辑名称转换为类型。

gv8xihay

gv8xihay4#

在绑定DataConext的窗口中使用contentpresenter。然后为您的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>
iyzzxitl

iyzzxitl5#

我发现公认的解决方案非常有用,但在实际尝试时,我发现它缺乏使UserControl(VM -> ViewMap产生的视图)停靠在托管窗口内以占据其提供的整个区域的能力。因此,我扩展了解决方案,以包含以下功能:

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();
r9f1avp5

r9f1avp56#

也许你可以传递窗口类型。
尝试使用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);
ccgok5k5

ccgok5k57#

这是一个小小的贡献。为了更通用,我们可以获取传递给showWindow方法的viewModel的类型,然后查找其对应的视图,最后将其示例化。

class WindowService : IWindowService
    {
        public void showWindow(object viewModel)
        {
            // Definición de variables
            string viewToSearch;
            Type foundViewType;

            // Se encuentra el tipo del viewModel
            Type t = viewModel.GetType();

            // Se obtiene la lista de views            
            List<Type> myViews = Assembly.GetExecutingAssembly().GetTypes()
                      .Where(t => t.Namespace == "[yourNameSpace].Views")
                      .ToList();

            // Se obtienen el nombre del view a buscar
            viewToSearch = t.Name.Replace("ViewModel", "View");
            foundViewType = myViews.Find(x => x.Name.Equals(viewToSearch));

            if (foundViewType != null)
            {
                var window = Activator.CreateInstance(foundViewType);

                ((Window)window).DataContext = viewModel;
                ((Window)window).Show();
            }

        }
    }

然后您可以在viewModel中调用它

WindowService windowService = new WindowService();
windowService.showWindow(this);

相关问题