Introduction
WeatherBar
is an application written in C# to show the weather in a specific location. One point that makes this application different from other weather applications and gadgets is the ability to integrate with the Windows 7 taskbar. Therefore, instead of constantly switching to the desktop to take a peek at the pinned gadget, the user can simply take a look at the taskbar, or right-click on the application icon to read the forecast.
Background
The application uses server APIs to build the final data. The foundation of the application is the Google Weather API. By building a request, raw XML data is returned from the Google server, which is then processed to extract the needed elements (for example, the current weather conditions and the forecast).
Besides, to correctly display the weather icon (image), the current time in the specified region is needed. A great API for this was one offered by TrueKnowledge, which also returns XML data per request. One of the benefits of this specific API is the possibility to directly pass the location or ZIP code to get the time, instead of working with API-specific codes.
Also, since the application should use Windows 7 - specific elements, the Windows API was used through a managed library - Windows API Code Pack for .NET Framework. It was used to build the WeatherBar
taskbar capabilities.
Using the Code
The application is separated in two distinct parts - the application itself (forms) and the Weather
class. The Weather
class contains all calls to the API that return either the current conditions or the forecast.
To make it easier operating with the received data, I've created a Conditions
class, instances of which store the data for different days. Here is the class skeleton:
public class Conditions
{
string city = "No Data";
string dayOfWeek = DateTime.Now.DayOfWeek.ToString();
string condition = "No Data";
string tempF = "No Data";
string tempC = "No Data";
string humidity = "No Data";
string wind = "No Data";
string high = "No Data";
string low = "No Data";
public string City
{
get { return city; }
set { city = value; }
}
public string Condition
{
get { return condition; }
set { condition = value; }
}
public string TempF
{
get { return tempF; }
set { tempF = value; }
}
public string TempC
{
get { return tempC; }
set { tempC = value; }
}
public string Humidity
{
get { return humidity; }
set { humidity = value; }
}
public string Wind
{
get { return wind; }
set { wind = value; }
}
public string DayOfWeek
{
get { return dayOfWeek; }
set { dayOfWeek = value; }
}
public string High
{
get { return high; }
set { high = value; }
}
public string Low
{
get { return low; }
set { low = value; }
}
}
Instances of this class are widely used in the application to manipulate with weather data.
Now, here is the code that obtains the actual data from the Google server and populates an instance of the Conditions
class:
public static Conditions GetCurrentConditions(string location)
{
Conditions conditions = new Conditions();
XmlDocument xmlConditions = new XmlDocument();
xmlConditions.Load(string.Format
("http://www.google.com/ig/api?weather={0}", location));
if (xmlConditions.SelectSingleNode("xml_api_reply/weather/problem_cause") != null)
{
conditions = null;
}
else
{
conditions.City = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/forecast_information/city").Attributes
["data"].InnerText;
conditions.Condition = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/current_conditions/condition").Attributes
["data"].InnerText;
conditions.TempC = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/current_conditions/temp_c").Attributes
["data"].InnerText;
conditions.TempF = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/current_conditions/temp_f").Attributes
["data"].InnerText;
conditions.Humidity = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/current_conditions/humidity").Attributes
["data"].InnerText;
conditions.Wind = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/current_conditions/wind_condition").Attributes
["data"].InnerText;
}
return conditions;
}
To get the forecast for the next four days, the same approach is used, the only difference being the fact that the returned type is a List
of Conditions
.
public static List<Conditions> GetForecast(string location)
{
List<Conditions> conditions = new List<Conditions>();
XmlDocument xmlConditions = new XmlDocument();
xmlConditions.Load(string.Format
("http://www.google.com/ig/api?weather={0}", location));
if (xmlConditions.SelectSingleNode("xml_api_reply/weather/problem_cause") != null)
{
conditions = null;
}
else
{
foreach (XmlNode node in xmlConditions.SelectNodes
("/xml_api_reply/weather/forecast_conditions"))
{
Conditions condition = new Conditions();
condition.City = xmlConditions.SelectSingleNode
("/xml_api_reply/weather/forecast_information/city").Attributes
["data"].InnerText;
condition.Condition = node.SelectSingleNode("condition").Attributes
["data"].InnerText;
condition.High = node.SelectSingleNode("high").Attributes["data"].InnerText;
condition.Low = node.SelectSingleNode("low").Attributes["data"].InnerText;
condition.DayOfWeek = node.SelectSingleNode("day_of_week").Attributes
["data"].InnerText;
conditions.Add(condition);
}
}
return conditions;
}
The Windows 7 jumplists have an interesting property - you cannot simply use specific icons with items in a jumplist. You need to reference them either from an assembly (DLL/EXE) or use the direct path to the icon.
What I did is, I created a Native Resource Template, populated with a variety of icons that can later be referenced directly from the assembly. What kind of icons? WeatherBar
displays the forecast in the jumplist as well as in the main form.
Therefore, to extract the icons from the embedded Win32 resource, the following code is used (calling the ExtractIcon
function from shell32.dll
):
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);
private Icon GetIcon(string fileName, int iconID)
{
Icon ico = null;
IntPtr hc;
if (System.IO.File.Exists(fileName))
{
try
{
hc = ExtractIcon(this.Handle, fileName, iconID);
if (!hc.Equals(IntPtr.Zero))
{
ico = Icon.FromHandle(hc);
}
}
catch{}
}
return ico;
}
Now here comes the code that determines whether it is AM or PM in the specified region. This is used to determine whether to show the sun or the moon as the icon for the weather conditions (if such apply). As mentioned above, this is done with the help of TrueKnowledge
API. I removed the API key from the public
code, but it is included with the source ZIP.
bool isAM(string location)
{
bool time = true;
XmlDocument doc = new XmlDocument();
try
{
doc.Load(string.Format
(https: question=what+is+the+time+in+{0}&api_account_id=api_application&
api_password=<API_KEY>, location));
XmlNamespaceManager nManager = new XmlNamespaceManager(doc.NameTable);
nManager.AddNamespace("tk", "http://www.trueknowledge.com/ns/kengine");
XmlNode node = doc.SelectSingleNode("//tk:text_result", nManager);
int hours = Convert.ToInt32(node.InnerText.Substring
(node.InnerText.IndexOf(',') + 1, 3));
if ((hours > 18) || (hours < 6))
time = false;
else
time = true;
}
catch
{
time = true;
}
return time;
}
It is simply parsing the XML for the hour data, and since it represents the time in 24-hours format, it is fairly easy to see whether it is AM or PM in that specific location.
Points of Interest
An interesting aspect of this application was dealing with the Google API weather conditions. There is no public list that would document all the conditions, therefore a lot of System.Diagnostics.Debug.Print had to be done
to see what conditions are obtained and what icon suit those best. If you encounter a white icon in the jumplist, click on the link to open the web browser and read through the XML file until you reach the day with the missing point.
The weather condition for that day was not introduced, therefore any reports are appreciated (so that I can make the list as complete as possible).
The humidity indicator is also present in the taskbar. Whenever the humidity is normal, the progress bar in the taskbar (indicating the humidity on 100% scale) is green.
When it is above average, it is yellow.
When it is high, the progress bar is red.
The icon in the taskbar represents the current weather conditions in the specified city.
Another interesting feature of WeatherBar
is the possibility to select the location through a map (although this only applies to the United States).
Whenever a state is selected (either through the list of states or by clicking on the map), the city list is automatically populated with the cities/towns from that specific state. This is done through a WebServiceEx web service:
public static void GetCitiesInState(string state, ListBox lb)
{
lb.Items.Clear();
XmlDocument doc = new XmlDocument();
try
{
doc.Load(string.Format
(http: state));
foreach (XmlNode node in doc.SelectSingleNode("NewDataSet"))
{
XmlNode childNode = node.SelectSingleNode("CITY");
string city = childNode.InnerText;
if (!lb.Items.Contains(city))
{
lb.Items.Add(city);
}
}
}
catch
{
MessageBox.Show("Error fetching data from server.",
"WeatherBar", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
lb.Sorted = true;
}
This function automatically populates the ListBox
passed as a parameter and sorts it alphabetically.
The conditions are set to automatically update every 3 minutes through a Timer.
For my personal use, I find this application much more handy, since I don't have to be distracted from work to look at the desktop (or online) for weather conditions. All I need is already displayed in the taskbar.