您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
windows Phone开发(三)-- 导航原理分析
 
作者:cc_net 来源:CSDN 发布于 2015-03-31
  1435  次浏览      15
 

前两篇文章中,我们的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;
}
}
}

我们仔细看看这个方法。

A. 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;
InitializeContentLoader初始化内容加载相关的对象

B. InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。

C. 然后我们看到一些列的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._c

我们看看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方法,将对象树呈现到屏幕上。

   
1435 次浏览       15
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...