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

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!
Comments