Depending on the Vm-ки tied to ContentPresenter , different UserControl displayed on the screen. Is it possible to somehow catch the moment when one control is changed to another in order to facilitate this transition?

 <ContentPresenter Content="{Binding VmBase}"> <ContentPresenter.Resources> <DataTemplate DataType="{x:Type vmR:LoginVm}"> <uc:LoginPage></uc:LoginPage> </DataTemplate> <DataTemplate DataType="{x:Type vmR:SignUpVm}"> <uc:SignUpPage></uc:SignUpPage> </DataTemplate> </ContentPresenter.Resources> </ContentPresenter> 

2 answers 2

Ok comment @Squidward @Athari is actually correct: Mahapps.Metro has the necessary controls with several predefined animations. But let's write such control independently.

We face two problems. First, we must remember the old content before it disappears, in order to correctly show it during the animation. Secondly, to display both old and new content, we need two ContentPresenter 's.

To do this, use the intermediate control, which will contain these same two ContentPresenter 's.

So, create a new control, let's call it AnimatableContentPresenter . We create custom control, not UserControl . (This is done through ProjectAddNew Item ...WPFCustom Control (WPF) in Visual Studio.)

For starters, the code with comments.

 // объявляем, что в шаблоне должна быть предоставлена анимация, которую мы запустим [TemplatePart(Name = "PART_Animation", Type = typeof(Storyboard))] public class AnimatableContentPresenter : Control { static AnimatableContentPresenter() { DefaultStyleKeyProperty.OverrideMetadata( typeof(AnimatableContentPresenter), new FrameworkPropertyMetadata(typeof(AnimatableContentPresenter))); } Storyboard animation; // текущая анимация bool isAnimationRunning = false; #region dp object Content, on change OnContentChanged public object Content { get { return (object)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( "Content", typeof(object), typeof(AnimatableContentPresenter), new PropertyMetadata(OnContentChangedStatic)); static void OnContentChangedStatic( DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (AnimatableContentPresenter)d; self.OnContentChanged(e.OldValue, e.NewValue); } #endregion #region dp object PreviousContent public object PreviousContent { get { return (object)GetValue(PreviousContentProperty); } set { SetValue(PreviousContentProperty, value); } } public static readonly DependencyProperty PreviousContentProperty = DependencyProperty.Register( "PreviousContent", typeof(object), typeof(AnimatableContentPresenter)); #endregion // когда Content поменяется... void OnContentChanged(object oldContent, object newContent) { if (isAnimationRunning) animation?.Stop(); // ... запомним старый Content в PreviousContent PreviousContent = oldContent; // и перезапустим анимацию if (animation != null) { animation.Begin(); isAnimationRunning = true; } } // при появлении шаблона, вычитаем из него анимацию public override void OnApplyTemplate() { base.OnApplyTemplate(); if (animation != null) animation.Completed -= OnAnimationCompleted; if (isAnimationRunning) { // TODO: начать новую анимацию там, где предыдущая завершилась? animation?.Stop(); } animation = (Storyboard)Template.FindName("PART_Animation", this); if (animation != null) // подпишемся на завершение анимации animation.Completed += OnAnimationCompleted; } // по окончанию анимации... private void OnAnimationCompleted(object sender, EventArgs e) { // выбросим старый контент PreviousContent = null; // сбросим эффект анимации animation.Remove(); isAnimationRunning = false; } } 

Now we need a template in Themes\Generic.xaml (most likely, the master of creating a new control has put it to you already).

 <Style TargetType="{x:Type local:AnimatableContentPresenter}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:AnimatableContentPresenter}"> <!-- несколько трюков с layout manager'ом, чтобы избежать умножения через конвертер --> <Grid Name="Root" ClipToBounds="True"> <Grid HorizontalAlignment="Left"> <!-- ширина вдвое больше Root --> <Grid.ColumnDefinitions> <ColumnDefinition Width="{Binding ActualWidth, ElementName=Root}"/> <ColumnDefinition Width="{Binding ActualWidth, ElementName=Root}"/> </Grid.ColumnDefinitions> <!-- растянем на всю ширину --> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.ColumnSpan="2"> <!-- старый контент --> <ContentPresenter Content="{TemplateBinding PreviousContent}" Style="{x:Null}" Width="{Binding ActualWidth, ElementName=Root}"/> <!-- текущий контент --> <ContentPresenter Content="{TemplateBinding Content}" Style="{x:Null}" Width="{Binding ActualWidth, ElementName=Root}"/> <!-- анимируемая распорка --> <Grid Width="{Binding ActualWidth, ElementName=Root}" Name="Strut"> <Grid.Resources> <Storyboard x:Key="Animation" x:Name="PART_Animation"> <DoubleAnimation Storyboard.TargetName="Strut" Storyboard.TargetProperty="Width" From="0" Duration="0:0:0.4"> <DoubleAnimation.EasingFunction> <ExponentialEase EasingMode="EaseInOut" Exponent="1.2"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Grid.Resources> </Grid> </StackPanel> </Grid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> 

Great, most of the work is done. Now we need to present it as a style for something — for example, for ContentControl . (This is ContentPresenter like ContentPresenter 'ab is a bit higher level.)

Put in our window (or application resources) style:

 <Style TargetType="ContentControl" x:Key="LeftToRightAnimatedContentControl"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <local:AnimatableContentPresenter Content="{Binding}"/> </DataTemplate> </Setter.Value> </Setter> </Style> 

We are experiencing.

 <ContentControl Content="{Binding}" Style="{StaticResource LeftToRightAnimatedContentControl}"/> 

We get the following animation:

animated cartoon

  • Thank you, cool answer! - Lightness
  • Understood how everything works, only here one line is a little confusing [TemplatePart(Name = "PART_Animation", Type = typeof(Storyboard))] . - Lightness
  • @Lightness: This line declares that in a template (a volume that in Generic.xaml) there must be an element called PART_Animation type Storyboard . We use this element in the code later. It is like documentation for those who write the style, that the code requires an element of the same name in the template that is of this type. - VladD 3:53 pm
  • @Lightness: Please! - VladD 3:53 pm
  • There was a need to use different animations when changing controls. It turns out for each animation you need to create <Style TargetType="{x:Type local:AnimatableContentPresenter}"> with the creation of a new Template ? - Lightness

This must be done by DataTrigger + StoryBoard ( here is an example). In your case, you cling to the properties of IsLoginFineshed (for example) and you cling to it with animation.