Windows Phone开发(三)– 导航原理分析

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

我们仔细看看这个方法。

  1. 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;InitializeContentLoader初始化内容加载相关的对
  2. InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。
  3. 然后我们看到一些列的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方法,将对象树呈现到屏幕上。

 



如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!
微信打赏 支付宝打赏

3 评论

  1. 回复:jptiancai
    WP7 本身的库没有太多东西。 这些也只是个人兴趣,项目不太用的到。用Reflector看的源码。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注