Windows Phone开发(四)– 导航事件和传值

从第一篇开始我们就看到了页面的导航切换,上一篇文章则介绍了框架实现导航的原理和过程。真正的导航功能是NavigationService类来实现的。而Frame是Page的载体,是负责导航,历史记录等功能的,相当于一个指挥官。这一篇就主要介绍一下导航的操作和相关的一些方法。

 

一 导航时发生错误

 

 

默认的我们建立一个Windows Phone程序,使用导航功能是不会出现这个问题的。我们先看一个列子:

private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
   // Set the root visual to allow the application to render
   if (RootVisual != RootFrame)
       //RootVisual = RootFrame;
       RootVisual = new MainPage();
    // Remove this handler since it is no longer needed
    RootFrame.Navigated -= CompleteInitializePhoneApplication;
}

我们新建一个项目后,不把Frame设置到RootVsual,而是用MainPage。我们从Mainpage导航到Page1. 使用两种方法:

private void Button_Click(object sender, RoutedEventArgs e)
{
     方法1:this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
     方法2:(Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
}

第一种方法是使用Page页面的NavigationSevice属性来导航,第二种方法是使用Frame对象来导航。从上一篇我们知道,两种方法是相同的。结果是使用第一种方法发生了NullReferenceException错误,为什么NavigationService对象是空?我们不是在App构造函数中就创建过NavigationServie对象的实例吗,并且在加载了XAML文件后设置到了依赖属性中。

obj2.SetValue(NavigationServiceProperty, this);  

上一篇文章介绍过,Page的NavigationService属性是通过依赖属性获得的。其实这里我要了解依赖属性的特点,虽然是一个static字段来维护,但是内部是有Hash表来存放不同对象的属性值。我们在构造函数的Load方法中,加载了配置文件中指定的MainPage,并生成了他的实例,然后设置了NavigationServiceProperty。而这里我们使用,MainPage的是一个新的实例,他的NavigationServiceProperty为Null。所以这里当然会报错了。 而且NavigationServiceProperty是internal属性,说以我们不能手动设置了。

从这个异常我们能发现,每次导航的新的页面,在加载完成Page之后,都会设置Page的NavigationServiceProperty的属性。所以并不是创建了Frame,我们就能使用NavigationService来导航。而是需要Frame来吧Page和NavigationService关联起来。而这里,我们new的Mainpage没有通过Frame加载显示的,所以无法导航。

接下来看看第二种方法。这里使用的是我们创建的RootFrame对象,因为此时Frame已经完全构造好了,NavigationSevice也创建好了。所以此时调用Navigate方法是不会报错的,而且返回的是true。因为这里不存在延迟导航,所以说明是成果了,但是为什么没显示呢?我们点击【<-】退出程序,但是点了2下才退出,这说明导航了吗?我们在做个试验,我们在Page1中加入以下的代码

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
    {
       bool ret = (Application.Current as App).RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
    }
}

我们在从MainPage用方法2导航到Page1后,在点【<-】退出程序,现在要点3下了。我们也可以设置断点来查看,实际完成了MainPage->Page1->Page2的导航。当时为什么没有显示呢?很简单了,因为Page要通过RootVisual来显示,导航的话Page需要Frame来加载的,当我们把Frame设置到RootVisual时,就能显示,而这里我们设置的MainPage,虽然Frame导航了,但没有被显示出来。但是RootVisual只能设置一次,所以这里没有办法让他显示出来了。

好了,到这里我们导航的原理就研究到这里,通过2个错误我们进步不理解了Frame和Page的关系。也明确了Frame导航和加载Page的这一功能。这也说明为什么必须有Frame才能导航。

 

二 导航的过程

 

 

在上面的列子中,我们看到了OnNavigatedTo方法,当导航到Page1时自动导航到了Page2。我们知道一个Page导航到另一个Page实际是一个卸载和装载的过程。在这个过程中会触发一系列的事件和方法,下面我们就来看下这些方法。

public class Page : UserControl
{
    // Methods
    internal Page();
    internal void InternalOnFragmentNavigation(FragmentNavigationEventArgs e);
    internal virtual void InternalOnNavigatedFrom(NavigationEventArgs e);
    internal virtual void InternalOnNavigatedTo(NavigationEventArgs e);
    internal void InternalOnNavigatingFrom(NavigatingCancelEventArgs e);
    protected virtual void OnFragmentNavigation(FragmentNavigationEventArgs e);
    protected virtual void OnNavigatedFrom(NavigationEventArgs e);
    protected virtual void OnNavigatedTo(NavigationEventArgs e);
    protected virtual void OnNavigatingFrom(NavigatingCancelEventArgs e);
}

在Page类中,我们看到了和导航相关的4个protected虚方法。对应有4个internal的方法,他们是在事件发生的时候触发的(系统已经给我们绑定了),他们实现很简单,就是调用这里的虚方法。我们可以通过重写这些虚方法来控制。而在PhoneApplicationPage类中没有重写这些方法,也没有加入其它相关的方法。

在我们Demo3的PhoneApp3中我们在MainPage和Page1重写这几个方法:

protected override void OnFragmentNavigation(System.Windows.Navigation.FragmentNavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnFragmentNavigation", this.ToString());
    base.OnFragmentNavigation(e);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
}

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatingFrom", this.ToString());
     base.OnNavigatingFrom(e);
}

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
     base.OnNavigatedFrom(e);
}

启动程序,从MainPage导航到Page1: 输出如下:

PhoneApp3.MainPage:OnNavigatedTo
PhoneApp3.MainPage:OnNavigatingFrom
PhoneApp3.MainPage:OnNavigatedFrom
PhoneApp3.Page1:OnNavigatedTo

点击【<-】按钮,输出如下:

PhoneApp3.Page1:OnNavigatingFrom
PhoneApp3.Page1:OnNavigatedFrom
PhoneApp3.MainPage:OnNavigatedTo

在点击【<-】按钮退出程序,输出如下:

PhoneApp3.MainPage:OnNavigatingFrom
PhoneApp3.MainPage:OnNavigatedFrom

好吧,这些方法的执行顺序很清楚了,我就不用什么介绍了。不清楚可以看MSDN。不过这里命名很容易让人产生误解。我开始就弄反了。这里To和From相对的对象都是当前的页面。OnFragmentNavigation 方法,在导航到包括片断的统一资源标识符 (URI) 时会发生。一个片断是片断分隔符 (#) 后的值。这个后面在介绍。

 

 

三 页面传值

 

 

和Web一样,我们在导航过程中可能需要传递值,对于WinForm程序来说,我们可以通过Form的构造函数传值,可以定义定义全局变量等多种方法,对于Web,我们可以有QueryString,Cookies,Session等方法,在这里我们可以用类似的方法传递,这里我们结合导航到过程来进行值的传递。这里我们导航时不能通过构造函数传递,也无法使用Cookies和Session。

我在Demo3的PhoneApp4中,MainPage和Page1放置了5个TextBox,每个对应一种传递方式:

 

1 全局变量传递

 

这个方法在WinForm很常见,我们在App类中定义一个变量,App是一个全局对象,所以可以定义在这里。

    public partial class App : Application
    {
        public string StaticVar { get; set; } //全局变量

        public PhoneApplicationFrame RootFrame { get; private set; }

        public App()
        {
        }
}

在Mainpage的OnNavigtedFrom方法中,把s1的值传递给全局变量,s1的值来源于TextBox1。当然你可以在导航前任意时刻获取TextBox的值,这里只是演示导航方法。

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
   Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
   base.OnNavigatedFrom(e);
   s1 = textBox1.Text;
   //全局变量传递
   (Application.Current as App).StaticVar = s1;
}

在Page1的OnNavigatedTo方法中接受并显示。同样,你可以在导航到Page1后的任意时刻来获取并显示,这里也是为了掩饰导航方法。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
    //从全局变量接受
    s1 = (Application.Current as App).StaticVar;
    textBox1.Text = s1;
}

使用全局变量很方便,但是占用内存空间,并且存在丢失的可能。这个以后文件会介绍。

 

2 页面QueryString传值

 

因为这里导航和Web相似,所以我们也可以用Uri后加上QueryString的传值方式。我们这里使用TextBox2.

首先修改导航按钮代码,我们必须在导航前,构造好导航的Uri,并且要获取TextBox2的值。QueryString格式为 Name1=Value1&Name2=Value2,多个参数用&间隔开。

private void button1_Click(object sender, RoutedEventArgs e)
{
     //构造QueryString
     s2 = textBox2.Text;
     string uri = "/Page1.xaml?s2=" + s2;

      //导航
       this.NavigationService.Navigate(new Uri(uri, UriKind.Relative));
}

而在Page1页面,我们通过NavigationContext属性的QueryString获得传递的值。这里是返回的是IDictionary类型,在使用前,必须检查Name对应的变量是否存在。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
    //从Uri接受
    IDictionary<string, string> queryString = this.NavigationContext.QueryString;
    if (queryString.ContainsKey("s2"))
    {
       s2 = queryString["s2"];
    }

    //显示到UI
    textBox2.Text = s2;
}

使用QueryString可以传递少量数据,因为Uri长度是有限制的,并且只能传递简单类型,如果要传递自定义类对象,就不适用了。另外传的是string类型,所以可能需要进行类型转化。在我们导航的时候,会建立一个JournalEntry对象,当新的xaml页面加载完后,会去解析URI中的QueryString数据存入到其中。

JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary

3 PhoneApplicationService类

 

如果我们需要传递自定义类型数据时,就需要用到这个对象。其实这个对象我们并不是很陌生,在前面我们就介绍过他实现了IApplicationService就接口,为Silverlight程序供了扩展功能,比如前面App中启动时Application_Launching,Application_Activated等四个方法。在这里他在内部提供了一个字典来维护一些全局的数据:

public IDictionary<string, object> State { get; }

所以我们不需要自己去定义全局变量,而可以直接使用他,通过PhoneApplicationService.Current.State就能访问到这个字典。我们这里使用TextBox3来演示。在MainPage中把TextBox3获得的值,设置到字典中,注意,这里的Key需要是唯一的。另外我们使用时要引入以下命名空间。

using Microsoft.Phone.Shell;
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
     base.OnNavigatedFrom(e);
     s3 = textBox3.Text;
     //使用PhoneApplicationService传递
      PhoneApplicationService.Current.State["s3"] = s3;
}

我们在Page1中,我们取得s3的值2,这里我们采用了TryGetValue来获得值,当然也能使用QueryString中的方法。两者都能实现。TryGetValue性能更高。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
     base.OnNavigatedTo(e);
     //从PhoneApplicationService获得数据
      object objS3;
     PhoneApplicationService.Current.State.TryGetValue("s3", out objS3);
     if (objS3 != null)
     {
         s3 = Convert.ToString(objS3);
     }

     //显示到UI
     textBox3.Text = s3;
}

这里我们要注意,这里是object类型,所以在读取数据时,存在类型转换。另外,这里传递的对象,必须是可以序列化的对象。

 

 

4 独立存储传递

 

使用独立存储IsolatedStorageSettings类时,需要引用以下命名空间。我们在MainPage中使用她来保存TextBox4的值。

using System.IO.IsolatedStorage;
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
    base.OnNavigatedFrom(e);
    s4 = textBox4.Text;
    //使用独立存储
     IsolatedStorageSettings.ApplicationSettings["s4"] = s4;
}

在Page1页面,我们获取S4的值,这里我们采用TryGetValue<>,这是一个泛型方法,从安全和性能上都比较好。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
     base.OnNavigatedTo(e);
     //从独立存储获得数据
      IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("s4", out s4);
     textBox4.Text = s4;
}

看起来也没有什么特别。我们在MainPage的OnNavigatedTo也加入上面Page1中的代码,我们从MainPage导航到Page,让后在关闭程序,在重新启动。嘿嘿。竟然在Mainpage中就显示了TextBox4之前的值。这是因为独立存储把值存到了本地,所以即便程序关闭也不会丢失,而前面则存在丢失的问题。对于全局变量和QueryString,即便程序不完全关闭,也是存在丢失的可能。所以一般来说,我们使用IsolatedStorageSettings来存储程序的一些配置信息。当然我们还能选择File和Database等方式来传递数据,当然对于页面传值来说就有点不太适合。


以上是各种方式传值的结果。

 

 

四 总结

 

 

这一篇文章首先继续谈了Frame和Page的关系, 然后介绍了在导航时会触发的事件方法,我们可以通过重写这些方法来控制页面的显示,比如进行值的传递。也详细介绍了值传递的四种方法和使用的环境。在下一篇文章将继续介绍导航的其他常用方法以及回退键,还有特殊页面的回退处理。

本文示例代码下载:Demo3.rar


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

发表评论

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