什么
它让我困扰了好几个月,但我终于想出了一种在WPF中视图之间转换的通用方法 - 这种方式允许我使用我想要的任何动画进行转换。不,VisualStateManager在这里根本没有帮助 - 它只适用于同一视图中的转换。
答案 0 :(得分:1)
已经有框架和技术,但坦率地说,他们都把我弄糊涂了,或者看起来很笨拙。 Another guy's solution还可以,但我不喜欢它,因为某些原因并没有让我夜不能寐。也许,虽然比大多数人简单,但它似乎仍然令人困惑,所以我得到了它的要点并制作了我自己的。然而,它确实激发了我的解决方案。
所以我导入了我的帮助库,下面是在视图之间进行转换所需的唯一代码。你可以使用你想要的任何动画,而不仅仅是我的淡入/淡出动画。如果有人让我知道上传我的解决方案的好地方,我将很乐意分享。
代码
有一个带红框(RedGridViewModel)和蓝框(BlueGridViewModel)的窗口。每个盒子下方都有一个按钮,可以将盒子交替改变为当前不是的颜色。按下按钮时,当前框淡出,新框淡入。按钮在转换过程中被禁用,两个按钮可以快速连续按下而不会使程序崩溃。我在这里使用了两个从属的ViewModel,但你可以使用你想要的多个。
<Window x:Class="ViewModelTransitionTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ViewModelTransitionTest"
xmlns:custom="clr-namespace:CustomTools;assembly=CustomTools"
Title="MainWindow" Height="350" Width="525"
DataContext="{x:Static local:StartingDataContext.DataContext}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--Reference to my helper library with all the goodies that make this whole thing work somehow-->
<ResourceDictionary Source="pack://application:,,,/CustomTools;component/ViewModelTransition/TransitionCapableViewModelResources.xaml"/>
<!--DataTemplates for the Red and Blue view models. Just tack on
the TransitionCapableViewModelStyleWithFadeInAndOut style and you're good to go.-->
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:BlueGridViewModel}">
<Grid
Background="Blue"
Opacity="0"
Style="{StaticResource TransitionCapableViewModelStyleWithFadeInAndOut}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:RedGridViewModel}">
<Grid
Background="Red"
Opacity="0"
Style="{StaticResource TransitionCapableViewModelStyleWithFadeInAndOut}"/>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter
Content="{Binding ViewModelA}"
Grid.Row="0"
Grid.Column="0"/>
<ContentPresenter
Content="{Binding ViewModelB}"
Grid.Row="0"
Grid.Column="1"/>
<Button
Content="Change ViewModel"
Grid.Row="1"
Grid.Column="0"
Command="{Binding ChangeViewModelA}"/>
<Button
Content="Change ViewModel"
Grid.Row="1"
Grid.Column="1"
Command="{Binding ChangeViewModelB}"/>
</Grid>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using CustomTools;
namespace ViewModelTransitionTest
{
/// <summary>
/// View Model for the main windown.
/// </summary>
class MainWindowViewModel : ContainsSwappableViewModelsBase
{
/// <summary>
/// ViewModel properties should always follow this basic format. You can use a string,
/// but I like type-safety and I don't have Visiual Studio 2012, so I use this clunky
/// extractPropertyNameMethod to convert the ProeprtyName to a string.
/// </summary>
public object ViewModelA
{
get
{
return viewModels[ExtractPropertyName(() => ViewModelA)];
}
}
public object ViewModelB
{
get
{
return viewModels[ExtractPropertyName(() => ViewModelB)];
}
}
public TransitionCapableViewModel NewViewModelA
{
get
{
if (ViewModelA is BlueGridViewModel)
return new RedGridViewModel();
return new BlueGridViewModel();
}
}
public TransitionCapableViewModel NewViewModelB
{
get
{
if (ViewModelB is BlueGridViewModel)
return new RedGridViewModel();
return new BlueGridViewModel();
}
}
/// <summary>
/// Each ViewModel property should have a command that changes it. That command should
/// call changeViewModel and check canChangeViewModel as follows.
/// </summary>
public ICommand ChangeViewModelA
{
get
{
return new RelayCommand(
x => changeViewModel(() => ViewModelA, NewViewModelA),
x => canChangeViewModel(() => ViewModelA, NewViewModelA));
}
}
public ICommand ChangeViewModelB
{
get
{
return new RelayCommand(
x => changeViewModel(() => ViewModelB, NewViewModelB),
x => canChangeViewModel(() => ViewModelB, NewViewModelB));
}
}
/// <summary>
/// In the constructor, you'll want to register each ViewModel property with a starting
/// value as follows. And don't forget to call the base constructor.
/// </summary>
/// <param name="viewModelA"></param>
/// <param name="viewModelB"></param>
public MainWindowViewModel(object viewModelA, object viewModelB)
:base()
{
addViewModelPropertyAndValueToDictionary(() => ViewModelA, viewModelA);
addViewModelPropertyAndValueToDictionary(() => ViewModelB, viewModelB);
}
/// <summary>
/// The only method you have to override from the base class is this one which regsiters
/// your commands with a corresponding viewmodel so that I can raise the relevant change
/// notifications because if you're as bad as me, you'll probably screw it up.
/// </summary>
protected override void associateCommandsWithViewModelProperties()
{
associateCommandWithViewModelProperty(ExtractPropertyName(() => ChangeViewModelA), ExtractPropertyName(() => ViewModelA));
associateCommandWithViewModelProperty(ExtractPropertyName(() => ChangeViewModelB), ExtractPropertyName(() => ViewModelB));
}
}
}
为了完整起见,这实际上是我为此示例编写的唯一其他代码(不包括对此线程有点冗长的非常小的帮助程序集):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CustomTools;
namespace ViewModelTransitionTest
{
static class StartingDataContext
{
public static MainWindowViewModel DataContext {
get
{
return new MainWindowViewModel(new BlueGridViewModel(), new RedGridViewModel());
}
}
}
class RedGridViewModel : TransitionCapableViewModel
{
}
class BlueGridViewModel : TransitionCapableViewModel
{
}
}
我使用了以下内容:
Can't find the original link, but binding wpf events to VM commands was important
Code Contracts Plugin, because I like code contracts
如果有兴趣,我可以发布管道代码或上传整个解决方案。它真的不是那么长 - 两个基类,以及一个包含两个样式和两个故事板的资源文件。