无法在同一视图中添加两个用户控件

时间:2012-11-29 10:27:15

标签: wpf caliburn.micro wpf-4.0 caliburn

我有一个列表视图,其中有两个子视图。一个是显示视图,另一个是编辑视图。这是我如何定义List(父)视图。请注意,我希望两个子UserControl占用Parent中的不同空间。

<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
    ....

    <ContentControl x:Name="GroupDetail" Grid.Row="2" />
    <TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"/>
</UserControl>

然后在我的视图模型中,我按照以下方式根据用户响应激活这些项目

**查看模型**

[Export(typeof(RelayListViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class RelayListViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<Group> {
    ....
    public void Edit() { //Requested Edit
        RelayEditViewModel viewModel = TryAndLocateViewModel(SelectedRelay.Group.Rack.Id, SelectedRelay.Group.Id);
        ActivateItem(viewModel);
    }
    ....

    public void ViewGroupDetail(Relay relay) { //Requested View
        GroupDetailViewModel viewModel = container.GetExportedValue<GroupDetailViewModel>();
        ActivateItem(viewModel);
    }

上面的代码可以工作,但是详细信息视图会加载到选项卡空间(用于编辑视图的空间)。实际上,ActivateItem(viewModel)确实选择了要显示的正确类型的视图,但是它被加载到显示视图的错误位置,也就是说,显示视图被加载到屏幕上的编辑视图空间中。当然,我错过了一些明显的东西。

总之,我们如何在Parent UserControl中定义两个UserControl以在其自己的空间中激活?

修改 - 1:

以下是两个屏幕截图,分别显示我需要加载编辑明细视图的位置。

Edit Screen Loaded

View Screen Loaded (Should not load in Edit Area)

正如您在第二个屏幕截图中看到的,详细信息视图将加载到详细信息区域以及编辑区域(选项卡)中。我不希望细节视图出现在细节区域中。编辑区域仅适用于编辑视图。

这是我用来生成屏幕截图的代码。

包含两种观点的主视图

<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
    <Grid>
         ....
                     <ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left" 
                                        cal:View.Context="GroupDetail" cal:View.Model="{Binding ActiveItem}"/>
                    <TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"
                                cal:View.Context="RelayEdit" cal:View.Model="{Binding ActiveItem}"/>
     </Grid>
</UserControl>

编辑2: 我想我非常接近让它发挥作用。根据您的建议,我修改了主(父)容器,如下所示。

<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
            <ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left" />
                <TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0" />

编辑屏幕和详细信息屏幕现在显示在适当的位置。但是,未调用Detail ViewModels OnActivate ,因此我得到一个没有填充变量的空白详细信息视图。所有加载的详细信息视图字段都在 OnActivate()覆盖上完成。以下是我的GroupDetailViewModel的定义方式

[Export(typeof(GroupDetailViewModel))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class GroupDetailViewModel : Screen {
    ...
    protected override void OnActivate() {
        base.OnActivate();
    ..
    } 

当然,我缺少一些属性。或者我是否必须在GroupDetailViewModel上调用某些方法来手动加载详细信息?

1 个答案:

答案 0 :(得分:1)

删除了原始答案,因为它很长,并没有太多帮助

修改

好的,不要理会上面的内容 - 看起来你试图在两个不同的视图模型上加载两个不同的视图,据我所知,这不是Context的设计目的。 Context属性在同一视图模型上加载两个不同的视图,例如在您的XAML中:

<ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left" 
    cal:View.Context="GroupDetail" 
    cal:View.Model="{Binding ActiveItem}"/>
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource  TabControlStyle}" Margin="5,0,0,0" 
    cal:View.Context="RelayEdit" 
    cal:View.Model="{Binding ActiveItem}"/>

如果通过RelayEditViewModel CM激活名称为ActivateItem()的VM,则会尝试加载以下视图:

RelayEdit.GroupDetail用于内容控制

RelayEdit.RelayEdit用于标签控件

请参阅:

http://caliburnmicro.codeplex.com/wikipage?title=View%2fViewModel%20Naming%20Conventions&referringTitle=Documentation

...

如果您尝试加载另一个ViewModel,则相同的约定将适用于查找视图

GroupDetailViewModel会产生

GroupDetail.GroupDetail用于内容控制

GroupDetail.RelayEdit用于标签控件

听起来这不是你想要的(我不确定为什么要加载任何东西 - 你的视图中有哪些命名空间?你自定义了视图定位器吗?)

我仍然试图了解您需要的生命周期支持,但听起来您希望编辑视图受生命周期管理(因为您需要加载/保存/保护类型支持),但详细信息视图是是只读的,并不关心它是否在没有被保护的情况下关闭

在这种情况下,您可能希望向ViewModel添加一个属性,该属性将保存对详细信息视图模型的引用,但不要激活它...只需设置该属性而不调用ActivateItem(vm)

示例:

[Export(typeof(RelayListViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class RelayListViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<Group> {
....
// Backing field + prop to hold the details view - the content control will bind to this
private IScreen _details;
public IViewAware Details { get { } set { } } // Implement standard NotifyOfPropertyChange here for this property

public void Edit() { //Requested Edit
    RelayEditViewModel viewModel = TryAndLocateViewModel(SelectedRelay.Group.Rack.Id, SelectedRelay.Group.Id);
    ActivateItem(viewModel);
}
....

public void ViewGroupDetail(Relay relay) { //Requested View
    GroupDetailViewModel viewModel = container.GetExportedValue<GroupDetailViewModel>();
    // Instead of activating, just assign the VM to the property and make sure Details calls NotifyOfPropertyChange to let CM know to start the binding logic
    Details = viewModel;
}

然后在你的XAML中

<!-- Just bind the details view to the Details property -->
<ContentControl x:Name="Details" HorizontalContentAlignment="Left" /> 
<!-- Leave this as-is, as it's working ok -->
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource  TabControlStyle}" Margin="5,0,0,0" /> 

(我假设您使用上面的TabControl默认约定,但必要时进行调整)

只要您相应地设置Context属性,就可以对详细信息和编辑视图使用相同的VM。

如果有帮助,请告诉我

编辑:

回答关于MVVM和耦合等的问题......

您所做的只是从几个更简单的视图模型中构建一个更复杂的视图模型(因此从几个更简单的视图中可以看到更复杂的视图)。只要您对详细信息VM的引用不是具体类型,VM之间就会出现非常松散的耦合。您可以将实现该接口的任何viewmodel类型分配给主VM上的Detail属性,CM将尝试为其定位视图并构建接口。这非常好(如果需要,您可以使用IoC获取详细信息窗口的类型)

如果您的详细信息视图需要生命周期,则应继承Screen,但我怀疑您的详细信息视图是否需要激活(因为它只是一个详细信息视图并且只是准备好了)所以只需实现IViewAware并继承来自PropertyChangedBase会做。但是,编辑视图需要具有生命周期,因此应该继承自Screen.

Conductor已包含ActiveItem属性,并提供通过ActivateItem()激活的子项的生命周期管理,您所做的就是为您创建额外的'bolt-on'属性引用附加vm的指挥(即你需要ActiveItemDetails