Windows Phone 8.1 开发实例 网络编程 天气预报
阅读原文时间:2023年07月15日阅读:3

首先感谢林政老师的博客,给了我很大的指导。

我的开发环境:

- Visual Studio 2013(With Update 4)

- Windows Phone 8.1

- Windows 8.1

我使用的是百度天气的api,所以你需要一个百度天气的ak,戳这里申请。记住你ak的值,像这样拼接uri:

http://api.map.baidu.com/telematics/v3/weather?location=城市&output=xml&ak=你的ak

可以获取一个xml格式的返回数据:

Created with Raphaël 2.1.0异步获取数据解析xml绑定数据

ForecastPeriod类,用来记录每天的天气和数据的绑定:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace WeatherForecastDIY
{
    /// <summary>
    /// 天气预报数据类
    /// </summary>
    class ForecastPeriod:INotifyPropertyChanged
    {
        private string weather;//天气
        private string wind;//风力
        private string maxTemperature;//最高温
        private string minTemperature;//最低温
        private string date;//日期
        private string dayPictureUrl;//天气图片
        private string temperature;//温度
        public string Weather
        {
            set
            {
                if (value != weather)
                {
                    weather = value;
                    OnpropertyChanged("Weather");
                }
            }
            get
            {
                return weather;
            }
        }
        public string Wind
        {
            set
            {
                if (value != wind)
                {
                    wind = value;
                    OnpropertyChanged("Wind");
                }
            }
            get
            {
                return wind;
            }
        }
        public string MaxTemperature
        {
            set
            {
                if (value != maxTemperature)
                {
                    maxTemperature = value;
                    OnpropertyChanged("MaxTemperature");
                }
            }
            get
            {
                analysisTemperature();
                return maxTemperature;
            }
        }
        public string MinTemperature
        {
            set
            {
                if (value != minTemperature)
                {
                    minTemperature = value;
                    OnpropertyChanged("MinTemperature");
                }
            }
            get
            {
                analysisTemperature();
                return minTemperature;
            }
        }
        public string Date
        {
            set
            {
                if (value != date)
                {
                    date = value;
                    OnpropertyChanged("Date");
                }
            }
            get
            {
                return date;
            }
        }
        public string DayPictureUrl
        {
            set
            {
                if (dayPictureUrl != value)
                {
                    dayPictureUrl = value;
                    OnpropertyChanged("DayPictureUrl");
                }
            }
            get
            {
                return dayPictureUrl;
            }
        }
        public string Temperature
        {
            set
            {
                if (value != temperature)
                {
                    temperature = value;
                    OnpropertyChanged("Temperature");
                }
            }
            get
            {
                return temperature;
            }
        }
        /// <summary>
        /// 分析温度字符串,得到最高最低温
        /// </summary>
        private void analysisTemperature()
        {
            if (Temperature == null)
                return;
            int p = -1;//波浪号的位置
            for (int i = 0; i < Temperature.Length; i++)
                if (Temperature[i] == '~') { p = i; break; }
            if(p==-1)//没有波浪号
            {
                maxTemperature = Temperature;
                minTemperature = Temperature;
            }
            else
            {
                maxTemperature = Temperature.Substring(0, p) + "℃";
                minTemperature = Temperature.Substring(p + 1, Temperature.Length - p - 1);
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnpropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if(handler!=null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

Forecast类,这个的主要功能是异步获取数据和解析Xml:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.ComponentModel;
using Windows.UI.Xaml;
using Windows.Storage;

namespace WeatherForecastDIY
{
    /// <summary>
    /// 天气预报类
    /// </summary>
    class Forecast:INotifyPropertyChanged
    {
        private string city;//城市名
        private string currentTemperature;//实时的温度
        private Visibility vis;//控制进度条是否可见
        public string City
        {
            set
            {
                if (city != value)
                {
                    city = value;
                    OnpropertyChanged("City");
                }
            }
            get
            {
                return city;
            }
        }
        public string CurrentTemperature//实时气温
        {
            set
            {
                if (currentTemperature != value)
                {
                    currentTemperature = value;
                    OnpropertyChanged("CurrentTemperature");
                }
            }
            get
            {
                return currentTemperature;
            }
        }
        public ForecastPeriod TodayForecast//当天的天气
        {
            set;
            get;
        }
        public Visibility Vis
        {
            set
            {
                if(value!=vis)
                {
                    vis = value;
                    OnpropertyChanged("Vis");
                }
            }
            get
            {
                return vis;
            }
        }
        public ObservableCollection<ForecastPeriod> ForecastList { set; get; }//不知道多少天的天气集合
        /// <summary>
        /// 构造函数,初始化
        /// </summary>
        /// <param name="CityName"></param>
        public Forecast(string CityName)
        {
            City = CityName;
            ForecastList = new ObservableCollection<ForecastPeriod>();
            TodayForecast=new ForecastPeriod();
        }
        public Forecast()
        {
            City = "成都";//默认是成都萌萌哒
            ForecastList = new ObservableCollection<ForecastPeriod>();
            TodayForecast = new ForecastPeriod();
        }
        /// <summary>
        /// 异步获取天气预报
        /// </summary>
        /// <returns></returns>
        private async void getForecastAsync()
        {
            //异步一开始设置进度条可见
            Vis = Visibility.Visible;
            //局部变量
            ObservableCollection<ForecastPeriod> newForecastList = new ObservableCollection<ForecastPeriod>();
            //异步获取数据,很多童鞋问为啥uri要加DateTime,那是因为如果uri是一样的,不知道哪里的缓存就不让你更新数据
            Uri uri = new Uri("http://api.map.baidu.com/telematics/v3/weather?location=" + City + "&output=xml&ak=HyXsPscHkQOfR0nxyUmYrV8l&date=" + System.DateTime.Now.ToString());
            HttpClient httpClient = new HttpClient();
            HttpResponseMessage response = await httpClient.GetAsync(uri);
            response.EnsureSuccessStatusCode();
            Stream stream = await response.Content.ReadAsStreamAsync();
            //搞到Xlm
            XElement now = XElement.Load(stream);
            //用完释放好习惯
            httpClient.Dispose();
            //解析Xml,请对照着百度返回的Xml看
            //判断是否成功
            if(now.Element("status").Value.Equals("success"))
            {
                //往下走啊往下走
                now = now.Element("results").Element("weather_data").Element("date");
                //这天的天气
                ForecastPeriod newForecastPeriod = new ForecastPeriod();
                //那一排Xml天气数据居然是并列的,解析解析
                while (now != null)
                {
                    //获取当前节点的名字
                    string nowName=now.Name.LocalName;
                    if (nowName.Equals("date"))
                    {
                        string tmp = now.Value;
                        //当日期的长度大于2,就是当天的日期,那个有实时天气的(这个半夜会迷之失效,为啥问度娘)
                        if (tmp.Length > 2)
                        {
                            newForecastPeriod.Date = tmp.Substring(0, 2);
                            //简单的说就是在提取实时气温
                            int p = -1;
                            for (int i = 0; i < tmp.Length; i++) if (tmp[i] == ':') { p = i; break; }
                            CurrentTemperature = tmp.Substring(p + 1, tmp.Length - p - 3);
                        }
                        else
                            newForecastPeriod.Date = now.Value;
                    }
                    else if (nowName.Equals("dayPictureUrl"))
                        newForecastPeriod.DayPictureUrl = now.Value;
                    else if (nowName.Equals("weather"))
                        newForecastPeriod.Weather = now.Value;
                    else if (nowName.Equals("wind"))
                        newForecastPeriod.Wind = now.Value;
                    else if (nowName.Equals("temperature"))
                    {
                        //每次到气温的时候就结束了一天的预报,把这一天的情况加入集合
                        newForecastPeriod.Temperature = now.Value;
                        newForecastList.Add(newForecastPeriod);
                        newForecastPeriod = new ForecastPeriod();
                    }
                    now = (XElement)now.NextNode;
                }
                //终于解析完了(长吁一口气)
                //接下来就是赋值了
                TodayForecast.Weather = newForecastList[0].Weather;
                TodayForecast.Wind = newForecastList[0].Wind;
                TodayForecast.MaxTemperature = newForecastList[0].MaxTemperature;
                TodayForecast.MinTemperature = newForecastList[0].MinTemperature;

                foreach (ForecastPeriod forecastPeriod in newForecastList)
                    ForecastList.Add(forecastPeriod);
            }
            else
            {
                //熊孩子一定乱输的城市,然后被度娘给了Error
                //所以要纠正过来,还是默认成都萌萌哒
                City = "成都";
                //改应用数据
                ApplicationDataContainer localsetting = Windows.Storage.ApplicationData.Current.LocalSettings;
                localsetting.Values["City"] = City;
                //这叫不叫递归调用呢?反正再来一次
                getForecastAsync();
            }
            //终于异步完了,关掉进度条
            Vis = Visibility.Collapsed;
        }
        /// <summary>
        /// 没什么用的函数,懒得改了,大家明白那个意思就好
        /// </summary>
        public void getForecast()
        {
            getForecastAsync();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// 绑定
        /// </summary>
        /// <param name="name"></param>
        protected void OnpropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

主界面,设计得太丑,还望美工好的童鞋给予指导和建议:

<Page
    x:Class="WeatherForecastDIY.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WeatherForecastDIY"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Page.BottomAppBar>
        <CommandBar>
            <AppBarButton x:Name="Setting" Icon="Setting" Label="设置" Click="Setting_Click"/>
            <AppBarButton x:Name="Refresh" Icon="Refresh" Label="刷新" Click="Refresh_Click"/>
            <AppBarButton x:Name="SolidStar" Icon="SolidStar" Label="为我打分"/>
        </CommandBar>
    </Page.BottomAppBar>

    <Grid>
        <Pivot Title="{Binding City}">
            <PivotItem Header="今日">
                <StackPanel x:Name="TodayStackPanel" Orientation="Horizontal">
                    <StackPanel x:Name="TodayWeatherStackPanel" Height="Auto" Width="15" Orientation="Vertical">
                        <TextBlock Text="天气  " TextWrapping="Wrap" Height="60" Width="Auto" FontSize="15"></TextBlock>
                        <TextBlock Text="{Binding Path=TodayForecast.Weather}" TextWrapping="Wrap" Height="Auto" Width="Auto" FontSize="15"></TextBlock>
                    </StackPanel>
                    <StackPanel x:Name="TodayWindStackPanel" Height="Auto" Width="15" Orientation="Vertical">
                        <TextBlock Text="风力  " TextWrapping="Wrap" Height="60" Width="Auto" FontSize="15"></TextBlock>
                        <TextBlock Text="{Binding Path=TodayForecast.Wind}" TextWrapping="Wrap" Height="Auto" Width="Auto" FontSize="15"></TextBlock>
                    </StackPanel>
                    <StackPanel x:Name="TodayMaxTemStackPanel" Height="Auto" Width="15" Orientation="Vertical">
                        <TextBlock Text="最高温 " TextWrapping="Wrap" Height="60" Width="Auto" FontSize="15"></TextBlock>
                        <TextBlock Text="{Binding Path=TodayForecast.MaxTemperature}" TextWrapping="Wrap" Height="Auto" Width="Auto" FontSize="15"></TextBlock>
                    </StackPanel>
                    <StackPanel x:Name="TodayMinTemStackPanel" Height="Auto" Width="15" Orientation="Vertical">
                        <TextBlock Text="最低温 " TextWrapping="Wrap" Height="60" Width="Auto" FontSize="15"></TextBlock>
                        <TextBlock Text="{Binding Path=TodayForecast.MinTemperature}" TextWrapping="Wrap" Height="Auto" Width="Auto" FontSize="15"></TextBlock>
                    </StackPanel>
                    <TextBlock x:Name="CurrentTemperature" Height="240" Width="100" Text="{Binding CurrentTemperature}" VerticalAlignment="Top" FontSize="100" FontStyle="Normal"></TextBlock>
                    <TextBlock Text="℃" Height="90" Width="90" VerticalAlignment="Top" FontSize="50"></TextBlock>
                </StackPanel>
            </PivotItem>
            <PivotItem Header="每天">
                <Grid x:Name="EverydayWeatherGrid">
                    <ListBox x:Name="EverydayWeatherListBox" Background="White">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Height="60" Width="Auto" Margin="0,0,0,15" Orientation="Horizontal">
                                    <TextBlock Text="{Binding Date}" Width="60" FontSize="30"></TextBlock>
                                    <Image Source="{Binding DayPictureUrl}" Width="60" Height="Auto"></Image>
                                    <TextBlock Text="{Binding Temperature}" Width="60" Height="Auto"></TextBlock>
                                </StackPanel>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </PivotItem>
        </Pivot>
        <ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="10" VerticalAlignment="Top" Visibility="{Binding Vis}" IsIndeterminate="True" Width="399.999969482422"/>

    </Grid>
</Page>

接下来是MainPage.xaml.cs,注释写得很详细:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Storage;

// “空白页”项模板在 http://go.microsoft.com/fwlink/?LinkId=391641 上有介绍

namespace WeatherForecastDIY
{
    /// <summary>
    /// 可用于自身或导航至 Frame 内部的空白页。
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private string city;//城市(我不是字幕君。。。)
        public string City
        {
            set
            {
                if(value!=city)
                {
                    city = value;
                }
            }
            get
            {
                return city;
            }
        }
        public MainPage()
        {
            this.InitializeComponent();
            this.NavigationCacheMode = NavigationCacheMode.Required;
            //第一次进来时就更新
            getCity();
            ReportForecast();
        }
        /// <summary>
        /// 报道天气
        /// </summary>
        private void ReportForecast()
        {
            Forecast forecast = new Forecast(City);
            forecast.getForecast();
            //数据绑定
            DataContext = forecast;
            EverydayWeatherListBox.ItemsSource = forecast.ForecastList;
        }
        /// <summary>
        /// 读应用数据的函数(突然意识到应该说方法,而不是函数)
        /// </summary>
        private void getCity()
        {
            ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
            if (!localSettings.Values.ContainsKey("City"))
                localSettings.Values["City"] = "成都";//怎样都是成都萌萌哒
            City = localSettings.Values["City"].ToString();
        }

        /// <summary>
        /// 在此页将要在 Frame 中显示时进行调用。
        /// </summary>
        /// <param name="e">描述如何访问此页的事件数据。
        /// 此参数通常用于配置页。</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // TODO: 准备此处显示的页面。

            // TODO: 如果您的应用程序包含多个页面,请确保
            // 通过注册以下事件来处理硬件“后退”按钮:
            // Windows.Phone.UI.Input.HardwareButtons.BackPressed 事件。
            // 如果使用由某些模板提供的 NavigationHelper,
            // 则系统会为您处理该事件。
            //上面一堆堆完全不知所以然,简单说就是这个界面从不见了到出现时调用这个方法
            getCity();
            ReportForecast();
        }
        /// <summary>
        /// 更新按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Refresh_Click(object sender, RoutedEventArgs e)
        {
            getCity();
            ReportForecast();
        }
        /// <summary>
        /// 设置按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Setting_Click(object sender, RoutedEventArgs e)
        {
            //调到SetCirt界面
            Frame.Navigate(typeof(SetCity), City);
        }
    }
}

接下来是SetCity的页面,也就是设置城市的页面,这个难点在于换页面。

设计:

<Page
    x:Class="WeatherForecastDIY.SetCity"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WeatherForecastDIY"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Page.BottomAppBar>
        <CommandBar>
            <AppBarButton x:Name="Accept" Icon="Accept" Label="确定" Click="Accept_Click"></AppBarButton>
            <AppBarButton x:Name="Cancel" Icon="Cancel" Label="取消" Click="Cancel_Click"></AppBarButton>
        </CommandBar>
    </Page.BottomAppBar>
    <Grid>
        <StackPanel x:Name="SetCityStackPanel">
            <TextBlock Margin="0,0,0,0" Height="15" FontSize="15" Text="输入城市"></TextBlock>
            <TextBox x:Name="TextBox" Height="15" Width="Auto" FontSize="15"></TextBox>
        </StackPanel>
    </Grid>
</Page>

SetCity.xaml.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Storage;

// “空白页”项模板在 http://go.microsoft.com/fwlink/?LinkID=390556 上有介绍

namespace WeatherForecastDIY
{
    /// <summary>
    /// 可用于自身或导航至 Frame 内部的空白页。
    /// </summary>
    public sealed partial class SetCity : Page
    {
        public SetCity()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// 在此页将要在 Frame 中显示时进行调用。
        /// </summary>
        /// <param name="e">描述如何访问此页的事件数据。
        /// 此参数通常用于配置页。</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            //想必大家都看见了上方微软君的迷之解释了吧。。。
            TextBox.Text = (string)e.Parameter;
        }

        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            TextBox.Text = "";
        }
        private void Accept_Click(object sender, RoutedEventArgs e)
        {
            //改改改,应用数据什么的
            ApplicationDataContainer localsetting = Windows.Storage.ApplicationData.Current.LocalSettings;
            localsetting.Values["City"] = TextBox.Text;
            //回到主界面
            Frame.GoBack();
        }
    }
}