65.9K
CodeProject 正在变化。 阅读更多。
Home

VS2017 中 Xamarin 的演练 - 第三部分

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.29/5 (4投票s)

2017年9月13日

CPOL

12分钟阅读

viewsIcon

15191

第三部分将继续扩展和现代化功能。

this.Title = default(string);
    this.Temperature = default(double);
    this.Wind = default(string);
    this.Humidity = default(string);
    this.Visibility = default(string);
    this.Sunrise = default(string);
    this.Sunset = default(string);
  DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

  return new Weather()
  {
    Title = (string)results["name"],
    Temperature = (string)results["main"]["temp"] + " F",
    Wind = (string)results["wind"]["speed"] + " mph",
    Humidity = (string)results["main"]["humidity"] + " %",
    Visibility = (string)results["weather"][0]["main"],
    Sunrise = $"{time.AddSeconds((double)results["sys"]["sunrise"]).ToString()} UTC",
    Sunset = $"{time.AddSeconds((double)results["sys"]["sunset"]).ToString()} UTC"
  };
}
else
  return null;

引言

在第二部分(https://codeproject.org.cn/Articles/1192180/Walkthrough-for-Xamarin-in-VS2017-Part-Two),我们已将应用程序更新为使用 XAML 而非代码隐藏。

在本文中,我们将开始扩展我们的应用程序,使其成为一个真正的程序,而不是演示代码。

我建议您进入 NuGet 并确保所有包都已更新(工具、NuGet 包管理器、解决方案的 NuGet 包管理器。更新选项卡)。特别是 Xamarin 控件的更新非常频繁。我们将使用 Picker(即 ComboBox),它最近才更新为支持数据绑定。

添加国家

并非所有人都住在 USA。天气网站允许我们将国家/地区作为可选参数指定。

不幸的是,天气网站没有获取国家/地区代码列表的方法,而是指向 ISO 标准 ISO 3166(https://www.openweathermap.org/current)。如果您搜索此 ISO 代码,您会找到国家/地区及其关联代码的列表,我们将把这些代码转换为一个字典。

在解决方案资源管理器中选择 WeatherApp 项目。右键单击,然后选择添加,然后选择类,并将您的新类命名为 Country.cs

using System.Collections.ObjectModel;

namespace WeatherApp
{
  public class Country
  {
    public string CountryCode;
    public string CountryName;

    //This returns the the CountryName (CountryCode) instead of WeatherApp.Country, which is used for display in the Country Picker drop-down.
    public override string ToString() => $"{CountryName} ({CountryCode})";

    public static ObservableCollection<Country> Init()
    {
      ObservableCollection<Country> CountryList = new ObservableCollection<Country>();
     
      CountryList.Add(new Country() { CountryCode = "AF", CountryName = "Afgahanistan"});
      CountryList.Add(new Country() { CountryCode = "AX", CountryName = "Åland Islands"});
      CountryList.Add(new Country() { CountryCode = "AL", CountryName = "Albania"});
      CountryList.Add(new Country() { CountryCode = "DZ", CountryName = "Algeria"});
      CountryList.Add(new Country() { CountryCode = "AS", CountryName = "American Samoa"});
      CountryList.Add(new Country() { CountryCode = "AD", CountryName = "Andorra"});
      CountryList.Add(new Country() { CountryCode = "AO", CountryName = "Angola"});
      CountryList.Add(new Country() { CountryCode = "AI", CountryName = "Anguilla"});
      CountryList.Add(new Country() { CountryCode = "AQ", CountryName = "Antarctica"});
      CountryList.Add(new Country() { CountryCode = "AG", CountryName = "Antigua and Barbuda"});
      CountryList.Add(new Country() { CountryCode = "AR", CountryName = "Argentina"});
      CountryList.Add(new Country() { CountryCode = "AM", CountryName = "Armenia"});
      CountryList.Add(new Country() { CountryCode = "AW", CountryName = "Aruba"});
      CountryList.Add(new Country() { CountryCode = "AU", CountryName = "Australia"});
      CountryList.Add(new Country() { CountryCode = "AT", CountryName = "Austria"});
      CountryList.Add(new Country() { CountryCode = "AZ", CountryName = "Azerbaijan"});
      CountryList.Add(new Country() { CountryCode = "BS", CountryName = "Bahamas"});
      CountryList.Add(new Country() { CountryCode = "BH", CountryName = "Bahrain"});
      CountryList.Add(new Country() { CountryCode = "BD", CountryName = "Bangladesh"});
      CountryList.Add(new Country() { CountryCode = "BB", CountryName = "Barbados"});
      CountryList.Add(new Country() { CountryCode = "BY", CountryName = "Belarus"});
      CountryList.Add(new Country() { CountryCode = "BE", CountryName = "Belgium"});
      CountryList.Add(new Country() { CountryCode = "BZ", CountryName = "Belize"});
      CountryList.Add(new Country() { CountryCode = "BJ", CountryName = "Benin"});
      CountryList.Add(new Country() { CountryCode = "BM", CountryName = "Bermuda"});
      CountryList.Add(new Country() { CountryCode = "BT", CountryName = "Bhutan"});
      CountryList.Add(new Country() { CountryCode = "BO", CountryName = "Bolivia, Plurinational State of"});
      CountryList.Add(new Country() { CountryCode = "BQ", CountryName = "Bonaire, Sint Eustatius and Saba"});
      CountryList.Add(new Country() { CountryCode = "BA", CountryName = "Bosnia and Herzegovina"});
      CountryList.Add(new Country() { CountryCode = "BW", CountryName = "Botswana"});
      CountryList.Add(new Country() { CountryCode = "BV", CountryName = "Bouvet Island"});
      CountryList.Add(new Country() { CountryCode = "BR", CountryName = "Brazil"});
      CountryList.Add(new Country() { CountryCode = "IO", CountryName = "British Indian Ocean Territory"});
      CountryList.Add(new Country() { CountryCode = "BN", CountryName = "Brunei Darussalam"});
      CountryList.Add(new Country() { CountryCode = "BG", CountryName = "Bulgaria"});
      CountryList.Add(new Country() { CountryCode = "BF", CountryName = "Burkina Faso"});
      CountryList.Add(new Country() { CountryCode = "BI", CountryName = "Burundi"});
      CountryList.Add(new Country() { CountryCode = "KH", CountryName = "Cambodia"});
      CountryList.Add(new Country() { CountryCode = "CM", CountryName = "Cameroon"});
      CountryList.Add(new Country() { CountryCode = "CA", CountryName = "Canada"});
      CountryList.Add(new Country() { CountryCode = "CV", CountryName = "Cape Verde"});
      CountryList.Add(new Country() { CountryCode = "KY", CountryName = "Cayman Islands"});
      CountryList.Add(new Country() { CountryCode = "CF", CountryName = "Central African Republic"});
      CountryList.Add(new Country() { CountryCode = "TD", CountryName = "Chad"});
      CountryList.Add(new Country() { CountryCode = "CL", CountryName = "Chile"});
      CountryList.Add(new Country() { CountryCode = "CN", CountryName = "China"});
      CountryList.Add(new Country() { CountryCode = "CX", CountryName = "Christmas Island"});
      CountryList.Add(new Country() { CountryCode = "CC", CountryName = "Cocos (Keeling) Islands"});
      CountryList.Add(new Country() { CountryCode = "CO", CountryName = "Colombia"});
      CountryList.Add(new Country() { CountryCode = "KM", CountryName = "Comoros"});
      CountryList.Add(new Country() { CountryCode = "CG", CountryName = "Congo"});
      CountryList.Add(new Country() { CountryCode = "CD", CountryName = "Congo, the Democratic Republic of the"});
      CountryList.Add(new Country() { CountryCode = "CK", CountryName = "Cook Islands"});
      CountryList.Add(new Country() { CountryCode = "CR", CountryName = "Costa Rica"});
      CountryList.Add(new Country() { CountryCode = "CI", CountryName = "Côte d'Ivoire"});
      CountryList.Add(new Country() { CountryCode = "HR", CountryName = "Croatia"});
      CountryList.Add(new Country() { CountryCode = "CU", CountryName = "Cuba"});
      CountryList.Add(new Country() { CountryCode = "CW", CountryName = "Curaçao"});
      CountryList.Add(new Country() { CountryCode = "CY", CountryName = "Cyprus"});
      CountryList.Add(new Country() { CountryCode = "CZ", CountryName = "Czech Republic"});
      CountryList.Add(new Country() { CountryCode = "DK", CountryName = "Denmark"});
      CountryList.Add(new Country() { CountryCode = "DJ", CountryName = "Djibouti"});
      CountryList.Add(new Country() { CountryCode = "DM", CountryName = "Dominica"});
      CountryList.Add(new Country() { CountryCode = "DO", CountryName = "Dominican Republic"});
      CountryList.Add(new Country() { CountryCode = "EC", CountryName = "Ecuador"});
      CountryList.Add(new Country() { CountryCode = "EG", CountryName = "Egypt"});
      CountryList.Add(new Country() { CountryCode = "SV", CountryName = "El Salvador"});
      CountryList.Add(new Country() { CountryCode = "GQ", CountryName = "Equatorial Guinea"});
      CountryList.Add(new Country() { CountryCode = "ER", CountryName = "Eritrea"});
      CountryList.Add(new Country() { CountryCode = "EE", CountryName = "Estonia"});
      CountryList.Add(new Country() { CountryCode = "ET", CountryName = "Ethiopia"});
      CountryList.Add(new Country() { CountryCode = "FK", CountryName = "Falkland Islands (Malvinas)"});
      CountryList.Add(new Country() { CountryCode = "FO", CountryName = "Faroe Islands"});
      CountryList.Add(new Country() { CountryCode = "FJ", CountryName = "Fiji"});
      CountryList.Add(new Country() { CountryCode = "FI", CountryName = "Finland"});
      CountryList.Add(new Country() { CountryCode = "FR", CountryName = "France"});
      CountryList.Add(new Country() { CountryCode = "GF", CountryName = "French Guiana"});
      CountryList.Add(new Country() { CountryCode = "PF", CountryName = "French Polynesia"});
      CountryList.Add(new Country() { CountryCode = "TF", CountryName = "French Southern Territories"});
      CountryList.Add(new Country() { CountryCode = "GA", CountryName = "Gabon"});
      CountryList.Add(new Country() { CountryCode = "GM", CountryName = "Gambia"});
      CountryList.Add(new Country() { CountryCode = "GE", CountryName = "Georgia"});
      CountryList.Add(new Country() { CountryCode = "DE", CountryName = "Germany"});
      CountryList.Add(new Country() { CountryCode = "GH", CountryName = "Ghana"});
      CountryList.Add(new Country() { CountryCode = "GI", CountryName = "Gibraltar"});
      CountryList.Add(new Country() { CountryCode = "GR", CountryName = "Greece"});
      CountryList.Add(new Country() { CountryCode = "GL", CountryName = "Greenland"});
      CountryList.Add(new Country() { CountryCode = "GD", CountryName = "Grenada"});
      CountryList.Add(new Country() { CountryCode = "GP", CountryName = "Guadeloupe"});
      CountryList.Add(new Country() { CountryCode = "GU", CountryName = "Guam"});
      CountryList.Add(new Country() { CountryCode = "GT", CountryName = "Guatemala"});
      CountryList.Add(new Country() { CountryCode = "GG", CountryName = "Guernsey"});
      CountryList.Add(new Country() { CountryCode = "GN", CountryName = "Guinea"});
      CountryList.Add(new Country() { CountryCode = "GW", CountryName = "Guinea-Bissau"});
      CountryList.Add(new Country() { CountryCode = "GY", CountryName = "Guyana"});
      CountryList.Add(new Country() { CountryCode = "HT", CountryName = "Haiti"});
      CountryList.Add(new Country() { CountryCode = "HM", CountryName = "Heard Island and McDonald Islands"});
      CountryList.Add(new Country() { CountryCode = "VA", CountryName = "Holy See (Vatican City State)"});
      CountryList.Add(new Country() { CountryCode = "HN", CountryName = "Honduras"});
      CountryList.Add(new Country() { CountryCode = "HK", CountryName = "Hong Kong"});
      CountryList.Add(new Country() { CountryCode = "HU", CountryName = "Hungary"});
      CountryList.Add(new Country() { CountryCode = "IS", CountryName = "Iceland"});
      CountryList.Add(new Country() { CountryCode = "IN", CountryName = "India"});
      CountryList.Add(new Country() { CountryCode = "ID", CountryName = "Indonesia"});
      CountryList.Add(new Country() { CountryCode = "IR", CountryName = "Iran, Islamic Republic of"});
      CountryList.Add(new Country() { CountryCode = "IQ", CountryName = "Iraq"});
      CountryList.Add(new Country() { CountryCode = "IE", CountryName = "Ireland"});
      CountryList.Add(new Country() { CountryCode = "IM", CountryName = "Isle of Man"});
      CountryList.Add(new Country() { CountryCode = "IL", CountryName = "Israel"});
      CountryList.Add(new Country() { CountryCode = "IT", CountryName = "Italy"});
      CountryList.Add(new Country() { CountryCode = "JM", CountryName = "Jamaica"});
      CountryList.Add(new Country() { CountryCode = "JP", CountryName = "Japan"});
      CountryList.Add(new Country() { CountryCode = "JE", CountryName = "Jersey"});
      CountryList.Add(new Country() { CountryCode = "JO", CountryName = "Jordan"});
      CountryList.Add(new Country() { CountryCode = "KZ", CountryName = "Kazakhstan"});
      CountryList.Add(new Country() { CountryCode = "KE", CountryName = "Kenya"});
      CountryList.Add(new Country() { CountryCode = "KI", CountryName = "Kiribati"});
      CountryList.Add(new Country() { CountryCode = "KP", CountryName = "Korea, Democratic People's Republic of"});
      CountryList.Add(new Country() { CountryCode = "KR", CountryName = "Korea, Republic of"});
      CountryList.Add(new Country() { CountryCode = "KW", CountryName = "Kuwait"});
      CountryList.Add(new Country() { CountryCode = "KG", CountryName = "Kyrgyzstan"});
      CountryList.Add(new Country() { CountryCode = "LA", CountryName = "Lao People's Democratic Republic"});
      CountryList.Add(new Country() { CountryCode = "LV", CountryName = "Latvia"});
      CountryList.Add(new Country() { CountryCode = "LB", CountryName = "Lebanon"});
      CountryList.Add(new Country() { CountryCode = "LS", CountryName = "Lesotho"});
      CountryList.Add(new Country() { CountryCode = "LR", CountryName = "Liberia"});
      CountryList.Add(new Country() { CountryCode = "LY", CountryName = "Libya"});
      CountryList.Add(new Country() { CountryCode = "LI", CountryName = "Liechtenstein"});
      CountryList.Add(new Country() { CountryCode = "LT", CountryName = "Lithuania"});
      CountryList.Add(new Country() { CountryCode = "LU", CountryName = "Luxembourg"});
      CountryList.Add(new Country() { CountryCode = "MO", CountryName = "Macao"});
      CountryList.Add(new Country() { CountryCode = "MK", CountryName = "Macedonia, the Former Yugoslav Republic of"});
      CountryList.Add(new Country() { CountryCode = "MG", CountryName = "Madagascar"});
      CountryList.Add(new Country() { CountryCode = "MW", CountryName = "Malawi"});
      CountryList.Add(new Country() { CountryCode = "MY", CountryName = "Malaysia"});
      CountryList.Add(new Country() { CountryCode = "MV", CountryName = "Maldives"});
      CountryList.Add(new Country() { CountryCode = "ML", CountryName = "Mali"});
      CountryList.Add(new Country() { CountryCode = "MT", CountryName = "Malta"});
      CountryList.Add(new Country() { CountryCode = "MH", CountryName = "Marshall Islands"});
      CountryList.Add(new Country() { CountryCode = "MQ", CountryName = "Martinique"});
      CountryList.Add(new Country() { CountryCode = "MR", CountryName = "Mauritania"});
      CountryList.Add(new Country() { CountryCode = "MU", CountryName = "Mauritius"});
      CountryList.Add(new Country() { CountryCode = "YT", CountryName = "Mayotte"});
      CountryList.Add(new Country() { CountryCode = "MX", CountryName = "Mexico"});
      CountryList.Add(new Country() { CountryCode = "FM", CountryName = "Micronesia, Federated States of"});
      CountryList.Add(new Country() { CountryCode = "MD", CountryName = "Moldova, Republic of"});
      CountryList.Add(new Country() { CountryCode = "MC", CountryName = "Monaco"});
      CountryList.Add(new Country() { CountryCode = "MN", CountryName = "Mongolia"});
      CountryList.Add(new Country() { CountryCode = "ME", CountryName = "Montenegro"});
      CountryList.Add(new Country() { CountryCode = "MS", CountryName = "Montserrat"});
      CountryList.Add(new Country() { CountryCode = "MA", CountryName = "Morocco"});
      CountryList.Add(new Country() { CountryCode = "MZ", CountryName = "Mozambique"});
      CountryList.Add(new Country() { CountryCode = "MM", CountryName = "Myanmar"});
      CountryList.Add(new Country() { CountryCode = "NA", CountryName = "Namibia"});
      CountryList.Add(new Country() { CountryCode = "NR", CountryName = "Nauru"});
      CountryList.Add(new Country() { CountryCode = "NP", CountryName = "Nepal"});
      CountryList.Add(new Country() { CountryCode = "NL", CountryName = "Netherlands"});
      CountryList.Add(new Country() { CountryCode = "NC", CountryName = "New Caledonia"});
      CountryList.Add(new Country() { CountryCode = "NZ", CountryName = "New Zealand"});
      CountryList.Add(new Country() { CountryCode = "NI", CountryName = "Nicaragua"});
      CountryList.Add(new Country() { CountryCode = "NE", CountryName = "Niger"});
      CountryList.Add(new Country() { CountryCode = "NG", CountryName = "Nigeria"});
      CountryList.Add(new Country() { CountryCode = "NU", CountryName = "Niue"});
      CountryList.Add(new Country() { CountryCode = "NF", CountryName = "Norfolk Island"});
      CountryList.Add(new Country() { CountryCode = "MP", CountryName = "Northern Mariana Islands"});
      CountryList.Add(new Country() { CountryCode = "NO", CountryName = "Norway"});
      CountryList.Add(new Country() { CountryCode = "OM", CountryName = "Oman"});
      CountryList.Add(new Country() { CountryCode = "PK", CountryName = "Pakistan"});
      CountryList.Add(new Country() { CountryCode = "PW", CountryName = "Palau"});
      CountryList.Add(new Country() { CountryCode = "PS", CountryName = "Palestine, State of"});
      CountryList.Add(new Country() { CountryCode = "PA", CountryName = "Panama"});
      CountryList.Add(new Country() { CountryCode = "PG", CountryName = "Papua New Guinea"});
      CountryList.Add(new Country() { CountryCode = "PY", CountryName = "Paraguay"});
      CountryList.Add(new Country() { CountryCode = "PE", CountryName = "Peru"});
      CountryList.Add(new Country() { CountryCode = "PH", CountryName = "Philippines"});
      CountryList.Add(new Country() { CountryCode = "PN", CountryName = "Pitcairn"});
      CountryList.Add(new Country() { CountryCode = "PL", CountryName = "Poland"});
      CountryList.Add(new Country() { CountryCode = "PT", CountryName = "Portugal"});
      CountryList.Add(new Country() { CountryCode = "PR", CountryName = "Puerto Rico"});
      CountryList.Add(new Country() { CountryCode = "QA", CountryName = "Qatar"});
      CountryList.Add(new Country() { CountryCode = "RE", CountryName = "Réunion"});
      CountryList.Add(new Country() { CountryCode = "RO", CountryName = "Romania"});
      CountryList.Add(new Country() { CountryCode = "RU", CountryName = "Russian Federation"});
      CountryList.Add(new Country() { CountryCode = "RW", CountryName = "Rwanda"});
      CountryList.Add(new Country() { CountryCode = "BL", CountryName = "Saint Barthélemy"});
      CountryList.Add(new Country() { CountryCode = "SH", CountryName = "Saint Helena, Ascension and Tristan da Cunha"});
      CountryList.Add(new Country() { CountryCode = "KN", CountryName = "Saint Kitts and Nevis"});
      CountryList.Add(new Country() { CountryCode = "LC", CountryName = "Saint Lucia"});
      CountryList.Add(new Country() { CountryCode = "MF", CountryName = "Saint Martin (French part)"});
      CountryList.Add(new Country() { CountryCode = "PM", CountryName = "Saint Pierre and Miquelon"});
      CountryList.Add(new Country() { CountryCode = "VC", CountryName = "Saint Vincent and the Grenadines"});
      CountryList.Add(new Country() { CountryCode = "WS", CountryName = "Samoa"});
      CountryList.Add(new Country() { CountryCode = "SM", CountryName = "San Marino"});
      CountryList.Add(new Country() { CountryCode = "ST", CountryName = "Sao Tome and Principe"});
      CountryList.Add(new Country() { CountryCode = "SA", CountryName = "Saudi Arabia"});
      CountryList.Add(new Country() { CountryCode = "SN", CountryName = "Senegal"});
      CountryList.Add(new Country() { CountryCode = "RS", CountryName = "Serbia"});
      CountryList.Add(new Country() { CountryCode = "SC", CountryName = "Seychelles"});
      CountryList.Add(new Country() { CountryCode = "SL", CountryName = "Sierra Leone"});
      CountryList.Add(new Country() { CountryCode = "SG", CountryName = "Singapore"});
      CountryList.Add(new Country() { CountryCode = "SX", CountryName = "Sint Maarten (Dutch part)"});
      CountryList.Add(new Country() { CountryCode = "SK", CountryName = "Slovakia"});
      CountryList.Add(new Country() { CountryCode = "SI", CountryName = "Slovenia"});
      CountryList.Add(new Country() { CountryCode = "SB", CountryName = "Solomon Islands"});
      CountryList.Add(new Country() { CountryCode = "SO", CountryName = "Somalia"});
      CountryList.Add(new Country() { CountryCode = "ZA", CountryName = "South Africa"});
      CountryList.Add(new Country() { CountryCode = "GS", CountryName = "South Georgia and the South Sandwich Islands"});
      CountryList.Add(new Country() { CountryCode = "SS", CountryName = "South Sudan"});
      CountryList.Add(new Country() { CountryCode = "ES", CountryName = "Spain"});
      CountryList.Add(new Country() { CountryCode = "LK", CountryName = "Sri Lanka"});
      CountryList.Add(new Country() { CountryCode = "SD", CountryName = "Sudan"});
      CountryList.Add(new Country() { CountryCode = "SR", CountryName = "Suriname"});
      CountryList.Add(new Country() { CountryCode = "SJ", CountryName = "Svalbard and Jan Mayen"});
      CountryList.Add(new Country() { CountryCode = "SZ", CountryName = "Swaziland"});
      CountryList.Add(new Country() { CountryCode = "SE", CountryName = "Sweden"});
      CountryList.Add(new Country() { CountryCode = "CH", CountryName = "Switzerland"});
      CountryList.Add(new Country() { CountryCode = "SY", CountryName = "Syrian Arab Republic"});
      CountryList.Add(new Country() { CountryCode = "TW", CountryName = "Taiwan, Province of China"});
      CountryList.Add(new Country() { CountryCode = "TJ", CountryName = "Tajikistan"});
      CountryList.Add(new Country() { CountryCode = "TZ", CountryName = "Tanzania, United Republic of"});
      CountryList.Add(new Country() { CountryCode = "TH", CountryName = "Thailand"});
      CountryList.Add(new Country() { CountryCode = "TL", CountryName = "Timor-Leste"});
      CountryList.Add(new Country() { CountryCode = "TG", CountryName = "Togo"});
      CountryList.Add(new Country() { CountryCode = "TK", CountryName = "Tokelau"});
      CountryList.Add(new Country() { CountryCode = "TO", CountryName = "Tonga"});
      CountryList.Add(new Country() { CountryCode = "TT", CountryName = "Trinidad and Tobago"});
      CountryList.Add(new Country() { CountryCode = "TN", CountryName = "Tunisia"});
      CountryList.Add(new Country() { CountryCode = "TR", CountryName = "Turkey"});
      CountryList.Add(new Country() { CountryCode = "TM", CountryName = "Turkmenistan"});
      CountryList.Add(new Country() { CountryCode = "TC", CountryName = "Turks and Caicos Islands"});
      CountryList.Add(new Country() { CountryCode = "TV", CountryName = "Tuvalu"});
      CountryList.Add(new Country() { CountryCode = "UG", CountryName = "Uganda"});
      CountryList.Add(new Country() { CountryCode = "UA", CountryName = "Ukraine"});
      CountryList.Add(new Country() { CountryCode = "AE", CountryName = "United Arab Emirates"});
      CountryList.Add(new Country() { CountryCode = "GB", CountryName = "United Kingdom"});
      CountryList.Add(new Country() { CountryCode = "US", CountryName = "United States"});
      CountryList.Add(new Country() { CountryCode = "UM", CountryName = "United States Minor Outlying Islands"});
      CountryList.Add(new Country() { CountryCode = "UY", CountryName = "Uruguay"});
      CountryList.Add(new Country() { CountryCode = "UZ", CountryName = "Uzbekistan"});
      CountryList.Add(new Country() { CountryCode = "VU", CountryName = "Vanuatu"});
      CountryList.Add(new Country() { CountryCode = "VE", CountryName = "Venezuela, Bolivarian Republic of"});
      CountryList.Add(new Country() { CountryCode = "VN", CountryName = "Viet Nam"});
      CountryList.Add(new Country() { CountryCode = "VG", CountryName = "Virgin Islands, British"});
      CountryList.Add(new Country() { CountryCode = "VI", CountryName = "Virgin Islands, U.S."});
      CountryList.Add(new Country() { CountryCode = "WF", CountryName = "Wallis and Futuna"});
      CountryList.Add(new Country() { CountryCode = "EH", CountryName = "Western Sahara"});
      CountryList.Add(new Country() { CountryCode = "YE", CountryName = "Yemen,YE"});
      CountryList.Add(new Country() { CountryCode = "ZM", CountryName = "Zambia"});
      CountryList.Add(new Country() { CountryCode = "ZW", CountryName = "Zimbabwe" });

      return CountryList;
    }
  }
}

打开 Core.cs,然后转到 async Task<Weather> GetWeather 方法。

首先,我们需要更新此函数以接受额外的 Country 参数

public static async Task<Weather> GetWeather(string pZipCode, string pCountryCode)
{

为了向后兼容,我们不应假定 Country Code 已填充。检查 Country Code 是否为空或 null,如果是,则像以前一样调用网站。如果设置了 Country Code,则将其包含在网站调用中以获取其他国家的天气

if (String.IsNullOrEmpty(pCountryCode)) //If Country is not specified, Open Weather Defaults to the US.
  queryString += pZipCode + ",us&appid=" + key + "&units=imperial";
else
  queryString += pZipCode + "," + pCountryCode + "&appid=" + key + "&units=imperial";

上面的代码有效,但有点难读。我们可以使用 C# 6 中添加的字符串插值功能来更新此语法

string queryString = default(String); //"http://api.openweathermap.org/data/2.5/weather?zip=";

if (String.IsNullOrEmpty(pCountryCode)) //If Country is not specified, Open Weather Defaults to the US.
  queryString = $"http://api.openweathermap.org/data/2.5/weather?zip={pZipCode},us&appid={key}&units=imperial";
else
  queryString = $"http://api.openweathermap.org/data/2.5/weather?zip={pZipCode},{pCountryCode}&appid={key}&units=imperial";

在我们进行清理的同时,更新/替换要返回的值的填充

1) 不创建变量来保存详细信息然后 **返回** 该值,而是在 return 语句中直接创建一个新的 Weather() 变量。

2) 不创建变量然后填充它 - 在创建语句中合并值的设置。

3) 最后,再次使用字符串插值来简化 Sunrise 和 Sunset 使用的字符串

if (results["weather"] != null)
{
  DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

  return new Weather()
  {
    Title = (string)results["name"],
    Temperature = (string)results["main"]["temp"] + " F",
    Wind = (string)results["wind"]["speed"] + " mph",
    Humidity = (string)results["main"]["humidity"] + " %",
    Visibility = (string)results["weather"][0]["main"],
    Sunrise = $"{time.AddSeconds((double)results["sys"]["sunrise"]).ToString()} UTC",
    Sunset = $"{time.AddSeconds((double)results["sys"]["sunset"]).ToString()} UTC"
  };
}
else
  return null;

接下来,我们应该清理并更新属性。目前,GetWeather、Title 等属性使用硬编码的字符串。我们将这些字符串(例如“Title”)更新为使用 nameof 函数(nameof(Title))。这样,如果属性名将来更改,数据绑定就不会中断。

此外,我们应该仅在值实际更改时更改值并触发更改事件。根据下方应用到属性的更改

  • GetWeather
  • 标题
  • 温度
  • Wind
  • 湿度
  • Sunrise
  • Sunset

在 GetWeather 方法中,您还需要传递用户选择的国家/地区代码。?. 运算符等同于检查 CountryCode 是否为 null,以防止 Null 异常。

public async void GetWeather()
{
  weather = await Core.GetWeather(ZipCode, SelectedCountry?.CountryCode);

  OnPropertyChanged(nameof(Title));
  OnPropertyChanged(nameof(Temperature));
  OnPropertyChanged(nameof(Wind));
  OnPropertyChanged(nameof(Humidity));
  OnPropertyChanged(nameof(Visibility));
  OnPropertyChanged(nameof(Sunrise));
  OnPropertyChanged(nameof(Sunset));
}

public string Title
{
  get { return weather.Title; }
  set 
  {
    if (weather.Title != value)
    {
       weather.Title = value;
       OnPropertyChanged(nameof(Title));
    }
  }
}

我们还可以改进我们的 OnPropertyChanged 方法,使其具有线程安全性,并防止没有代码侦听我们的事件,这可能会导致异常

protected virtual void OnPropertyChanged(string propertyName)
{
  var changed = PropertyChanged;

  if (changed != null)
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

?. 运算符等同于内联   != null  

现在我们需要公开我们的国家/地区列表,以便在屏幕上显示。仍然在 Core.cs 中,添加一些变量来保存可用的国家/地区和用户选择的国家/地区

using System.Collections.ObjectModel;

namespace WeatherApp
{
  public class Core: INotifyPropertyChanged
  {
    private Country _SelectedCountry;     
    public Country SelectedCountry     
    { get { return _SelectedCountry; }       
      set       
      {          
        if (_SelectedCountry != value)          
        {             
          _SelectedCountry = value;             
          OnPropertyChanged(nameof(SelectedCountry));          
        }       
      }     
    }        
 
    public ObservableCollection<Country> Countries = Country.Init();

不幸的是,Xamarin Picker 的当前版本在 XAML 中指定 DataContext 时似乎不起作用。所以暂时将其注释掉(在 MainPage.xaml 中),并添加一个 Picker 来显示国家/地区列表

<?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"/>
                <Picker x:Name="cbxCountry" WidthRequest="200" SelectedItem="{Binding SelectedCountry, Mode=TwoWay}" SelectedIndex="0" Title="Country" TextColor="White"/>
                <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>
现在在 MainPage.xaml.cs 中,我们需要创建我们的 Binding Context 并将 Picker 链接到我们的国家/地区列表
 
public MainPage()
{
  InitializeComponent();

  BindingContext = new Core();
   
  cbxCountry.ItemsSource = ((Core)this.BindingContext).Countries;
}

ItemsSource *应该* 可以在 XAML 中设置,但同样,它似乎在 Xamarin Picker 的当前版本中不起作用。

如果您现在运行应用程序,应该会有一个额外的下拉列表。如果您选择美国并输入我们的测试邮政编码 80801,程序应该可以像前两次演练一样工作。

但是现在您可以选择另一个国家/地区,例如选择澳大利亚 (AU) 并输入邮政编码 4034

默认值和可用性

我可以让国家/地区列表只显示名称,但是我添加了国家/地区代码。在支持您的应用程序时,拥有标识符可以非常有价值。只是不要让您的用户使用/记住 ID 和唯一代码 - 这非常不友好。

应用程序可以更加用户友好。目前,我们默认为列表中的第一个国家/地区。但实际上,用户通常会搜索他们当前所在国家/地区的天气。正是这些可用性功能让人们喜欢您的应用程序。

在 Core.cs 中添加一个新的过程

public void SelectCountryByISOCode()
{
  foreach (Country item in Countries)
    if (item.CountryCode == System.Globalization.RegionInfo.CurrentRegion.TwoLetterISORegionName)
      SelectedCountry = item;
}

并在 MainPage.xaml.cs 中,添加对我们新过程的调用

public MainPage()
{
  InitializeComponent();

  BindingContext = new Core();
   
  cbxCountry.ItemsSource = ((Core)this.BindingContext).Countries;

  ((Core)this.BindingContext).SelectCountryByISOCode();
}

现在,如果您再次运行应用程序,您的国家/地区应该会默认选中。

为了更好的可用性,我们应该确保用户从“最佳”位置开始,以尽量减少导航量

protected override void OnAppearing()
{
  base.OnAppearing();

  if (cbxCountry.SelectedIndex > -1)
    edtZipCode.Focus();
  else
    cbxCountry.Focus();
}

并且如果用户碰巧在使用支持输入的平台,则触发按钮按下

public MainPage()
{
  InitializeComponent();

  BindingContext = new Core();

  cbxCountry.ItemsSource = ((Core)this.BindingContext).Countries;

  ((Core)this.BindingContext).SelectCountryByISOCode();

  edtZipCode.Completed += (s, e) => btnGetWeather_Click(s,e);
}

开始移除硬编码

Windows 可以为我们提供有关选定区域的信息。在我们的应用程序中,我们感兴趣的是温度单位,华氏度或摄氏度。

在 Core.cs 中,添加一个新变量

 private bool UseMetric;

然后更新 Country 属性以填充此信息

public class Core: INotifyPropertyChanged
{
  private Country _SelectedCountry;
  public Country SelectedCountry
  { get { return _SelectedCountry; }
    set
    {
       if (_SelectedCountry != value)
       {
          _SelectedCountry = value;
          OnPropertyChanged(nameof(SelectedCountry));

         UseMetric = new RegionInfo(SelectedCountry?.CountryCode).IsMetric;

         ZipCode = "";
      }
    }
}

private string _ZipCode;
public string ZipCode
{
  get { return _ZipCode; }
  set
  { 
    if (_ZipCode != value)
    { 
      _ZipCode = value;
      OnPropertyChanged(nameof(ZipCode));
    } 
  }
}


我们还会在更改国家/地区时清除邮政编码,因为格式因国家/地区而异。

在 Core.cs 中添加对 Globalization 功能的访问

using System.Globalization;

 

在 GetWeather 方法中,将此标志发送到获取天气的函数

public async void GetWeather()
{
  weather = await Core.GetWeather(ZipCode, SelectedCountry?.CountryCode, UseMetric);

  OnPropertyChanged(nameof(Title));
  OnPropertyChanged(nameof(Temperature));

在 GetWeather async 方法中,添加参数,添加一个新变量来保存国家/地区是否使用公制或英制测量,然后将该参数作为 queryString 发送到网站

public static async Task<Weather> GetWeather(string pZipCode, string pCountryCode, bool pUseMetric)
{
  //Sign up for a free API key at http://openweathermap.org/appid 
  string key = "f3748390cfea7374d3fb0580af0cf4ae";
  string queryString = default(String);

  //IF pUseMetric, set the unitsString to "metric" else "imperial"
  string unitsString = (pUseMetric ? "metric" : "imperial");

  if (String.IsNullOrEmpty(pCountryCode)) //If Country is not specified, Open Weather Defaults to the US.
    queryString = $"http://api.openweathermap.org/data/2.5/weather?zip={pZipCode},us&appid={key}&units={unitsString}";
  else
    queryString = $"http://api.openweathermap.org/data/2.5/weather?zip={pZipCode},{pCountryCode}&appid={key}&units={unitsString}";

 

最后,我们需要更新结果的填充以使用适当的单位

string temperatureUnits = (pUseMetric ? "C" : "F");
string windUnits = (pUseMetric ? "km/h" : "mph");

  dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

  if (results["weather"] != null)
  {
    DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

    return new Weather()
    {
      Title = (string)results["name"],
      Temperature = $"{(string)results["main"]["temp"]} {temperatureUnits}",
      Wind = $"{(string)results["wind"]["speed"]} {windUnits}",
      Humidity = (string)results["main"]["humidity"] + " %",
      Visibility = (string)results["weather"][0]["main"],
      Sunrise = $"{time.AddSeconds((double)results["sys"]["sunrise"]).ToString()} UTC",
      Sunset = $"{time.AddSeconds((double)results["sys"]["sunset"]).ToString()} UTC"
    };
 }

现在再次运行澳大利亚和美国,您将看到正确的单位正在显示。

清理实现

对于那些看过 MVVM 的人来说,很明显这个程序正在利用这个模型。视觉组件在 View 中,Weather 类是我们的 Model,Core.cs 是我们的 View Model。然而,我们现有程序的一些方面与 MVVM 不一致。接下来,我们将通过将更多 UI 逻辑移到 ViewModel 中来使我们的程序更符合 MVVM 模型。

首先,让我们移除按钮中的硬编码文本。通过将其绑定到我们的 VM,我们可以根据上下文轻松更改显示的文本。

例如,您可能有一个 Modify 按钮,它会弹出一个单独的页面来显示用户编辑的某些数据的完整详细信息。我们希望按钮文本显示“Modify”。但是,如果数据可以被冻结呢?在这种情况下,您将不允许用户进入详细信息页面进行修改,而只能查看。为了向用户传达信息而不必解释为什么字段是只读的,VM(根据数据的状态)会将按钮文本更改为“View”。
 

在 Core.cs 中为按钮文本添加一个新属性

  public string ButtonContent { get { return "Get Weather"; } }

并且在我们的 MainPage.xaml 中,更新 Text 以绑定到我们的新属性

<StackLayout  Orientation="Horizontal" VerticalOptions="Start">
  <Entry WidthRequest="100" x:Name="edtZipCode"  VerticalOptions="Start" Text="{Binding ZipCode, Mode=TwoWay}"/>
  <Button Text="{Binding ButtonContent}" x:Name="btnGetWeather" VerticalOptions="Start" Clicked="BtnWeather_Click"/>
</StackLayout>

您可能希望绑定按钮标题的另一个原因是支持多语言。

 

下一步是将 Click 事件绑定到 VM,而不是我们 MainPage.xaml 中的 C# 代码。为此,我们可以在 Core.cs 中实现一个简化的 ICommand 版本

using System.Windows.Input;

 

private bool _CanGetWeather = true;
public bool CanGetWeather
{
  get { return _CanGetWeather; }
  set
  {
    if (_CanGetWeather != value)
    {
      _CanGetWeather = value;
      OnPropertyChanged(nameof(CanGetWeather));
    }
  }
}

public ICommand GetWeather { get; private set; }

public Core()
{
  GetWeather = new Command(async () => await _GetWeather(), () => CanGetWeather);
}

现在我们有了新的 Command,更新我们的 Button 以调用它

<StackLayout  Orientation="Horizontal" VerticalOptions="Start">
  <Entry WidthRequest="100" x:Name="edtZipCode"  VerticalOptions="Start" Text="{Binding ZipCode, Mode=TwoWay}"/>
  <Button Text="{Binding ButtonContent}" x:Name="btnGetWeather" VerticalOptions="Start" IsEnabled="{Binding CanGetWeather, Mode=TwoWay}" Command="{Binding GetWeather}"/>
</StackLayout>

关于数据访问层是分开的、在 ViewModel 中还是在 Model 中,存在激烈的讨论。就本练习而言,我决定将其放在 Model 中,原因是 Model 应该了解其数据,在我看来,这包括如何检索它。我相信其他人会有不同的观点,并且有很好的理由。

将 public async Task<Weather> GetWeather 从 Core.cs 移动到 Weather.cs。我还为了清晰起见重命名了 Task。最后,我们需要将作用域更改为 public,以便可以访问它

public static async Task<Weather> RetrieveWeather(string pZipCode, string pCountryCode, bool pUseMetric)
{
  //Sign up for a free API key at http://openweathermap.org/appid 
  string key = "YOUR KEY";
  string queryString = default(String);

  string unitsString = (pUseMetric ? "metric" : "imperial");

  if (String.IsNullOrEmpty(pCountryCode)) //If Country is not specified, Open Weather Defaults to the US.
    queryString = $"http://api.openweathermap.org/data/2.5/weather?zip={pZipCode},us&appid={key}&units={unitsString}";
  else
    queryString = $"http://api.openweathermap.org/data/2.5/weather?zip={pZipCode},{pCountryCode}&appid={key}&units={unitsString}";

  //Make sure developers running this sample replaced the API key
  if (key != "YOUR KEY")
  {
    throw new ArgumentOutOfRangeException("You must obtain an API key from openweathermap.org/appid and save it in the 'key' variable.");
    return null;
  }

  string temperatureUnits = (pUseMetric ? "C" : "F");
  string windUnits = (pUseMetric ? "km/h" : "mph");

  try
  {
    dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

    if (results["weather"] != null)
    {
      DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

      return new Weather()
      {
        Title = (string)results["name"],
        Temperature = $"{(string)results["main"]["temp"]} {temperatureUnits}",
        Wind = $"{(string)results["wind"]["speed"]} {windUnits}",
        Humidity = (string)results["main"]["humidity"] + " %",
        Visibility = (string)results["weather"][0]["main"],
        Sunrise = $"{time.AddSeconds((double)results["sys"]["sunrise"]).ToString()} UTC",
        Sunset = $"{time.AddSeconds((double)results["sys"]["sunset"]).ToString()} UTC"
      };
    }
    else
      throw new Exception((string)results["message"]);
  }
  catch (Exception ex)
  {
    //This catching and re-raising of an exception does not do much, however in larger applications
    //generally there is some form of error logging for reporting back to Administrators. That logging
    //would go here.  You could also have mulitple Catch statments to trap different types of errors
    //to give better feed-back to users as to what went wrong.
    throw new Exception(ex.Message);
  }
}

重命名并重写我们现有的 Core.cs GetWeather 方法为 async task _GetWeather,更新我们的 ZipCode Property 和 Weather 变量

private Weather _weather = new Weather();
public Weather Weather
{ get { return _weather; }
  private set
  {
    if ((_weather != value) && (value != null))
    {
      _weather = value;

      OnPropertyChanged(nameof(Title));
      OnPropertyChanged(nameof(Temperature));
      OnPropertyChanged(nameof(Wind));
      OnPropertyChanged(nameof(Humidity));
      OnPropertyChanged(nameof(Visibility));
      OnPropertyChanged(nameof(Sunrise));
      OnPropertyChanged(nameof(Sunset));
    }
  }
}

private async Task _GetWeather()
{
  CanGetWeather = false;

  try
  {
    Weather = await Weather.RetrieveWeather(ZipCode, SelectedCountry?.CountryCode, UseMetric);
  }
  finally
  {
    CanGetWeather = true;
  }
}

private string _ZipCode;
    public string ZipCode
    {
      get
      { return _ZipCode; }
      set
      { if (_ZipCode != value)
        {
          _ZipCode = value;
          OnPropertyChanged(nameof(ZipCode));

          CanGetWeather = !String.IsNullOrEmpty(value);
        }
      }
    }

仍在 Core.cs 中添加 using

using Xamarin.Forms;

最后,我们更新我们的 MainPage.cs & MainPage.xaml 以使用我们更新的 ViewModel

public MainPage()
{
  InitializeComponent();

  BindingContext = new Core();

  cbxCountry.ItemsSource = ((Core)this.BindingContext).Countries;

  ((Core)this.BindingContext).SelectCountryByISOCode();

  edtZipCode.Completed += (s, e) => this.GetWeather();
}

private void GetWeather()
{
  if (((Core)BindingContext).GetWeather.CanExecute(null))
    ((Core)BindingContext).GetWeather.Execute(null);
}

使用转换器移除硬编码

总的来说,我们不会将返回的数据存储为字符串,而是存储为更合适的数据类型,例如 Wind、Humidity 和 Temperature Doubles。为了很好地显示数据,我们有几种选择,不幸的是,Xamarin 中有许多选项不可用……

总的来说,在 WPF 中,我们会使用 Converter 和/或 multi-binding 来显示信息。Multi-Binding 尚未在 Xamarin 中实现,并且我们不能使用 Converter,因为您不能将 ConverterParameter 绑定到 IsMetric 值。话虽如此,网上有一些扩展允许您将值绑定到 ConverterParameters,这正是我理想中解决此问题的方法。

但就本练习而言,我不想依赖扩展,这也可以说明解决问题的方法不止一种。

拥有一个 Display Property

这是我最不喜欢的选项,因为这是 Converter 存在的原因之一。在此场景中它有效,但是如果绑定到的控件是 Textbox/Entry 控件,我们就必须解析返回值。

在 Weather.cs 中,将 Temperature 属性更新为 double。

public class Weather
{
  public string Title { get; set; }
  public double Temperature { get; set; }
  public string Wind { get; set; }
  public string Humidity { get; set; }
  public string Visibility { get; set; }
  public string Sunrise { get; set; }
  public string Sunset { get; set; }

  private bool _useMetric;
   

  public Weather()
  {
    //Because labels bind to these values, set them to an empty string to 
    //ensure that the label appears on all platforms by default. 
    this.Title = default(string);
    this.Temperature = default(double);
    this.Wind = default(string);
    this.Humidity = default(string);
    this.Visibility = default(string);
    this.Sunrise = default(string);
    this.Sunset = default(string);
  }

在 Core.cs 中,更新

public double Temperature
{
  get { return _weather.Temperature; }
  set
  {
    _weather.Temperature = value;
   
    OnPropertyChanged(nameof(Temperature));
    OnPropertyChanged(nameof(TemperatureDisplay));
  }
}
public string TemperatureDisplay => $"{Temperature.ToString()} {(UseMetric ? TemperatureUnits.tuCelsius.EnumDescription() : TemperatureUnits.tuFahrenheit.EnumDescription())}";

 

    private Weather _weather = new Weather();
    public Weather Weather
    {
      get { return _weather; }
      private set
      {
        if (_weather != value)
        {
          _weather = value;

          OnPropertyChanged(nameof(Title));
          OnPropertyChanged(nameof(Temperature));
          OnPropertyChanged(nameof(TemperatureDisplay));
          OnPropertyChanged(nameof(Wind));

 

在 MainPage.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 TemperatureDisplay}" Margin="10,0,0,10" x:Name="txtTemperature"/>

在 Weather.cs 中创建一个枚举。请记住从 Weather.cs 中的 RetrieveWeather 方法中删除 TemperatureUnits 字符串变量。

using System.ComponentModel;
using System.Threading.Tasks;

namespace WeatherApp
{
  public enum TemperatureUnits
  {
    [Description("C")]
    tuCelsius,
    [Description("F")]
    tuFahrenheit
  }

  public class Weather

我们还需要包含一些代码来使用我们的 Description,因为这在 Xamarin 中目前不可用。在解决方案资源管理器中,右键单击 WeatherApp 项目,添加,类,并将其命名为 DescriptionAttribute.cs

using System;
using System.Linq;
using System.Reflection;

//http://taoffi.isosoft.org/post/2017/05/03/Xamarin-missing-Description-attribute

namespace WeatherApp
{
  [AttributeUsage(validOn: AttributeTargets.All, AllowMultiple = false, Inherited = false)]
  public class DescriptionAttribute : Attribute
  {
    public string _description;

    public DescriptionAttribute(string description)
    {
      _description = description;
    }

    public string Description
    {
      get { return _description; }
      set { _description = value; }
    }
  }

  public static class EnumDescriptions
  {
    public static string EnumDescription(this Enum value)
    {
      if (value == null)
        return null;

      var type = value.GetType();
      TypeInfo typeInfo = type.GetTypeInfo();
      string typeName = Enum.GetName(type, value);

      if (string.IsNullOrEmpty(typeName))
        return null;

      var field = typeInfo.DeclaredFields.FirstOrDefault(f => f.Name == typeName);

      if (field == null)
        return typeName;

      var attrib = field.GetCustomAttribute<DescriptionAttribute>();
      return attrib == null ? typeName : attrib.Description;
    }
  }
}

最后,我们必须更新 Weather.cs 中的 RetrieveWeather 以将 Temp 填充为数字,而不是字符串

      if (results["weather"] != null)
      {
        DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

        return new Weather()
        {
          Title = (string)results["name"],
          //Temperature = $"{(string)results["main"]["temp"]} {Temperature}",
          Temperature = (double)results["main"]["temp"],
          Wind = $"{(string)results["wind"]["speed"]} {windUnits}",

 

现在,当您运行应用程序时,您将看到相同的详细信息。这种方法有一个副作用,它显示 0 F 的值(而不是空白)。

多个字段

由于我们不能有多重绑定,我们可以使用多个字段。

在 MainPage.xaml 中,将现有的 Wind 标签替换为包含在 StackLayout 中的两个表

<Label Text ="Wind Speed" TextColor="#FFA8A8A8" FontSize="14"/>
<StackLayout Orientation="Horizontal">
  <Label Text ="{Binding Wind}" Margin="10,0,0,10" x:Name="txtWind"/>
  <Label Text ="{Binding WindUnits}" Margin="10,0,0,10" x:Name="txtWindUnits"/>
</StackLayout>
<Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>

在 Core.cs 中,将我们的 Wind Property 更新为 double 并添加一个新的 Property 来返回 Wind Units

public double Wind
{
  get { return _weather.Wind; }
  set
  {
    _weather.Wind = value;
    OnPropertyChanged(nameof(Wind));
  }
}
public string WindUnits => $"{(UseMetric ? WindUnits.wuKmh.EnumDescription() : WindUnits.wuMph.EnumDescription())}";

还更新 Wind 的初始化以使用 double 并添加 OnPropertyChanged 来更新屏幕

public Weather Weather
    { get { return _weather; }
      private set
      {
        if (_weather != value)
        {
          _weather = value;

          OnPropertyChanged(nameof(Title));
          OnPropertyChanged(nameof(Temperature));
          OnPropertyChanged(nameof(TemperatureDisplay));
          OnPropertyChanged(nameof(Wind));
          OnPropertyChanged(nameof(WindUnits));
          OnPropertyChanged(nameof(Humidity));
          OnPropertyChanged(nameof(Visibility));
          OnPropertyChanged(nameof(Sunrise));
          OnPropertyChanged(nameof(Sunset));
        }
      }
    }

在 Weather.cs 中添加一个新的单位枚举,并将 Wind Property 更新为 double

namespace WeatherApp
{
  public enum TemperatureUnits
  {
    [Description("C")]
    tuCelsius,
    [Description("F")]
    tuFahrenheit
  }
  public enum WindUnits
  {
    [Description("mph")]
    wuMph,
    [Description("km/h")]
    wuKmh
  }

public class Weather
  {
    public string Title { get; set; }
    public double Temperature { get; set; }
    public double Wind { get; set; }
    public double Humidity { get; set; }

从 Weather.cs 中删除 WindUnits 字符串。

public Weather()
{
  //Because labels bind to these values, set them to an empty string to 
  //ensure that the label appears on all platforms by default. 
  this.Title = default(string);
  this.Temperature = default(double);
  this.Wind = default(double);
  this.Humidity = default(double);
  this.Visibility = default(string);
  this.Sunrise = default(string);
  this.Sunset = default(string);
}

最后,在 RetrieveWeather 任务中更新值填充的位置

try
{
  dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

  if (results["weather"] != null)
  {
    DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
         
    return new Weather()
          {
            Title = (string)results["name"],
            //Temperature = $"{(string)results["main"]["temp"]} {temperatureUnits}",
            _useMetric = pUseMetric,
            Temperature = (double)results["main"]["temp"],
            //Wind = $"{(double)results["wind"]["speed"]} {windUnits}",
            Wind = (double)results["wind"]["speed"],
            //Humidity = (double)results["main"]["humidity"] + " %",
            Humidity = (double)results["main"]["humidity"],
            Visibility = (string)results["weather"][0]["main"],
            Sunrise = $"{time.AddSeconds((double)results["sys"]["sunrise"]).ToString()} UTC",
            Sunset = $"{time.AddSeconds((double)results["sys"]["sunset"]).ToString()} UTC"
          };
        }

为确保我们的 Humidty 值仍然显示 %,请使用 Converter 更新 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.Resources>
  <ResourceDictionary>
    <local:PercentageConverter x:Key="PercentageCustomString"/>
  </ResourceDictionary>
</ContentPage.Resources>
<Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>
<Label Text ="{Binding Humidity, Converter={StaticResource PercentageCustomString}, Mode=OneWay}" Margin="10,0,0,10" x:Name="txtHumidity"/>
<Label Text ="Visibility" TextColor="#FFA8A8A8" FontSize="14"/>

在 Core.cs 中

    public double Humidity
    {
      get { return _weather.Humidity; }
      set
      {
        if (_weather.Humidity != value)
        {
          _weather.Humidity = value;
          OnPropertyChanged(nameof(Humidity));
        }
      }
    }

在 Weather.cs 中

      if (results["weather"] != null)
      {
        DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

        return new Weather()
        {
          Title = (string)results["name"],
          //Temperature = $"{(string)results["main"]["temp"]} {Temperature}",
          Temperature = (double)results["main"]["temp"],
          //Wind = $"{(double)results["wind"]["speed"]} {windUnits}",
          Wind = (double)results["wind"]["speed"],
          Humidity = (double)results["main"]["humidity"],
          Visibility = (string)results["weather"][0]["main"],

向项目添加一个名为 Percentage.cs 的新类

using System;
using System.Globalization;
using Xamarin.Forms;

namespace WeatherApp
{
  class PercentageConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return $"{value.ToString()} %";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

我发现,我经常需要关闭 Visual Studio 才能确保 Converter 正确注册。

再次运行我们的应用程序,Wind、Wind Units 和 Humidity 都已漂亮地打印出来。

向用户反馈 / 错误消息

在本演练的最后一部分,我们需要向用户提供一些反馈。在我们的应用程序中,最常见的问题是:

  1. 无效的国家/地区/邮政编码
  2. 缺乏互联网连接

在 MainPage.xaml 中添加一个新的控件来显示任何错误状态

<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"/>
    <Picker x:Name="cbxCountry" WidthRequest="200" SelectedItem="{Binding SelectedCountry, Mode=TwoWay}" SelectedIndex="0" Title="Country"  TextColor="White"/>
    <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="{Binding ButtonContent}" x:Name="btnGetWeather" VerticalOptions="Start" IsEnabled="{Binding CanGetWeather, Mode=TwoWay}" Command="{Binding GetWeather}"/>
    </StackLayout>
    <Label Text="{Binding ErrorMessage}" Margin="10" x:Name="lblErrorMessage" TextColor="DarkRed"/>
  </StackLayout>
</StackLayout>

 

在 Core.cs 中添加一个新变量和属性来存储 Error State,并创建一个新的 try..catch 来填充

private string _ErrorMessage = default(string);
public string ErrorMessage
{
  get { return _ErrorMessage; }
  set
  {
    if (value != _ErrorMessage)
    {
      _ErrorMessage = value;
      OnPropertyChanged(nameof(ErrorMessage));
    }
  }
}

private async Task _GetWeather()
{
  CanGetWeather = false;

  try
  {
    try
    {
      ErrorMessage = default(string);

      //Clear our the results so that we are not displaying incorrect information should an excption occur.
      Title = default(String);
      Temperature = default(double);
      Wind = default(double);
      Humidity = default(double);
      Visibility = default(String);
      Sunrise = default(String);
      Sunset = default(String);

      Weather = await Weather.RetrieveWeather(ZipCode, SelectedCountry?.CountryCode, UseMetric);
    }
    catch (Exception ex)
    {
      ErrorMessage = ex.Message;
    }
  }
  finally
  {
    CanGetWeather = true;
  }
}

现在您运行,输入一个不正确的邮政编码,我们会收到一条漂亮的错误消息。但是,如果您断开 PC 连接并运行,我们会收到一条错误消息。但它不太用户友好。

用更有用的错误消息信息更新 Weather.cs,当没有数据返回时

   throw new ArgumentOutOfRangeException("You must obtain an API key from openweathermap.org/appid and save it in the 'key' variable.");

 dynamic results = null;

  try
  {
    results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

    if (results["weather"] != null)
    {
      DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
         
      return new Weather()
          {
            Title = (string)results["name"],
            _useMetric = pUseMetric,
            Temperature = (double)results["main"]["temp"],
            Wind = (double)results["wind"]["speed"],
            Humidity = (double)results["main"]["humidity"],
            Visibility = (string)results["weather"][0]["main"],
            Sunrise = $"{time.AddSeconds((double)results["sys"]["sunrise"]).ToString()} UTC",
            Sunset = $"{time.AddSeconds((double)results["sys"]["sunset"]).ToString()} UTC"
         };
     }
     else
        throw new Exception((string)results["message"]);
    }
    catch (Exception ex)
    {
      if (results == null)
        throw new Exception($"Unable to retreived Weather data.  Please check your internet connection ({ex.Message})");
      else
        throw new Exception(ex.Message);
    }
  }

 

从哪里开始?

这是本系列的最后一篇 - 感谢您的阅读!有很多方法可以继续扩展此应用程序。如果我要继续,我会通过以下方式扩展此应用程序……

  1. 在它们被填充之前隐藏结果标签。
  2. 为 Visibility 创建一个新的 Converter,该 Converter 返回一个图片并将其显示为背景(例如,晴天、多云、雨天)。
  3. 将时间更改为本地时间(即天气所在地点)和/或您设备所在位置的时间。
  4. 将国家/地区详细信息放入 SQLite 数据库。
  5. 添加一个配置屏幕,以便用户可以选择颜色,如果他们希望温度/风显示为地区偏好,或者始终显示为 C/kwh 或 F/mph(或两者都显示)。
  6. 在控件方面更有创意,以更好地处理大小调整。
  7. 天气网站还有许多其他功能可以集成,例如历史天气状况。显示去年的天气与今天的天气相比。

对于那些想要源代码副本的人(欢迎分叉并进行自己的改进),它位于 GitHub 上:https://github.com/Rodimus7/WeatherApp.git

 

我期待看到您关于如何将此示例应用到您自己的应用程序的评论。

 

 

© . All rights reserved.