前两篇文章中,我们的Demo代码都是基于页面切换的,而我们在Silverlight框架下开发的就是以XAML文件为基础的事件驱动程序。也就是说我们的程序会由一个或多个页面作成,这一点和Web程序很相似,所以页面间的切换就很重要。 这一篇文章就来将介绍Windows Phone平台上导航功能。
一 导航控件
从Silverlight3开始,提供了内置的导航框架,可以比较轻松的在 Silverlight Page之间进行切换,并且可以和浏览器的前进、后退按钮集成。在Silverlight 3之前的版本,Silverlight没有特定的导航框架,项目中页面之间的切换是通过修改RootVisual布局容器的内容而实现的。利用SDK中提供了Frame和Page可以完成导航操纵。其中Frame是导航的框架,Page通过Frame加载显示并实现页面间的导航。
Windows Phone程序也是基于Sliverlight的Page Model进行导航,同时你也可以使用后退按钮进行后退操纵。WP上核心的导航容器控件为PhoneApplicationFrame,他可以容纳一个PhoneApplicationPage。我们可以创建多个页面,通过Frame来进行导航。
对于Windows Phone来说,只允许有一个Frame,一个Frame有一下特性:
- 操作寄宿的Page页面的属性,比如orientation
- 指定页面呈现的客户端区域
- 为状态栏和程序栏保留空间
- 监听obscured和unobscured事件
而对于Page来说,他会填充满Frame所在的空间。除此之外,程序中还有Status bar和Application Bar,他们都能设置visible 属性;Window Phone也支持屏幕旋转,但是只有在转动设备时才能使之旋转,而不能通过编程的方式实现,因为orientation是只读属性;我们只能通过设置SupportedOrientations来完成;机器的后退按钮可以完成导航中的后退功能,也能关闭键盘,菜单,弹出窗体,同时还能关闭程序。
关于使用这两个空间导航,参见前两篇文章的例子。定义一个Frame,设置到VisualRoot。在配置文件中设置开始导航的页面。然后通过Frame或者NavigationService来进行导航。参考 :Frame and Page Navigation for Windows Phone
二 导航框架分析
我们还是接着上一篇文章程序启动来展开,看看Frame是如何去导航的。
1 导航框架的初始化
程序启动的的第一步是在App类中,我们创建了一个PhoneApplicationFrame实例。因为PhoneApplicationFrame是继承于Frame的,所以我们先看看Frame构造函数做了什么。
internal Frame()
{
base.DefaultStyleKey = typeof(Frame);
base.Loaded += new RoutedEventHandler(this.Frame_Loaded);
this._hostInfo = new HostInfo();
}
构造函数中绑定一个Loaded事件和设置Host信息(如果看过SilverLight的源码,会发现和这的构造函数是有区别的)。接着就看看PhoneApplicationFrame的构造函数
//有删减
public PhoneApplicationFrame()
{
Action a = null;
ShellFrame.Initialize();
base.DefaultStyleKey = typeof(PhoneApplicationFrame);
this.Orientation = PageOrientation.PortraitUp;
this._visibleRegion = rect;
base._navigationService = new NavigationService(this);
if (!Frame.IsInDesignMode() && !base._hostInfo.Rehydrated)
{
if (a == null)
{
a = delegate {
base.Load();
};
}
Deployment.Current.Dispatcher.BeginInvoke(a);
}
if (Current == null)
{
Current = this;
}
}
这里面前面一部分是设置界面方向相关的一些内容,然后设置了可见区域,这里被省略了,这些都是上面说的Frame有的特性;接下来的工作很重要定义了一个NavigationService对象,保存在Frame的字段中。我们接着看看这个NavigationService对象构造函数
internal NavigationService(PhoneApplicationFrame nav)
{
this._navigationPendingLock = new object();
this._cacheRequiredPages = new Dictionary<string, PhoneApplicationPage>();
PerfUtil.BeginLogMarker(MarkerEvents.TH_INIT_NAVIGATIONSERVICE, "NavigationService started");
Guard.ArgumentNotNull(nav, "nav");
this._host = nav;
HostFrame = nav;
HostInfo info = new HostInfo();
this._shellPageManagerCallback = new ShellPageManagerCallback();
this._shellPageManagerCallback.OnCancelRequestEventHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.OnCancelRequestEventHandler, new EventHandler(this.ShellPageManager_OnCancelRequest));
this._shellPageManagerCallback.OnPageStackReactivatedEventHandler = (EventHandler<PageStackReactivatedEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnPageStackReactivatedEventHandler, new EventHandler<PageStackReactivatedEventArgs>(this.ShellPageManager_OnPageStackReactivated));
this._shellPageManagerCallback.OnResumePageRequestEventHandler = (EventHandler<ResumePageRequestEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnResumePageRequestEventHandler, new EventHandler<ResumePageRequestEventArgs>(this.ShellPageManager_OnResumePageRequest));
this._shellPageManagerCallback.OnInvokeReturningEventHandler = (EventHandler<InvokeReturningEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnInvokeReturningEventHandler, new EventHandler<InvokeReturningEventArgs>(this.OnInvokeReturning));
this._shellPageManager = new ShellPageManager(info.LastInstanceId, info.HostWnd, this._shellPageManagerCallback);
this._shellPageManager.OnObscurityChangeEventHandler += new EventHandler<ObscurityChangeEventArgs>(this._host.ShellPageManager_OnObscurityChange);
this._shellPageManager.OnLockStateChangeEventHandler += new EventHandler<LockStateChangeEventArgs>(this._host.ShellPageManager_OnLockStateChange);
this._shellPageManager.PauseSupported = true;
this._quirkShouldNotAllowBackgroundNavigation = QuirksMode.ShouldNotAllowBackgroundNavigation();
this._quirkShouldCallOnNavigatingFromPageForExternalNav = QuirksMode.ShouldCallOnNavigatingFromPageForExternalNavigations();
this._quirkShouldForceTextBindings = QuirksMode.ShouldForceTextBindings();
ChooserListener.Initialize();
}
这里主要是定义了一个_cacheRequiredPages和设置了host和Frame的值为传入的PhoneApplicationFrame对象。这时,PhoneApplicationFrame和NavigationService相互引用对方,和面绑定了一些事件。而PhoneApplicationFrame构造函数继续执行,Dispatcher.BeginInvoke(a)来执行Frame.Load方法。
internal void Load()
{
if (!this._loaded)
{
this._navigationService.InitializeJournal();
this._navigationService.InitializeContentLoader();
this._navigationService.InitializeNavigationCache();
this._loaded = true;
if (!IsInDesignMode())
{
UriMapperBase uriMapper = this.UriMapper;
if (!this._navigationService.Resume())
{
string uriString = this.ApplyDeepLinks();
if (uriString != null)
{
this.Navigate(new Uri(uriString, UriKind.Relative));
}
else if (this._deferredNavigation != null)
{
this.Navigate(this._deferredNavigation);
this._deferredNavigation = null;
}
else if (this.Source != null)
{
this.Navigate(this.Source);
}
else if (uriMapper != null)
{
Uri uri = new Uri(string.Empty, UriKind.Relative);
Uri uri2 = uriMapper.MapUri(uri);
if ((uri2 != null) && !string.IsNullOrEmpty(uri2.OriginalString))
{
this.Navigate(uri);
}
}
}
}
else if (this.Source != null)
{
base.Content = string.Format(CultureInfo.InvariantCulture, Resource.Frame_DefaultContent, new object[] { this.Source.ToString() });
}
else
{
base.Content = typeof(Frame).Name;
}
}
}
我们仔细看看这个方法。
- 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;InitializeContentLoader初始化内容加载相关的对
- InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。
- 然后我们看到一些列的Navigate方法。记得上一篇中我们谈到过为什么没有调用Navigate或者设置Source属性,还能显示MainPage.xaml的问题。是的,就在这里,在这里调用了Navigate方法。到底是那一个方法呢?我们在这里做个小实验。
//NavigationPage="MainPage.xaml" 删除这个属性
<Tasks>
<DefaultTask Name ="_default">
</Tasks>
修改WMAppMainfest.xml文件。这样就没有默认的页面了,我们在App中加入对Source的设置和Navigate调用。
RootFrame = new PhoneApplicationFrame();
RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative);
RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
RootFrame.Navigated += CompleteInitializePhoneApplication;
运行程序发现程序正常显示了Page2.xaml页面,那么说明第一个条件判断的ApplyDeepLinks方法是从配置文件中获取NavigationPage的方法。具体如何获得的这里就不深究了,有兴趣自己看源码吧,通过调试发现是在Host对象的TaskPage属性中。这时Frame就导航到了指定的页面。此时也完成了导航框架的初始化。那么系统如何获得Page2的Uri的呢?这个后面解释。
private void InitializePhoneApplication()
{
if (phoneApplicationInitialized)
return;
RootFrame = new PhoneApplicationFrame();
RootFrame.Navigated += CompleteInitializePhoneApplication;
RootFrame.NavigationFailed += RootFrame_NavigationFailed;
phoneApplicationInitialized = true;
}
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
RootVisual = RootFrame;
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
}
我们在回头看看上一篇留下的疑问,为什么这里要绑定Navigated事件。现在应该明白了,在PhoneApplicationPhone的构造函数中,调用了Navigate方法导航到配置文件中的MainPage.xaml,当导航完成时,就吧Frame设置到VisualRoot,这时就能把Frame中的Page显示出来了。这里可能会有个疑问,在构造函数中调用Navigation,但在对象构造完才绑定Navigated事件,这能行吗? 在构造函数中调用的是下面的方法:
Deployment.Current.Dispatcher.BeginInvoke(a);
这方法实际是把操作发送UI线程执行,但是目前我们还在执行App的构造函数,所有只有构造好App对象UI才能处理这个Load操作,这是我个人看法不知道对不对,有待在研究。
2 Frame加载XAML文件
框架初始化完成后,执行Load方法中的Navigate,让Frame导航到指定页面,那么导航到这个页面是怎么加载这个页面的呢? 我们先看看Frame的Navigate方法到底是怎么执行的。
public bool Navigate(Uri source)
{
if (this._loaded)
{
return this._navigationService.Navigate(source);
}
this._deferredNavigation = source;
return true;
}
我们发现塔实际是调用NavigationService的方法,实际上Frame和NavigationService中有很多同名的方法,实际都是在NavigationService类中实现的,他才是实际负责导航和加载页面的。这也和我们开始标注的Frame的4点特性吻合。
这里还有个_deferredNavigation字段,表示延迟导航。实际这个我们在上面的例子中留下的疑问。上面的例子我们删除掉配置文件中的MainPage.xaml,而增加了调用RootFrame.Navigate方法显示了Page2,这时并没有真的导航,应为Frame还没有初始化好,只是包Uri保存到了这个字段中。所以返回True不代表完成导航。等App构造完成,执行Load方法时没有获取配置文件,而_deferredNavigation字段又有值,就导航到了Page2。
public bool Navigate(Uri source)
{
Action a = null;
Uri uri = source;
NavigationMode mode = NavigationMode.New;
PerfUtil.BeginLogMarker(MarkerEvents.TH_PAGE_NAVIGATION, "Page navigation: " + ((uri == null) ? "" : uri.ToString()));
try
{
JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri);
this.Journal.AddHistoryPoint(journalEntry);
return true;
}
catch (Exception exception)
{
if (this.RaiseNavigationFailed(uri, exception))
{
throw;
}
return true;
}
}
以上代码是NavigationService.Navigate方法的实现,只保留了关键代码。其中将传入的Uri保存到了一个JournalEntry的实体类中,此类是表示后退或前进导航历史记录中的一个条目。我们把加载的Page都用AddHistoryPoint保存起来。
internal void AddHistoryPoint(JournalEntry journalEntry)
{
Guard.ArgumentNotNull(journalEntry, "journalEntry");
this._shellPagePending = this._shellPageManager.CreatePage(journalEntry.Source.ToString());
if (this._shellPagePending == null)
{
throw new InvalidOperationException("Unable to create ShellPage");
}
this._shellPagePending.ShellPageCallback.OnNavigateAwayEventHandler += new EventHandler<NavigateAwayEventArgs>(this.ShellPage_NavigatedAway);
this._shellPagePending.ShellPageCallback.OnNavigateToEventHandler += new EventHandler<NavigateToEventArgs>(this.ShellPage_NavigatedTo);
this._shellPagePending.ShellPageCallback.OnRemoveEventHandler += new EventHandler<RemoveEventArgs>(this.ShellPage_RemovedPage);
this._shellPagePending.ShellPageCallback.OnBackKeyPressInternalEventHandler += new EventHandler<BackKeyPressEventArgs>(this.ShellPage_BackKeyPressed);
this._shellPageManager.NavigateTo(this._shellPagePending);
this.IsBusy = true;
}
通过CreatePage创建了一个ShellPage对象,这是一个未公开的类型,所以没有资料,但是应该是和界面有关,这里不深入。然后注册了相关的事件,最后调用了NavigateTo方法。但是此方法看不到具体实现。根据这里情况推测应该是跳转到指定的ShellPage页面。这个时候会触发Journal对象的OnNavigateAwayEvent事件,执行绑定的方法:
private void ShellPage_NavigatedAway(object sender, NavigateAwayEventArgs args)
{
if (!args.IsExternal)
{
if ((args.Direction == Direction.Back) && (this._backStack.Count > 0))
{
this.IsBusy = false;
this._lastRemovedEntry = this._currentEntry;
this._currentEntry = this._backStack.Pop();
NavigationMode back = NavigationMode.Back;
this.UpdateObservables(this._currentEntry, back);
}
}
else
{
this.OnNavigatedExternally("", new Uri("app://external/", UriKind.Absolute), (args.Direction == Direction.Back) ? NavigationMode.Back : NavigationMode.New);
}
}
此方法中包含一个名为UpdateObservables方法,此方法是
private void UpdateObservables(JournalEntry currentEntry, NavigationMode mode)
{
bool isPaused = null == currentEntry.PageInstance;
this.OnNavigated(currentEntry.Name, currentEntry.Source, mode, isPaused);
}
OnNavigate触发了在NavigateService中InitializeJournal()注册的Journal的Navigated事件
this._journal.Navigated += new EventHandler<JournalEventArgs>(this.Journal_Navigated)
最后执行绑定的NativagetService中的Journal_Navigated方法:
private void Journal_Navigated(object sender, JournalEventArgs args)
{
this.NavigateCore_ContinueNavigation(args.Uri, args.NavigationMode, args.IsPaused);
}
private bool NavigateCore_ContinueNavigation(Uri uri, NavigationMode mode, bool isPagePaused)
{
try
{
Uri mappedUri = this.GetMappedUri(uri);
Uri mergedUriAfterMapping = this.GetMergedUriAfterMapping(mappedUri);
Uri mergedUri = this.GetMergedUri(uri);
this._currentNavigation = new NavigationOperation(mergedUriAfterMapping, mergedUri, uri, mode);
this.IsNavigationInProgress = true;
if (!isPagePaused && (mode == NavigationMode.Back))
{
PhoneApplicationPage reusedPage = this._journal.CurrentEntry.PageInstance;
this.Host.Dispatcher.BeginInvoke(delegate {
this.CompleteNavigation(reusedPage, mode);
});
}
else
{
if (!isPagePaused && (mode != NavigationMode.New))
{
throw new InvalidOperationException("Invalid NavigationMode, only New and Back are supported");
}
this._contentLoader.BeginLoad(this._currentNavigation.Uri, new AsyncCallback(this.ContentLoader_BeginLoad_Callback), this._currentNavigation);
}
}
catch (Exception exception)
{
if (this.RaiseNavigationFailed(uri, exception))
{
throw;
}
}
return true;
}
实际上执行的就是NavigateCore_ContinueNavigation 方法,我们注意到 ._contentLoader.BeginLoad()方法,这里用到了我们前面Frame初始化说到的PageResourceContentLoader对象来加载指定XAML文件。
以上就是Frame加载XAML文件的全部过程。从Navigate到实际的NavigateCore_ContinueNavigation之间很负责,涉及了一些没有公开的类型。但实际意图很简单,就是对要导航的页面进行历史记录操作。
三 页面的显示
上面介绍了XAML被PageResourceContentLoader对象加载到Frame。那么XAML是如何显示到界面上的呢?
Silverlight 导航系统使用此类作为其默认的内容加载程序。此类是 INavigationContentLoader 的默认实现,且此类的实例是 Frame.ContentLoader 属性的默认值。虽然您通常会加载 Page 实例,Silverlight 导航系统要求导航目标应为 UserControl 实例。Page 类派生自 UserControl 类,并提供附加导航支持
以上是MSDN对PageResourceContentLoader的解释,实际总用就是加载应用程序包(.xap 文件)中对应于给定 URI 的页。我们根据源码也看到Navigate方法最终是通过此对象来加载URI页面。
this._contentLoader.BeginLoad(this._currentNavigation.Uri, new AsyncCallback(this.ContentLoader_BeginLoad_Callback), this._currentNavigation);
我们看看BeginLoad方法的实现
public override IAsyncResult BeginLoad(Uri uri, AsyncCallback userCallback, object asyncState)
{
SendOrPostCallback d = null;
Action a = null;
PageResourceContentLoaderAsyncResult result = new PageResourceContentLoaderAsyncResult(uri, asyncState);
if (uri == null)
{
result.Exception = new ArgumentNullException("uri");
}
if (SynchronizationContext.Current != null)
{
if (d == null)
{
d = delegate (object args) {
BeginLoad_OnUIThread(userCallback, result);
};
}
SynchronizationContext.Current.Post(d, null);
}
else
{
if (a == null)
{
a = delegate {
BeginLoad_OnUIThread(userCallback, result);
};
}
Deployment.Current.Dispatcher.BeginInvoke(a);
}
return result;
}
实际是调用了BeginLoad_OnUIThread方法,而里面又调用了GetLocalXaml方法来加载本地的XAML文件,而且获得了xclass属性指定的类型。最后使用Activator.CreateInstance创建了此XAML文件类对象的实例,保存到result中。
private static void BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result)
{
if (result.Exception != null)
{
result.IsCompleted = true;
userCallback(result);
}
else
{
try
{
string pagePathAndName = UriParsingHelper.InternalUriGetBaseValue(result.Uri);
string localXaml = GetLocalXaml(pagePathAndName);
if (string.IsNullOrEmpty(localXaml))
{
result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_NoXAMLWasFound, new object[] { pagePathAndName }));
}
else
{
string xClass = GetXClass(localXaml);
if (string.IsNullOrEmpty(xClass))
{
try
{
result.Content = XamlReader.Load(localXaml);
}
catch (Exception exception)
{
result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_XAMLWasUnloadable, new object[] { pagePathAndName }), exception);
}
}
else
{
Type typeFromAnyLoadedAssembly = GetTypeFromAnyLoadedAssembly(xClass);
if (typeFromAnyLoadedAssembly == null)
{
result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_TheTypeSpecifiedInTheXClassCouldNotBeFound, new object[] { xClass, pagePathAndName }));
}
else
{
result.Content = Activator.CreateInstance(typeFromAnyLoadedAssembly);
}
}
}
}
catch (Exception exception2)
{
result.Exception = exception2;
}
finally
{
result.IsCompleted = true;
if (userCallback != null)
{
userCallback(result);
}
}
}
}
这里BeginLoad是用异步的方式来加载,避免UI线程等待。而加载完成后执行回调方法 ContentLoader_BeginLoad_Callback
private void ContentLoader_BeginLoad_Callback(IAsyncResult result)
{
DependencyObject obj2 = null;
Uri uriBeforeMapping = null;
try
{
NavigationOperation asyncState = result.AsyncState as NavigationOperation;
NavigationOperation operation2 = this._currentNavigation;
if ((operation2 != null) && (operation2.Uri == asyncState.Uri))
{
uriBeforeMapping = operation2.UriBeforeMapping;
obj2 = this._contentLoader.EndLoad(result) as DependencyObject;
if (!(obj2 is UserControl))
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.NavigationService_ContentIsNotAUserControl, new object[] { (obj2 == null) ? "null" : obj2.GetType().ToString(), "System.Windows.Controls.UserControl" }));
}
PhoneApplicationPage p = obj2 as PhoneApplicationPage;
if (p != null)
{
this._journal.CompletePendingShellPage(p);
Frame host = this.Host;
p.InternalVisibleRegionChanged += new EventHandler<VisibleRegionChangeEventArgs>(host.OnVisibleRegionChanged);
Frame frame2 = this.Host;
p.BeginOrientationChanged += new EventHandler<OrientationChangedEventArgs>(frame2.OnBeginOrientationChanged);
Frame frame3 = this.Host;
p.BeginLayoutChanged += new EventHandler<OrientationChangedEventArgs>(frame3.OnBeginLayoutChanged);
Frame frame4 = this.Host;
p.InternalBackKeyPress += new EventHandler<CancelEventArgs>(frame4.OnBackKeyPress);
}
JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary(asyncState.Uri, true)));
obj2.SetValue(NavigationServiceProperty, this);
this.CompleteNavigation(obj2, asyncState.Mode);
}
}
catch (Exception exception)
{
if (this.RaiseNavigationFailed(uriBeforeMapping, exception))
{
throw;
}
}
}
加载完成后,从result中获得PhoneApplicationPage对象,也就是之前加载的XAML文件。然后设置了导航的Context内容,这个用来页面间传值的。接下来 obj2.SetValue(NavigationServiceProperty, this)方法设置了NavigationServiceProperty依赖属性。这个属性和导航时的空引用异常有关。而 CompleteNavigation方法用来完成导航。
this.RaiseNavigated(content, uriBeforeMapping, mode, null != existingContentPage, existingContentPage, newContentPage);
其中一段代码RaiseNavigated出发了Frame的Navigated事件,也就是会执行我们在App中的CompleteInitializePhoneApplication方法。此时我们已经获得了要显示页面的Page对象。
我们知道在WPF中有对象树和可视数的概念,而在Silverlight上只有对象树。 RootVisual属性是设置显示的元素,把Frame设置为RootVisual,此时就会把Frame加载到对象树上,而前面提到过,Frame初始化时绑定了Loaded事件,这个事件此时会被执行。
private void Frame_Loaded(object sender, RoutedEventArgs e)
{
this.Load();
}
实际上她执行的也是Load方法,这个方法已经在PhoneApplicationFrame的构造函数中执行过了,有_loaded字段标记,也就不会在执行了。
public UIElement RootVisual
{
get
{
return (XcpImports.Application_GetVisualRoot() as UIElement);
}
[SecuritySafeCritical]
set
{
XcpImports.CheckThread();
if ((value == null) || !XcpImports.DependencyObject_IsPointerValid(value))
{
throw new InvalidOperationException(Resx.GetString("Application_InvalidRootVisual"));
}
XcpImports.Application_SetVisualRoot(value);
this._rootVisual = value;
}
}
RootVisual的实现看不到,这里贴的是Silverlight的实现。这里用到了XcpImports,这个对象上一篇文章介绍过了,最终是使用Core presentation framework的方法设置了RootVisual,通过Frame得到当前的Page,这时Presentation Core会根据当前的对象树生成显卡可以识别的三角形,最终显示到屏幕上。这方面内容可以参考WPF Presentation
四 PhoneApplicationPage和NavigateService
前面介绍了Frame框架的初始化和第一个页面导航加载到显示的过程。我们已经知道,实际负责页面导航的是NavigateService这个类。但只有使用了Frame框架才能完成导航。而在PhoneApplicationPage中有一个NavigateService属性:
internal static NavigationService GetNavigationService(DependencyObject dependencyObject)
{
Guard.ArgumentNotNull(dependencyObject, "dependencyObject");
return (dependencyObject.GetValue(NavigationServiceProperty) as NavigationService);
}
他实际是返回了NavigationServiceProperty依赖属性。而这个属性上面提到过,是在初始化时加,BeginLoad加载了XAML后的回调函数中设置了此属性。所以我们可以在Page类中使用下面代码进行导航,也可以获得App类的RootFrame对象来导航。
this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
(Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
另外我们在Page中还有OnNavigateTo和OnNavigateFrom等方法,来控制导航。这些内容将在下一篇文章中介绍。
五 Silverlight的不同之处
同Windows Phone导航不同,我们默认建立一个Silverlight的普通项目是没有添加Frame框架的。我们可以仿照Windows Phone修改App文件。
private void Application_Startup(object sender, StartupEventArgs e)
{
RootFrame = new Frame();
RootFrame.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
this.RootVisual = RootFrame;
}
在程序启动的时候执行,整个过程和前面基本一样。相比Windows Phone,他们的构造函数简单了很多。
public Frame()
{
base.DefaultStyleKey = typeof(Frame);
base.Loaded += new RoutedEventHandler(this.Frame_Loaded);
this._navigationService = new NavigationService(this);
}
internal NavigationService(Frame nav)
{
this._cacheRequiredPages = new Dictionary<string, Page>();
Guard.ArgumentNotNull(nav, "nav");
this._host = nav;
}
Frame中注册了Loaded事件,和WP不同的是没有在Frame的构造函数中就执行。那么他只能在要显示的时候运行,也就是我们设置了RootVisual属性后才能执行Navigate方法。另外没有配置文件设置导航初始画面,所以我们也必须在这里调用Navigate方法或设置Source属性。
当页面开始呈现,加载了Frame到对象树后触发了Loaded时间, 最终调用了NavigateService.Navigate方法。
public bool Navigate(Uri source)
{
return this.NavigateCore(source, NavigationMode.New, false, false);
}
这里也和Windows Phone不同,NavigateService中直接调用了NavigateCore方法。此方法实现和Windows Phone一样,此方法中有用的是NavigateCore_StartNavigation,最终是通过的this._contentLoader.BeginLoad来完成的。然后显示核心把加载的页面显示到屏幕上。
以上是Silverlight的过程,相比Windows Phone简单一些。因为Loaded事件触发的时间不同,所以WP会自动根据配置文件设置在App对象建立后就导航到指定页面;而SL是在加载对象准备呈现时触发,而且没有配置文件,所以必须手动设置一下。这也就解释了我们前一篇的疑问。也是为什么SL模仿WP来创建框架,却不能在Navigated时间中才设置RootVisual。因为此事件需要设置运行了Loaded方法才能触发。
六 总结
本文主要对导航框架源代码进行了简单的解析,了解了程序启动后导航到第一个界面的过程。首先创建了PhoneApplication对象,在此对象构建完成后,调用了Frame的Loaded方法来导航到配置文件指定的URI;然后加载此XAML文件,然后触发Frame的Navigated方法设置RootVisual,此属性会调用显示框架的Native方法,将对象树呈现到屏幕上。
嗯,支持博主!:)回复:cc_net
回复:jptiancai
WP7 本身的库没有太多东西。 这些也只是个人兴趣,项目不太用的到。用Reflector看的源码。
楼主研究的很深入的啊,佩服! 是一边看着MSDN一边学习的吗?