Creating Non-Visual Controls

In Visual Basic 6.0, some controls (such as Timer) were non-visual, meaning they had no user-interface aspect. Although these controls were placed directly onto the form, just like any other control, they were only visible at design time. In Visual Basic .NET, non-visual controls are still common (the Timer is still around, for example), but they are referred to as components. Instead of going directly onto the form, they sit in a special area (commonly known as the component tray) below the main design window (see Figure 8.4).

Figure 8.4. Non-visual controls, or components, appear in their own special area of the Windows Forms designer.


To create a non-visual control yourself, you need to inherit from System.ComponentModel.Component, as shown in Listing 8.8.

By inheriting from Component, your class has gained all the code necessary to be added to the toolbox and dragged out onto any design surface. Even if you are creating a regular class library without any user-interface aspects, marking classes as Components provides the user of your code with an easy drag-and-drop experience. As a sample of a non-visual component, let's walk through creating a component that will perform a few simple Web-related tasks. This component will:

  • Retrieve the HTML text of a Web page

  • Return the current IP address of your machine

  • Watch a specified Web URL and let you know if it becomes unavailable

Listing 8.8. Inheriting from Component Allows Your New Class to Be Dragged onto a Design Surface as a Non-Visual Control
Public Class nonvisual
    Inherits System.ComponentModel.Component
End Class

That is quite a range of functions, but it will illustrate a few useful concepts, including raising events from a component, and it has the side-effect when interacting with the Web from your .NET programs. The most complicated of these functions is ensuring the URL is available, so let's discuss the other two first.

Getting Your Current IP Address(es)

The DNS (Domain Name Service) turns the names into addresses, thus allowing you to type http://msdn.microsoft.com and have it resolve to the external IP Address of that server. Luckily, your local machine has a bit of DNS-related smarts too; you can use the System.Net.Dns class to determine the hostname as well as any associated IP addresses. Listing 8.9 shows the full code to retrieve a string array of the local machine's current addresses.

Listing 8.9. The System.Net.Dns Class Makes it Easy to Retrieve IP Information About a Machine Using the Hostname
<DnsPermission( _
    Security.Permissions.SecurityAction.Demand)> _
Public Function MyIPAddresses() As String()
    Dim host As IPHostEntry = _
        Dns.GetHostByName(Dns.GetHostName())

    Dim ipList As IPAddress()
    ipList = host.AddressList

    Dim output As New ArrayList
    For Each ip As IPAddress In ipList
        output.Add(ip.ToString)
    Next
    Return output.ToArray(GetType(String))
End Function

The first thing you might notice about this listing is the DnsPermission attribute attached to the function declaration. This attribute forces a check of the entire code path that has led to this function call, and will raise an error if any part of that path lacks the permission to access DNS information. Similar checks have been added to the other methods of this component. For more details on permissions and other code security topics, check out my Web site for a set of related links (http://www.duncanmackenzie.net/kickstartvb/chapter8).

The rest of the code follows this path: you retrieve the local machine's hostname using Dns.GetHostName(), and then retrieve the IPHostEntry for that machine name using Dns.GetHostByName. Once you have the IPHostEntry (which represents the available DNS information on a specific host), retrieving the list of addresses is easy (host.AddressList). You simply have to convert the list into an array of strings for ease of use.

Retrieving the Contents of a Web Page

Pulling back the HTML contents of a Web page might not be an everyday activity, but it does seem to come up often, so it is a useful bit of code to have around. Listing 8.10 shows one way you can code this function, but you can also use the HttpWebRequest and HttpWebResponse classes to pull back the information.

Listing 8.10. The WebClient Class Makes Retrieving a Web Page Almost Too Easy
<WebPermission( _
    Security.Permissions.SecurityAction.Demand)> _
Public Function RetrieveHTML(ByVal URL As String) As String
    Dim wc As New WebClient
    Dim reader As New IO.StreamReader(wc.OpenRead(URL))
    Return reader.ReadToEnd
End Function
					

The OpenRead function of System.Net.WebClient opens a stream from which you can read the response. The ReadToEnd function of the new IO.StreamReader then grabs the contents of the server response and stuffs it into a string.

Monitoring a URL

As mentioned earlier, monitoring the availability of a specific URL was the most complicated of the three functions of this component, so it requires a relatively lengthy explanation.

Checking the URL itself is the first issue; how do you determine whether a URL is available? The network utility PING comes to mind, but PING verifies that you can get a response from a specified IP address, not a URL. It is possible to have a server that is responding to a ping, but isn't functioning in some other way, and conversely it is possible to have a perfectly functioning Web site that won't respond to a ping at all (try pinging www.microsoft.com for example).

If you can't use ping, the alternative is to try to retrieve the URL using the HTTP protocol. Both the System.Net.WebClient and System.Net.HttpWebRequest classes are capable of making a request against a URL, but HttpWebRequest is used here because it allows you to specify the method of the request. In HTTP, the method indicates what type of request you are sending to the Web server and must be one of GET, HEAD, POST, PUT, DELETE, TRACE, or OPTIONS. GET is the default and indicates that you want to retrieve the specified resource, but this example uses HEAD for the sake of efficiency. Specifying HEAD indicates that all you want back are the headers, which are sufficient to determine the availability of a Web page, and might mean less data transferred than retrieving the entire resource. Even once you have the headers back, there is still the matter of determining which response indicates that the resource is “available.” Should you say that a resource is available if you get back a 404 (not found) status code? Should only a 200 (OK) code count? In the HTTP protocol, all of the various status codes in the 200's are some form of successful response, so this example takes any of those values as indication that the URL was available. A private CheckURL function (shown in Listing 8.11) handles making the request and interpreting the results.

Listing 8.11. Any Exception in the CheckURL Function Is Taken to Mean that the URL Is not Available
<WebPermission( _
    Security.Permissions.SecurityAction.Demand)> _
Private Sub CheckURL(ByVal url As String)
    Dim e As New WatchedURLEventArgs
    e.URLBeingWatched = url
    Me.m_lastCheck = Now
    e.CheckedTime = Me.m_lastCheck

    Try
        Dim wr As WebRequest = WebRequest.Create(url)
        If TypeOf wr Is HttpWebRequest Then
            Dim httpWR As HttpWebRequest
            httpWR = DirectCast(wr, HttpWebRequest)
            httpWR.Method = "HEAD"
            httpWR.AllowAutoRedirect = False
            Dim wResponse As WebResponse
            wResponse = httpWR.GetResponse()
            If TypeOf wResponse Is HttpWebResponse Then
                Dim httpResponse As HttpWebResponse
                httpResponse = DirectCast( _
                    wResponse, HttpWebResponse)
                Dim response As Integer
                response = CInt(httpResponse.StatusCode)
                Me.m_lastResponse = _
                    httpResponse.StatusDescription

                e.Status = response
                e.StatusDescription = Me.lastResponse

                If response >= 200 And response < 300 Then
                    e.URLAvailable = True
                    Me.m_WatchedURLAvailable = True
                Else
                    Me.m_WatchedURLAvailable = False
                    e.URLAvailable = False
                End If
            Else
                Me.m_WatchedURLAvailable = False
                Me.m_lastResponse = "Check Failed"

                e.Status = 0
                e.StatusDescription = Me.lastResponse
            End If
        Else
            Me.m_WatchedURLAvailable = False
            Me.m_lastResponse = "Invalid URL Type"

            e.Status = 0
            e.StatusDescription = Me.lastResponse
        End If
    Catch ex As Exception
        Me.m_WatchedURLAvailable = False
        Me.m_lastResponse = _
            "Error Checking: " & ex.Message

        e.Status = 0
        e.StatusDescription = Me.lastResponse
    Finally
        Me.OnWatchedURLChecked(e)
        RaiseEvent WatchedURLChecked(Me, e)
        If Not Me.WatchedURLAvailable Then
            Me.OnWatchedURLUnavailable(e)
            RaiseEvent WatchedURLUnavailable(Me, e)
        End If
    End Try
End Sub
					

Regardless of the availability of the resource, you must raise the WatchedURLChecked event and call the internal routine OnWatchedURLChecked. If you determine that the resource is not available, you raise the WatchedURLUnavailable event and call the corresponding internal routine. For both events, you populate an instance of the WatchedURLEventArgs class to send the status information along with the event.

Now that the code to check whether a URL is available is in place, you still have to know when to call it. The example creates an instance of a Timer class, which is set up to fire an event every second when it is active. At those one-second intervals (see Listing 8.12), the code checks to see whether the total number of seconds since the last check of the URL has exceeded the desired interval and, if so, performs the check of the URL.

Listing 8.12. The Timer Stops During the Elapsed Event Handler So That the Next Event Won't Fire Before the First One Has Completed
Private Sub m_internalTimer_Elapsed( _
    ByVal sender As Object, _
    ByVal e As System.Timers.ElapsedEventArgs) _
    Handles m_internalTimer.Elapsed
    Me.m_internalTimer.Stop()
    If Now.Subtract(Me.m_lastCheck).TotalSeconds _
            >= Me.m_SecondsBetweenCheck Then
        If (Not Me.m_WatchedURL Is Nothing) _
            AndAlso (Not Me.m_WatchedURL = String.Empty) Then
            CheckURL(Me.m_WatchedURL)
        End If
    End If
    Me.m_internalTimer.Start()
End Sub
					

Overall, a fair bit of code, but if you download the code sample for this chapter, you will find it easier to understand when it is open inside Visual Studio.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.231.106