MVVM Light & WPF - 将多个窗口实例与一个ViewModel进行绑定

6
我正在MVVM中进行第一个项目,我选择使用MVVM Light Toolkit。 我有一个 GameViewModel 处理我的游戏主屏幕上的业务。 当执行命令时,我需要找到如何打开一个新的窗口(AdventurerView),并传递 Adventurer 的实例作为参数,将其绑定到 AdventurerViewModel,并显示和返回数据。这个窗口的实例会频繁地打开和关闭。 我已经陷入了这个问题几天了,让我感到非常疯狂。 我想学习如何以MVVM友好的方式做到这一点,最好是使用MVVM Light提供的工具或纯XAML。
我尝试过使用 MVVM Light 的 ViewModelLocator,但由于 AdventurerView 是一个窗口,它不起作用; 它会说“无法在样式中放置窗口”,尽管程序仍然编译和运行。有没有什么我可以改变使其工作? 或者有没有其他方法在 XAML 中绑定它们? 或者完全另一种方法? 我真的很想能够解决这个问题。我也尝试过使用 MVVM Light 的 messenger,但是没有成功(它仍然没有解决视图/视图模型的问题)。
我只需要能够创建一个绑定到 AdventurerViewModel 的窗口并显示/返回适当的数据即可。
目前 AdventurerView.xaml 处于其默认状态,但我认为如果我能绑定适当的数据,那可能有所帮助(DataContext)。
AdventurerViewModel 也非常简洁。
class AdventurerViewModel : ViewModelBase
{
    #region Members

    private Adventurer _adv;

    #endregion

    #region Properties

    public Adventurer Adv
    {
        get { return _adv; }
        set { _adv = value; }
    }

    #endregion

    #region Construction

    public AdventurerViewModel(Adventurer adv)
    {
        this._adv = adv;
    }

    #endregion
}

底部为无法工作的DataTemplate的App.xaml:

<Application StartupUri="MainWindow.xaml"
         xmlns:views="clr-namespace:AoW.Views"
         xmlns:vm="clr-namespace:AoW.ViewModels" 
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="AoW.App" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         mc:Ignorable="d">

<Application.Resources>
    <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />

    <DataTemplate DataType="{x:Type vm:GameViewModel}">
        <views:GameView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:TitleViewModel}">
        <views:TitleView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:AdventurerViewModel}">
        <views:AdventurerView />
    </DataTemplate>

</Application.Resources>
</Application>

GameViewModel 中的命令(消息框只是确认该命令正在触发):

    private void ExecuteShowAdvCommand(Adventurer adv)
    {
        System.Windows.MessageBox.Show(adv.Name);
    }

我不太清楚还有什么其他的要包括。

在IT技术方面,您需要做些什么?


1
你使用过mvvm-light附带的Messenger吗?简单来说,你首先不需要在MainWindow的xaml中将一个新窗口声明为DataTemplate。你需要做的是,在GameViewModel中当你需要创建AdventureView时,向MainWindow发送一条消息,该消息应该在代码后台注册和接收,创建AdventureView并适当地调用Show()ShowDialog()SimpleIoC应该处理AdventureViewModel的VM创建。这对你有意义吗? - Viv
1
https://dev59.com/dnA75IYBdhLWcg3wRWyc 展示了使用MVVM Light打开新窗口的示例。还有其他几种做同样事情的选项,但这应该是一个不错的开始。现在,当发送这些“消息”时,您可以传递任何参数,使传递参数信息变得简单。 - Viv
1
关于获取响应,这取决于您如何获取数据。如果它是一个对话框,您可以调用ShowDialog,它将是模态的并检查其返回值,或者再次使用Messenger将结果发送回GameViewModel - Viv
谢谢。我会去看看的。我目前正在探索Messenger,尽管我从未成功使用过它。 - Jason D
接收端将是 Messenger.Default.Register<MyMessage>(this, (args) => /* Do something with args parameter */);。在这个例子中,我假设 MyMessage 类有一个名为 SomeProp 的字符串属性。MVVM Light 自带一些内置的消息类型。请查看此文章 http://www.galasoft.ch/mvvm/ 中的 Messenger 部分。 - Viv
显示剩余3条评论
2个回答

23

好的,我制作了一个演示,希望这样可以更容易理解。 下载链接

功能:

  • 总共有3个窗口(MainWindowModalWindowNonModalWindow
  • MainWindow 有一个文本框,你可以在里面输入任何你想要的内容。
  • 顶部有2个按钮,将打开模式窗口/非模式窗口
  • 每个窗口打开时都会在其中的 TextBlock 中显示 MainWindow 的 TextBox 中的消息。
  • 在每个窗口中,您可以选中 CheckBox ,以更新 MainWindow 中结果文本块中的值(对于模态窗口,当模态窗口关闭时才会起作用。对于非模态窗口,更改可以立即看到)

功能就是这些,

概念:

  • 使用 SimpleIoC 注册多个 VM,并使用 GetInstance(...) 请求它们。
  • 使用自定义消息类型 OpenWindowMessage 的 Messenger 类用法
  • 打开模式窗口/非模式窗口,保持 MVVM 原则的父级 VM
  • 在窗口之间传递数据 (仅在非模态下显示)

重要说明:

在此示例中,设置模态窗口的非 DP DialogResult 的方法不符合 MVVM 模式,因为它使用代码后台来在 Window.Closing 事件上设置 DialogResult 属性,这应该被避免(如果需要进行“可测试”处理)。我更倾向于使用一种有点冗长但非常详细记录的方法,可以在这里找到(问题和回答混合)。这也是我为了这个示例而忽略它的原因。

1
非常感谢您的回复。我认为现在我几乎已经解决了问题。但是,当我尝试打开新窗口时,出现了一个错误 - "XamlParseExcption",指向模态窗口中有关DataContext的行,我不知道为什么会这样。 - Jason D
1
再次感谢您。我已经接受了您在另一个问题中的答案。我现在遇到的问题是,我需要两种类型的功能。我需要能够打开多个窗口,但只有在我点击按钮时才发送数据。我会继续尝试您提供给我的示例,但我希望您告诉我是否可以在不进行过多修改的情况下实现这一点。 - Jason D
你也可以将代码后面的内容放在ModelViewLocator的静态方法中。我会在另一个答案中发布更新后的项目来展示这一点。更好吗?我不知道,但我不喜欢代码后置文件。 - srock
这是我在SO上看过的最好的答案之一,做得很棒! - reggaeguitar
实际上,原始的DL链接现在有效了。感谢添加镜像! - Contango
显示剩余9条评论

1

回复Viv的问题,我修改了示例,包括打开窗口但不使用代码后台的示例。

示例项目在这里。

我正在使用ViewModelLocator单例和静态方法,通过实例化ViewModel、Window和Data Context来代替代码后台。

详细信息请参见博客文章。 请告诉我哪种方法更可取。我不喜欢使用代码后台,但可能会有一些利弊我没有考虑到。


4
首先,“厌恶”代码后台(完全没有具体原因)是一个非常薄弱的论点。你可以找到无数人说“并非所有MVVM中的代码后台都是错的”的例子,所以我不会在这里再次开始那个话题。忽略那一点,这种方法基本上并没有太多“附加优势”,而且引入了很多没有必要的间接性。因此,如果我必须详细说明和证明这一点,首先创建新窗口并显示它的过程是不“可单元测试的”。 - Viv
1
其次,现在你所得到的是在MainViewModel中添加了对ViewModelLocator的强依赖(以调用静态函数)。因此,MainViewModel的单元测试现在必须提供这个类,但不能提供与实际代码相同的类,因为那涉及到创建UI元素。因此,你需要一个ViewModelLocator的模拟,并可能将其作为接口服务来传递不同的实例,以进行单元测试和实际代码。 - Viv
2
另一方面,您会收到一条消息,可以从单元测试中订阅并完成订阅。因此,将代码从代码后台移动到“ViewModelLocator”可以实现“我的代码后台类为空”的目的,而本来不必如此,并且认为应该这样做是错误的。 此外,“ViewModelLocator”正如其名称所示是一个IoC助手。 让它执行打开/关闭窗口的操作非常奇怪。 我宁愿知道哪个窗口链接到其他窗口,并从中派生我的依赖项。 - Viv
1
抱歉,我并不是要否定你的想法,只是个人认为这不是我会选择的东西,而且我也建议大家不要“害怕”在MVVM中使用“代码后台文件”。了解哪些代码属于什么,可以避免一些麻烦。 - Viv
3
我不喜欢Code Behind并不意味着它是错的。但是在.NET中,我发现有一件事情是真实可信的:做某件事总有很多正确的方法。如果你使用MVVM框架,那么我的例子就是使用该框架的其中一种方法。 - srock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接