How to create your own NavigationTransitionInfo

 

I always wondered how NavigationTransitionInfo-s work if they have no publicly exposed animation/navigation data

Looking at the public definition of NavigationTransitionInfo Runtime 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 Runtime 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

IDA search results

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!

A video showcasing the custom NavigationTransitionInfo

Comments