WPF Weather API with DataGrid


This entry is part 5 of 6 in the series WPF DataGrid Control

In this example program we have a DataGrid that is filled with data from an API. This is a WPF project. For an introduction to Windows Presentation Foundation programming, have a look at our post called C# WPF Introduction.

This post was inspired by the content from C# Helper in the article called Get a weather forecast from openweathermap.org in C#. As the article says: “This example uses the OpenWeatherMap service. (I don’t particularly recommend this site over the others, but it is fairly easy to use.)”.

Here is the screenshot of the running program. The user has selected City in the drop-down and types in Toronto, CA and then pressed the Get Temperature Forecast button. The five pieces of information have been filled in as well as the DataGrid that’s below. The DataGrid has the day, time and temperature in Fahrenheit and Celcius.

Here is the XAML code.

<Window x:Class="OpenWeatherMapDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OpenWeatherMapDataGrid"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        Loaded="Window_Loaded"
        Title="OpenWeatherMapDataGrid - inspired by csharphelper.com" 
        Height="400" Width="350" MinWidth="350" MinHeight="300">
    
    <Grid Margin="4" Background="Bisque">
        <!-- Define rows and columns -->
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <WrapPanel Grid.Row="0" Margin="2">
            <ComboBox Name="cboQuery" Width="80"></ComboBox>
            <TextBox Name="txtLocation" Width="220" Margin="10,0,0,0"></TextBox>
        </WrapPanel>
        <Button Grid.Row="1" Name="btnForecast" Width="150" Height="23" Click="BtnForecast_Click" Margin="4">Get Temperature Forecast</Button>
        <Grid Grid.Row="2" Margin="4">
            <!-- Define rows and columns -->
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition/>
                <ColumnDefinition Width="80"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0">City:</Label>
            <TextBox Name="txtCity" Grid.Row="0" Grid.Column="1" ></TextBox>

            <Label Grid.Row="0" Grid.Column="2" Margin="10,0,0,0">Country:</Label>
            <TextBox Name="txtCountry" Grid.Row="0" Grid.Column="3"></TextBox>

            <Label Grid.Row="1" Grid.Column="0">Lat:</Label>
            <TextBox Name="txtLat" Grid.Row="1" Grid.Column="1"></TextBox>

            <Label Grid.Row="1" Grid.Column="2" Margin="10,0,0,0">Long:</Label>
            <TextBox Name="txtLong" Grid.Row="1" Grid.Column="3"></TextBox>

            <Label Grid.Row="2" Grid.Column="0">ID:</Label>
            <TextBox Name="txtId" Grid.Row="2" Grid.Column="1"></TextBox>
        </Grid>
        <!-- IsSharedSizeScope="True" -->
        <DataGrid Grid.Row="3" Name="dataGrid"
                  CanUserReorderColumns="False"
                  CanUserSortColumns="False"
                  CanUserAddRows="False"
                  CanUserDeleteRows="False"
                  CanUserResizeColumns="False"
                  CanUserResizeRows="False"
                  >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Day of the Week" Binding="{Binding DayOfWeek , Mode=OneWay}"/>
                <DataGridTextColumn Header="Time of the Day" Binding="{Binding TimeOfDay , Mode=OneWay}"/>
                <DataGridTextColumn Header="Farenheit" Binding="{Binding Farenheit, Mode=OneWay}"/>
                <DataGridTextColumn Header="Celcius" Binding="{Binding Celcius, Mode=OneWay}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Here is the C# code behind.

using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Windows;
using System.Xml;

namespace OpenWeatherMapDataGrid
{
    public partial class MainWindow : Window
    {
        private string[] QueryCodes = { "q", "zip", "id", };

        public MainWindow()
        {
            InitializeComponent();
        }
        // api key is 32 characters long and this is just a sample.
        // you need to get your own free one
        private const string API_KEY = "12345678901234567890123456789012";

        // Query URLs. Replace @LOC@ with the location.
        private const string CurrentUrl =
            "http://api.openweathermap.org/data/2.5/weather?" +
            "@QUERY@=@LOC@&mode=xml&units=imperial&APPID=" + API_KEY;
        private const string ForecastUrl =
            "http://api.openweathermap.org/data/2.5/forecast?" +
            "@QUERY@=@LOC@&mode=xml&units=imperial&APPID=" + API_KEY;

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // don't forget Loaded="Window_Loaded" in XAML
            cboQuery.Items.Add("City");
            cboQuery.Items.Add("ZIP");
            cboQuery.Items.Add("ID");

            cboQuery.SelectedIndex = 0; // select the first one (City) by default.
        }
        private void BtnForecast_Click(object sender, RoutedEventArgs e)
        {
            // Compose the query URL.
            string url = ForecastUrl.Replace("@LOC@", txtLocation.Text);
            url = url.Replace("@QUERY@", QueryCodes[cboQuery.SelectedIndex]);

            // Create a web client.
            using (WebClient client = new WebClient())
            {
                // Get the response string from the URL.
                try
                {
                    DisplayForecast(client.DownloadString(url));
                }
                catch (WebException ex)
                {
                    DisplayError(ex);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Unknown error\n" + ex.Message);
                }
            }
        }
        // Display the forecast.
        private void DisplayForecast(string xml)
        {
            dataGrid.Items.Clear();
            // Load the response into an XML document.
            XmlDocument xml_doc = new XmlDocument();
            xml_doc.LoadXml(xml);

            // Get the city, country, latitude, and longitude.
            XmlNode loc_node = xml_doc.SelectSingleNode("weatherdata/location");
            txtCity.Text = loc_node.SelectSingleNode("name").InnerText;
            txtCountry.Text = loc_node.SelectSingleNode("country").InnerText;
            XmlNode geo_node = loc_node.SelectSingleNode("location");

            txtLat.Text = geo_node.Attributes["latitude"].Value;
            txtLong.Text = geo_node.Attributes["longitude"].Value;
            txtId.Text = geo_node.Attributes["geobaseid"].Value;

            char degrees = (char)176;

            foreach (XmlNode time_node in xml_doc.SelectNodes("//time"))
            {
                // Get the time in UTC.
                DateTime time =
                   DateTime.Parse(time_node.Attributes["from"].Value,
                        null, DateTimeStyles.AssumeUniversal);

                // Get the temperature.
                XmlNode temp_node = time_node.SelectSingleNode("temperature");
                string temp = temp_node.Attributes["value"].Value;

                WeatherInfo wi = new WeatherInfo
                {
                    DayOfWeek = time.DayOfWeek.ToString(),
                    TimeOfDay = time.ToShortTimeString(),
                    Farenheit = temp + degrees + "F",
                    Celcius = (ConvertF2C(temp)) + degrees + "C"
                };
                dataGrid.Items.Add(wi);
            }
        }
        // Display an error message.
        private void DisplayError(WebException exception)
        {
            try
            {
                StreamReader reader = new StreamReader(exception.Response.GetResponseStream());
                XmlDocument response_doc = new XmlDocument();
                response_doc.LoadXml(reader.ReadToEnd());
                XmlNode message_node = response_doc.SelectSingleNode("//message");
                MessageBox.Show(message_node.InnerText);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unknown error\n" + ex.Message);
            }
        }
        public string ConvertF2C(string tempF)
        {   // farenheit to celcius
            double degC = 0.0;
            double degF = 0.0;
            degF = Convert.ToDouble(tempF);
            degC = (degF - 32.0) * 5 / 9;
            degC = Math.Round(degC, 2);
            return degC.ToString();
        }
    }
    public class WeatherInfo
    {
        public string DayOfWeek { get; set; }
        public string TimeOfDay { get; set; }
        public string Farenheit { get; set; }
        public string Celcius { get; set; }
    }
}

The post over at C# Helper includes an example listing of the response you might get from OpenWeatherMap. This is not the response I got when I ran it. The method DisplayForecast() in the listing above parses the data to get the forecast.

<weatherdata>
  <location>
    <name>Washington D.C.</name>
    <type/>
    <country>US</country>
    <timezone/>
    <location altitude="0" latitude="38.895" longitude="-77.0367" geobase="geonames" geobaseid="420008701"/>
  </location>
  <credit/>
  <meta>
    <lastupdate/>
    <calctime>0.0043</calctime>
    <nextupdate/>
  </meta>
  <sun rise="2018-07-30T10:08:00" set="2018-07-31T00:20:33"/>
  <forecast>
    <time from="2018-07-30T15:00:00" to="2018-07-30T18:00:00">
      <symbol number="500" name="light rain" var="10d"/>
      <precipitation unit="3h" value="0.11" type="rain"/>
      <windDirection deg="124.5" code="SE" name="SouthEast"/>
      <windSpeed mps="10.36" name="Fresh Breeze"/>
      <temperature unit="imperial" value="74.68" min="74.68" max="76.74"/>
      <pressure unit="hPa" value="1026.45"/>
      <humidity value="82" unit="%"/>
      <clouds value="overcast clouds" all="92" unit="%"/>
    </time>
    <time from="2018-07-30T18:00:00" to="2018-07-30T21:00:00">
      <symbol number="500" name="light rain" var="10d"/>
      <precipitation unit="3h" value="0.0075" type="rain"/>
      <windDirection deg="125.004" code="SE" name="SouthEast"/>
      <windSpeed mps="10.27" name="Fresh Breeze"/>
      <temperature unit="imperial" value="73.36" min="73.36" max="74.9"/>
      <pressure unit="hPa" value="1025.8"/>
      <humidity value="83" unit="%"/>
      <clouds value="broken clouds" all="80" unit="%"/>
    </time>
    ...
  </forecast>
</weatherdata>
Series Navigation<< WPF DataGrid Control Auto-Generated ColumnsWPF DataGrid Get Drive Information >>