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

时间:2015-04-13 14:28:42

标签: c# wpf mvvm caliburn.micro

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

共享ViewModel

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()
    {

    }        
}

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

这里的问题是永远不会调用ConfirmDelete方法。我猜问题是子视图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; }
        }
    }

TestView

<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

窥探结果:

Snoop Result

2 个答案:

答案 0 :(得分:1)

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

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

因此,ModelView控件的任何实例都会将.DataContext设置为ModelViewModel对象以进行绑定。

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

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

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

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

<!-- 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,所以我不确定我能在那里帮助你。

快速谷歌搜索表明它可能与命名控件不直接在主视图中的事实有关,因此Caliburn搜索在视图中查找具有该特定名称的元素可能无法正常工作正如所料。

This answer建议明确地编写绑定,如下所示:

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

答案 1 :(得分:0)

我认为问题在于命名约定不起作用,因为视图未使用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>