使用WPF和Caliburn.Micro在视图内添加多个视图

15

我正在尝试使用Caliburn.Micro和WPF学习。如何在一个视图中添加多个视图?

<Window x:Class="ProjectName.Views.MainView"
         ...>
<Grid>
        <views:MyControlView  />
</Grid>
</Window>

另一个视图,搭配视图模型:MyControlViewModel

<UserControl x:Class="ProjectName.Views.MyControlView"
         ...>
<Grid>
    ...
</Grid>
</UserControl>
如果我只是添加视图,它不会检测到具有相应名称的视图模型。我该如何将其绑定到视图模型?
我尝试了不同的引导程序并使用类似于cal:Bind.Model="path/classname/merge of the two"的东西。尝试将其添加到mainview和usercontrol(MyControlView)中。非常感谢任何关于此问题的帮助。我卡住了,我真的很想使用Caliburn.Micro :)
最好的问候, diamondfish
编辑:我仍然无法使其工作,问题似乎在引导程序或其他地方。但是为了澄清,在这里是我运行测试项目的代码。
MainView xaml:
<Window x:Class="Test.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
    xmlns:views="clr-namespace:Test.Views"
    Title="MainWindow" Height="360" Width="640">
<Grid>
    <views:MyControlView />
</Grid>

主ViewModel代码:

public partial class MainViewModel : PropertyChangedBase
{
}

我ControlView的XAML代码:

<UserControl x:Class="Test.Views.MyControlView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
         cal:Bind.Model="Test.MyControlViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <TextBlock Text="{Binding MyProp}"/>
</Grid>

我的 ControlView 代码:

public class MyControlViewModel : PropertyChangedBase
{
    public string MyProp
    {
        get { return "Working"; }
    }
}

错误截图:http://clip2net.com/s/1gtgt

我已经尝试过

cal:Bind.Model="Test.ViewModels.MyControlViewModel" 

还尝试了 cal-reference 但也没有用:

xmlns:cal="http://www.caliburnproject.org"

我项目的截图:http://clip2net.com/s/1gthM

由于文档大多是针对silverlight,有时是针对Caliburn而不是CM,所以我可能实现了错误的引导程序。 对于这个测试项目,它只是这样的:(在App.xaml中更改.xaml)

public class BootStrapper : Bootstrapper<MainViewModel>
{
}

请在这里帮助我!似乎我错过了一些基础知识 :)


编辑了帖子并添加了MVVM标签,欢迎来到S.O! - EtherDragon
请检查答案 - 我添加了一个关于导出类型的部分。这是c.m找到与视图相关的ViewModel的重要要求。 - EtherDragon
3个回答

17

编辑 - 更完整的回答如下:

好的,C.M正在为您完成许多工作,这都是为了让您的类和xaml准备好供C.M查找。如上所述,我更喜欢显式编写代码,而不是依赖框架的隐式代码假设。

因此,默认的C.M项目中的引导程序就足够了。

public class AppBootstrapper : Bootstrapper<MainViewModel>
{
    // ... You shouldn't need to change much, if anything
}

`引导程序'部分非常重要,因为它指示了在应用程序启动时哪个ViewModel是您的第一个或主屏幕。

[Export(Typeof(MainViewModel))]
public class MainViewModel : Screen,  IShell
{
    [ImportingConstructor]
    public MainViewModel(YourFirstViewModel firstViewModel, YourSecondViewModel secondviewModel) // etc, for each child ViewModel
    {
    }
}

[ImportingConstructor]中,您不需要做任何其他工作,只需指定MainViewModel需要其他ViewModel的存在即可。在我的特定情况下,我喜欢我的MainViewModel成为一个容器,而仅仅是一个容器,因为事件逻辑是在其他地方处理的。但您也可以很容易地在此处处理Handle逻辑 - 但这是另一种讨论。

现在,每个子视图模型还需要导出自己,以便C.M知道在哪里找到它们。
[Export(Typeof(YourFirstViewModel))]
public class YourFirstViewModel : IShell
{
    // VM properties and events here
}

如果您只是使用默认构造函数,则无需指定导入构造函数。

现在,每个视图的外观都将类似于:

<UserControl x:Class="Your.Namespace.MainView"
             xmlns:views="clr-namespace:Your.Namespace.Views"
             xmlns:cal="http://www.caliburnproject.org"
             cal:Bind.Model="Your.Namespace.ViewModels.MainViewModel"
             MinWidth="800" MinHeight="600">
    <StackPanel x:Name="RootVisual">
        <views:YourFirstView />
        <views:YourSecondView />
        <!-- other controls as needed -->
    </StackPanel>
</UserControl>

XAMl或其子视图之一

<UserControl x:Class="Your.Namespace.Views.YourFirstView"
             xmlns:cal="http://www.caliburnproject.org"
             cal:Bind.Model="Your.Namespace.ViewModels.YourFirstViewModel"
             MinWidth="800" MinHeight="600">
    <Grid x:Name="RootVisual">
        <!-- A bunch of controls here -->
    </Grid>
</UserControl>

这里到底发生了什么?C.M 在引导程序中看到,由于指定了 public class AppBootstrapper : Bootstrapper<MainViewModel> 这一行,所以 MainViewModel 是起点。MainViewModel 需要在其构造函数中包含 YourFirstViewModel 和 YourSecondViewModel(以及其他 ViewModel),因此 C.M 构建了每个 ViewModel 并将它们全部放入 IoC 容器中(这样后面就可以更轻松地使用它们——当然,这是另一个讨论的话题)。
C.M 代表您处理了将数据上下文分配给每个视图的工作,因为您使用了类似于 cal:Bind.Model="Your.Namespace.ViewModels.YourFirstViewModel" 的代码来指定要绑定到哪个 VM。
希望这能让你有所启发。此外,还可以参考 C.M 的示例项目 Caliburn.Micro.HelloEventAggregator,因为它正是你所需要的(尽管它被描述为事件聚合器演示,这也非常有用——但这又是另一个讨论的话题)。
(以下是原始答案供参考)
你需要这样做:
<UserControl x:Class="Your.Namespace.Here.YourView"
             xmlns:cal="http://www.caliburnproject.org"
             cal:Bind.Model="Your.Namespace.Here.YourViewModel"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="1024">
  <YourControlLayout />
</UserControl>

注意这一行 cal:Bind.Model="Your.Namespace.Here.YourViewModel" 它指定了要将此视图绑定到的确切视图模型。

别忘了导出你的类类型,否则 c.m 找不到它。

[Export(typeof(YourViewModel))]
public class YourViewModel : IShell
{
    ...
}

然后,您可以按照您的意愿嵌套用户控件。这是利用C.M的非常好的方法,您会发现它高度可扩展。唯一的弱点是视图(View)和视图模型(ViewModel)必须在同一项目中(据我所知)。但是,这种方法的优点是,如果您希望让事情井然有序,可以将视图和视图模型类分别放在不同的名称空间(在同一项目中)中。
对于c.m的评论,实际上我更喜欢这种方法,即使我不必嵌套View UserControls等。我宁愿明确声明一个View绑定到哪个VM(并仍然让C.M在IoC中处理所有繁重的工作),而不是让c.m从隐含的代码“找出来”。
即使有一个好的框架:显式代码比隐含代码更易维护。指定绑定的视图模型有助于清楚地说明期望的数据上下文,因此您以后就不需要猜测了。

1
你走在正确的轨道上 - 我们只是遗漏了一些愚蠢的东西。关于导出的信息已添加。 - EtherDragon
1
这个例子展示了如何实现你所需要的功能。一个包含两个子视图模型的主视图模型(或者壳)。 - EtherDragon
我使用了这个答案来解决我的问题。我成功地加载了视图,但是子视图中的控件与模型类中的属性没有绑定。我将NotifyPropertyChanged作为子视图模型和iShell接口的基类,并使用CM示例中的MEFBootstrapper。如果不使用它,就无法工作。更新:必须使用Binding Source=propname而不是Binding=propname。 - Mounhim
更新:奇怪的是,我的用户控件(子控件)上的控件绑定到了主视图模型上的属性,但没有绑定到子视图模型上。我真的迷失了。因此,MainViewModel.Prop1 在 ChildView1 的 Textblock 中显示其值,但 ChildViewModel.Prop1 在 ChildView1 的 Textblock 中不显示其值。 - Mounhim
1
非常好的答案,这应该取代Caliburn的入门/新手指南 :) - Adam Plocher
显示剩余3条评论

17

更好的方法是在你的主视图上使用ContentControl,并将其命名为MainViewModel上的类型为MyControlViewModel的公共属性相同的名称。例如:

MainView.xaml

<ContentControl x:Name="MyControlViewModel" />

MainViewModel.cs

// Constructor
public MainViewModel()
{
  // It would be better to use dependency injection here
  this.MyControlViewModel = new MyControlViewModel();     
}

public MyControlViewModel MyControlViewModel
{
  get { return this.myControlViewModel; }
  set { this.myControlViewModel = value; this.NotifyOfPropertyChanged(...); }
}

我已经让它工作了。但它似乎没有充分利用C.M吗?虽然这是一个不错的方法,非常感谢! - diamondfish
我还发现在使用ContentControl时,在VS中编辑时无法看到MainView中MyControl的接口。有什么办法可以解决吗? - diamondfish
3
你正在使用Caliburn.Micro,它通过将ContentControl的名称与你的视图模型属性名称匹配来定位视图,并将视图注入到ContentControl中,然后绑定该视图控件与视图模型属性。这是使用Caliburn.Micro进行视图组合的推荐方法。 - devdigital
关于在Visual Studio设计器中运行的约定,我认为目前还不可能实现,因此您需要单独编辑每个视图的接口。 - devdigital

1
在文件App.xaml.cs中的GetInstance方法中添加以下行。
protected override object GetInstance(Type service, string key)
{
    if (service == null && !string.IsNullOrWhiteSpace(key))
    {
        service = Type.GetType(key);
        key = null;
    }
    // the rest of method
}

在我的引导程序中使用Ninject时,我解决了“值不能为空”的错误。非常感谢 :) - Dave

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