VS2017 中 Xamarin 的演练 – 第二部分
将 Microsoft Xamarin 天气教程提升到更高水平的第二部分。
引言
在第一部分中,我们创建了一个基本的应用程序来检索给定邮政编码的天气信息 - 请参阅 https://codeproject.org.cn/Articles/1191947/Walkthrough-of-Xamarin-in-VS2017-Part-One
....
下一步是什么? 目前,该应用程序使用代码隐藏 (C#) 从输入/编辑框中获取邮政编码,并将结果分配给文本框。 然而,在现代应用程序中,我们期望将这些文本属性绑定到我们的数据类,并让系统在数据更改时更新屏幕。
我们将更新应用程序以使用类似于 MVVM 的设计模式来使用 XAML 绑定。
首先,我们需要更新 Core.cs 以将视图(屏幕 XAML)链接到 ViewModel(core.cs)。
using System.ComponentModel;
...
public class Core : INotifyPropertyChanged
并添加属性更改实现
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
=> 此声明和代码允许框架在底层数据更改时更新我们的屏幕。
并添加一个变量来保存邮政编码以及访问该变量的属性
private string _ZipCode;
public string ZipCode
{ get
{ return _ZipCode; }
set
{ _ZipCode = value; }
}
现在,我们更新 (MainPage) XAML 以填充邮政编码。 首先,添加一个名为 vm 的 ViewModel 引用。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WeatherApp"
xmlns:vm="clr-namespace:WeatherApp"
x:Class="WeatherApp.MainPage">
并添加一个 BindingContext 到 Core.cs 类
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WeatherApp"
xmlns:vm="clr-namespace:WeatherApp"
x:Class="WeatherApp.MainPage">
<ContentPage.BindingContext>
<vm:Core/>
</ContentPage.BindingContext>
最后,更新编辑框的 Text 属性以绑定到 Core 类中的 ZipCode 属性。
<StackLayout Orientation="Horizontal" VerticalOptions="Start">
<Entry WidthRequest="100" x:Name="edtZipCode" VerticalOptions="Start" Text="{Binding ZipCode, Mode=TwoWay}"/>
<Button Text="Get Weather" x:Name="btnGetWeather" VerticalOptions="Start"/>
</StackLayout>
此时,应用程序没有使用绑定的数据,但是可以通过在 ZipCode 属性的 get/set 方法上设置断点并运行来检查它是否有效。
在 Core.cs 中添加一个新的变量来保存任何错误状态和 Weather 类的一个实例
public string ErrorMessage { get; set; }
private Weather weather = new Weather();
将 GetWeather 函数更改为私有并添加第二个参数;
private static async Task<Weather> GetWeather(string pZipCode, string pErrorMessage)
并添加一个新的过程来调用原始函数。 由于我们没有设置单个参数,我们需要调用 OnPropertyChanged 事件来更新屏幕
public async void GetWeather()
{
weather = await GetWeather(ZipCode, ErrorMesage);
OnPropertyChanged("Title");
OnPropertyChanged("Temperature");
OnPropertyChanged("Wind");
OnPropertyChanged("Humidity");
OnPropertyChanged("Visibility");
OnPropertyChanged("Sunrise");
OnPropertyChanged("Sunset");
}
为我们要显示的每个值添加属性
public string Title
{
get
{
return weather.Title;
}
set
{
weather.Title = value;
OnPropertyChanged("Title");
}
}
public string Temperature
{
get
{
return weather.Temperature;
}
set
{
weather.Temperature = value;
OnPropertyChanged("Temperature");
}
}
public string Wind
{
get
{
return weather.Wind;
}
set
{
weather.Wind = value;
OnPropertyChanged("Wind");
}
}
public string Humidity
{
get
{
return weather.Humidity;
}
set
{
weather.Humidity = value;
OnPropertyChanged("Humidity");
}
}
public string Visibility
{
get
{
return weather.Visibility;
}
set
{
weather.Visibility = value;
OnPropertyChanged("Visibility");
}
}
public string Sunrise
{
get
{
return weather.Sunrise;
}
set
{
weather.Sunrise = value;
OnPropertyChanged("Sunrise");
}
}
public string Sunset
{
get
{
return weather.Sunset;
}
set
{
weather.Sunset = value;
OnPropertyChanged("Sunset");
}
}
将显示结果的 XAML 字段绑定到这些新属性
<StackLayout VerticalOptions="StartAndExpand">
<Label Text ="Location" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Title}" Margin="10,0,0,10" x:Name="txtLocation"/>
<Label Text ="Temperature" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Temperature}" Margin="10,0,0,10" x:Name="txtTemperature"/>
<Label Text ="Wind Speed" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Wind}" Margin="10,0,0,10" x:Name="txtWind"/>
<Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Humidity}" Margin="10,0,0,10" x:Name="txtHumidity"/>
<Label Text ="Visibility" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Visibility}" Margin="10,0,0,10" x:Name="txtVisibility"/>
<Label Text ="Sunrise" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Sunrise}" Margin="10,0,0,10" x:Name="txtSunrise"/>
<Label Text ="Sunset" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Sunset}" Margin="10,0,0,10" x:Name="txtSunset"/>
</StackLayout>
在 MainPage.xaml.cs 中删除 代码隐藏链接的 按钮单击事件
public MainPage()
{
InitializeComponent();
//btnGetWeather.Clicked += btnGetWeather_Click;
}
在 MainPage.xaml 中绑定到 XAML 中的单击事件
<Button Text="Get Weather" x:Name="btnGetWeather" VerticalOptions="Start" Clicked="BtnGetWeather_Click"/>
最后,更新按钮单击事件以删除填充屏幕字段的代码隐藏并调用新的 GetWeather 过程
private async void btnGetWeather_Click(object sender, EventArgs e)
{
//if (!String.IsNullOrEmpty(edtZipCode.Text))
//{
// Weather weather = await Core.GetWeather(edtZipCode.Text);
// if (weather != null)
// {
// txtLocation.Text = weather.Title;
// txtTemperature.Text = weather.Temperature;
// txtWind.Text = weather.Wind;
// txtVisibility.Text = weather.Visibility;
// txtHumidity.Text = weather.Humidity;
// txtSunrise.Text = weather.Sunrise;
// txtSunset.Text = weather.Sunset;
// btnGetWeather.Text = "Search Again";
// }
//}
if (!String.IsNullOrEmpty(edtZipCode.Text))
((Core)BindingContext).GetWeather();
}
已经应用了很多更改,所以这是我们当前应用程序的列表
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WeatherApp"
xmlns:vm="clr-namespace:WeatherApp"
x:Class="WeatherApp.MainPage">
<ContentPage.BindingContext>
<vm:Core/>
</ContentPage.BindingContext>
<StackLayout>
<StackLayout Margin="10,0,0,0" VerticalOptions="Start" HorizontalOptions="Start" WidthRequest="400" BackgroundColor="#545454">
<Label Text="Weather App" x:Name="lblTitle"/>
<StackLayout HorizontalOptions="Start" Margin="10,10,0,0" VerticalOptions="Start" WidthRequest="400">
<Label Text="Search by Zip Code" FontAttributes="Bold" TextColor="White" Margin="10" x:Name="lblSearchCriteria" VerticalOptions="Start"/>
<Label Text="Zip Code" TextColor="White" Margin="10" x:Name="lblZipCode"/>
<StackLayout Orientation="Horizontal" VerticalOptions="Start">
<Entry WidthRequest="100" x:Name="edtZipCode" VerticalOptions="Start" Text="{Binding ZipCode, Mode=TwoWay}"/>
<Button Text="Get Weather" x:Name="btnGetWeather" VerticalOptions="Start" Clicked="btnGetWeather_Click"/>
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout VerticalOptions="StartAndExpand">
<Label Text ="Location" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Title}" Margin="10,0,0,10" x:Name="txtLocation"/>
<Label Text ="Temperature" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Temperature}" Margin="10,0,0,10" x:Name="txtTemperature"/>
<Label Text ="Wind Speed" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Wind}" Margin="10,0,0,10" x:Name="txtWind"/>
<Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Humidity}" Margin="10,0,0,10" x:Name="txtHumidity"/>
<Label Text ="Visibility" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Visibility}" Margin="10,0,0,10" x:Name="txtVisibility"/>
<Label Text ="Sunrise" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Sunrise}" Margin="10,0,0,10" x:Name="txtSunrise"/>
<Label Text ="Sunset" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Sunset}" Margin="10,0,0,10" x:Name="txtSunset"/>
</StackLayout>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
using System;
using Xamarin.Forms;
namespace WeatherApp
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void BtnGetWeather_Click(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(edtZipCode.Text))
((Core)BindingContext).GetWeather();
}
}
}
Core.cs
using System;
using System.ComponentModel;
using System.Threading.Tasks;
namespace WeatherApp
{
public class Core: INotifyPropertyChanged
{
private string _ZipCode;
public string ZipCode
{ get
{ return _ZipCode; }
set
{ _ZipCode = value; }
}
public string ErrorMesage { get; set; }
private Weather weather = new Weather();
public async void GetWeather()
{
weather = await Core.GetWeather(ZipCode, ErrorMesage);
OnPropertyChanged("Title");
OnPropertyChanged("Temperature");
OnPropertyChanged("Wind");
OnPropertyChanged("Humidity");
OnPropertyChanged("Visibility");
OnPropertyChanged("Sunrise");
OnPropertyChanged("Sunset");
}
public string Title
{
get
{
return weather.Title;
}
set
{
weather.Title = value;
OnPropertyChanged("Title");
}
}
public string Temperature
{
get
{
return weather.Temperature;
}
set
{
weather.Temperature = value;
OnPropertyChanged("Temperature");
}
}
public string Wind
{
get
{
return weather.Wind;
}
set
{
weather.Wind = value;
OnPropertyChanged("Wind");
}
}
public string Humidity
{
get
{
return weather.Humidity;
}
set
{
weather.Humidity = value;
OnPropertyChanged("Humidity");
}
}
public string Visibility
{
get
{
return weather.Visibility;
}
set
{
weather.Visibility = value;
OnPropertyChanged("Visibility");
}
}
public string Sunrise
{
get
{
return weather.Sunrise;
}
set
{
weather.Sunrise = value;
OnPropertyChanged("Sunrise");
}
}
public string Sunset
{
get
{
return weather.Sunset;
}
set
{
weather.Sunset = value;
OnPropertyChanged("Sunset");
}
}
public static async Task<Weather> GetWeather(string pZipCode, string pErrorMessage)
{
//Sign up for a free API key at http://openweathermap.org/appid
string key = "f3748390cfea7374d3fb0580af0cf4ae";
string queryString = "http://api.openweathermap.org/data/2.5/weather?zip="
+ pZipCode + ",us&appid=" + key + "&units=imperial";
//Make sure developers running this sample replaced the API key
if (key != "f3748390cfea7374d3fb0580af0cf4ae")
{
pErrorMessage = "You must obtain an API key from openweathermap.org/appid and save it in the 'key' variable.";
return null;
}
try
{
dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);
if (results["weather"] != null)
{
Weather weather = new Weather();
weather.Title = (string)results["name"];
weather.Temperature = (string)results["main"]["temp"] + " F";
weather.Wind = (string)results["wind"]["speed"] + " mph";
weather.Humidity = (string)results["main"]["humidity"] + " %";
weather.Visibility = (string)results["weather"][0]["main"];
DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
DateTime sunrise = time.AddSeconds((double)results["sys"]["sunrise"]);
DateTime sunset = time.AddSeconds((double)results["sys"]["sunset"]);
weather.Sunrise = sunrise.ToString() + " UTC";
weather.Sunset = sunset.ToString() + " UTC";
return weather;
}
else
{
pErrorMessage = (string)results["message"];
return null;
}
}
catch (Exception ex)
{
pErrorMessage = ex.Message;
return null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
就这样,第二部分到此结束。从功能上讲,该应用程序与第一部分结束时没有更好或不同的表现 - 但我们的代码现在正在使用 Xamarin 应用程序中预期的现代技术。
让我们在第三部分中继续改进该应用程序 (https://codeproject.org.cn/Articles/1192813/Walkthrough-for-Xamarin-in-VS2017-Part-Three)。