使用自定义控件预测天气






4.50/5 (8投票s)
2006年11月8日
8分钟阅读

52968

1623
本文介绍了一个自定义控件的构建,该控件用于根据指定的邮政编码显示为期三天的天气预报。
引言
本文介绍了一个自定义控件的构建,该控件用于根据指定的邮政编码显示为期三天的天气预报。该控件由一个公共的、免费的 Web 服务驱动,该服务可根据邮政编码或地点返回美国任何地区的七天天气预报。此演示仅使用七天预报中的前三天,并且仅实现了基于邮政编码的预报数据请求。
除了返回天气预报外,该 Web 服务还返回地点名称(例如城市)、州以及邮政编码的经纬度对。它还返回一些可能感兴趣的其他内容,例如该地点的 FIPS 代码。
该 Web 服务的另一项有趣功能是,它还会返回一个反映天气预报的图像路径(例如,下雨、晴天等的图片)。对于涉及降水的预报图像,降水百分比也会显示在预报图像之外。在调用 Web 服务的 Web 方法“GetWeatherByZipCode”时,此路径用于动态加载图像。
在使用该控件时,如果您要保留用户的邮政编码或将其存储在用户计算机上的 cookie 中,用户在返回站点时将看到其特定地理位置的天气预报。在演示项目中,提供了控件使用预设邮政编码进行初始化的示例,并演示了即时更改邮政编码。
随附的下载文件包括控件本身的源代码以及一个演示网站。美国公共天气预报 Web 服务可以在此地址找到: http://www.webservicex.net/WS/WSDetails.aspx?CATID=12&WSID=68。
图 1:正在使用的天气预报自定义控件
入门
此项目包含的文件包括一个 Web 控件库项目和一个演示 Web 网站。要开始使用,请打开随附的 zip 文件并将两个项目安装到您的文件系统中。打开 IIS 并为 Web 应用程序创建一个虚拟目录。打开 Visual Studio 2005 中的解决方案,并进行任何必要的更改,将两个项目包含到解决方案中。配置正确后,您的解决方案资源管理器应显示这些项目、引用和文件。
图 2:包含 Web 应用和控件库的解决方案资源管理器
在检查解决方案时,请注意,“WeatherReport”控件库仅包含一个控件,该控件名为“Forecast”。该项目还包括一个指向 http://www.webservicex.net 站点的 Web 引用;此公共站点提供用于捕获控件显示的美国天气预报信息的 Web 服务。
Web 应用程序仅包含一个 Web 页面(default.aspx),并包含对“WeatherReport”DLL 的引用。
Web 应用程序用作测试自定义控件的容器;default.aspx 页面包含单个 Forecast 控件以及用于更改应用于预报的邮政编码的一些控件。网页上显示的日历仅用于美观,而超链接将打开一个新窗口,显示美国邮政服务的邮政编码查找器页面。
代码:Forecast
“Forecast
”自定义控件旨在在初始化时从 Web 服务检索信息,并使用该信息显示天气预报的前七天。在此演示中,我在视图状态中维护邮政编码,但每次控件初始化时都会重新提供预报数据。最好将所有预报值都维护在视图状态中,并且仅在更新邮政编码后响应回发事件来更新它们。为使代码简短,在此演示中我选择不这样做。
Web 服务将请求的数据作为名为 WeatherForecasts
的类返回,并且每天的天气详细信息都包含在名为 WeatherDetails
的从属类集合中。WeatherForecasts
对象包含有关地点(城市、州、纬度、经度等)的信息,而 WeatherDetails
包含日期、最低和最高温度(华氏度和摄氏度)以及相应天气预报图像的路径。
在检查代码时,请注意,项目中仅包含默认导入。该类本身继承自 WebControl
类。
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Xml
<ToolboxData("<{0}:Forecast runat=server></{0}:Forecast>")> _
Public Class Forecast
Inherits WebControl
在类声明之后,创建了一个名为“Declarations”(声明)的区域,在该区域内声明了控件内部使用的私有成员变量。
#Region "Declarations"
Private mForecast As net.webservicex.www.WeatherForecast
Private WxDetails() As net.webservicex.www.WeatherData
Private Wx As net.webservicex.www.WeatherForecasts
#End Region
变量声明之后,定义了另一个名为“Methods”(方法)的区域,在该区域内是用于从 Web 服务捕获数据并填充 mForecast
成员变量的代码。初始化处理程序在每次控件初始化时都会调用一个名为“GetWeather”的子例程。GetWeather
接受一个字符串参数,该字符串包含五位邮政编码。
在 GetWeather
内部,将 mForecast
对象定义为 Web 服务天气预报类的新实例。从该类中,捕获天气报告和天气详细信息,并将其分配给相应的变量。这些变量直接在渲染过程中使用,以定义控件的内容。
“Methods”区域中的代码如下:
#Region "Methods"
Private Sub Forecast_Init(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles Me.Init
If Not String.IsNullOrEmpty(ZipCode) Then
GetWeather(ZipCode)
Else
GetWeather("36201")
End If
End Sub
Public Sub GetWeather(ByVal zip As String)
Try
mForecast = New net.webservicex.www.WeatherForecast
Wx = mForecast.GetWeatherByZipCode(zip)
WxDetails = Wx.Details
Catch
Exit Sub
End Try
End Sub
#End Region
代码中定义的下一个区域称为“Properties”(属性);此部分包含控件使用的属性。在这种情况下,除了通过继承 WebControl
类传递下来的内容外,要定义的唯一属性是用于存储邮政编码的字符串值,该值存储在视图状态中。
为了提高效率,最好也将天气预报和天气详细信息存储在视图状态或控件状态中。
“Properties”区域及其单个属性定义如下:
#Region "Properties"
<Category("Weather"), _
Description("Set Forecast Zip Code"), Browsable(True)> _
Property ZipCode() As String
Get
Dim s As String = CStr(ViewState("ZipCode"))
If s Is Nothing Then
Return String.Empty
Else
Return s
End If
End Get
Set(ByVal Value As String)
ViewState("ZipCode") = Value
End Set
End Property
#End Region
category、browsable 和 description 属性用于为自定义控件提供设计时支持。当使用该控件的开发人员选择此控件时,IDE 的属性编辑器将显示 category 和 description 文本。
通过 Web 服务捕获了控件所需的必要值后,剩下的就是实际在页面上渲染控件。
用于渲染控件的代码非常简单;使用 HtmlTextWriter
定义一个表并设置其特性(本例中为单元格内边距),表的每一行包含一个单元格,在单元格内,将文本写入以标记值,然后添加该值本身。写入表中的所有数据后,将渲染结束标签,控件即完成。
表定义的每个部分都进行了注释,空行将表渲染代码分隔成特定部分。如果您遵循注释和分隔符,您应该可以轻松地看到控件是如何渲染的。基本过程是定义一行,添加一个单元格,向单元格添加内容,关闭单元格,关闭行,然后移至下一行。
当然,您可以通过修改 HtmlTextWriter
中定义的 HTML 来更改表的配置或删除 Web 服务返回的一些数据。RenderContents
子例程被重写,HTML 通过使用 HtmlTextWriter
在此子例程中进行格式化。
如果您想使控件更有用,可以考虑使用垂直和水平布局选项,并使用渲染器中的 select case 语句将表布局在一行中,或者像我在以下示例中那样(全部在一列中)。还可能需要允许开发人员指定天数(1 到 7),并使用该值来确定天气报告中显示的天数。
#Region "Rendering"
Protected Overrides Sub _
RenderContents(ByVal output As HtmlTextWriter)
' the web service actually returns
' seven days, I am just using the
' first three days to make a 3 day
' forecast but the additional days
' could be added in a similar manner
Try
' set padding and start the table
output.AddStyleAttribute(HtmlTextWriterStyle.Padding, "3")
output.RenderBeginTag(HtmlTextWriterTag.Table)
' display location information based on zip code
' in first row of the table
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Location: </b>" & _
Wx.PlaceName.ToString() & ", " & _
Wx.StateCode.ToString() & "<br/>")
output.Write("<b>Zip Code: </b>" & ZipCode & "<br/>")
output.Write("<b>Lat/Long: </b>" & Wx.Latitude.ToString() & _
"/" & Wx.Longitude.ToString() & "<br/>")
output.RenderEndTag()
output.RenderEndTag()
' display highs and lows for day 1
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<hr/>")
output.Write("<b> Day: </b>" & _
WxDetails(0).Day.ToString() & _
"<br/>")
output.Write("<b> High/Low: </b>" & _
WxDetails(0).MaxTemperatureF.ToString() & _
"/" & WxDetails(0).MinTemperatureF.ToString() & _
"<br/><br/>")
output.RenderEndTag()
output.RenderEndTag()
' get weather service image and add it to control
output.AddAttribute(HtmlTextWriterAttribute.Align, "center")
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
Dim img As New Image()
img.ImageUrl = WxDetails(0).WeatherImage.ToString()
img.BorderStyle = WebControls.BorderStyle.Inset
img.BorderWidth = 2
img.RenderControl(output)
output.RenderEndTag()
output.RenderEndTag()
' display highs and lows for day 2
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<hr/>")
output.Write("<b>Day: </b>" & _
WxDetails(1).Day.ToString() & _
"<br/>")
output.Write("&;lt;b>High/Low: </b>" & _
WxDetails(1).MaxTemperatureF.ToString() & _
"/" & WxDetails(1).MinTemperatureF.ToString() & _
"<br/><br/>")
output.RenderEndTag()
output.RenderEndTag()
' get weather service image and add it to control
output.AddAttribute(HtmlTextWriterAttribute.Align, "center")
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
Dim img2 As New Image()
img2.ImageUrl = WxDetails(1).WeatherImage.ToString()
img2.BorderStyle = WebControls.BorderStyle.Inset
img2.BorderWidth = 2
img2.RenderControl(output)
output.RenderEndTag()
output.RenderEndTag()
' display highs and lows for day 3
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<hr/>")
output.Write("<b>Day: </b>" & _
WxDetails(2).Day.ToString() & _
"<br/>")
output.Write("<b>High/Low: </b>" & _
WxDetails(2).MaxTemperatureF.ToString() & _
"/" & WxDetails(2).MinTemperatureF.ToString() & _
"<br/><br/>")
output.RenderEndTag()
output.RenderEndTag()
' get weather service image and add it to control
output.AddAttribute(HtmlTextWriterAttribute.Align, "center")
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
Dim img3 As New Image()
img3.ImageUrl = WxDetails(2).WeatherImage.ToString()
img3.BorderStyle = WebControls.BorderStyle.Inset
img3.BorderWidth = 2
img3.RenderControl(output)
output.Write("<br/><br/>")
output.RenderEndTag()
output.RenderEndTag()
' close the table
output.RenderEndTag()
Catch
' the control will not render without contacting the web service
' so just display text if the data is unavailable or the web
' service web method has not be evoked
output.Write("Weather Report Control")
End Try
End Sub
#End Region
代码:演示站点的默认页面
演示站点中包含的 default.aspx 页面仅用作控件的测试容器。该页面包含一个表格,该表格的布局是左侧列有三行,右侧列有一行(三个合并的单元格)。在右侧列中,添加了一个标签,并设置为显示“Your 3-day Forecast”(您的 3 天预报)。在该标签下方放置了一个自定义天气预报控件。控件的邮政编码属性设置为“36201”,这是阿拉巴马州的一个有效邮政编码。
在表格的左侧,第一个单元格包含一个文本框和一个按钮,用于更新应用于自定义控件的邮政编码。该单元格还包含一个超链接,用于打开美国邮政服务的邮政编码查找器网站。我在中间单元格中放置了一个日历控件,但它除了显示日期外,没有其他任何实际用途。左侧列的底部单元格为空。
图 3:在设计时设置天气预报控件属性
default.aspx 页面中的代码不多;用于更新邮政编码的按钮单击事件处理程序是唯一有趣的部分。在此代码中,将检查文本框是否包含内容以及是否包含字母,如果检查通过,将更新自定义控件的邮政编码属性,并调用控件的公共“GetWeather”子例程。一旦邮政编码属性发生更改,GetWeather
子例程将强制更新自定义控件的天气信息,并在控件中显示新数据。
单击事件处理程序的代码如下:
Protected Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Button1.Click
If Not String.IsNullOrEmpty(txtZipCode.Text.ToString()) Then
Try
Dim chr() As Char = txtZipCode.Text.ToCharArray()
Dim iLoop As Integer
For iLoop = 0 To chr.Length - 1
If Char.IsLetter(chr(iLoop)) Then
txtZipCode.Text = "INVALID"
Exit Sub
End If
Next
Forecast1.ZipCode = txtZipCode.Text
Forecast1.GetWeather(txtZipCode.Text)
Catch ex As Exception
txtZipCode.Text = "ERROR"
End Try
End If
End Sub
摘要
此项目旨在介绍一个有用且易于构建的自定义控件。虽然此演示仅限于介绍 Forecast
自定义控件,但此处采用的方法也适用于各种其他自定义控件。