Click here to Skip to main content
Click here to Skip to main content

WPF: OpenWeather

, 8 Aug 2013
Rate this:
Please Sign up or sign in to vote.
WPF application that displays weather data using the OpenWeatherMap API
Prize winner in Competition "Best VB.NET article of August 2013"


OpenWeather is a WPF application that displays current weather and forecast information. This app is a replacement for my WPF application of the same nature, which has been made redundant due to the shutting-down of the Google Weather API, which it acquired data from. OpenWeather makes use of data provided by the OpenWeatherMap API.


OpenWeatherMap provides free, current and forecast weather data for 70,000 cities worldwide. The data is sourced from more than 40,000 weather stations and can be acquired in JSON, XML or HTML format. To make effective use of the API you obtain an API key/AppID, through a simple and painless registration process, after which you'll get an AppID in your account page. According to OpenWeatherMap getting an AppID, "... guarantee[s] availability and accuracy of weather data."

The following is a sample of XML data returned by the API,

<?xml version="1.0" encoding="utf-8"?>
    <location altitude="0" latitude="-1.28333" longitude="36.816669" geobase="geonames" geobaseid="0"/>
  <sun rise="2013-08-01T03:37:07" set="2013-08-01T15:40:56"/>
    <time day="2013-08-01">
      <symbol number="500" name="light rain" var="10d"/>
      <precipitation value="0.5" type="rain"/>
      <windDirection deg="90" code="E" name="East"/>
      <windSpeed mps="1.81" name="Light breeze"/>
      <temperature day="19" min="14.26" max="20.2" night="14.26" eve="18.16" morn="19"/>
      <pressure unit="hPa" value="842.57"/>
      <humidity value="82" unit="%"/>
      <clouds value="sky is clear" all="8" unit="%"/>
    <time day="2013-08-02">
      <symbol number="500" name="light rain" var="10d"/>
      <precipitation value="3" type="rain"/>
      <windDirection deg="125" code="SE" name="SouthEast"/>
      <windSpeed mps="1.85" name="Light breeze"/>
      <temperature day="18.33" min="12.78" max="18.63" night="14.14" eve="16.9" morn="12.78"/>
      <pressure unit="hPa" value="843.16"/>
      <humidity value="84" unit="%"/>
      <clouds value="broken clouds" all="56" unit="%"/>
    <time day="2013-08-03">
      <symbol number="501" name="moderate rain" var="10d"/>
      <precipitation value="7" type="rain"/>
      <windDirection deg="112" code="ESE" name="East-southeast"/>
      <windSpeed mps="1.85" name="Light breeze"/>
      <temperature day="15.16" min="13.21" max="16.66" night="13.56" eve="15.38" morn="13.21"/>
      <pressure unit="hPa" value="843.21"/>
      <humidity value="99" unit="%"/>
      <clouds value="overcast clouds" all="92" unit="%"/>

The XML markup above shows three day forecast data. Please note that the value of the var attribute of the <symbol> element refers to an icon in the OpenWeatherMap icon list. You can read more about the API here.


The project contains a class named WeatherDetails that acts as the model,

Public Class WeatherDetails
    Public Property Weather As String
    Public Property WeatherIcon As String
    Public Property WeatherDay As String
    Public Property Temperature As String
    Public Property MaxTemperature As String
    Public Property MinTemperature As String
    Public Property WindDirection As String
    Public Property WindSpeed As String
    Public Property Humidity As String
End Class
class WeatherDetails
    public string Weather { get; set; }
    public string WeatherIcon { get; set; }
    public string WeatherDay { get; set; }
    public string Temperature { get; set; }
    public string MaxTemperature { get; set; }
    public string MinTemperature { get; set; }
    public string WindDirection { get; set; }
    public string WindSpeed { get; set; }
    public string Humidity { get; set; }

The view will bind to MainWindowViewModel to display weather data,

Imports System.ComponentModel
Imports System.Collections.ObjectModel

Public Class MainWindowViewModel
    Implements INotifyPropertyChanged

    Private Property _weatherCommand As ICommand

    Public ReadOnly Property WeatherCommand As ICommand
            Return _weatherCommand
        End Get
    End Property

    Public Sub New()
        _weatherCommand = New AsyncCommand(Function() GetWeather(), Function() CanGetWeather())
    End Sub

    Private _forecast As New List(Of WeatherDetails)

    Public Property Forecast As List(Of WeatherDetails)
            Return _forecast
        End Get
        Set(value As List(Of WeatherDetails))
            _forecast = value
        End Set
    End Property

    Private _currentWeather As New WeatherDetails

    Public Property CurrentWeather As WeatherDetails
            Return _currentWeather
        End Get
        Set(value As WeatherDetails)
            _currentWeather = value
        End Set
    End Property

    Private _location As String

    Public Property Location As String
            Return _location
        End Get
        Set(value As String)
            _location = value
        End Set
    End Property

    Public Async Function GetWeather() As Task
        Dim weatherInfo = Await Weather.GetWeather(Location)
        If (weatherInfo.Count <> 0) Then
            CurrentWeather = weatherInfo.First
            Forecast = weatherInfo.Skip(1).ToList
        End If
    End Function

    Public Function CanGetWeather() As Boolean
        If (Location IsNot String.Empty) Then
            Return True
            Return False
        End If
    End Function

    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) _
        Implements INotifyPropertyChanged.PropertyChanged

    Private Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class
namespace OpenWeather.ViewModel
    class MainWindowViewModel: INotifyPropertyChanged
        private readonly ICommand _weatherCommand;

        public ICommand WeatherCommand
            get{ return _weatherCommand; }

        public MainWindowViewModel()
            _weatherCommand = new AsyncCommand(() => GetWeather(), () => CanGetWeather());

        private List<WeatherDetails> _forecast = new List<WeatherDetails>();

        public List<WeatherDetails> Forecast
            get { return _forecast; }
                _forecast = value;

        private WeatherDetails _currentWeather = new WeatherDetails();

        public WeatherDetails CurrentWeather
            get { return _currentWeather; }
                _currentWeather = value;

        private string _location;

        public string Location
            get { return _location; }
                _location = value;

        public async Task GetWeather()
            List<WeatherDetails> weatherInfo = await Weather.GetWeather(Location);
            if (weatherInfo.Count != 0)
                CurrentWeather = weatherInfo.First();
                Forecast = weatherInfo.Skip(1).ToList();

        public Boolean CanGetWeather()
            if (Location != string.Empty)
                return true;
                return false;

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

The Command property, WeatherCommand, calls an asynchronous method; GetWeather(). To make the property asynchronous I use an AsyncCommand object,

Public Class AsyncCommand
    Implements ICommand

    Private ReadOnly _execute As Func(Of Task)
    Private ReadOnly _canExecute As Func(Of Boolean)
    Private isExecuting As Boolean

    Public Sub New(execute As Func(Of Task))
        Me.New(execute, Function() True)
    End Sub

    Public Sub New(execute As Func(Of Task), canExecute As Func(Of Boolean))
        _execute = execute
        _canExecute = canExecute
    End Sub

    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return (Not isExecuting AndAlso _canExecute())
    End Function

    Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged

    Public Async Sub Execute(parameter As Object) Implements ICommand.Execute
        isExecuting = True
            Await _execute()
            isExecuting = False
        End Try
    End Sub

    Protected Overridable Sub OnCanExecuteChanged()
        RaiseEvent CanExecuteChanged(Me, New EventArgs())
    End Sub
End Class
namespace OpenWeather.Command
    class AsyncCommand: ICommand
        private readonly Func<Task> _execute;
        private readonly Func<bool> _canExecute;
        private bool isExecuting;

        public AsyncCommand(Func<Task> execute) : this(execute, () => true) { }

        public AsyncCommand(Func<Task> execute, Func<bool> canExecute)
            _execute = execute;
            _canExecute = canExecute;

        public bool CanExecute(object parameter)
            return !(isExecuting && _canExecute());

        public event EventHandler CanExecuteChanged;

        public async void Execute(object parameter)
            isExecuting = true;
                await _execute();
                isExecuting = false;

        protected virtual void OnCanExecuteChanged()
            if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());

The asynchronous GetWeather() method sets the value of two properties in the view-model; CurrentWeather & Forecast, after calling an Async Shared method in class Weather which returns a List(Of WeatherDetails),

Imports System.Net.Http

Public Class Weather

    Private Shared AppID As String = "PLACE-YOUR-APP-ID-HERE"

    Public Shared Async Function GetWeather(ByVal location As String) As Task(Of List(Of WeatherDetails))
        Dim url = String.Format _
             location, AppID)
        Using client As New HttpClient
                Dim response = Await client.GetStringAsync(url)
                If Not (response.Contains("message") And response.Contains("cod")) Then
                    Dim xEl = XElement.Load(New System.IO.StringReader(response))
                    Return GetWeatherInfo(xEl)
                    Return New List(Of WeatherDetails)
                End If
            Catch ex As HttpRequestException
                Return New List(Of WeatherDetails)
            End Try
        End Using
    End Function

    Private Shared Function GetWeatherInfo(ByVal xEl As XElement) As List(Of WeatherDetails)
        Dim w = xEl...<time>.Select(Function(el) New WeatherDetails With {.Humidity = el.<humidity>.@value & "%",
                                                                         .MaxTemperature = el.<temperature>.@max & "°",
                                                                         .MinTemperature = el.<temperature>.@min & "°",
                                                                         .Temperature = el.<temperature>.@day & "°",
                                                                         .Weather = el.<symbol>.@name,
                                                                         .WeatherDay = DayOfTheWeek(el),
                                                                         .WeatherIcon = WeatherIconPath(el),
                                                                         .WindDirection = el.<windDirection>.@name,
                                                                         .WindSpeed = el.<windSpeed>.@mps & "mps"})
        Return (w.ToList)
    End Function

    Private Shared Function DayOfTheWeek(ByVal el As XElement) As String
        Dim dW = CDate(el.@day).DayOfWeek
        Return dW.ToString
    End Function

    Private Shared Function WeatherIconPath(ByVal el As XElement) As String
        Dim symbolVar = el.<symbol>.@var
        Dim symbolNumber = el.<symbol>.@number
        Dim dayOrNight = symbolVar.ElementAt(2) ' d or n
        Return (String.Format("WeatherIcons/{0}{1}.png", symbolNumber, dayOrNight))
    End Function
End Class
namespace OpenWeather.HelperClass
    static class Weather
        private static string AppID = "PLACE-YOUR-APP-ID-HERE";

        public static async Task<List<WeatherDetails>> GetWeather(string location)
            string url = string.Format
                location, AppID);
            using (HttpClient client = new HttpClient())
                    string response = await client.GetStringAsync(url);
                    if (!(response.Contains("message") && response.Contains("cod")))
                        XElement xEl = XElement.Load(new System.IO.StringReader(response));
                        return GetWeatherInfo(xEl);
                        return new List<WeatherDetails>();
                catch (HttpRequestException)
                    return new List<WeatherDetails>();

        private static List<WeatherDetails> GetWeatherInfo(XElement xEl)
            IEnumerable<WeatherDetails> w = xEl.Descendants("time").Select((el) =>
                new WeatherDetails
                    Humidity = el.Element("humidity").Attribute("value").Value + "%",
                    MaxTemperature = el.Element("temperature").Attribute("max").Value + "°",
                    MinTemperature = el.Element("temperature").Attribute("min").Value + "°",
                    Temperature = el.Element("temperature").Attribute("day").Value + "°",
                    Weather = el.Element("symbol").Attribute("name").Value,
                    WeatherDay = DayOfTheWeek(el),
                    WeatherIcon = WeatherIconPath(el),
                    WindDirection = el.Element("windDirection").Attribute("name").Value,
                    WindSpeed = el.Element("windSpeed").Attribute("mps").Value + "mps"

            return w.ToList();

        private static string DayOfTheWeek(XElement el)
            DayOfWeek dW = Convert.ToDateTime(el.Attribute("day").Value).DayOfWeek;
            return dW.ToString();

        private static string WeatherIconPath(XElement el)
            string symbolVar = el.Element("symbol").Attribute("var").Value;
            string symbolNumber = el.Element("symbol").Attribute("number").Value;
            string dayOrNight = symbolVar.ElementAt(2).ToString(); // d or n
            return String.Format("WeatherIcons/{0}{1}.png", symbolNumber, dayOrNight);

The URL for the GET request is in the following form,

The request queries the OpenWeatherMap API for three day forecast data in XML format. The following list describes the names used in the query string,

  • q: name of the city to get data for,
  • type: accuracy level of the city name search which can be one of two values; accurate or like. If the value specified is accurate the results will be exactly equivalent to the search word,
  • mode: data output format,
  • units: metric system; internal, metric, or imperial,
  • cnt: number of forecast days. The API provides data for upto 14 days,
  • appid: API key/AppID you received when registering with the OpenWeatherMap API

In the VB sample note that in the GetWeatherInfo() method I'm making use of XML Axis Properties to get the attribute values from the XElement; el.<temperature>.@max.

NB: The OpenWeatherMap API returns the following message if you enter an unknown location,



The OpenWeatherMap API icons did not exactly fit my design needs. I instead make use of some really well designed weather icons by VClouds, which are under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License. The icons are in the project's WeatherIcons folder.

The View

For the Window styling I've used MahApps.Metro,

    SaveWindowPosition="True" ShowMaxRestoreButton="False" 
    ShowMinButton="False" ShowTitleBar="False">
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colours.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Blue.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml"/>

I wanted all the text in the TextBox to be selected when the user pressed the Enter key. For this I use a Behavior,

Imports System.Windows.Interactivity

Public Class SelectAllTextOnEnterKeyPressBehavior
    Inherits Behavior(Of TextBox)

    Protected Overrides Sub OnAttached()
        AddHandler AssociatedObject.PreviewKeyUp, AddressOf AssociatedObject_PreviewKeyUp
    End Sub

    Protected Overrides Sub OnDetaching()
        RemoveHandler AssociatedObject.PreviewKeyUp, AddressOf AssociatedObject_PreviewKeyUp
    End Sub

    Private Sub AssociatedObject_PreviewKeyUp(ByVal sender As Object, ByVal e As KeyEventArgs)
        If (e.Key = Key.Enter) Then
        End If
    End Sub
End Class
namespace OpenWeather.Behavior
    public class SelectAllTextOnEnterKeyPressBehavior : Behavior<TextBox>

        protected override void OnAttached()
            AssociatedObject.PreviewKeyUp += AssociatedObject_PreviewKeyUp;

        protected override void OnDetaching()
            AssociatedObject.PreviewKeyUp -= AssociatedObject_PreviewKeyUp;

        private void AssociatedObject_PreviewKeyUp(object sender, KeyEventArgs e)
            if (e.Key == Key.Enter)

The following code snippet is the xaml markup for the TextBox,

<TextBox x:Name="LocationTextBox" Height="25" Width="270" Margin="10,0,10,10" Background="White"
       VerticalAlignment="Bottom" HorizontalAlignment="Center" SelectionBrush="#FFB21212"
       Controls:TextboxHelper.Watermark="Enter location & press ENTER"
       Text="{Binding Location, UpdateSourceTrigger=PropertyChanged}">            
           <KeyBinding Command="{Binding WeatherCommand}" Key="Enter"/>

In the markup above you can see that I've used a KeyBinding to invoke the WeatherCommand when the user presses the Enter key.

The forecast information is displayed in an ItemsControl whose ItemsSource property is bound to the Forecast property in the View-Model. The ItemsControl uses the following DataTemplate,

<DataTemplate x:Key="ForecastDataTemplate">
    <Grid Margin="0,2,0,0" Height="83">
        <Border BorderBrush="#FF5E727C" BorderThickness="0,1,0,0" HorizontalAlignment="Center" Height="83" 
               Margin="0" VerticalAlignment="Center" Width="265">
            <StackPanel Margin="0">
                <Grid Height="24" Margin="0,5,0,0">
                    <TextBlock TextWrapping="Wrap" FontSize="14" Foreground="#FF333B42" Padding="2,0,0,0" Width="72.5"
                           Margin="0" TextAlignment="Center" FontWeight="Bold" HorizontalAlignment="Left" 
                           Text="{Binding WeatherDay}"/>
                    <TextBlock TextWrapping="Wrap" FontSize="14" Foreground="#FF292929" Padding="0" Margin="77.5,0,0,0" 
                           TextAlignment="Left" FontWeight="Normal" FontFamily="Segoe UI Semibold"
                           Text="{Binding Weather}"/>
                <Grid Height="51.5">
                    <StackPanel Margin="0" Orientation="Horizontal" HorizontalAlignment="Left">
                        <Image HorizontalAlignment="Left" Width="71.5" Source="{Binding WeatherIcon}"/>
                        <TextBlock HorizontalAlignment="Left" Height="51.5" TextWrapping="Wrap" VerticalAlignment="Top"
                               Width="83.5" FontFamily="Segoe UI" FontSize="24" TextAlignment="Center" 
                               Padding="0,5,0,0" Foreground="#FFA47D14" Text="{Binding Temperature}"/>
                    <StackPanel Margin="0,26,0,3.5" Orientation="Horizontal" HorizontalAlignment="Right" Width="115">
                        <TextBlock TextWrapping="Wrap" Text="Wind:" FontFamily="Segoe UI Semibold" FontSize="14"
                               Foreground="#FF056C9C" TextAlignment="Center" Width="49.5"/>
                        <TextBlock TextWrapping="Wrap" FontFamily="Segoe UI Semibold" FontSize="14" 
                               Foreground="#FF9B8C5E" Padding="0" Width="76.666" Text="{Binding WindSpeed}"/>
                    <StackPanel Margin="0,4,0,25" Orientation="Horizontal" HorizontalAlignment="Right" Width="120">
                        <TextBlock Height="22" TextWrapping="Wrap" VerticalAlignment="Top" Width="65" 
                               FontFamily="Segoe UI Semibold" FontSize="14" Foreground="#FF333B42"
                               HorizontalAlignment="Left" Padding="0" TextAlignment="Center"
                               Text="{Binding MaxTemperature}"/>
                        <TextBlock TextWrapping="Wrap" FontFamily="Segoe UI Semibold" FontSize="14"
                               Foreground="#FF6F7C87" Margin="0" Padding="0" Width="67"
                               Text="{Binding MinTemperature}"/>

Most of the design work was done in Expression Blend so I didn't do a whole lot of XAML hand-coding. The design is quite simple and minimalist, focusing on a subset of the data available through the View-Model.


The OpenWeatherMap API currently seems to be in beta but it provides what seems to be 'quality data'. The API doesn't fancy too many requests from one device over a short period of time; they advice, "Do not send requests more then 1 time per 10 minutes from one device", so expect it to at times ignore requests if you do so.


  • 1st Aug 2013: Initial post


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Meshack Musundi
Software Developer
Kenya Kenya
Meshack is an avid programmer with a bias towards WPF and VB.NET. He has about 5 years of programming experience initially starting off with Java before shifting to .NET, thanks to the allure of WPF. He has developed several applications, and written several articles about them, which can be viewed here on CodeProject. He currently resides in a small town in Kiambu county, Kenya.
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • Best VB.NET article of August 2013
  • Best VB.NET article of February 2013
  • Best VB.NET article of October 2012
  • Best VB.NET article of July 2012
  • Best VB.NET article of February 2012
  • Best VB.NET article of January 2012
  • Best VB.NET article of November 2011
  • Best VB.NET article of June 2011
  • Best VB.NET article of May 2011
  • Best VB.NET article of March 2011
  • Best VB.NET article of February 2011
  • Best VB.NET article of January 2011
  • Best VB.NET article of December 2010
  • Best VB.NET article of November 2010

Comments and Discussions

QuestionQuestion - Vs 2013 Pinmembermelspring8-Jul-14 3:31 
AnswerRe: Question - Vs 2013 PinpremiumMeshack Musundi8-Jul-14 3:39 
GeneralRe: Question - Vs 2013 Pinmembermelspring8-Jul-14 14:15 
GeneralRe: Question - Vs 2013 PinpremiumMeshack Musundi8-Jul-14 22:24 
GeneralRe: Question - Vs 2013 Pinmembermelspring9-Jul-14 2:12 
SuggestionFix PinprofessionalSperneder Patrick13-Jun-14 21:54 
GeneralRe: Fix PinpremiumMeshack Musundi13-Jun-14 22:00 
QuestionProblem when opening the project PinmemberN. Henrik Lauridsen24-Sep-13 10:46 
AnswerRe: Problem when opening the project PinmvpMeshack Musundi24-Sep-13 19:09 
GeneralRe: Problem when opening the project PinmemberN. Henrik Lauridsen24-Sep-13 19:36 
GeneralMy vote of 5 Pingroupvinodhmahen23-Sep-13 23:48 
GeneralRe: My vote of 5 PinmvpMeshack Musundi24-Sep-13 4:23 
GeneralMy vote of 5 PinprofessionalRenju Vinod16-Sep-13 20:33 
GeneralRe: My vote of 5 PinmvpMeshack Musundi16-Sep-13 21:38 
GeneralMy vote of 5 PinmvpMika Wendelius15-Sep-13 21:38 
GeneralRe: My vote of 5 PinmvpMeshack Musundi15-Sep-13 23:22 
Questionvery nice PinmemberCIDev11-Sep-13 4:40 
GeneralRe: very nice PinmvpMeshack Musundi11-Sep-13 9:26 
GeneralMy vote of 5 PinmemberUday P.Singh10-Sep-13 0:47 
GeneralRe: My vote of 5 PinmvpMeshack Musundi10-Sep-13 20:53 
GeneralMy vote of 5 PinmemberJuan R. Huertas16-Aug-13 3:32 
GeneralRe: My vote of 5 PinmvpMeshack Musundi16-Aug-13 4:59 
GeneralMy vote of 5 PingroupPaul @ The Computer Station14-Aug-13 3:18 
GeneralRe: My vote of 5 PinmvpMeshack Musundi14-Aug-13 6:16 
GeneralMy vote of 5 PinmemberBryanWilkins9-Aug-13 9:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 8 Aug 2013
Article Copyright 2013 by Meshack Musundi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid