⬆️ ⬇️

Weather for Windows Phone 7. Working with XML

About the author: Pasi Manninen develops mobile and web applications, is a Nokia Developer Champion and Adobe Professional Professional, an Adobe-certified Adobe expert in Flex, Flash and Flash Mobile. Graduated from the University of Jyväskylä (one of the largest universities in Finland) with a degree in Applied Mathematics and Computer Science.



This article describes how to work with XML data on the example of an application that displays the weather forecast in any cities of the world.



Introduction



In this article, I will show how to create an application for Window Phone 7 that downloads the weather forecast from World Weather Online . You can read an interesting article on working with Weather Online, but I want to delve into the principles of storing selected cities in the phone and the method of creating a panoramic view mode for different cities.

')





The application is designed as a panorama for WP7. First, the application loads the saved list of cities and the API key to access Weather Online from the device’s memory, and then receives the weather forecast for all cities. Each forecast is displayed in its own Panorama View. The user can specify ApiKey and selected cities on the settings page.



API key for Weather Online



You will need your own API key to access Weather Online. From the relevant article you can learn how to do it.



Windows Phone 7.1 SDK



To develop applications for Windows Phone 7 devices, you need to install the appropriate SDK. Download the latest version of the Windows Phone SDK here .



Application for Windows Phone



To create a new Windows Phone application, open Microsoft Visual Studio, create a new project, and select the Windows Phone Application Template template. In addition to it, there is a template called Panorama Template, but we will use the above. In this case, the code of our application will be very simple and clear, and we will program the panoramic viewing mode by ourselves.







In this example, I chose C # as the development language.



Parsing XML in Windows Phone 7



There are many ways to load and parse XML data on Windows Phone 7. In this example, I’ll use XML Deserialization to load an XML document and process it. Add links to System.Xml.Serialization and System.Xml.Linq in your project. Right-click on “References” in the Solutions Explorer of your project and select “Add new Reference ...”.



Download and display images from the network



When downloading a large number of images from the network simultaneously, Windows Phone may have some difficulties. On the Internet, you can find assemblies for downloading images in the background that solve this problem. One of them is PhonePerformance (you can download the compiled assembly of PhonePerformance here with the source codes). Copy PhonePerformance.dll from the zip-archive to your project and add a new link to it using Solution Explorer.



You may receive a warning that the PhonePerformance.dll assembly was downloaded from the Internet and needs to be unlocked. Just close Visual Studio, open Windows Explorer, locate the PhonePerformance.dll file, right-click it and unlock it with the appropriate button in the file properties.



Application source code



To add new classes to your project, right-click on the project in Solutions Explorer, select Add first and then Class.



Forecast.cs


Weather Online provides a wealth of weather information. In this example, I will only use those shown in the Forecast class. This class is used when weather data is downloaded from 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


I will link all weather information to Panorama View. Each Panorama View view has basic information about the current weather and a five-day forecast. The Forecast class created above is used here in the list of forecasts.



 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



Design (MainPage.xaml)


This application generates as many Panorama View as the number of cities specified in the application settings. The title and background changes only at the first panorama:



 <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> 




You can add your background image to the project. To do this, create a new folder for images in Solutions Explorer, and then copy your image using Windows Explorer. Then add this image to the project by selecting the folder with images in Solutions Explorer and selecting Add Existing Item ... It is important to use the image of the correct size: they must be at least 480x800 and not more than 1024x800 pixels.



I will use two templates to display the weather forecast in Panorama View. These templates are stored in App.xaml.



Design (App.xaml)


All Panorama View are the same, only the weather information for a particular city is changing. I used DataTemplates to display the weather forecast. The App.xaml file contains an Application.Resources element where you can store your DataTemplates.



The template below is used to display a five-day weather forecast. Each row displays the date, image, and minimum / maximum temperature.



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> 




The main template for Panorama View is described below. It has a space of 150 pixels wide for the image corresponding to the weather forecast on the left, and the current weather is on the right. Downstairs is the StackPanel for five days of weather. First, add a header TextBlock, and then one ListBox for weather forecasts. You can use other templates with the ItemTemplate attribute in the 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> 




Do not forget to add the namespace for PhonePerformance.dll to load images:

 xmlns:delay="clr-namespace:Delay;assembly=PhonePerformance" 


All data is associated with interface elements in the MainPage.xaml.cs class.



Programming (MainPage.xaml.cs)


The whole process of loading XML occurs in the MainPage.xml.cs file. All cities specified in the settings are first loaded into ObservableCollection, and the API key is saved in the apikey string. The LoadForecast method will be called as many times as we have cities specified in IsolatedStorageSettings.



Specify class variables:



 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"; 




In the MainPage constructor, we first get an instance to access the application settings, and then check the availability of the Internet connection:



 // 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; } } 




Each time the MainPage is displayed, the OnNavigatedTo method is called. In it, we load the list of all cities and the API key from IsolatedStorage. After that, all Panorama View will be deleted, and the new weather forecast will be downloaded for all cities.



 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 uses the WebClient class to asynchronously download XML data from a Weather Online server.



 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); } 




The ForecastDownloaded method will be called when the download of the new weather forecast from the Weather Online server is completed. In this method, we process current weather information directly from XML. At the end of processing, we create a new PanoramaItem using the AddPanoramaItem method.



 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); } } 




The AddPanoramaItem method takes as its arguments an object with information about the current weather and a list with weather information for the next five days. First, a PanoramaItemObject object will be created to bind all the data to the interface. Then, we will create a real Panorama View object with the name of the city in the panorama header and with the ForecastTemplate to display weather information. After that information about the next city in the list of cities will be loaded.



 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(); } 




Settings



Application settings are stored in isolated IsolatedStorage. The settings page opens when the user clicks on the settings icon (or text) in the Application Bar.



image



Design (Main.xaml)


You can display the icon and text in the application bar. Copy your image into the folder with images and set its action during assembly to the Content position in the file settings panel in Solution Explorer. Add event handling to the icon and text.



 <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> 




Programming (Main.xaml.cs)


The settings page will open when the user clicks the settings link on the MainPage page.



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




Settings page



To create a new page (Page) in your project, right-click on your project in Solution Explorer, select Add, and then New Item. In the window that appears, select the Windows Phone Portrait Page Page and name the new page as SettingsPage.xaml.



Design (SettingsPage.xaml)


This page is a regular page in portrait mode. The name of the application and the name of the page are displayed in its upper area.







The user can add and change the API key, as well as add cities to the CitiesList. Added cities can be deleted by clicking on the name of the city in the list (after which confirmation for deletion via MessageBox will be requested).



 <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> 




Programming (SettingsPage.xaml.cs)


First we need to check if there is a weather forecast for the city added by the user. If the weather forecast exists (and you can get it with the specified API key), then the API key and city are added to the class variables. Settings are saved when the user returns to the main pages of the application.



The class variables are the same as we used in the Main.xaml.cs class; an instance of the settings class is also loaded in this way.



Each time the settings page is displayed, the OnNavigatedTo method is called. It loads the list of all cities and the API key from 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 will be called, on the contrary, when the user leaves the settings page. All changes will be saved to 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); } 




The user on the settings page can also verify that the API key or city is entered correctly. A new object of the WebClient class will be created to verify the success of data entry. If no errors are found and everything works as expected, then the new city is saved in the CitiesList list of cities.



 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"; } } } 




The user can remove cities from the CitiesList list by clicking on the name of the city in the list. It must be remembered that the city should not be deleted from the list itself, but from the 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); } } 




Conclusion



I wrote several XML related articles on the Nokia Developer Wiki. I hope you find these articles useful, and they will help you with working with XML on Windows Phone 7.



Source codes can be downloaded at the link: PTM_Weather.zip .

Source: https://habr.com/ru/post/138325/



All Articles