文章首发在我的个人博客
实现的功能
在这个项目中我们将使用WPF实现一个可以实时查看天气的APP,具体实现方法为使用心知天气的API,通过HTTP请求获取实时天气的数据解析后显示在我们的APP界面上。
UI界面的设计
Material Design 控件库的引入
为了得到更好看的的UI界面,在这个项目中我们用到了开源WPF控件库<font color=#FF8C00>Material Design</font>.在VS2019的Nuget中下载安装<font color=#FF8C00>MaterialDesignThemes</font> 类库.根据MaterialDesign 官方文档 配置好WPF项目,之后就可以在我们的项目中使用这个控件库了.
窗体的布局
重置标题栏
第一步要做的是在设置窗体的两个属性值。
<Window ...
...
WindowStyle="None"
AllowsTransparency="True">
之后在窗体的顶端右上角添加三个按钮,他们的功能为 应用设置按钮、最小化按钮、关闭按钮。窗体的布局文件如下:
<!--Title bar-->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="0.7*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Weather"/>
<!--setting button-->
<Button Grid.Column="2"
Name="BtnSetting"
VerticalAlignment="Center"
BorderThickness="0"
BorderBrush="{StaticResource MaterialDesignLightBackground}"
Background="{StaticResource MaterialDesignShadowBrush}"
Style="{StaticResource MaterialDesignRaisedButton}"
Click="BtnSetting_Click">
<materialDesign:PackIcon Kind="Settings" />
</Button>
<!--window minimize button-->
<Button Grid.Column="3"
Name="BtnMin"
VerticalAlignment="Center"
BorderThickness="0"
BorderBrush="{StaticResource MaterialDesignLightBackground}"
Background="{StaticResource MaterialDesignShadowBrush}"
Style="{StaticResource MaterialDesignRaisedButton}"
Click="BtnMin_Click">
<materialDesign:PackIcon Kind="WindowMinimize" />
</Button>
<!--window maximam button-->
<Button Grid.Column="4"
Name="BtnClose"
VerticalAlignment="Center"
BorderThickness="0"
BorderBrush="{StaticResource MaterialDesignLightBackground}"
Background="{StaticResource MaterialDesignShadowBrush}"
Style="{StaticResource MaterialDesignRaisedButton}"
Click="BtnClose_Click" >
<materialDesign:PackIcon Kind="WindowClose"/>
</Button>
</Grid>
窗体其余部分的设计
窗体的其余部分则是用来显示获取到的天气信息,这一部分的布局具体实现可以查看源码。
获取天气数据
为了从心知天气获取到天气的数据,我们需要使用到<font color = orange>HttpClient</font>的异步方法<font color = orange>GetStringAsync</font>,此方法可以异步的向指定的Uri发送HTTP的GET请求获取数据,并将数据返回到一个字符串中。
async public Task<string> AsyncRequset()
{
string value = await _httpClient.GetStringAsync(_requestUri);
return value;
}
HttpClinet位于命名空间 System.Net.Http 之下,它是对 httpWebRequest 的进一步封装,使用它的好处就是可以更加方便的实现异步GET请求和POST请求,也可以更容易的从指定的Uri中获取和发送数据。
获取到的Json格式数据的解析
GET请求从心知天气获取到的为Json格式的数据,它包含了位置、天气、温湿度、风力、风向等数据,为了从中解析出我们需要到的数据,我们需要使用专门用来解析Json数据的类库,在本次项目中,我们使用Newtonsoft.Json库来达到我们的需求。
{
"results": [
{
"location": {
"id": "WT3SYNJHF48X",
"name": "鄂州",
"country": "CN",
"path": "鄂州,鄂州,湖北,中国",
"timezone": "Asia/Shanghai",
"timezone_offset": "+08:00"
},
"now": {
"text": "晴",
"code": "1",
"temperature": "12",
"feels_like": "11",
"pressure": "1018",
"humidity": "54",
"visibility": "19.4",
"wind_direction": "东",
"wind_direction_degree": "93",
"wind_speed": "16.56",
"wind_scale": "3",
"clouds": "0",
"dew_point": ""
},
"last_update": "2020-02-05T18:56:00+08:00"
}
]
}
首先我们需要获得<font color = orange>位置</font>信息和<font color = orange>最后更新时间</font>。
/// <summary>
/// parse the data we want form the json
/// </summary>
/// <param name="weatherDataJson"></param>
/// <returns>weather object</returns>
private Weather ParseDataFromJson(string weatherDataJson)
{
JObject weatherRequest = JObject.Parse(weatherDataJson);
JToken result = weatherRequest["results"].ToList().FirstOrDefault();
LocationName = result["location"]["name"].ToString();
LastUpdateTime = DateTime.Parse(result["last_update"].ToString()).ToShortTimeString();
return result["now"].ToObject<Weather>();
}
紧接着要做的是把我们想要从Json中获取的数据封装到Weather类中,使用ToObject方法会自动为Weather类中与Json同名的公有属性赋值。
/// <summary>
/// weather object
/// </summary>
public class Weather
{
public string Text { set; get; }
public string Code { set; get; }
public string Temperature { set; get; }
public string Humidity { set; get; }
public string Wind_Direction { set; get; }
public string Wind_Direction_Degree { set; get; }
public string Wind_Speed { set; get; }
public string Wind_Scale { set; get; }
}
最后我们想要显示的数据被存储到了<font color = orange>Weather</font>对象和变量<font color = orange>LocationName</font>变量<font color = orange>LastUpdateTime</font>中。
天气数据的显示
由于所有用来显示数据的控件的最顶层控件的数据上下文被绑定到了WeatherRequest对象上,所以每当WeatherRequest对象中的属性(Weather LocationName LastUpdateTime)发生变化的时候,UI界面也会发生变化。每当获取到新的数据并成功解析之后,UI界面也会随之刷新.
this.MainPageCard.DataContext = _weatherRequest;
BackgroundWorker的使用
在没有使用BackgroundWorkerr之前会出现这样一个现象,当点击更新天气数据,异步发送GET请求获取天气数据的时候,UI界面会卡死。所以为了避免出现这种情况,在这个项目中,我们引入BackgroundWorker组件。
- 在MianWindow类中添加一个类型为BackgroundWorker的私有变量_bgWorker.
private BackgroundWorker _bgWorker = new BackgroundWorker();
- 为BackgroundWorker添加需要后台执行的工作.
_bgWorker.DoWork += _bgWorker_DoWork;
- 为BackgroundWorker添加后台操作完成事件触发后执行的方法.
_bgWorker.RunWorkerCompleted += _bgWorker_RunWorkerCompleted;
数据持久化
每次从心知天气获得的天气数据都会以文本的形式存储到本地,每当软件重新启动的时候都会自动加载这些历史数据.
/// <summary>
/// save data to json file
/// </summary>
/// <param name="weatherDataJson"></param>
private void SaveDataToJson(string weatherDataJson)
{
using (Stream s = File.Open("data.Json", FileMode.Create))
{
using (StreamWriter sw = new StreamWriter(s))
{
sw.WriteLine(weatherDataJson);
}
}
}
/// <summary>
/// Load data from json
/// </summary>
/// <returns>json string</returns>
private string LoadDataFromJson()
{
using (Stream s = File.OpenRead("data.Json"))
{
using (StreamReader sr = new StreamReader(s))
{
return sr.ReadToEnd();
}
}
}
项目完整代码,欢迎Star😎