Create a GUI for an FTP Client with VB.NET

Attach the FTP Client to the GUI

Next, you need to attach the FtpClient class library to your GUI. You can accomplish this easily from an architectural perspective by using the Observer pattern. From a practical point of view, the Observer pattern is manifested as events.

To attach the FtpClient to the GUI, declare and create an instance of the FtpClient and then define and attach event handlers to the FtpClient using the AddHandler statement. The code that demonstrates how to manually add event handlers is shown in the Form1_Load event handler in Listing 3 and in context in the Form's code listing at the end of this article.

(Rather than remove the code for the FileListUserControl, I show the code for dynamically creating the UserControl in Listing 3 too.) The AddHandler statement requires an object and its event field, and the second parameter is the AddressOf the event handler. Implicitly, AddressOf creates an instance of a delegate object, which is a class that encapsulates event handlers.

Update a StatusStrip

Whidbey has replaced the StatusBar with the StatusStrip. Although it is a little more clumsy to use than the StatusBar, it is more OOPy but will feel a tad foreign. Basically, the StatusStrip is a status bar that has more advanced designers similar to the MenuStrip's designers. Instead of repeating that information here, let's talk about a proper way to update status information on your presentation layer.

Status information could literally come from anywhere. The FtpClient's state will change, and you might elect to report that status information. The FileListUserControl's or Form1's status might change, and you might elect to report that status information. Obviously, you could define a method in the Form that permits updating status, but then every status reporting object would need to have an instance of the Form. That would be a spaghetti code nightmare. If you asked yourself why, consider an example using just one reporting object, the Form. Suppose the FtpClient is told about the main form in the Windows client. This means the Windows client must have a reference to the FtpClient to invoke behaviors and the FtpClient must have a reference to the Windows Form to update status. In addition to being a circular reference, the FtpClient class also breaks if Form1 goes away, and you can't reuse the FtpClient in some other application without dragging the Windows Forms client along with it. This is called tightly coupled code and it is a bad thing.

A better alternative would be to define an OnStatusChange method in the FtpClient, attach the Windows Form to that event, and raise the event when status changes. While this represents looser coupling and better cohesion, it still means that other status-reporting objects would need to follow suit with events and event handlers.

The best solution can be had by again using the Observer pattern. However, in this context, think of the Observer pattern by its alter ego publish-subscribe or what I call broadcaster and listeners. (Think radio station and radio listeners.) Defining a broadcaster and listener means that you can broadcast status messages (from anywhere) and any listener can tune it. By combining the Singleton (to ensure one instance of the broadcaster) and the Observer patterns, you can send status messages from anywhere without any knowledge of who or what is listening. (If you need some validation for this solution, keep in mind that it is precisely how Microsoft implemented the System.Diagnostics.Trace class and TraceListeners to send Trace information to the Output window in VS.NET itself.)

Implement the Listener

Listening is comprised of two parts: an interface named IListener and a typed collection of IListeners. Using an interface means that any class that implements IListener can tune in to broadcasts. You might think inheritance would work here, but remember an obvious listener is a Form and it already inherits from the System.Windows.Forms.Form and multiple inheritance is not allowed in VB.NET.

Next, your broadcaster may want to multicast. Thus, you need a means of knowing about multiple listeners; for example, you might want to log status messages in the EventLog. Such a scenario is ideally suited for a strongly typed collection of IListeners. Listing 4 shows both the IListener and ListenerCollection. (For more information about typed collections, read my book Visual Basic .NET Power Coding from Addison-Wesley, and for more information about the Observer pattern, refer to Design Patterns by Erich Gamma, et. al., also from Addison-Wesley.)

Listing 4: The IListener and ListenerCollection Classes

Public Interface IListener
ReadOnly Property Listening() As Boolean
Sub Listen(ByVal message As String)
End Interface
Imports System
Imports System.Collections
Public Class ListenerCollection
Inherits CollectionBase
Default Public Property Item(ByVal index As Integer) As IListener
Get
Return CType(List(index), IListener)
End Get
Set(ByVal value As IListener)
List(index) = value
End Set
End Property
Public Function Add(ByVal value As IListener) As Integer
Return List.Add(value)
End Function
Public Sub Remove(ByVal value As IListener)
Dim I As Integer
For I = 0 To List.Count - 1
If (Item(I) Is value) Then
List.RemoveAt(I)
Exit For
End If
Next
End Sub
End Class

Whidbey also has added a new project item template for interfaces (see Figure 4).

Figure 4: Whidbey Supports Several More Template Items, Generating More Code for You.

Implementing the Broadcaster

The Broadcaster class uses the Singleton pattern to ensure only one Broadcaster exists. You typically implement the Singleton pattern by making the constructor non-public and using a Shared method to request an instance of the class, which is created only once. Also, the Broadcaster contains Add and Remove methods that add and remove IListeners from an internal ListenerCollection held by the Broadcaster. Listing 5 provides the implementation of the Broadcaster class.

Listing 5: The Broadcaster Piece of Your Solution Plays the Role of Subject in the Observer Pattern.

Public Class Broadcaster
Private listeners As ListenerCollection = Nothing
Private Shared FInstance As Broadcaster = Nothing
Protected Sub New()
listeners = New ListenerCollection
End Sub
Public Shared Sub Add(ByVal value As IListener)
Instance.listeners.Add(value)
End Sub
Public Shared Sub Remove(ByVal value As IListener)
Instance.listeners.Remove(value)
End Sub
Public Shared Sub Broadcast(ByVal message As String)
Dim listener As IListener
For Each listener In Instance.listeners
If (listener.Listening) Then
listener.Listen(message)
End If
Next
End Sub
Protected Shared ReadOnly Property Instance() As Broadcaster
Get
If (FInstance Is Nothing) Then
FInstance = New Broadcaster
End If
Return FInstance
End Get
End Property
End Class

All that remains is referring to the assembly that contains the broadcaster and listener and beginning broadcasting and listening. To Broadcast information, call Broadcaster.Broadcast. To Listen, reference the assembly containing the Broadcaster and Listener, realize the IListener interface, and register as a listener with the Broadcaster. The Form code in Listing 6 demonstrates the mechanics of the Broadcaster and IListener in practice.

Reference: Form Code Listing

Listing 6 contains the complete listing for the form for your Windows FTP GUI. Keep in mind that generated code is placed in a *.Designer.vb file in Whidbey.

The Elements for a Complete Solution

Writing software is hard. Writing good software is especially difficult, and great software is exceedingly rare. However, great software has some common elements and one of these is the use of well-known patterns in its design and implementation.

This article has demonstrated several kinds of skills you might need to write good software. Although a complete solution would not fit in an article, what you have here are the elements that you need to implement a whole solution. For example, you used the Observer and Singleton patterns, as well as delegates (a.k.a., events) to decouple the FtpClient from any particular GUI. You extended your FtpClient a bit more and got a chance to explore some of the new controls in Whidbey. (Keep in mind that Whidbey is beta software and is subject to change.)

Biography

Paul Kimmel, the VB Today columnist, is a software architect who has written several books on .NET programming. Look for more information on patterns and software design in his upcoming book UML DeMystified from McGraw-Hill/Osborne (Spring 2005). You may contact him at pkimmel@softconcepts.com if you need assistance developing software or are interested in joining the Lansing Area .NET Users Group (glugnet.org).