WPF MVVM 模式中两个视图之间的共享视图模型

Shared viewmodel between two Views in WPF MVVM pattern

我正在尝试寻找一种从其他视图中显示模态视图的技术,但我遇到了问题。这是我正在尝试做的一个简单示例:

共享视图模型

class ClientesViewModel : Screen
{
    private bool _deleteconfirmvisible;
    public bool DeleteConfirmVisible
    {
        get { return _deleteconfirmvisible; }
        set
        {
            _deleteconfirmvisible = value;
            NotifyOfPropertyChange("DeleteConfirmVisible");
        }
    }

    public void ShowDeleteConfirm()
    {
        this.DeleteConfirmVisible = true;
    }

    public ModalViewModel ModalDelete
    {
        get { return new ModalViewModel(); }            
    }

    public void ConfirmDelete()
    {
        //Actually delete the record
        //WCFService.DeleteRecord(Record)
    }
}

第一次观看

<UserControl x:Class="Ohmio.Client.ClientesView"
             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:mahapps="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
             xmlns:local="clr-namespace:Ohmio.Client"   
             mc:Ignorable="d" 
             d:DesignHeight="364" d:DesignWidth="792">

    <UserControl.Resources>
        <DataTemplate DataType="{x:Type local:ModalViewModel}">
            <local:ModalView/>
        </DataTemplate>
    </UserControl.Resources>
    <local:ModalContentPresenter DataContext="{Binding}" IsModal="{Binding DeleteConfirmVisible}" Grid.ColumnSpan="5" Grid.RowSpan="4" ModalContent="{Binding Path=ModalDelete}">
        <Grid>        
            <Button x:Name="ShowDeleteConfirm" Margin="5" Grid.Column="2" Content="Delete Record"/>        
        </Grid>
    </local:ModalContentPresenter>
</UserControl>

第二个视图(模态内容)

<UserControl x:Class="Ohmio.Client.ModalView"
             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:local="clr-namespace:Ohmio.Client"   
             mc:Ignorable="d" Height="145" Width="476">
    <Grid>        
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>            
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="50"></RowDefinition>
        </Grid.RowDefinitions>        
        <Label Content="Are you sure you want to delete this record?" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center"></Label>
        <Button x:Name="ConfirmDelete" IsDefault="True" Grid.Row="2" Content="Aceptar" Margin="10"></Button>
        <Button x:Name="TryClose" IsCancel="True"  Grid.Row="2" Grid.Column="1" Content="Cancelar" Margin="10"></Button>        
    </Grid>        
</UserControl>

ModalViewModel

class ModalViewModel : Screen
{        
    public ModalViewModel()
    {

    }        
}

所以基本思想是让两个视图共享同一个视图模型。此视图模型具有用于显示模态内容和删除记录的属性。

这里的问题是 ConfirmDelete 方法永远不会 called.I 猜测问题是子视图 DataContext 与父视图不同。那么我该如何解决这个问题?

谢谢!

编辑

忘了说了,我用的是Caliburn.Micro

编辑 2

我按照 Rachel 的建议划分了视图模型。问题仍然存在。这是我的代码现在的样子:

TestViewModel

class TestViewModel :Screen
    {
        private bool _deleteconfirmvisible;
        TestModalViewModel _modaldelete;

        public TestViewModel()
        {
            _modaldelete = new TestModalViewModel();
        }
        public bool DeleteConfirmVisible
        {
            get { return _deleteconfirmvisible; }
            set
            {
                _deleteconfirmvisible = value;
                NotifyOfPropertyChange("DeleteConfirmVisible");
            }
        }

        public void ShowDeleteConfirm()
        {
            this.DeleteConfirmVisible = true;
        }

        public TestModalViewModel ModalDelete
        {
            get { return _modaldelete; }
        }
    }

测试视图

<UserControl x:Class="Ohmio.Client.TestView"
             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:mahapps="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
             xmlns:local="clr-namespace:Ohmio.Client"   
             mc:Ignorable="d" 
             d:DesignHeight="364" d:DesignWidth="792">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type local:TestModalViewModel}">
            <local:TestModalView/>
        </DataTemplate>
    </UserControl.Resources>    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="40"></RowDefinition>
            </Grid.RowDefinitions>
        <ContentPresenter Content="{Binding Path=ModalDelete}"></ContentPresenter>
        <Button x:Name="ShowDeleteConfirm" Margin="5" Grid.Row="1" Content="Delete Record"/>
        </Grid>    
</UserControl>

TestModalViewModel

class TestModalViewModel : Screen
    {
        private Boolean _result;

        public TestModalViewModel()
        {
            _result = false;
        }        

        public void ConfirmAction()
        {
            _result = true;
            TryClose();
        }        

        public bool Result
        {
            get { return _result; }            
        }
    }    

TestModalView

<UserControl x:Class="Ohmio.Client.TestModalView"
             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:local="clr-namespace:Ohmio.Client"   
             mc:Ignorable="d" Height="145" Width="476">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"></RowDefinition>            
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <Label Content="Are you sure you want to delete this record?" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center"></Label>        
        <Button x:Name="ConfirmAction" IsDefault="True" Grid.Row="2" Content="Aceptar" Margin="10"></Button>
        <Button x:Name="TryClose" IsCancel="True"  Grid.Row="2" Grid.Column="1" Content="Cancelar" Margin="10"></Button>
    </Grid>
</UserControl>

我更改了 ModalContentPresenter 和 ContentPresenter,但问题仍然存在:从未调用 ConfirmAction,我不明白为什么。谁能告诉我为什么?

编辑 3

窥探结果:

当您使用隐式 DataTemplate 时,WPF 会自动将 .DataContext 属性 设置为任何数据对象。

<DataTemplate DataType="{x:Type local:ModalViewModel}">
    <local:ModalView/> <!-- DataContext is set to the ModelViewModel object -->
</DataTemplate>

因此,您的 ModelView 控件的任何实例都将 .DataContext 设置为 ModelViewModel 对象以用于绑定目的。

您可以更改您的特定绑定以指向与当前 DataContext 不同的源,如下所示:

<Button x:Name="ConfirmDelete" 
        Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ModalContentPresenter}}, 
                          Path=DataContext.ConfirmDelete }" ... />

然而这并不理想,因为它依赖于开发人员了解使用特定的 UserControl 结构,例如确保 ModelView 始终嵌套在 ModelContentPresenter 控件中

更好的解决方案是确保您的代码正确分离,ModelView 只需要担心显示 Model,而其他代码在模板的另一部分。

<!-- This layer is used to display the entire ClientesViewModel object -->
<UserControl>

    <!-- this UserControl is only responsible for displaying the ModalViewModel object -->
    <UserControl> 
        <!-- However ModelViewModel should look... -->
        <Label Content="Are you sure you want to delete this record?" ... />
    </UserControl>

    <!-- DataContext here is ClientesViewModel, so these bindings work -->  
    <Button Content="Aceptar" Command="{Binding ConfirmDelete}" ... /> 
    <Button Content="Cancelar" Command="{Binding TryClose}" ... />

</UserControl>     

对于同一个数据对象,您可以轻松地拥有多个 UserControl。一个用于“客户端”屏幕,一个用于“删除”对话框屏幕。

尽管最好的解决方案可能是正确分隔代码,这样所有删除代码都在一个对象中,而所有客户端代码都在另一个对象中

<!-- This layer is used to display the entire ClientesViewModel object -->
<local:ClientsView>

    <!-- this UserControl is only responsible for displaying the ModalDelete object -->
    <local:DeleteView />

</local:ClientsView>

class ClientesViewModel
{
    bool DeleteConfirmVisible;
    void ShowDeleteConfirm();
    ModalViewModel ModalDelete;
}

public class ModalViewModel
{
    ICommand ConfirmDelete;
    ICommand TryClose;
}

编辑

根据对您的问题的更新,我的最佳猜测是 Caliburn Micro 的自动绑定的实现存在问题。我以前从未使用过 Caliburn Micro,所以我不确定我能否在这方面为您提供帮助。

快速 google 搜索表明它可能与命名控件不直接位于主视图中这一事实有关,因此 Caliburn 在视图中查找具有该特定名称的元素的搜索可能不会按预期工作。

This answer 建议显式编写绑定,如下所示:

<Button cal:Message.Attach="ConfirmDelete" />

我认为问题在于命名约定不起作用,因为视图不是使用 Caliburn.Micro 的 ViewLocator 设置的。

尝试明确设置模型。

<local:ModalContentPresenter cal:Bind.Model="{Binding}" 
          DataContext="{Binding}" IsModal="{Binding DeleteConfirmVisible}" Grid.ColumnSpan="5" Grid.RowSpan="4" ModalContent="{Binding Path=ModalDelete}">
    <Grid>        
        <Button x:Name="ShowDeleteConfirm" Margin="5" Grid.Column="2" Content="Delete Record"/>        
    </Grid>
</local:ModalContentPresenter>