在系统托盘中显示当前温度






4.62/5 (20投票s)
2002年10月11日
6分钟阅读

209874

5186
使用 Web 服务检索给定邮政编码的温度,
给定一个邮政编码,在任务栏系统托盘中显示当前温度:
引言
本文将指导您完成构建示例应用程序所需的主要步骤,并涉及多个主题,包括如何
- 调用 Web 服务
- 在运行时绘制系统托盘图标
- 最小化时隐藏应用程序
- 访问本地 Win32 API (DllImport)
- 使用定时器
- 将用户设置写入/从 XML 文件读取
- 获取用户的应用程序数据目录
访问 Web 服务
要获取给定邮政编码的天气数据,我使用 EJSE Inc. 的 Web 服务,该服务是在 http://www.xmethods.com/ 上找到的。创建 C# 窗体应用程序后,您需要添加一个访问 Web 服务的代理类。可以通过几种方式完成此操作。最简单的方法是右键单击“解决方案资源管理器”中的项目,然后选择“添加 Web 引用...”并键入 .asmx 文件或服务的 WSDL 文件(Web 服务描述语言)的 URL。在这种情况下,URL 是:http://www.ejseinc.com/WeatherService/Service.asmx。这将向您的项目添加一个命名空间,其中包含一个代理类,该类公开 Web 服务的各个方法以及用于存储返回数据的几个数据结构。
生成代理类的另一种方法是从命令行执行(这是本项目采用的方式)
wsdl /l:CS /protocol:SOAP http://www.ejse.com/WeatherService/Service.asmx
这会指示 wsdl 工具生成一个 C# 类,该类使用 SOAP(简单对象访问协议)(而不是 HTTP GET 或 HTTP POST 协议)访问 Web 服务。然后,您可以将生成的 .cs 文件添加到项目中。wsdl 工具根据 WSDL 文件中指定的服务的名称生成文件名和类名。由于此服务名称比较通用,为“Service”,我手动更改了生成的文件名和类名,将其改为“WeatherService”。
提示:要运行 wsdl 命令,请不要启动常规命令提示符,因为其中最有可能没有将 wsdl.exe 添加到您的路径中。而是启动“开始”菜单中“Visual Studio .NET 工具”文件夹下的“Visual Studio .NET 命令提示符”。这将设置运行命令行工具所需的所有环境变量。
一旦将 WeatherService
类包含在项目中,请在 Form 的构造函数中创建一个实例并将其分配给成员变量
public class TemperatureForm : System.Windows.Forms.Form
{
private WeatherService m_weatherService;
...
public TemperatureForm()
{
...
m_weatherService = new WeatherService();
...
要获取天气数据,您只需调用 m_weatherService 对象上的所需方法即可。将 Web 服务调用放入 try/catch 块中非常重要,因为任何事情都可能出错。例如,Web 服务可能暂时不可用。请注意,在下面的代码片段中,数据是以 WeatherInfo 实例的形式返回的。这是在从 wsdl 文件生成代码时自动定义的数据类型之一。
// Get the temperature for the current zipcode using a web service
private void UpdateTemperature()
{
try
{
if (zipcodeBox.Text.Length > 0)
{
int zipcode = Int32.Parse(zipcodeBox.Text);
WeatherInfo info = m_weatherService.GetWeatherInfo(zipcode);
...
(show the relavant weather info data on the
form and call UpdateTaskBar)
...
}
}
catch (Exception e)
{
// don't show message when we're minimized
if (this.WindowState == FormWindowState.Normal)
{
String messageStr = String.Format(
"Couldn't retrieve weather data for zipcode {0}",
zipcodeBox.Text);
MessageBox.Show(this, messageStr, "Temperature",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
}
Console.WriteLine(e.ToString());
}
}
在系统托盘图标中显示温度
现在我们有了 WeatherInfo
数据,我们可以将温度显示在任务栏系统托盘中。为应用程序添加系统托盘图标非常简单:只需将 NotifyIcon
从工具箱拖到窗体上。要更改运行时图标以显示当前温度,请使用 Graphics.FromImage()
创建一个 Graphics
实例。这允许您将温度字符串绘制到任何 Image
的子类(例如位图或图元文件)上。由于 Icon
不是 Image
的子类,因此我们必须先绘制到位图,然后将其转换为 Icon
,然后才能将其用于系统托盘图标。
// Draw the current temperature to a bitmap to show
// in the taskbar system tray
private void UpdateTaskBar()
{
if (temperatureLabel.Text.Length == 0)
return;
// Create a graphics instance that draws to a bitmap
Bitmap bitmap = new Bitmap(16, 16);
SolidBrush brush = new SolidBrush(fontDialog1.Color);
Graphics graphics = Graphics.FromImage(bitmap);
// Draw the temperature and the degrees symbol
// to the bitmap using the user selected font
int index = temperatureLabel.Text.IndexOf('°');
// don't want 'F' after the degree symbol
String temperatureOnlyStr = temperatureLabel.Text.Substring(0, index+1);
graphics.DrawString(temperatureOnlyStr, fontDialog1.Font, brush, 0, 0);
// Convert the bitmap into an icon and use it for the system tray icon
IntPtr hIcon = bitmap.GetHicon();
Icon icon = Icon.FromHandle(hIcon);
notifyIcon1.Icon = icon;
// unfortunately, GetHicon creates an
// unmanaged handle which must be manually destroyed
DestroyIcon(hIcon);
}
如上面代码中的注释所述,用于将位图转换为图标的图标句柄必须手动销毁(根据我阅读过的多个新闻组消息)。如果您不这样做,每当更新任务栏图标时,句柄都会泄漏。使用 Windows 任务管理器并检查 GDI 对象列可以轻松验证这一点。销毁句柄的唯一方法是使用 Win32 API 的 DestroyIcon 函数。访问 API 非常简单(此技术也称为平台调用或 P/Invoke)。
[DllImport("user32.dll", EntryPoint="DestroyIcon")]
static extern bool DestroyIcon(IntPtr hIcon);
最小化时隐藏
用户可以选择在应用程序最小化时不显示在任务栏上(尽管它仍会显示在系统托盘中)。为此,请重写 OnResize 方法,当用户最小化窗体时会调用该方法。
protected override void OnResize(EventArgs e)
{
if (hideCB.Checked && this.WindowState == FormWindowState.Minimized)
this.Hide();
base.OnResize(e);
}
要重新显示窗体,请为通知图标添加双击处理程序。
private void notifyIcon1_MouseDoubleClick(object sender, System.EventArgs e)
{
this.Show();
this.WindowState = FormWindowState.Normal;
}
提示:窗体设计器通过双击任何控件即可轻松地为代码添加“OnClicked”处理程序,但在此例中我们需要一个双击处理程序。为此,请选择窗体设计器屏幕底部的 notifyIcon,然后选择属性浏览器顶部的闪电图标。这将显示所选控件的事件列表。要创建新处理程序,请双击所需的事件。
设置定时器
为了能在指定的时间间隔唤醒以更新温度,我们使用 System.Windows.Forms.Timer
类。同样,设置它非常简单。首先,将 Timer
从工具箱拖到窗体上。然后,双击窗体设计页面底部的 timer1 图标。这将为计时器的“Tick”事件创建一个事件处理方法,该事件在计时器到期时触发。在这种情况下,我们的处理程序只需调用上面看到的 UpdateTemperature()
方法。
我们还必须设置计时器的间隔及其启动/停止状态,这些状态基于窗体控件的值。
private void UpdateTimer()
{
if (minutesBox.Text.Length > 0)
{
int minutes = Int32.Parse(minutesBox.Text);
timer1.Interval = minutes * 60 * 1000; // milliseconds
if (autoUpdateCB.Checked && zipcodeBox.Text.Length > 0)
timer1.Start();
else
timer1.Stop();
}
}
保存和读取设置
为了保存用户的设置,如邮政编码和字体,我尝试了 XML 和 BinaryFormatter。代码目前使用 XMLTextWriter 和 XMLTextReader 来将设置保存到 XML 文件/从 XML 文件加载设置,但我目前不推荐这种方法。我将其包含进来,因为它是我学习这些类如何工作的绝佳练习,并希望在这些方面对您也有所帮助。特别是 Load 函数比较混乱,因为它包含了遍历 XML 文件的逻辑。我认为更好的方法是设计出更具可重用性且能隐藏 XML 解析的方案,例如,请参阅 XML 序列化 - 设置类。另请参阅 使用动态属性和 AppSettingsReader 来了解另一种方法。BinaryFormatter 方法效果很好(代码包含在项目中,但单独注释在 #region 中),当然,生成的文件是不可读的。
// save values to the defaults file
private void SaveSettings()
{
XmlTextWriter writer = new XmlTextWriter(
m_settingsPath + "\\settings.xml", null);
writer.Formatting = Formatting.Indented;
writer.Indentation = 4;
writer.WriteComment("Settings for Temperature program");
writer.WriteStartElement("settings");
writer.WriteElementString("zipcode", zipcodeBox.Text);
writer.WriteStartElement("font");
writer.WriteElementString("name", fontDialog1.Font.Name);
writer.WriteElementString("size",
fontDialog1.Font.SizeInPoints.ToString());
writer.WriteElementString("style", fontDialog1.Font.Style.ToString());
writer.WriteElementString("color", fontDialog1.Color.Name);
writer.WriteEndElement();
writer.WriteElementString("minutes", minutesBox.Text);
writer.WriteElementString("autoupdate", autoUpdateCB.Checked.ToString());
writer.WriteElementString("hide", hideCB.Checked.ToString());
writer.WriteEndElement();
writer.Flush();
writer.Close();
}
// init values from the the defaults file
private void LoadSettings()
{
XmlTextReader reader = null;
try
{
reader = new XmlTextReader(m_settingsPath + "\\settings.xml");
reader.WhitespaceHandling = WhitespaceHandling.None;
string elementName = null;
string fontName = null;
float fontSize = 0;
FontStyle fontStyle = FontStyle.Regular;
while (reader.Read())
{
//Console.WriteLine("type = {0}, name = {1}, value = {2}", reader.NodeType,
// reader.Name, reader.Value);
if (reader.NodeType == XmlNodeType.Element)
elementName = reader.Name;
else if (reader.NodeType ==
XmlNodeType.EndElement && reader.Name == "font")
fontDialog1.Font = new Font(fontName, fontSize, fontStyle);
else if (reader.NodeType == XmlNodeType.Text)
{
switch (elementName)
{
case "zipcode": zipcodeBox.Text = reader.Value; break;
case "name": fontName = reader.Value; break;
case "size":
fontSize = float.Parse(reader.Value); break;
case "style": fontStyle =
(FontStyle)Enum.Parse(
fontDialog1.Font.Style.GetType(),
reader.Value); break;
case "color": fontDialog1.Color =
Color.FromName(reader.Value); break;
case "minutes": minutesBox.Text = reader.Value; break;
case "autoupdate": autoUpdateCB.Checked =
bool.Parse(reader.Value);break;
case "hide": hideCB.Checked =
bool.Parse(reader.Value); break;
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
if (reader != null)
reader.Close();
}
设置会保存到用户应用程序数据目录中的文件中。
private string BuildSettingsPath()
{
string pathName;
try
{
pathName = Environment.GetFolderPath(
System.Environment.SpecialFolder.ApplicationData);
pathName += "\\Serlio\\Temperature";
if (!Directory.Exists(pathName))
Directory.CreateDirectory(pathName);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
pathName = "";
}
return pathName;
}
启动应用程序时最小化
我在启动文件夹中有一个快捷方式,因此温度应用程序会在我每次启动计算机时都启动。提示:在快捷方式的属性页面上,将“运行”设置为“最小化”。这样应用程序就会以最小化状态启动。
结论
编写此应用程序对我来说是一次很好的 VS.NET 入门体验:我能够探索和学习几个不同的主题,并且编写过程很有趣。
历史
- 2002 年 11 月 10 日 - 更新了下载
- 2004 年 11 月 12 日 - 更新了下载