65.9K
CodeProject 正在变化。 阅读更多。
Home

使用自定义控件预测天气

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (8投票s)

2006年11月8日

8分钟阅读

viewsIcon

52968

downloadIcon

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 自定义控件,但此处采用的方法也适用于各种其他自定义控件。

© . All rights reserved.