Как стать автором
Обновить
0

Погода для Windows Phone 7. Работа с XML

Время на прочтение14 мин
Количество просмотров8.6K
Автор оригинала: Pasi Manninen
Об авторе: Паси Маннинен разрабатывает мобильные и веб-приложения, является Чемпионом Nokia Developer и Профессионалом Adobe Community, сертифицированным экспертом и преподавателем Adobe по Flex, Flash и Flash Mobile. Окончил магистратуру Университета Ювяскюля (одного из крупнейших вузов Финляндии) по специальностям «Прикладная математика» и «Компьютерные науки».

Эта статья рассказывает, как работать с данными XML на примере приложения, отображающего прогноз погоды в любых городах мира.

Вступление


В этой статье я покажу как создать приложение для Window Phone 7, которое загружает прогноз погоды из службы World Weather Online. Вы можете почитать интересную статью по работе с Weather Online, но я хочу углубиться именно в принципы хранения избранных городов в телефоне и методику создания панорамного режима просмотра прогноза погоды для различных городов.



Приложение выполнено в виде панорамы для WP7. Сначала приложение загружает сохранённый список городов и API-ключ для доступа к Weather Online из памяти устройства, после чего получает прогноз погоды для всех городов. Каждый прогноз отображается в собственном Panorama View. Пользователь же может указать ApiKey и избранные города на странице настроек.

API-ключ для Weather Online


Вам будет нужен ваш собственный API-ключ для доступа к Weather Online. Из соответствующей статьи можно узнать, как это сделать.

Windows Phone 7.1 SDK


Для разработки приложений под устройства Windows Phone 7 нужно установить соответствующий SDK. Загрузить последнюю версию SDK для Windows Phone можно здесь.

Приложение для Windows Phone


Чтобы создать новое приложение Windows Phone, откройте Microsoft Visual Studio, создайте новый проект и выберите шаблон Windows Phone Application Template. Помимо него, есть и шаблон под названием Panorama Template, но мы будем использовать указанный выше. В этом случае код нашего приложения будет очень простым и понятным, а панорамный режим просмотра мы запрограммируем самостоятельно.



В этом примере я выбрал C# языком разработки.

Парсинг XML в Windows Phone 7


Существует множество способов загрузки и парсинга XML-данных в Windows Phone 7. В данном примере я буду использовать XML Deserialization для загрузки XML-документа и его обработки. Добавьте ссылки на System.Xml.Serialization и System.Xml.Linq в вашем проекте. Щёлкните правой кнопкой на «References» в Solutions Explorer вашего проекта и выберите «Add new Reference…».

Загрузка и отображение картинок из сети


При одновременной загрузке большого числа изображений из сети у Windows Phone могут возникнуть некоторые трудности. В интернете можно найти сборки для загрузки изображений в фоне, которые решают данную проблему. Одна из них — это PhonePerformance (вы можете скачать скомпилированную сборку PhonePerformance здесь вместе с исходными кодами). Скопируйте PhonePerformance.dll из zip-архива в ваш проект и добавьте новую ссылку на неё с помощью Solution Explorer.

Вы можете получить предупреждение о том, что сборка PhonePerformance.dll была скачана из интернета и её необходимо разблокировать. Просто закройте Visual Studio, откройте Windows Explorer, найдите файл PhonePerformance.dll, щёлкните по нему правой кнопкой мышки и разблокируйте соответствующей кнопкой в свойствах файла.

Исходный код приложения


Для добавления новых классов в ваш проект кликните правой кнопкой по проекту в Solutions Explorer, выберите сначала Add, а затем Class.

Forecast.cs

Weather Online предоставляет большое количество информации о погоде. В данном примере я буду использовать только те, которые показаны в классе Forecast. Этот класс используется тогда, когда данные о прогнозе погоды загружаются из Weather Online.

namespace Weather
{
    public class Forecast
    {
        public string query { get; set; } // cityname, countryname
        public string observation_time { get; set; }
        public string date { get; set; }
        public string temp_C { get; set; }
        public string tempMaxC { get; set; }
        public string tempMinC { get; set; }
        public string weatherIconUrl { get; set; }
        public string windspeedKmph { get; set; }
        public string humidity { get; set; }
    }
}


PanoramaItemObject.cs

Я привяжу всю информацию о прогнозе погоды к Panorama View. К каждому виду Panorama View прикреплена базовая информация о текущей погоде и прогнозе на пять дней. Созданный выше класс Forecast используется здесь в списке прогнозов.

using System.Collections.Generic;
 
namespace Weather
{
    public class PanoramaItemObject
    {
        public string observation_time { get; set; }
        public string date { get; set; }
        public string temperature { get; set; }
        public string huminity { get; set; }
        public string windspeed { get; set; }
        public string weatherIconUrl { get; set; }
        // five day's forecast
        public List<Forecast> forecasts { get; set; } 
    }
}


Panorama View


Дизайн (MainPage.xaml)

Данное приложение генерирует столько Panorama View, сколько городов указано в настройках приложения. Заголовок и фон меняется только у первой панорамы:

<Grid x:Name="LayoutRoot" Background="Transparent">
        <controls:Panorama Title="Weather Forecast" x:Name="Panorama">
            <controls:Panorama.TitleTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
                               Foreground="White" 
                               FontSize="100"
                               Margin="0,60,0,0"/>
                </DataTemplate>
            </controls:Panorama.TitleTemplate>
            <controls:Panorama.Background>
                <ImageBrush ImageSource="Images/Background3.jpg"/>
            </controls:Panorama.Background>
        </controls:Panorama>
    </Grid>


Вы можете добавить своё фоновое изображение к проекту. Для этого создайте новую папку для картинок в Solutions Explorer, а затем скопируйте ваше изображение с помощью Windows Explorer. Затем, добавьте это изображение в проект, выделив папку с изображениями в Solutions Explorer и выбрав Add Existing Item… Важно использовать изображение правильных размеров: они должны быть не менее 480x800 и не более 1024x800 пикселей.

Я буду использовать два шаблона для отображения прогноза погоды в Panorama View. Эти шаблоны хранятся в App.xaml.

Дизайн (App.xaml)

Все Panorama View одинаковы, меняется только информация о прогнозе погоды для конкретного города. Я использовал DataTemplates для отображения прогноза погоды. Файл App.xaml содержит элемент Application.Resources, где вы можете хранить ваши DataTemplates.

Шаблон ниже используется для отображения прогноза погоды на пять дней. Каждая строка отображает дату, изображение и минимальную/максимальную температуры.

image

<DataTemplate x:Key="ForecastsDataTemplate">
            <StackPanel Height="40" Orientation="Horizontal" Margin="0,10,0,0">
                <TextBlock Text="{Binding date}" FontSize="22" TextAlignment="Left" Width="150"/>
                <TextBlock Text="   " FontSize="20"/>
                <Image delay:LowProfileImageLoader.UriSource="{Binding weatherIconUrl}" Width="40" Height="40"/>
                <TextBlock Text="   " FontSize="20"/>
                <TextBlock Text="{Binding tempMaxC, StringFormat='\{0\} °C'}" FontSize="22" TextAlignment="Right" Width="70"/>
                <TextBlock Text="   " FontSize="20"/>
                <TextBlock Text="{Binding tempMinC, StringFormat='\{0\} °C'}" FontSize="22" TextAlignment="Right" Width="70"/>
            </StackPanel>
        </DataTemplate>


Главный шаблон для Panorama View описан ниже. В нём есть пространство шириной в 150 пикселей для изображения, соответствующего прогнозу погоды слева, а справа отображается текущая погода. Внизу расположена StackPanel для прогноза погоды на пять дней. Вначале добавлены заголовочные TextBlock, а затем один ListBox для прогнозов погоды. Вы можете использовать другие шаблоны с атрибутом ItemTemplate в ListBox.

image

<DataTemplate x:Key="ForecastTemplate">
      <Grid x:Name="ContentPanel" Grid.Row="0" Margin="0,-10,0,0">
          <Grid Height="150" VerticalAlignment="Top">
              <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="150"/>
                  <ColumnDefinition Width="*"/>
              </Grid.ColumnDefinitions>
              <Image delay:LowProfileImageLoader.UriSource="{Binding weatherIconUrl}" Width="120" Height="120" Grid.Column="0" VerticalAlignment="Top"/>
              <StackPanel Grid.Column="1" Height="200" VerticalAlignment="Top">
                      <TextBlock Text="{Binding temperature}" FontSize="22"/>
                      <TextBlock Text="{Binding observation_time}" FontSize="22"/>
                      <TextBlock Text="{Binding huminity}" FontSize="22"/>
                      <TextBlock Text="{Binding windspeed}" FontSize="22"/>
              </StackPanel>
              </Grid>
              <Grid Height="300" VerticalAlignment="Bottom">
                      <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="0,0,0,0">
                      <StackPanel Grid.Row="4" Height="40" Orientation="Horizontal" Margin="0,0,0,0">
                      <TextBlock Text="Date" FontSize="22" TextAlignment="Left" Width="170"/>
                      <TextBlock Text="FC" FontSize="22" TextAlignment="Left" Width="60"/>
                      <TextBlock Text="Max" FontSize="22" TextAlignment="Right" Width="60"/>
                      <TextBlock Text="Min" FontSize="22" TextAlignment="Right" Width="90"/>
                  </StackPanel>
                  <ListBox ItemTemplate="{StaticResource ForecastsDataTemplate}" ItemsSource="{Binding forecasts}"/>
              </StackPanel>
           </Grid>
      </Grid>
</DataTemplate>


Не забудьте добавить пространство имён для PhonePerformance.dll для загрузки картинок:
xmlns:delay="clr-namespace:Delay;assembly=PhonePerformance"

Все данные связываются с элементами интерфейса в классе MainPage.xaml.cs.

Программирование (MainPage.xaml.cs)

Весь процесс загрузки XML происходит в файле MainPage.xml.cs. Все указанные в настройках города сначала загружаются в ObservableCollection, а API-ключ сохраняется в строке apikey. Метод LoadForecast будет вызван столько раз, сколько городов у нас указано в IsolatedStorageSettings.

Укажем переменные класса:

private ObservableCollection<String> queries = new ObservableCollection<String>();
private int query;
 
private string weatherURL = "http://free.worldweatheronline.com/feed/weather.ashx?q=";
private string apiKey;
 
private IsolatedStorageSettings appSettings;
const string QueriesSettingsKey = "QueriesKey";
const string APISettingsKey = "APIKey";


В конструкторе MainPage мы сначала получим экземпляр для доступа к настройкам приложения, а затем проверим доступность соединения с интернетом:

// Constructor
public MainPage()
{
            InitializeComponent();
 
            // get settings for this application
            appSettings = IsolatedStorageSettings.ApplicationSettings;
 
            // is there network connection available
            if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
            {
                MessageBox.Show("There is no network connection available!");
                return;
            }
}


Каждый раз, когда отображается MainPage, вызывается метод OnNavigatedTo. В нём мы загружаем список всех городов и API-ключ из IsolatedStorage. После этого все Panorama View будут удалены, а новый прогноз погоды будет загружен для всех городов.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
            if (appSettings.Contains(QueriesSettingsKey))
            {
                queries = (ObservableCollection<String>)appSettings[QueriesSettingsKey];
            }
 
            if (appSettings.Contains(APISettingsKey))
            {
                apiKey = (string)appSettings[APISettingsKey];
            }
            else
            {
                apiKey = "";
            }
 
            // delete old Panorama Items
            Panorama.Items.Clear();
 
            // start loading weather forecast
            query = 0;
            if (queries.Count() > 0 && apiKey != "") LoadForecast();
}


LoadForecast использует класс WebClient для асинхронной загрузки XML-данных с сервера Weather Online.

private void LoadForecast() 
{
            WebClient downloader = new WebClient();
            Uri uri = new Uri(weatherURL + queries.ElementAt(query) + "&format=xml&num_of_days=5&key=" + apiKey, UriKind.Absolute);
            downloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(ForecastDownloaded);
            downloader.DownloadStringAsync(uri);
}


Метод ForecastDownloaded будет вызван, когда загрузка нового прогноза погоды с сервера Weather Online будет закончена. В этом методе мы обрабатываем информацию о текущей погоде прямо из XML. В конце обработки мы создаём новый PanoramaItem с помощью метода AddPanoramaItem.

private void ForecastDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
            if (e.Result == null || e.Error != null)
            {
                MessageBox.Show("Cannot load Weather Forecast!");
            }
            else
            {
                XDocument document = XDocument.Parse(e.Result);
                var data1 = from query in document.Descendants("current_condition")
                           select new Forecast
                           {
                               observation_time = (string) query.Element("observation_time"),
                               temp_C = (string)query.Element("temp_C"),
                               weatherIconUrl = (string)query.Element("weatherIconUrl"),
                               humidity = (string)query.Element("humidity"),
                               windspeedKmph = (string)query.Element("windspeedKmph")
                           };
 
                Forecast forecast = data1.ToList<Forecast>()[0];
 
                var data2 = from query in document.Descendants("weather")
                            select new Forecast
                            {
                                date = (string)query.Element("date"),
                                tempMaxC = (string)query.Element("tempMaxC"),
                                tempMinC = (string)query.Element("tempMinC"),
                                weatherIconUrl = (string)query.Element("weatherIconUrl"),
                            };
 
                List<Forecast> forecasts = data2.ToList<Forecast>();
 
                for (int i = 0; i < forecasts.Count(); i++)
                {
                    forecasts[i].date = DateTime.Parse(forecasts[i].date).ToString("dddd");
                }
 
 
                AddPanoramaItem(forecast,forecasts); 
            }
}


Метод AddPanoramaItem получает в качестве аргументов объект с информацией о текущей погоде и список с информацией о погоде на последующие пять дней. Вначале будет создан объект PanoramaItemObject, чтобы связать все данные с интерфейсом. Затем же, будем создан реальный объект Panorama View с именем города в заголовке панорамы и с ForecastTemplate для отображения информации о погоде. После этого будет загружена информация о следующем городе в списке городов.

private void AddPanoramaItem(Forecast forecast, List<Forecast> forecasts)
{
            // create object to bind the data to UI
            PanoramaItemObject pio = new PanoramaItemObject();
            pio.temperature = "Temperature: " + forecast.temp_C + " °C";
            pio.observation_time = "Observ. Time: " + forecast.observation_time;
            pio.windspeed = "Wind Speed: " + forecast.windspeedKmph + " Kmph";
            pio.huminity = "Huminity: " + forecast.humidity + " %";
            pio.weatherIconUrl = forecast.weatherIconUrl;
            pio.forecasts = forecasts;
 
            // create PanoramaItem
            PanoramaItem panoramaItem = new PanoramaItem();
            panoramaItem.Header = queries[query];
            // modify header to show only city (not the country)
            int index = queries[query].IndexOf(",");
            if (index != -1) panoramaItem.Header = queries[query].Substring(0, queries[query].IndexOf(","));
            else panoramaItem.Header = queries[query];
            // use ForecastTemplate in Panorama Item
            panoramaItem.ContentTemplate = (DataTemplate)Application.Current.Resources["ForecastTemplate"];
            panoramaItem.Content = pio;
            // add Panorama Item to Panorama             
            Panorama.Items.Add(panoramaItem);
            // query next city forecast
            query++;
            if (query < queries.Count()) LoadForecast();
}


Настройки


Настройки приложения хранятся в изолированном IsolatedStorage. Страница настроек открывается, когда пользователь щёлкает по иконке настроек (или по тексту) в строке приложения (Application Bar).

image

Дизайн (Main.xaml)

Вы можете отобразить иконку и текст в строке приложения. Скопируйте ваше изображение в папку с изображениями и установите для него действие во время сборки в положение Content в панели настроек файла в Solution Explorer. Добавьте обработку событий к иконке и тексту.

<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar.feature.settings.rest.png" Text="Settings" Click="Settings_Click"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="Settings" Click="Settings_Click"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>


Программирование (Main.xaml.cs)

Страница настроек будет открыта, когда пользователь щёлкнет по ссылке на настройки на странице MainPage.

private void Settings_Click(object sender, EventArgs e)
{
            this.NavigationService.Navigate(new Uri("/SettingsPage.xaml", UriKind.Relative));
}


Страница настроек


Для создания новой страницы (Page) в вашем проекте, щёлкните правой кнопкой по вашему проекту в Solution Explorer, выберите Add, а затем New Item. Выберите в появившемся окне Windows Phone Portrait Page и назовите новую страницу именем SettingsPage.xaml.

Дизайн (SettingsPage.xaml)

Данная страница — обычная страница в портретном режиме. Название приложения и название страницы отображаются в её верхней области.



Пользователь может добавлять и изменять API-ключ, а также добавлять города к списку CitiesList. Добавленные города могут быть удалены нажатием по названию города в списке (после чего будет запрошено подтверждение на удаление через MessageBox).

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel Orientation="Vertical">
                <TextBlock Text="API Key"/>
                <TextBox x:Name="APIKey" Text=""/>
                <TextBlock Text="Add City"/>
                <TextBox x:Name="NewCityName" Text="Cityname, Countryname"/>
                <Button Content="Test and Add" Click="Test_Click"/>
                <TextBlock Text="Cities (click city to remove)"/>
                <ListBox x:Name="CitiesList" VerticalAlignment="Top" FontSize="30" ItemsSource="{Binding queries}"
                             Height="280" Margin="30,10,0,0" SelectionChanged="CitiesList_SelectionChanged"/>
            </StackPanel>
        </Grid>


Программирование (SettingsPage.xaml.cs)

Для начала мы должны проверить, есть ли прогноз погоды для добавленного пользователем города. Если прогноз погоды существует (и его можно получить с указанным API-ключём), то API-ключ и город добавляются к переменным класса. Настройки сохраняются тогда, когда пользователь возвращается к основным страницам приложения.

Переменные класса такие же, какие мы использовали в классе Main.xaml.cs; экземпляр класса настроек также загружается подобным образом.

Каждый раз при отображении страницы настроек вызывается метод OnNavigatedTo. В нём загружается список всех городов и API-ключ из IsolatedStorage.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
            if (appSettings.Contains(QueriesSettingsKey))
            {
                queries = (ObservableCollection<String>)appSettings[QueriesSettingsKey];
            }
            if (appSettings.Contains(APISettingsKey))
            {
                apiKey = (string)appSettings[APISettingsKey];
                APIKey.Text = apiKey;
            }
            // add cites to CitiesList
            CitiesList.ItemsSource = queries;
}


OnNavigatedFrom будет вызван, напротив, когда пользователь покидает страницу настроек. Все изменения будут сохранены в IsolatedStorage.

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
            // add queries to isolated storage
            appSettings.Remove(QueriesSettingsKey);
            appSettings.Add(QueriesSettingsKey, queries);
 
            // add apikey to isolated storage
            appSettings.Remove(APISettingsKey);
            appSettings.Add(APISettingsKey, apiKey);
}


Пользователь на странице настроек может также проверить правильность ввода API-ключа или города. Новый объект класса WebClient будет создан для проверки успешности ввода данных. Если никаких ошибок не обнаружено и всё работает так, как ожидалось, то новый город сохраняется в списке городов CitiesList.

private void ForecastDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
            if (e.Result == null || e.Error != null)
            {
                MessageBox.Show("Cannot load Weather Forecast!");
            }
            else
            {
                XDocument document = XDocument.Parse(e.Result);
                XElement xmlRoot = document.Root;
 
                if (xmlRoot.Descendants("error").Count() > 0)
                {
                    MessageBox.Show("There is no weather forecast available for " + query + " or your apikey is wrong!");
                    NewCityName.Text = query;
                }
                else
                {
                    queries.Add(query);
                    NewCityName.Text = "Cityname,Countryname";
                }
            }
}


Пользователь может удалить города из списка CitiesList, щёлкнув по названию города в списке. Нужно помнить, что удалять город следует не из самого списка, а из ObservableCollection.

private void CitiesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
            int selectedIndex = (sender as ListBox).SelectedIndex;
            if (selectedIndex == -1) return;
            MessageBoxResult m = MessageBox.Show("Do you want to delete " + queries[selectedIndex] + " from the list?","Delete City?", MessageBoxButton.OKCancel);
            if (m == MessageBoxResult.OK)
            {
                queries.RemoveAt(selectedIndex);
            }
}


Заключение


Я написал несколько статей, связанных с XML, в Nokia Developer Wiki. Надеюсь, вы найдёте эти статьи полезными, и они помогут вам в работе с XML на Windows Phone 7.

Исходные коды можно загрузить по ссылке: PTM_Weather.zip.
Теги:
Хабы:
Всего голосов 21: ↑13 и ↓8+5
Комментарии0

Публикации

Информация

Сайт
www.microsoft.com
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Финляндия

Истории