专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > WinRT Metro

博客园客户端UAP开发漫笔 - 奔跑吧,页面

发布时间:2011-06-23 13:56:29 文章来源:www.iduyao.cn 采编人员:星星草
博客园客户端UAP开发随笔 -- 奔跑吧,页面!

前言

页面导航,是App中的基本功,一般的App,一来一去,只需要简单的Navigate + Back就行了,一个复杂的App可能需要很多导航模式的混合才能实现最佳用户体验。

SplashScreen 启动屏幕

我们先从最开始的SplashScreen说起吧。如果你把启动屏幕做成一个Page,启动时先显示一下,然后假装忙乎两秒,跳到下一个主页面开始进入正题,这个好像看上去也很美好。但是当用户玩命儿按Back键时,哦,露出马脚了,启动页面被唤出了。不过这个bug倒是不妨作为一个新奇的体验。

MSDN里有一节专门讲了如何添加一个SplashScreen,但是说实话,我试了两次,都没成功,人太笨!

算了,自己想办法吧!如果在MainPage里加一个开关会不会简单一些呢?于是这样改装MainPage.xaml:

<Page x:Name="page"
 >
    <Grid>
        <Grid x:Name="grid_Splash">
            <Image Height="100" Source="ms-appx:///Assets/Logo.100.White.png" />
        </Grid>
        <Grid x:Name="grid_Main" Visibility="Collapsed">
            ……content here……
        </Grid>
    </Grid>
</Page>

这里把不重要的代码都删除了,只看干货:<Grid x:Name=”grid_Splash”>,这一项定义了一个Grid, 盖在了主要内容的前面,因为下面的<Grid x:Name=grid_Main Visibility=”Collapsed”>在初始状态被搞成隐藏了,如此一来,grid_Splash中的Image就会在应用启动后,首先映入眼帘。

什么时候把它从用户眼中抠走呢?有两种方法可供选择。

1)在MainPage的构造函数里开始装载你的data,比如是从远程,考虑到网络状况,可能需要几秒钟。那么你就在Splash里放一个ProgressRing,让它转啊转,转啊转……差不多等用户烦了,你的远程数据也拿回来了,然后写一句:

this.grid_Splash.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
this.grid_Main.Visibility = Windows.UI.Xaml.Visibility.Visible;

如此一来Splash被隐藏,你的主要内容在数据来到后也化妆完毕(绑定好了),可以出来见公婆了。

2)如果你不需要从远程调用数据,而是从本地取数据,那么上面的过程就会一闪而过,晃瞎用户的K金G眼,体验很糟糕。这时你可以采用第二种办法:在启动应用时启动一个2秒的计时器:

ThreadPoolTimer.CreateTimer(SplashTimeOut, new TimeSpan(0, 0, 2));

其中定义了回调函数(应该叫做delegate),当两秒时间到时,在函数SplashTimeOut里面:

void SplashTimeOut(......)
{
    this.grid_Splash.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
    this.grid_Main.Visibility = Windows.UI.Xaml.Visibility.Visible;
}

这样会和第一种方法的体验一样,只不过是假装忙活了一下,干等2秒而已,目的是让用户有一种有人替他干活的虚荣感,而且让你漂亮的启动页面给用户流下深刻印象。

Basic页面

除了MainPage以外,如果你还有其它二级页面需要添加的话,请在VS中选择这里:

image

你如果偏爱Blank Page我也不拦着你,但是Basic Page这个选择能帮你省老鼻子事儿了,它在你的项目中自动添加了这些帮助文件:

image

其中,NavigationHelper.cs里面实现了在页面上处理Back按键的事件,即,当用户在底层页面按Back键时,会回到上级页面。但是在主页面中不建议处理Back键,而是让系统自动处理,把App放到后台。

基本页面导航

基本页面导航MSDN里有,和Silverlight有很大不同。记得SL理面有个什么NavigationService,听上去蛮不错的,到了WinRT里,一律用这个:

this.Frame.Navigate(typeof(NewsReadingPage), obj);

如果在Control里面做页面导航,比如按了个自定义Control,而上层代码又不太容易能得到具体是按了哪个Control,就只好在Control里触发导航动作了,尽管我们不建议这样做:

Frame frame = Window.Current.Content as Frame;
frame.Navigate(typeof(....),....);

这里的Frame很模糊,看上去像个全局的,但是在前面那个例子里,又是用this.Frame,就是说在Page对象里还有个Frame。有个文档专门说这事儿,但是绕来绕去的我没看懂,人太笨!如果有搞明白了这事儿的园友们可以给大家一个说明,谢谢先!

参数传递

基本页面导航中的obj,就可以当作参数传递给下级页面。但是有时候一些下级页面需要返回一些信息回来给调用者,怎么做呢?因为在页面的GoBack()方法中没有参数。有三种方法可以解决这个问题:

1)利用好那个obj,把它即作为[in],又作为[out],下级页面给obj里面的字段赋值,上级页面通过解析obj里的约定字段来得到参数。

2)在下级页面中用static字段,返回之前给它赋值,然后上级页面可以使用。

3)弄个外部类,比如一个静态类,或者一个单例的类,弄个变量在里面当参数。注意用完之后带上手套把指纹擦掉,把该变量“归零”就可以了,隐藏你的作案痕迹,避免下次使用时搞不清当前状态。

前跳式页面导航

z博客园UAP里没有这个例子,用我们做的另一个App--豆瓣一刻来举例说明吧(顺便做一广告,豆瓣一刻 for WP已经上线了,名字叫做“一刻”,link is here:一刻)。

咱们看图说话(注意,以下逻辑很绕,没有耐心的可能看不懂):

image

按正常逻辑,阅读文章时,可以查看评论;如果想发表评论,点击下方按钮,进入“写评论”页面。但是此时用户可能还没有登录,不能匿名发表评论,所以需要自动跳到登录页面,登录成功后,自动进入写评论页面。

每当PM说起“自动”这个词时,我就头大!什么所谓自动,都是我们程序员手动搞出来的!有时候自动能实现,有时候实现不了,这个要和PM讲清楚。

针对这个具体例子,有几种方式备选,我们先看第一种:

1)看评论页->点击发表评论->发现没登录->进入登录页->登录成功->进入发表评论页

这个流程是最朴素的想法了,但是先别动手,仔细想想:在用户提交评论后,页面该回到哪里?从目前的情况看,是回到登录成功的页面了,会让用户感到困惑。而且,在点击发表评论按钮后,还需要做一个分支判断:如果用户登录了,直接到写评论页;如果用户没登录,要进入登录页面。

看看第二种:

2)看评论页->点击发表评论->进入写评论页->发现没登录->进入登录页->登录成功->“自动”返回写评论

这个看上去好一些,没有第一种方式的两个缺点。用户写完评论后,从stack看,是能直接回到最开始的看评论页的。但是需要解决的问题是如何“自动“返回写评论页?我的刁民小计是:

a) 在CommentWritingPage.Page_Loaded事件中,判断用户是否登录:

private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            if (!Settings.Current.IsLogin)
            {
                if (this.backFromLogonPage)
                {

                }
                else
                {
                    this.Frame.Navigate(typeof(SettingsPage2), new DataModel.SettingNavigationParameter() { targetPivot = TargetPivotItemName.LogonAndBack, targetCss = 0 });
                }
            }
        }

如果登录了,啥也不做,留在当前写评论页面即可;如果没登录,跳到SettingsPage.Logon页面,并且带上一个参数: LogonAndBack。

b) 在SettingsPage.Logon页面,当有登录成功的事件返回后(因为登录是一个异步过程),判断参数是不是LogonAndBack:

void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (Settings.Current.IsLogin && this.logonAndBack)
            {
                Settings.Current.PropertyChanged -= Current_PropertyChanged;
                this.Frame.GoBack();
            }
        }

如果是LogonAndBack,就用this.Frame.GoBack()返回调用者页面,也就是发表评论页面。

如此一来,当用户发表完评论后,自动回阅读评论页面了,行云流水(本来想说飞檐走壁,但是想了想,觉得还没那么离谱)!

需要特别说明的是,在CommentWritingPage中,要在Page_Loaded事件中才能跳转到其它页,如果在OnNavigatedTo()事件中调用this.Frame.Navigate(),nothing happened,没用,什么也不会发生。

后跳式页面导航

让我们来看一个更复杂的例子。下图是我们开发的另一个App,还没有弄完,功能类似个浏览器。

image

第一张图是在一个WebViewPage里,已经加载了一个页面,点击右上角的窗口管理,进入第二张窗口管理页面(比较丑陋,因为designer休假了,还没完工)。可以看到一共6个窗口,只有第一个窗口被使用了。此时我们点击第二个窗口,想启动一个新窗口来浏览其它网站。按理说应用程序应该把第二个窗口激活,但是窗口中啥都没有,大白板一个,用户体验很差,应该自行惭愧地立刻返回到主控页让用户有更多的选择项(就是目前所显示的第三张蓝色页面)。这个如何做?

一个很自然的想法就是,从窗口管理页Navigate到主控页。不行滴!如此一来,当用户在主控页按Back时,会回到窗口管理页,而窗口管理页只是一个辅助页面,类似弹出式菜单,不应该在stack里存留。主控页是程序的根,按Back时必须要退出应用。

解决办法是在窗口管理页里收到点击事件后,返回到WebViewPage(第一张图):

// 窗口管理页的点击事件
private void lv_ItemClick(object sender, ItemClickEventArgs e)
        {
            WebViewHelper wvh = e.ClickedItem as WebViewHelper;
            this.pool.SetActiveWindow(wvh);
            if (this.Frame.CanGoBack)
            {
                this.pool.BackFromStatusPage = true;
                this.Frame.GoBack();
            }
        }

在此页面中,判断当前活动窗口是否为空,注意,也是要在Page_Loaded事件中处理:

private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;

            if (this.pool.BackFromStatusPage)
            {
                this.pool.BackFromStatusPage = false;
                if (this.pool.GetActiveWindow().IsEmptyView)
                {
                    // back to main page to wait for input
                    if (this.Frame.CanGoBack)
                    {
                        this.Frame.GoBack();
                    }
                }
                else
                {
                    // stay at webview page to show current web content
                }
            }
            else
            {
                this.ctrl_Input.Url = this.wvActive.Url;
            }
        }

如果IsEmptyView == true,再调用GoBack返回到主控页面(第三张图),而不是跳转(前进)到主控页面。

这个solution的基本思路,或者说是设计理念,就是窗口控制页面(第二张图)一定是个叶子节点,不能让它作为中间导航节点。

小结

我和一些桥牌的初学者讲过很多次:打每一张牌都要有你的思路,不能说”红桃花色没打过,我试试看“,而是说”从叫牌过程分析,我的同伴有红桃大牌,我要帮助他一下,穿过明手的红桃Q”。写程序做设计也一样,当有多种选择时,一定要首先确定一个设计理念,然后再确定解决方法。

 

分享代码,改变世界!

Windows Phone Store App link:

http://www.windowsphone.com/zh-cn/store/app/博客园-uap/500f08f0-5be8-4723-aff9-a397beee52fc

Windows Store App link:

http://apps.microsoft.com/windows/zh-cn/app/c76b99a0-9abd-4a4e-86f0-b29bfcc51059

GitHub open source link:

https://github.com/MS-UAP/cnblogs-UAP

MSDN Sample Code:

https://code.msdn.microsoft.com/CNBlogs-Client-Universal-477943ab

 

MS-UAP

2015/2

4楼Xucheee
学习了。
3楼SoftwareTeacher
一刻 的链接在IE 中显示到不了目标网页
Re: MS-UAP
@SoftwareTeacher,已修复。Link没变,只是在手机上解析URL时被中文quot;一刻quot;两字截断了,是一个浏览器的bug。
2楼SoftwareTeacher
》名手,明手
Re: MS-UAP
@SoftwareTeacher,谢谢,已修改。
1楼h82258652
这里的Frame很模糊,看上去像个全局的,但是在前面那个例子里,又是用this.Frame,就是说在Page对象里还有个Frame。有个文档专门说这事儿,但是绕来绕去的我没看懂,人太笨!如果有搞明白了这事儿的园友们可以给大家一个说明,谢谢先!,,因为Frame不仅仅是可以作为Window的子对象,也能挂接在VisualTree上。,例如:,lt;!-- 回复竟然没xaml,只好用html了 --gt;lt;Pagegt; lt;Gridgt; lt;Framegt; lt;!-- 另一个Page --gt; lt;/Framegt; lt;/Gridgt;lt;/Pagegt;,这样也是可以的,相当于html里的iframe。,Window.Current.Content as Frame就相当于获取最顶层的Frame,因为Window是最顶级的对象,Content一般为Frame(放其他应该也可以,没试过)。,而Page类实例的话,this.Frame则指代包含这个Page的Frame。因为Page必然得在Frame里。,像我上面给出的那个xaml里,这两个Frame的含义就是不一样的了。,Window,→Frame,→→Page,→→→Grid,→→→→Frame,上面xaml的结构就是这样。
友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: