wp8手机浏览器项目

项目需求如下:

1.页面布局

最上方为搜索/网址框

中间为网页显示区,默认主页为百度搜索

最下方为功能栏,分别有后退,前进,窗口和更多功能

在更多功能中有

分享给好友

发送网址到桌面

查看历史记录等


2.搜索/网址框

用户在最上方的搜索/网址框中可以进行搜索或者输入网址

如果输入的网址则跳转到该网址

如果是文字内容,则对该文字进行百度搜索

用户点击输入时弹出url软键盘

回车进行搜索或者网址跳转

跳转之后软键盘关闭

在用户浏览网页时,搜索/网址框显示该网页的标题

当用户点击搜索/网址框进行操作时,显示该网页的网址


3.功能栏

点击后退前进分别实现对应的操作

点击窗口可以查看所有的窗口信息,并可以进行新增和删除浏览的窗口

新增窗口时,搜索/网址框显示当前网页的信息

删除窗口时,浏览的窗口自动跳到第一个

当窗口只有一个时无法进行删除窗口操作

窗口信息由网页的标题和网址组成

用户点击对应的窗口之后切换到该窗口显示浏览的网页

点击网页显示区时打开的窗口栏自动关闭

在更多功能总分别提供

分享给好友

发送网址到桌面

查看历史记录

三个功能

其中

用户点击发送给好友时弹出信息窗,并将对应的网页信息填入

点击发送网址到桌面时,在Windows Phone的主窗口中添加对应网址的快捷方式

点击历史记录时可以查看今天,昨天以及更久以前的浏览历史,在成功浏览网页的时候将该网页存入历史记录中


4.错误页导航

当网络连接失败或者网址出错时显示错误页导航

提供刷新和推荐网址




在程序开始编写之前 进行分析

由于浏览器具有多窗口的功能

所以在切换窗口的时候要显示不同的网页和网页的信息

所以将WebBrowser控件和TextBox控件封装成一个用户控件,取名为BrowserCtrl

在主界面通过一个ContentControl来放置给个BrowserCtrl

每次新建一个窗口就new一个BrowserCtrl,并将ContentControl切换为当前的BrowserCtrl

所以需要一个类来记录每一个BrowserCtrl的标题和url等信息

代码如下:

public class BrowserInfo:DependencyObject
    {


        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(BrowserInfo),null);



        public string Url
        {
            get { return (string)GetValue(UrlProperty); }
            set { SetValue(UrlProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Url.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UrlProperty =
            DependencyProperty.Register("Url", typeof(string), typeof(BrowserInfo), null);

        
    }


BrowserCtrl控件布局如下图:

wp8手机浏览器项目_第1张图片



并在后台xaml.cs文件中定义一个属性,提供访问本BrowserCtrl的相关信息

//每一个浏览器窗口的信息属性,包含当前网页的标题和url地址
        public BrowserInfo Info { get; private set; }


xml代码如下:

搜索/网址框


设置其软键盘打开的方式为Url,并为其添加KeyDown,GetFocus,LostFocus事件

 private void txtAddress_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            //如果按下的是回车键
            if (e.Key == Key.Enter)
            {
                //检查输入的数据是否为空
                if (!string.IsNullOrWhiteSpace(txtAddress.Text.Trim()))
                {
                    string address = txtAddress.Text.Trim();
                    //如果输入的数据中包含 . 则认为用户输入的是网址
                    if (address.Contains("."))
                    {
                        //如果该网址没有以http和https开头则补上
                        if (!address.StartsWith("http://") && !address.StartsWith("https://"))
                        {
                            address = "http://" + address;
                        }
                    }
                    //不包含 . 则认为输入的是文字,进行搜索
                    else
                    {
                        //跳转到百度搜索
                        address = "http://3g.baidu.com/ssid=0/from=0/bd_page_type=1/uid=0/baiduid=392259EC9FC46C4A8E303ACE709A203C/pu=sz%40224_220%2Cta%40middle___3_537/baiduid=392259EC9FC46C4A8E303ACE709A203C/s?ref=www_colorful&sa=tb&prest=111041&rn=10&st=111041&tn=middle&uc_param_str=upssntdnvelami&word=" + address + "&su=搜索";
                    }
                    //进行页面跳转
                    myBrowser.Navigate(new Uri(address));
                    //浏览器获得焦点,软键盘关闭
                    myBrowser.Focus();
                }
            }
        }

        //当文本框获得焦点时
        private void txtAddress_GotFocus(object sender, RoutedEventArgs e)
        {
            //显示当前网页的网址
            if (Info != null)
            {
                txtAddress.Text = Info.Url;
            }
        }

        //当文本框失去焦点时
        private void txtAddress_LostFocus(object sender, RoutedEventArgs e)
        {
            //显示当前网页的标题
            if (Info != null)
            {
                txtAddress.Text = Info.Title;
            }
        }

浏览器控件:


默认js功能开启,设置其Navigated(页面跳转成功)和NavigationFailed(跳转失败)事件

由于浏览器控件不提供方法来获得当前浏览的页面的title

但是可以利用浏览器控件的InvokeScript方法来执行页面的js函数execScript来返回当前的document.title

#region 执行页面js代码
        /// 
        /// 根据函数名和参数执行页面中已存在的一个js函数
        /// 
        /// js函数名
        /// 所需的参数
        /// 返回执行的结果
        public string InvokeScript(string funcName, params  string[] parameters)
        {
            return (string)myBrowser.InvokeScript(funcName, parameters);
        }

        /// 
        /// 使用页面的execScript函数执行自定义的一段js代码(页面中可能不存在该js代码),该函数无返回值
        /// 
        /// 要执行的js代码块
        public void ExecuteScript(string js)
        {
            InvokeScript("execScript", js);
        }

        /// 
        /// 执行自定义的一段js代码,带返回值
        /// 
        /// 要执行的js代码块
        /// 返回执行的结果
        public string ExecuteReturnableScript(string js)
        {
            //随机获得一个不重复的方法名
            string funcName = "j" + Guid.NewGuid().ToString("N");
            //window.jdwqdqwvveqfeiw2dqw=function(){return ducoment.title;}类似的js代码   将一个函数加入window对象中
            ExecuteScript("window." + funcName + "=function(){" + js + "}");
            //由于window对象中已经有了上述的函数,所以直接根据该函数名直接调用并获得返回值
            string str = InvokeScript(funcName);
            //将该函数清空
            ExecuteScript("window." + funcName + "=null");
            return str;
        } 
        #endregion

当页面跳转失败的时候要跳转到错误页导航,但是在这里因为错误页还没有配置所以先做跳转成功的功能

#region myBrowser

        private void myBrowser_Navigated(object sender, NavigationEventArgs e)
        {
            //执行js代码返回当前页面的title
            Info.Title = ExecuteReturnableScript("if(document.title==null){return '';} else {return document.title;}");
            Info.Url = myBrowser.Source.ToString();

            //跳转成功之后将文本框显示的数据改变为当前页面的标题
            txtAddress.Text = Info.Title;
        }

        private void myBrowser_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {

        }

        #endregion

这时,BrowserCtrl已经差不多完成了,只差一个跳转失败和跳转成功之后写入历史记录的功能

然后先进行主界面的编写

界面如图:

wp8手机浏览器项目_第2张图片

xml代码如下:

    
        
        
            
                
                    
                        
                            
                                
                                    
                                    
                                
                                
                                    
                                    
                                
                                
                                
                                
                            
                        
                    
                
                
            
        
    

    
        
            
            
            
            
                
                
                
            
        
    

总体的布局大概为:

最上方是一个ContentControl用来放置浏览器控件

中间部分是一个Popup,点击窗口按钮时显示,里面放置了一个ListBox用来显示每一个窗口的信息,ListBox后面是一个新建窗口的按钮

最下方是ApplicationBar功能条


在点击功能条的窗口按钮时,会出现如下的界面

wp8手机浏览器项目_第3张图片

要求ListBox中显示每一个BrowserCtrl的信息,只要对BrowserCtrl对应的BrowserInfo集合进行数据绑定即可

同时要实现点击一个窗口信息就切换到改窗口,需要一个字典集合来记录每一个BrowserInfo对应的BrowserCtrl

并且当新增或者删除一个窗口时,功能栏的窗口按钮图标要显示为对应几个窗口的图标,所以需要对BrowserInfo集合的CollectionChanged事件进行处理

所以在后台xaml.cs中定义两个属性

//BrowserInfo的集合,用于ListBox的数据绑定,显示每个BrowserCtrl的信息
        public ObservableCollection Infos { get; private set; }

        //字典集合记录了每一个BrowserInfo对应的BrowserCtrl,用于多窗口时,点击一个窗口信息然后找到对应的BrowserCtrl并切换显示
        public Dictionary Dic { get; private set; }
在构造函数中:

// 构造函数
        public MainPage()
        {
            InitializeComponent();

            //初始化Infos和Dic
            Infos = new ObservableCollection();
            Dic = new Dictionary();
            //绑定lbWins的数据源
            lbWins.ItemsSource = Infos;
            Infos.CollectionChanged += Infos_CollectionChanged;
        }

        //Infos集合发生改变时对功能条的窗口按钮的图标进行切换
        void Infos_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            //获得集合中的总数
            int count =
                Infos.Count;
            //获得功能条的窗口按钮
            ApplicationBarIconButton btnWins = (ApplicationBarIconButton) ApplicationBar.Buttons[2];
            //动态设置图标
            if (count <= 6)
            {
                btnWins.IconUri = new Uri("/Content/Images/win" + count + ".png", UriKind.Relative);
            }
            else
            {
                btnWins.IconUri = new Uri("/Content/Images/winN.png", UriKind.Relative);
            }

        }

在主界面刚被加载的时候,显示默认的主页

 //xaml窗体之间跳转的事件
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            //如果是跳转到一个新的xaml(当相遇Load事件)
            if (e.NavigationMode == NavigationMode.New)
            {
                //显示默认的主页
                CreateNewWin("http://3g.baidu.com");
            }
        }

        /// 
        /// 新建窗口并显示指定的url页面
        /// 
        /// 
        private void CreateNewWin(string url)
        {
            //创建一个新的BrowserCtrl
            BrowserCtrl browserCtrl = new BrowserCtrl();
            //设置其铺满全屏
            browserCtrl.Height = Application.Current.Host.Content.ActualHeight;
            browserCtrl.Width = Application.Current.Host.Content.ActualWidth;
            //跳转到指定的url
            browserCtrl.myBrowser.Navigate(new Uri(url));
            //显示在主界面
            browserContent.Content = browserCtrl;

            //每新建一个窗口就向Infos集合和Dic集合中添加对应的BrowserInfo,绑定Infos数据源的ListBox就可以显示出每一个窗口的信息
            Infos.Add(browserCtrl.Info);
            Dic[browserCtrl.Info] = browserCtrl;
        }

关闭窗口功能:

//关闭窗体按钮事件
        private void btnCloseWin_Click(object sender, System.Windows.Input.GestureEventArgs e)
        {
            //如果窗体的数量大于1才可以进行操作
            if (Infos.Count > 1)
            {
                //获得当前点击的元素的数据上下文
                FrameworkElement element = sender as FrameworkElement;
                BrowserInfo info = (BrowserInfo) element.DataContext;
                //从两个集合中移除该项
                Infos.Remove(info);
                Dic.Remove(info);
                //浏览器切回第一个窗体
                browserContent.Content = Dic[Infos[0]];
            }
        }

在ListBox中点击某一项切换到对应的窗体:

//窗体之间切换事件
        private void btnToOtherWin_Click(object sender, System.Windows.Input.GestureEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            browserContent.Content = Dic[(BrowserInfo)element.DataContext];
            popShow.IsOpen = false;
        }

//新建窗体按钮事件
        private void btnCreateWin_Click(object sender, System.Windows.Input.GestureEventArgs e)
        {
            CreateNewWin("http://3g.baidu.com");
        }



然后编写功能栏的后退,前进和窗口按钮功能

实现当前浏览的BrowserCtrl页面后退,前进功能最好的方式就是使用js实现

因此可以利用当前BrowserCtrl里面调用js函数的功能

所以需要定义一个属性记录当前正在浏览的BrowserCtrl

//当前正在浏览的BrowserCtrl
        public BrowserCtrl ActivedBrowserCtrl
        {
            get
            {
                return (BrowserCtrl)browserContent.Content; 
            }
        }

之后就可以调用ActivedBrowserCtrl的公有方法了

#region ApplicationBar功能

        //后退按钮事件
        private void btnBack_Click(object sender, EventArgs e)
        {
            ActivedBrowserCtrl.ExecuteScript("history.go(-1)");
        }

        //前进按钮事件
        private void btnForward_Click(object sender, EventArgs e)
        {
            ActivedBrowserCtrl.ExecuteScript("history.go(11)");
        }

        //窗口按钮事件
        private void btnWins_Click(object sender, EventArgs e)
        {
            //根据popShow当前的IsOpen改变显示
            popShow.IsOpen = !popShow.IsOpen;
        } 

        #endregion
接下来就是更多功能的编写实现了

首先是通过短信将网页地址分享给好友功能

通过内置的SmsComposeTask可以轻易的实现这个功能

private void btnShared_Click(object sender, EventArgs e)
        {
            //拼接发送的内容
            string msg = "我发现了一个很不错的网站,你也去看看吧~地址是:" + ActivedBrowserCtrl.myBrowser.Source;
            //调用信息发送
            SmsComposeTask smsComposeTask = new SmsComposeTask();
            //设置信息发送的内容
            smsComposeTask.Body = msg;
            //显示信息
            smsComposeTask.Show();
        }

效果如下图:

wp8手机浏览器项目_第4张图片

发送到桌面:

这里使用的是一个别人写好的静态方法,如下:

/// 
        /// 放置图标到桌面
        /// 
        /// 图标的标题
        /// 图标图片
        /// 背面标题
        /// 背面的描述
        /// 背面图片
        /// 要转到的url
        public static void PinToStart(string title, string image, 
            string backTitle, string backContent, string backImage, string url)
        {
            //如果存在则删除,并在下面重新Pin到桌面
            ShellTile oldTile = ShellTile.ActiveTiles.FirstOrDefault
                (e => e.NavigationUri.ToString().Contains(url));
            if (oldTile != null)
            {
                oldTile.Delete();
            }

            //生成Tile
            StandardTileData myTile = new StandardTileData
            {
                BackgroundImage = new Uri(image, UriKind.Relative),
                Title = title,
                Count = 0,
                BackTitle = backTitle,
                BackContent = backContent,
                BackBackgroundImage = new Uri(backImage, UriKind.Relative)
            };
            //固定到开始界面
            //url为点击后导向的页面地址
            ShellTile.Create(new Uri(url, UriKind.Relative), myTile);
        }

通过该方法可以再主菜单上新增一个相应的快捷方式图标

private void btnSendToDesk_Click(object sender, EventArgs e)
        {
            string title = ActivedBrowserCtrl.Info.Title;
            string url = ActivedBrowserCtrl.Info.Url;

            CommonHelper.PinToStart(title, "/Content/Images/AppIcon.png", title, url, "", "MainPage.xaml?url=" + url);
        }

这里需要注意的是,用户点击桌面的快捷方式,然后启动本浏览器,跳转到对应的url

实际上也是一个启动浏览器加载MainPage.xaml页面然后跳转的过程

所以将图标的url设置为"MainPage.xaml?url=" + url形式,先到主页面然后在跳转大哦哦url

所以需要在MainPage加载的时候判断是否有url参数

修正版:

//xaml窗体之间跳转的事件
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            //如果是跳转到一个新的xaml(当相遇Load事件)
            if (e.NavigationMode == NavigationMode.New)
            {
                //判断请求的路径中是否包含url参数
                if (NavigationContext.QueryString.ContainsKey("url"))
                {
                    //如果是则跳转到对应的url页面
                    string url = NavigationContext.QueryString["url"];
                    CreateNewWin(url);
                }
                else
                {
                    //如果不是则显示默认的主页
                    CreateNewWin("http://3g.baidu.com");
                }
            }
        }

历史记录:

用户点击历史记录,跳转到历史记录的xaml页面,可以显示今天,昨天以及更久之前的浏览历史

页面设计 如下:

wp8手机浏览器项目_第5张图片

页面布局如下:


        
        
            
            
                
                    
                        
                            
                                
                                
                            
                        
                    
                
            

            
            
                
                    
                        
                            
                                
                                
                            
                        
                    
                
            
            
                
                    
                        
                            
                                
                                
                            
                        
                    
                
            
        
    

大致布局为

三个枢轴分别为今天,昨天以及更久以前

每个枢轴项中都放置着一个ListBox,并且绑定ListBox的数据源,在ListBox的ItemTemplate中放置两个TextBlock并分别绑定数据源的Title和Url属性

这个时候很明显需要一个类的集合来当做ListBox的数据源

此时符合有Title和Url属性的类之后BrowserInfo,因此可以将其作为数据源的对象

但是由于对历史记录有很多操作,包括加载,添加,和保存等,而BrowserInfo主要的作用是保存当前浏览器的页面信息

另外,保存历史记录信息的类还需要有一个最后浏览时间的属性,用来判断该记录是今天还是昨天还是更久之前

所以需要另外一个类来完成历史记录的任务

 public class HistoryItem : DependencyObject
    {
        public string Url
        {
            get { return (string)GetValue(UrlProperty); }
            set { SetValue(UrlProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Url.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UrlProperty =
            DependencyProperty.Register("Url", typeof(string), typeof(HistoryItem), null);




        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(HistoryItem), null);





        public DateTime LastVisitDateTime
        {
            get { return (DateTime)GetValue(LastVisitDateTimeProperty); }
            set { SetValue(LastVisitDateTimeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LastVisitDateTime.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LastVisitDateTimeProperty =
            DependencyProperty.Register("LastVisitDateTime", typeof(DateTime), typeof(HistoryItem), null);

        
    }

同时添加一个HistoryManager工具类来实现对历史记录的操作

需要注意的是,历史记录的信息以xml形式保存在手机的独立存储中

格式如下:






















HistoryManager中的公有操纵方法
        /// 
        /// 添加一条历史记录
        /// 
        /// 
        /// 
        public void Add(string url, string title)
        {
            Items.Add(new HistoryItem { LastVisitDateTime=DateTime.Now,Title=title,Url=url});
            Save();
        }

        /// 
        /// 从XML文件中加载历史记录(历史记录的信息以xml格式保存在手机的独立存储区中)
        /// 
        public void Load()
        {
            //获得独立存储区
            IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
            //如果是否有历史记录的文件,如果不存在则是第一次,不加载
            if (!isf.FileExists("History.xml"))
            {
                return;
            }
            //如果存在,则将xml文件读入流中
            using (Stream stream = isf.OpenFile("History.xml",FileMode.Open))
            {
                //使用XDocument进行流解析,得到xml对象模型
                XDocument xdoc = XDocument.Load(stream);
                //遍历根节点,并将读取到的历史记录信息添加到历史记录信息集合中
                foreach (var elementItem in xdoc.Root.Elements())
                {
                    string url = elementItem.Attribute("Url").Value;
                    string title = elementItem.Attribute("Title").Value;
                    string lastVisitDateTime = elementItem.Attribute("LastVisitDateTime").Value;

                    HistoryItem item = new HistoryItem();
                    item.Url = url;
                    item.Title = title;
                    item.LastVisitDateTime = DateTime.Parse(lastVisitDateTime);
                    
                    Items.Add(item);
                }
            }            
        }

        /// 
        /// 保存历史记录
        /// 
        public void Save()
        {
            //获得独立存储区
            IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
            //创建History.xml文件
            using (Stream stream = isf.CreateFile("History.xml"))
            {
                //创建根节点
                XDocument xdoc = new XDocument(new XElement("Items"));
                //遍历历史记录信息集合
                foreach (var item in Items)
                {
                    XElement elementItem = new XElement("Item");
                    elementItem.SetAttributeValue("Url", item.Url);
                    elementItem.SetAttributeValue("Title", item.Title);
                    elementItem.SetAttributeValue("LastVisitDateTime", item.LastVisitDateTime);

                    xdoc.Root.Add(elementItem);
                }
                xdoc.Save(stream);
            }
        }

在历史记录页面后台中的代码处理:

//某一项历史记录被点击时
        private void panelItemTap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            //获得被点击的元素
            FrameworkElement element = sender as FrameworkElement;
            //获得该元素的数据上下文
            HistoryItem item = (HistoryItem) element.DataContext;
            //跳转到指定的url
            NavigationService.Navigate(new Uri("MainPage.xaml?url=" + Uri.EscapeUriString(item.Url), UriKind.Relative));
        }

//历史记录页面被加载的时候
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            if (e.NavigationMode == NavigationMode.New)
            {
                //查询历史记录信息集合中最后访问时间大于今天的最后一秒的集合
                var today = from item in HistoryManager.Instance.Items
                    where item.LastVisitDateTime >= DateTime.Today
                    select item;
                //查询历史记录信息集合中最后访问时间小于今天的最后一秒并且大于昨天最后一秒的集合
                var yestoday = from item in HistoryManager.Instance.Items
                    where
                        item.LastVisitDateTime < DateTime.Today && item.LastVisitDateTime >= DateTime.Today.AddDays(-1)
                    select item;
                //查询历史记录信息集合中最后访问时间小昨天的最后一秒的集合
                var longlong = from item in HistoryManager.Instance.Items
                               where item.LastVisitDateTime < DateTime.Today.AddDays(-1)
                               select item;
                //为对应的ListBox绑定数据源
                lbToday.ItemsSource = today;
                lbYesToday.ItemsSource = yestoday;
                lbLongLong.ItemsSource = longlong;
            }
        }

最后要完成的功能是错误页面导航

在项目中新建一个LocalPage文件夹用来保存错误页的htm

(因为用户链接出错的原因不仅仅是因为网址错误,还有可能是网络不好,所以要将错误页做成静态页放在本地中,这样不用联网也可以访问到,而真实的项目中都是定时定点从服务器请求最新的错误页面信息,这里只是将其写死在本地中)

ErrorPage.htm简单代码如下:


    
         
    
    
        抱歉,页面访问失败!您可以刷新
您可以试试下面的网站:

在浏览器控件中

//浏览器加载出错事件
        private void myBrowser_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            //从程序的资源中获得错误页面的流
            var errorPageStream = Application.GetResourceStream(new Uri("LocalPage/ErrorPage.htm", UriKind.Relative));
            using (Stream stream = errorPageStream.Stream)
            {
                //读取流
                using (StreamReader reader = new StreamReader(stream))
                {
                    string htm = reader.ReadToEnd();
                    //设置刷新页面的url
                    htm = htm.Replace("$url", Info.Url);
                    //导向该错误页
                    myBrowser.NavigateToString(CommonHelper.ConvertExtendedAscii(htm));
                }
            }
            //显示错误页的相关信息
            Info.Title = "出错啦!";
            Info.Url = "ErrorPage.htm";
        }


最后,wp8简单的手机浏览器完成!


转载于:https://www.cnblogs.com/jchubby/p/4429734.html

你可能感兴趣的:(wp8手机浏览器项目)