Introduction
The WPF weather app provides the user with information on the current and forecast weather conditions. The app shows the current weather condition (e.g. Cloudy), temperature (in both degrees Celsius and Fahrenheit), wind speed and direction, and humidity. The application also provides two day forecast data of expected conditions and temperature highs and lows (in degrees Fahrenheit). The user can specify a location and, if internet connection is available, get the weather data for the specified location.
Background
The WPF weather app makes use of the Google Weather API which returns XML data with weather information (I will explain more on this API later). You will also note that in the code I make use of the LINQ to XML API, to query data from the weather API, plus XML axis properties which are a VB.NET language feature.
Requirements
To open the solution provided from the download link above, you require either of the following;
- Visual Studio 2010 Express
- Expression Blend 4
The other requirements are that you should be conversant with XML, LINQ and threading in WPF.
NB: If you experience compilation errors when trying to run the project, ensure that you have installed the WPF Toolkit - February 2010 Release. If you don't have the toolkit installed, you will notice the following error:
Missing reference to 'System.Windows.Controls.Input.Toolkit'.
The Google Weather API
The Google Weather API returns weather information but as of the time of writing it has no official documentation. The use of the API therefore has a caveat since the structure of the data returned may change or the API may be deprecated entirely. Its only official use is on iGoogle for getting weather information. That said, its current 'unofficial' status does not prevent us from making use of it while we still can.
The Google Weather API returns weather information by passing one of the following four values:
- Zip code
- City name, e.g. Dubai
- City name and state, e.g. Boston, MA
- City name and country name, e.g. Nairobi, Kenya
For the WPF Weather App, the listed values will suffice to provide you with the necessary information. That said, some unofficial sources indicate that the API accepts the following three parameters:
Place
: Either one of the four possible values listed above. This is a required field.
Language
: An ISO 639-1 Language Code. This is an optional field.
Unit
: Temperature values passed in degrees Celsius or Fahrenheit (default). This is an optional field.
NB: The API does not provide feedback for all locations so don't be surprised if you don't get data for an area that you may be familiar with.
The following is the structure of XML data returned by GOOWAPI [goo-wap-i], I will refer to the weather API by this name from here onwards:
="1.0" ="utf-8"
<xml_api_reply version="1">
<weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0">
<forecast_information>
<city data="Nairobi, Nairobi" />
<postal_code data="Nairobi, Kenya" />
<latitude_e6 data="" />
<longitude_e6 data="" />
<forecast_date data="2010-10-27" />
<current_date_time data="2010-10-27 04:00:00 +0000" />
<unit_system data="US" />
</forecast_information>
<current_conditions>
<condition data="Cloudy" />
<temp_f data="63" />
<temp_c data="17" />
<humidity data="Humidity: 94%" />
<icon data="/ig/images/weather/cloudy.gif" />
<wind_condition data="Wind: NE at 6 mph" />
</current_conditions>
<forecast_conditions>
<day_of_week data="Wed" />
<low data="59" />
<high data="75" />
<icon data="/ig/images/weather/chance_of_storm.gif" />
<condition data="Chance of Storm" />
</forecast_conditions>
<forecast_conditions>
<day_of_week data="Thu" />
<low data="57" />
<high data="82" />
<icon data="/ig/images/weather/chance_of_rain.gif" />
<condition data="Chance of Rain" />
</forecast_conditions>
<forecast_conditions>
<day_of_week data="Fri" />
<low data="57" />
<high data="78" />
<icon data="/ig/images/weather/chance_of_rain.gif" />
<condition data="Chance of Rain" />
</forecast_conditions>
<forecast_conditions>
<day_of_week data="Sat" />
<low data="59" />
<high data="77" />
<icon data="/ig/images/weather/chance_of_storm.gif" />
<condition data="Chance of Storm" />
</forecast_conditions>
</weather>
</xml_api_reply>
As you can see, there's a <current_conditions>
element with child elements containing values of current weather conditions in their data
attributes. The same applies to <forecast_conditions>
elements that have forecast data in the data
attributes of their child elements. Our focus of interest will therefore be on the <current_conditions>
and the <forecast_conditions>
elements.
NB: While goowapi provides four day forecast information, the WPF Weather App will only gather information for two days, i.e., the first day after the current day and the second day after the current day.
The Weather App
How It Works
In order to display the current weather and forecast conditions of a particular location, the user types in a location in the textbox and clicks on the Go button (the arrow next to the text box). For this app, it is recommended that the user enters a location based on the 2nd, 3rd and 4th values specified as possible parameters in the above Google Weather API section, i.e., city name, city name & state (for State side data), or city name and country.
Note that when entering certain values, like Berlin, a list of possible location(s) is displayed as you type. This is because the textbox is an AutoCompleteBox
that contains several values in its ItemSource
property.
A progress bar is shown at the top of the application while data is being downloaded. This shouldn't be a very visible feature if you have fast internet connection.
To display weather information, two conditions have to be fulfilled:
- There should be internet connection.
- If there's internet connection, the location provided should be one that goowapi is conversant with. Otherwise, the application displays a message box informing the user that it was unable to get weather information for the desired location.
The User Interface
I first designed the UI in Expression Design and added some extra elements in Expression Blend. I will not cover the details of the design process, but I recommend that if you want to have a better look at the UI elements, do so in Expression Blend (4).
The following image shows the UI elements that are of interest for displaying weather information. The rest of the elements are just rectangles, ellipses, and a combination of rectagles and ellipses (the thermometer and wind turbine).
The Code
When the application is initialized, the following code is executed:
Private Sub MainWindow_Initialized(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Initialized
timer = New DispatcherTimer
AddHandler timer.Tick, AddressOf timer_Tick
timer.Interval = New TimeSpan(0, 5, 0)
timer.Start()
SearchAutoTextBox.ItemsSource = My.Settings.UserLocations
End Sub
We initialize a DispatcherTimer
object and add a handler for its Tick
event. We also set the ItemsSource
property of the AutoCompleteBox
with values from an application setting named UserLocations. This setting is of type StringCollection
, which is found in the System.Collections.Specialized
namespace. To view the settings, open the project properties window and click on the Settings tab.
The UserLocations setting contains a collection of string
values and in this case, the default values provided are city and country names in the format; City
, CountryName
. Click on the ellipse in the Value section of the UserLocations setting to view or add default values in the String Collection Editor.
In the Loaded
event of the Window
, the following method is called:
Private Sub MainWindow_Loaded(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Dim thread As New Thread(AddressOf WeatherConditions)
thread.Start()
End Sub
Here we simply call the WeatherConditions
method on a background thread.
Private Sub WeatherConditions()
location = My.Settings.Location
src = "http://www.google.com/ig/api?weather=" & location
Try
weatherDoc = XDocument.Load(src)
Catch ex As System.Net.WebException
MessageBox.Show("Internet connection unavailable. " & _
"Unable to acquire latest weather info.", "Error!", _
MessageBoxButton.OK, MessageBoxImage.Error)
Exit Sub
End Try
Dim problem = weatherDoc...<problem_cause>(0)
If problem IsNot Nothing Then
MessageBox.Show("Unable to get the weather conditions of " & location & _
vbCrLf & "Try again and enter data in the format " & _
": city, country" & vbCrLf & "e.g. London, England", "Error")
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf ClearSearchTextBox))
Exit Sub
End If
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf CurrentConditions))
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf ForecastConditions))
End Sub
The WeatherConditions
method assigns a location value to a string
variable, location
, based on a value from the Location application setting. A Try...Catch
block is used to catch a System.Net.WebException
that may be thrown when attempting to download data from goowapi and a possible goowapi error is dealt with accordingly.
GOOWAPI Exceptions
Goowapi will throw an exception if a location is passed that it is not conversant with (this is an assumption based on personal tests). There may be other reasons for generating exceptions which, as of the time of writing, I'm not familiar with. The following XML data is returned by the API if it throws an exception:
="1.0" ="utf-8"
<xml_api_reply version="1">
<weather module_id="0" tab_id="0" mobile_row="0"
mobile_zipped="1" row="0" section="0">
<problem_cause data="" />
</weather>
</xml_api_reply>
The code Dim problem = weatherDoc...<problem_cause>(0)
, in WeatherConditons
creates an XElement
variable, using type inference, which we check to ensure that an error was not returned. The line of code can also be written as...
Dim problem = weatherDoc...<problem_cause>.Single()
...since there's only a single element in the generated sequence.
Updating the UI
Since WeatherConditions
is executed on a background thread, we schedule the CurrentConditions
and ForecastConditons
methods as tasks for the Dispatcher
. These two methods update the UI elements with values generated from goowapi:
Private Sub CurrentConditions()
Dim currCondition = weatherDoc...<current_conditions>.Single() _
.Element("condition").@data
Dim temp_f = weatherDoc...<current_conditions>.Single() _
.Element("temp_f").@data
Dim temp_c = weatherDoc...<current_conditions>.Single() _
.Element("temp_c").@data
Dim humidity = weatherDoc...<current_conditions>.Single() _
.Element("humidity").@data
Dim imageSrc = "http://www.google.com" & weatherDoc...<current_conditions> _
.Single().Element("icon").@data
Dim windCondition = (weatherDoc...<current_conditions>.Single() _
.Element("wind_condition").@data).Substring(6)
cityTxtBlck.Text = My.Settings.Location
currConditionTxtBlck.Text = currCondition
temp_fTxtBlck.Text = temp_f & "° F"
temp_cTxtBlck.Text = temp_c & "° C"
humidityTxtBlck.Text = humidity
windSpeedTxtBlck.Text = windCondition
Dim bi As New BitmapImage
bi.BeginInit()
bi.UriSource = New Uri(imageSrc)
bi.EndInit()
currConditionIcon.Source = bi
End Sub
Private Sub ForecastConditions()
Dim dayOfWeek1 = weatherDoc...<forecast_conditions>(0).Element("day_of_week").@data
Dim dayOfWeek2 = weatherDoc...<forecast_conditions>(1).Element("day_of_week").@data
Dim low1 = weatherDoc...<forecast_conditions>(0).Element("low").@data
Dim low2 = weatherDoc...<forecast_conditions>(1).Element("low").@data
Dim high1 = weatherDoc...<forecast_conditions>(0).Element("high").@data
Dim high2 = weatherDoc...<forecast_conditions>(1).Element("high").@data
Dim condition1 = weatherDoc...<forecast_conditions>(0).Element("condition").@data
Dim condition2 = weatherDoc...<forecast_conditions>(1).Element("condition").@data
Dim imageSrc1 = "http://www.google.com" & _
weatherDoc...<forecast_conditions>(0).Element("icon").@data
Dim imageSrc2 = "http://www.google.com" & _
weatherDoc...<forecast_conditions>(1).Element("icon").@data
fcastDayTxtBlck1.Text = dayOfWeek1
fcastDayTxtBlck2.Text = dayOfWeek2
fcastLowTxtBlck1.Text = low1 & "° F"
fcastLowTxtBlck2.Text = low2 & "° F"
fcastHighTxtBlck1.Text = high1 & "° F"
fcastHighTxtBlck2.Text = high2 & "° F"
fcastConditionTxtBlck1.Text = condition1
fcastConditionTxtBlck2.Text = condition2
Dim bi1 As New BitmapImage
Dim bi2 As New BitmapImage
bi1.BeginInit()
bi1.UriSource = New Uri(imageSrc1)
bi1.EndInit()
bi2.BeginInit()
bi2.UriSource = New Uri(imageSrc2)
bi2.EndInit()
fCastIcon1.Source = bi1
fCastIcon2.Source = bi2
End Sub
Forecast Data of User Specified Location
When the user enters a location in the AutoCompleteBox
and clicks on the GoButton
, the following code is executed:
Private Sub GoButton_Click(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) Handles GoButton.Click
CheckAutoCompleteBox()
If proceed = False Then
Exit Sub
Else
Dim thread As New Thread(AddressOf UserSpecifiedLocationForecast)
thread.Start()
End If
End Sub
In the GoButton_Click
event handler, we first call a method that checks whether the AutoCompleteBox
is empty and proceed to call the UserSpecifiedLocationForecast
method on a background thread if a value is present.
Private Sub CheckAutoCompleteBox()
If SearchAutoTextBox.Text <> String.Empty Then
location = SearchAutoTextBox.Text
src = "http://www.google.com/ig/api?weather=" & location
proceed = True
Else
MessageBox.Show("Enter a location in the location textbox.", "Weather")
proceed = False
End If
End Sub
Private Sub UserSpecifiedLocationForecast()
timer.Stop()
Try
weatherDoc = XDocument.Load(src)
Catch ex As System.Net.WebException
MessageBox.Show("Internet connection failed. Ensure that you are" & _
" connected to the internet.", "Error", MessageBoxButton.OK, _
MessageBoxImage.Error)
Exit Sub
End Try
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf ShowProgressBar))
Dim problem = weatherDoc...<problem_cause>(0)
If problem IsNot Nothing Then
MessageBox.Show("Unable to get the weather conditions of " & location & _
vbCrLf & "Try again and enter data in the format " & _
": city, country" & vbCrLf & "e.g. London, England", "Error")
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf ClearSearchTextBox))
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf HideProgressBar))
Exit Sub
End If
My.Settings.Location = location
If My.Settings.UserLocations.Contains(location) <> True Then
My.Settings.UserLocations.Add(location)
End If
My.Settings.Save()
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf CurrentConditions))
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf ForecastConditions))
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf ClearSearchTextBox))
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New ThreadStart(AddressOf HideProgressBar))
timer.Start()
End Sub
In the UserSpecifiedLocationForecast
method, we first stop the timer, whose purpose I'll explain later, and check for network and goowapi errors. We then set the Location setting to the value provided in the AutoCompleteBox
, which at this point is valid having passed the necessary checks, and add the location provided to the collection of strings in the UserLocations setting. We then persist the settings by calling the Settings.Save()
method and proceed to update the user interface. Notice that when downloading data from the Google server, a ProgressBar
will be shown and hidden when necessary.
UI Updates
Weather changes with time. It can be warm-and-sunny now and cold-and-rainy the 'next' minute. To keep up with weather changes, the WPF Weather App checks for current weather conditions every five minutes. This is done using the DispatcherTimer
object which we initialized in the MainWindow
's Initialized
event handler. The event handler for timer
's Tick
event is shown below:
Private Sub timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
Dim thread As New Thread(AddressOf WeatherConditions)
thread.Start()
End Sub
NB: I'm not sure how often the weather data is updated by Google, so I have used my own time assumption for updating the UI.
Animation
You must have noticed the wind turbine whose blades are constantly rotating. I wrote no code for this, just grouped three ellipses, adjusted the group's center point and created a storyboard, OnLoaded1
, where I rotated the group 360° clockwise on a timeline of two seconds. I then set the RepeatBehavior
to Forever
. You can view the storyboard in Expression Blend by opening OnLoaded1
from the Objects and Timeline panel.
The storyboard is triggered by the application's OnLoaded
event.
Current Weather Condition Image
The image shown in the application for the current weather condition is certainly not the prettiest due to scaling. If the Image
control were smaller it would look better, like those of the forecast conditions. You can therefore write a Select Case
checking for a particular image and display your custom images or elements. To do that, check for the following values that are returned by goowapi in the data
attributes of the <icon>
elements of either the <current_conditions>
or <forecast_conditions>
elements:
- /ig/images/weather/sunny.gif
- /ig/images/weather/mostly_sunny.gif
- /ig/images/weather/partly_cloudy.gif
- /ig/images/weather/mostly_cloudy.gif
- /ig/images/weather/chance_of_storm.gif
- /ig/images/weather/rain.gif
- /ig/images/weather/chance_of_rain.gif
- /ig/images/weather/chance_of_snow.gif
- /ig/images/weather/cloudy.gif
- /ig/images/weather/mist.gif
- /ig/images/weather/storm.gif
- /ig/images/weather/thunderstorm.gif
- /ig/images/weather/chance_of_tstorm.gif
- /ig/images/weather/sleet.gif
- /ig/images/weather/snow.gif
- /ig/images/weather/icy.gif
- /ig/images/weather/dust.gif
- /ig/images/weather/fog.gif
- /ig/images/weather/smoke.gif
- /ig/images/weather/haze.gif
- /ig/images/weather/flurries.gif
Tip: You can create animations in Blend and run them when appropriate.
Credits
I have to give credit to Minhajul Shaoun whose article spurred my interest in the Google Weather API. I'm not an ASP.NET guy, but the article was quite enlightening. Credit also goes to Joseph C. Rattz, JR. and Dennis Hayes whose book, Pro LINQ: Language Integrated Query in VB 2008, provided me with wonderful insight on LINQing in VB.NET. I recommend this book to anyone interested in learning the details of Language Integrated Query.
Conclusion
If you've read the whole article up to this point, "thank you" for taking the time to do so. I hope it was useful in one way or another. That said, I hope goowapi will be around for a long time. It is definitely useful to both desktop and web developers. The acronym I've christened the Google Weather API will also hopefully stick and save us from having to say two words and an acronym too many times when describing the API to friends, family, clients or fellow developers.
History
- 1st November, 2010: Initial post