Skip to main content
I always wondered how NavigationTransitionInfo-s work if they have no publicly exposed animation/navigation data. Looking at the public definition of NavigationTransitionInfo WinRT class, it appears that it implements only 2 interfaces, INavigationTransitionInfo and INavigationTransitionInfoOverrides INavigationTransitionInfo is totally empty
namespace Windows.UI.Xaml.Media.Animation
{    
    [ComImport]    
    [ExclusiveTo(typeof(NavigationTransitionInfo))]       
    [ContractVersion(typeof(UniversalApiContract), 65536u)]        
    [Windows.Foundation.Metadata.Guid(2846904465u, 44618, 17266, 134, 37, 33, 183, 168, 185, 140, 164)]    
    [WebHostHidden]
    internal interface INavigationTransitionInfo
    {
	
    }
}
INavigationTransitionInfoOverrides has 2 members
namespace Windows.UI.Xaml.Media.Animation
{    
    [ComImport]    
    [ExclusiveTo(typeof(NavigationTransitionInfo))]    
    [ContractVersion(typeof(UniversalApiContract), 65536u)]    
    [Windows.Foundation.Metadata.Guid(3645996650u, 43472, 19447, 157, 176, 70, 51, 166, 157, 175, 242)]    
    [WebHostHidden]    
    internal interface INavigationTransitionInfoOverrides    
    {        
        string GetNavigationStateCore();        
        void SetNavigationStateCore([In] string navigationState);    
    }
}
However, none of these 2 function seem to hold the animation data So I guessed that there must be another hidden interface used to hold the animation data, so I assumed that it must be called something like INavigationTransitionInfoPrivate or INavigationTransitionInfoInternal So I started searching for the dll that implements the NavigationTransitionInfo WinRT class, after some search in Registry I found that it’s in Windows.UI.Xaml.Phone.dll I dropped that dll in IDA Pro and started searching for “INavigationTransitionInfoPrivate And YES! there was actually an interface with that name! IDA1 Here comes ADeltaX’s big help, he showed me how to get the GUID and how to locate the vftable and the interface member functions Big thanks to ADeltaX! Now, we have the GUID (1ab93e41-46d2-4a19-b20a-e8d042fe5740) and member functions and their signatures:
CreateStoryboards([in] Windows::UI::Xaml::UIElement* pElement, 
[in] Windows::UI::Xaml::Media::Animation::NavigationTrigger trigger, 
[in] Windows::Foundation::Collections::IVector<Windows.UI::Xaml::Media::Animation::Storyboard*>* pStoryboards);
Then we had to find out what NavigationTrigger is, ADeltaX was able to find it in Windows.UI.Xaml public symbols:
enum Windows::UI::Xaml::Media::Animation::NavigationTrigger 
{
    NavigationTrigger_NavigatingAway = 0x0,
    NavigationTrigger_NavigatingTo = 0x1,
    NavigationTrigger_BackNavigatingAway = 0x2,
    NavigationTrigger_BackNavigatingTo = 0x3
}
So now we can write some code! Wrote the com import and enum definition code:
public enum NavigationTrigger 
{
    NavigatingAway = 0x0,
    NavigatingTo = 0x1,
    BackNavigatingAway = 0x2,
    BackNavigatingTo = 0x3
};

[ComImport, Guid("1ab93e41-46d2-4a19-b20a-e8d042fe5740")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface INavigationTransitionInfoPrivate 
{
    void CreateStoryboards(UIElement pElement, 
                           NavigationTrigger trigger, 
                           IList<Storyboard> pStoryboards);
}
Created custom NavigationTransitionInfo class called CustomPageTransitionTest that inherits from NavigationTransitionInfo and INavigationTransitionInfoPrivate:
public class CustomPageTransitionTest : NavigationTransitionInfo, INavigationTransitionInfoPrivate
{
    void INavigationTransitionInfoPrivate.CreateStoryboards(UIElement pElement, NavigationTrigger trigger, IList<Storyboard> pStoryboards)
    {
        switch (trigger)
        {
            case NavigationTrigger.NavigatingAway:
                break;
            case NavigationTrigger.NavigatingTo:
                break;
            case NavigationTrigger.BackNavigatingAway:
                break;
            case NavigationTrigger.BackNavigatingTo:
                break;
        }
    }

    //For some reasons, this is required in some cases...    
    protected override string GetNavigationStateCore()
    {
        return base.GetNavigationStateCore() ?? "0";
    }
}
Wrote simple animation code:
void INavigationTransitionInfoPrivate.CreateStoryboards(UIElement pElement, NavigationTrigger trigger, IList<Storyboard> pStoryboards)
{
    switch (trigger)
    {
        case NavigationTrigger.NavigatingAway:
        {
            CompositeTransform transform = new CompositeTransform();
            pElement.RenderTransformOrigin = new Point(0.5, 0.5);
            pElement.RenderTransform = transform;

            Storyboard board = new Storyboard();

            DoubleAnimation anim1 = new DoubleAnimation();
            anim1.From = 0;
            anim1.To = 360;
            anim1.Duration = TimeSpan.FromSeconds(3);
            anim1.EnableDependentAnimation = false;
            Storyboard.SetTargetProperty(anim1, "Rotation");
            Storyboard.SetTarget(anim1, transform);
            board.Children.Add(anim1);

            DoubleAnimation anim2 = new DoubleAnimation();
            anim2.From = 1;
            anim2.To = 0;
            anim2.Duration = TimeSpan.FromSeconds(3);
            anim2.EnableDependentAnimation = false;
            Storyboard.SetTargetProperty(anim2, "ScaleX");
            Storyboard.SetTarget(anim2, transform);
            board.Children.Add(anim2);

            DoubleAnimation anim3 = new DoubleAnimation();
            anim3.From = 1;
            anim3.To = 0;
            anim3.Duration = TimeSpan.FromSeconds(3);
            anim3.EnableDependentAnimation = false;
            Storyboard.SetTargetProperty(anim3, "ScaleY");
            Storyboard.SetTarget(anim3, transform);
            board.Children.Add(anim3);

            DoubleAnimation anim4 = new DoubleAnimation();
            anim4.From = 1;
            anim4.To = 0;
            anim4.Duration = TimeSpan.FromSeconds(3);
            anim4.EasingFunction = new SineEase();
            anim4.EnableDependentAnimation = true;
            Storyboard.SetTargetProperty(anim4, "Opacity");
            Storyboard.SetTarget(anim4, pElement);
            board.Children.Add(anim4);

            pStoryboards.Add(board);
            break;
        }
        case NavigationTrigger.NavigatingTo:
            break;
        case NavigationTrigger.BackNavigatingAway:
            break;
        case NavigationTrigger.BackNavigatingTo:
            break;
    }
}
Everything looks perfect, right? No There’s no exception and the navigation waits for the Storyboard to finish but… no animation shows on screen. It turns out that the API doesn’t animate the provided UIElement directly but another IFrameworkElement TargetElement (private property?) of UIElement returned using the helper GetLogicalTargetElement private function instead. Unfortunately I wasn’t able to RE the function because many information about it was stripped out from the public symbols. But luckily some NavigationTransitionInfo-s use the PropertyPath (UIElement.TransitionTarget) instead so we can use that. So using that PropertyPath I was able to rewrite the code to use it instead:
void INavigationTransitionInfoPrivate.CreateStoryboards(UIElement pElement, NavigationTrigger trigger, IList<Storyboard> pStoryboards)
{
    switch (trigger)
    {
        case NavigationTrigger.NavigatingAway:
        {
            Storyboard board = new Storyboard();

            PointAnimation pAnim = new PointAnimation();
            pAnim.From = new Point(0, 0);
            pAnim.To = new Point(0.5, 0.5);
            pAnim.Duration = TimeSpan.FromMilliseconds(1);
            pAnim.EnableDependentAnimation = true;
            Storyboard.SetTargetProperty(pAnim, "(UIElement.TransitionTarget).TransformOrigin");
            Storyboard.SetTarget(pAnim, pElement);
            board.Children.Add(pAnim);

            DoubleAnimation anim1 = new DoubleAnimation();
            anim1.From = 0;
            anim1.To = 360;
            anim1.Duration = TimeSpan.FromSeconds(3);
            anim1.EnableDependentAnimation = false;
            Storyboard.SetTargetProperty(anim1, "(UIElement.TransitionTarget).(TransitionTarget.CompositeTransform).Rotation");
            Storyboard.SetTarget(anim1, pElement);
            board.Children.Add(anim1);

            DoubleAnimation anim2 = new DoubleAnimation();
            anim2.From = 1;
            anim2.To = 0;
            anim2.Duration = TimeSpan.FromSeconds(3);
            anim2.EnableDependentAnimation = false;
            Storyboard.SetTargetProperty(anim2, "(UIElement.TransitionTarget).(TransitionTarget.CompositeTransform).ScaleX");
            Storyboard.SetTarget(anim2, pElement);
            board.Children.Add(anim2);

            DoubleAnimation anim3 = new DoubleAnimation();
            anim3.From = 1;
            anim3.To = 0;
            anim3.Duration = TimeSpan.FromSeconds(3);
            anim3.EnableDependentAnimation = false;
            Storyboard.SetTargetProperty(anim3, "(UIElement.TransitionTarget).(TransitionTarget.CompositeTransform).ScaleY");
            Storyboard.SetTarget(anim3, pElement);
            board.Children.Add(anim3);

            DoubleAnimation anim4 = new DoubleAnimation();
            anim4.From = 1;
            anim4.To = 0;
            anim4.Duration = TimeSpan.FromSeconds(3);
            anim4.EasingFunction = new SineEase();
            anim4.EnableDependentAnimation = true;
            Storyboard.SetTargetProperty(anim4, "(UIElement.TransitionTarget).Opacity");
            Storyboard.SetTarget(anim4, pElement);
            board.Children.Add(anim4);

            pStoryboards.Add(board);
            break;
        }
        case NavigationTrigger.NavigatingTo:
            break;
        case NavigationTrigger.BackNavigatingAway:
            break;
        case NavigationTrigger.BackNavigatingTo:
            break;
    }

The moment of the truth

did it work? let’s find out… YES! it did!
Last modified on February 20, 2026