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>
我正在尝试寻找一种从其他视图中显示模态视图的技术,但我遇到了问题。这是我正在尝试做的一个简单示例:
共享视图模型
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>