Virtual Developer Workshop: Containerized Development with Docker
Tech Ed ended last Thursday, June 21st. Microsoft heralded its new implementation of Visual Studio.NET, the new C# (Sharp) programming language, and its continued commitment to the Visual Basic language.
On Tuesday Bill Gates and Air Bixhorn demonstrated the revised donkey.bas game, originally written by Chairman Bill, in its new incarnation as donkey.net. (Donkey.net will be available for download from Microsoft at http://msdn.microsoft.com/vbasic/donkey.asp, purportedly sometime the week of June 25th.) There was stark contrast between the two games. Donkey.bas, implemented in QBasic used ANSI characters for simple shape drawing, and donkey.net demonstrated 3D graphics rendering, forced feedback game controls, shaped Windows Forms, and Web Services that implemented new characters, including Evil Bunny and Peg-leg Pirate.
In addition to the hysterical peg-leg pirate hopping up and down on a twenty foot screen and being run over by Chairman Bill, is the fact that all of the capabilities on display in the simple game were implemented with Visual Basic.NET. In this article we will look at a couple of the new features supported by Windows Forms, including the new stay-on-top and Opacity capabilities implemented as properties.
Making a Form Stay On Top
Sometimes you want a modeless form to stay on top. An obvious means of bringing a form into the foreground is to show the form as a modal form, accomplished with the Form.ShowDialog method in VB.NET. But, what if you want a form to stay on top in a modeless state? For example, you may have contrived a status that is tracking your applications progress in the background and keeping the displayed status in the foreground.
To make something stay on top in VB.NET all you have to do is set the TopMost property to True and the form will stay in the zero z-order position even if the TopMost form does not have the focus. Because using the property is so simple, the code listing demonstrates a context in which you might actually use a TopMost form.
When I am building software I like to use a Debug tool that allows me to write a log file and have a view of what's going on while the program is running, a tracing mechanism if you will. This is convenient as you can run the program outside of the IDE and still have a practical log of what the application is doing; the technique described approximates using the EventLog, which is another good solution.
To demonstrate the TopMost property, we will assume that we have a requirement to have a debug view of our application, to be able to track what is happening behind the scenes. The sample application uses one form with a StatusBar. The StatusBar is used to track application status. The second form is the trace view, a form named DebugWindow. The DebugWindow is always in the foreground, so its TopMost property is set to True in the Properties window. (Figure 1 shows the application. Figure 2 shows the DebugWindow.)
Figure 1: An application.
Figure 2: A TopMost DebugWindow.
To simulate processing, each time the Broadcast Button is pressed the StatusBar is updated with the current date and time—simulating the deployed behavior of the application—and the right window (figure 2) shows the Debug Window. Perhaps the debug window is only available during black box testing to provide testers with a trace file. No matter what is happening on the main form (Form1), the DebugWindow form remains in the foreground.
To implement the trace view behavior I defined an interface. The interface defines one method named OnListen. Every class that implements the interface must implement on listen. The second class is a Broadcaster class. Broadcaster is a Singleton, which means there is only one instance of the class. The Broadcaster keeps track of the listeners. Every class that implements the listener interface can be registered with the broadcaster. To send a message to all listeners all any client needs to do is use the Broadcaster Singleton object and call the Broadcast method. To become a listener all any client needs to do is implement the Listener interface and register with the Broadcaster as a Listener using the AddListener method. The complete code listing follows. Each portion of the listing is followed by a brief synopsis of the code.
Listing 1 Demonstrates using an interface and the new TopMost property in VB.NET to implement a trace window.
Public Class Form1 Inherits System.Windows.Forms.Form Implements IListener [ Windows Form Designer generated code ] Private Sub OnListen(ByVal Text As String) Implements IListener.OnListen StatusBar1.Text = Text End Sub Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click Broadcaster.Broadcast(Now) End Sub Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Broadcaster.AddListener(Me) DebugWindow.ShowWindow() End Sub End Class
Form1 is subclass from System.Windows.Forms.Form and implements the IListener interface. The Sub OnListen demonstrates how to implement interface methods in VB.NET. The line [ Windows Form Designer generated code ] represents the code outlining capability of Visual Studio.Net.
Public Class DebugWindow Inherits System.Windows.Forms.Form Implements IListener [ Windows Form Designer generated code ] Shared Sub ShowWindow() Dim Form As New DebugWindow() Form.Show() End Sub Private Sub Add(ByVal Text As String) TextBox1.Text = Text & vbCrLf & TextBox1.Text End Sub Private Sub OnListen(ByVal Text As String) _ Implements IListener.OnListen Add(Text) End Sub Private Sub DebugWindow_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load TextBox1.Text = vbNullString Broadcaster.AddListener(Me) End Sub Protected Overrides Sub Finalize() Broadcaster.RemoveListener(Me) MyBase.Finalize() End Sub End Class
The DebugWindow form represents the trace window. If you were to expand the collapsed generated code you would find Me.TopMost = True in the InitializeComponent method. DebugWindow.vb also inherits from Form and implements IListener. The Shared method creats an instance of the DebugWindow and shows the form. (This is a convenient technique to avoiding writing the same code to create and show the form.) Form1 calls the DebugWindow.ShowWindow method in Form1's OnLoad event but you could easily turn this feature on and off. When the OnListen method of DebugWindow is called by the Broadcaster the text is added to the top of the Multiline textbox. When DebugWindow is Finalized the form is removed from the list of Broadcast recipients.
Public Interface IListener Sub OnListen(ByVal Text As String) End Interface Public Class BroadcasterClass Private FListeners As New Collection() Sub Broadcast(ByVal Text As String) Dim Listener As IListener For Each Listener In FListeners Listener.OnListen(Text) Next End Sub Sub AddListener(ByVal Listener As IListener) FListeners.Add(Listener) End Sub Sub RemoveListener(ByVal Listener As IListener) Dim I As Integer For I = 1 To FListeners.Count If (FListeners(I).Equals(Listener)) Then FListeners.Remove(I) Exit For End If Next End Sub End Class Public Module Factory Public Function Broadcaster() As BroadcasterClass Static FBroadcaster As BroadcasterClass If (FBroadcaster Is Nothing) Then FBroadcaster = New BroadcasterClass() End If Return FBroadcaster End Function End Module
The Listener.vb file actually contains three elements: the IListener interface, the BroadcasterClass class, and the Factory module that creates only one instance of a Broadcaster on demand. IListener declares only one method, OnListen. All IListeners must implement OnListen. The Factory module declares a Static BroadcasterClass. The first time the Broadcaster function is called FBroadcaster is Nothing and the object is created; on each subsequent call the local, Static object is returned. The BroadcasterClass defines three methods: AddListener, RemoveListener, and Broadcast, as well as a Collection of Listeners. When an object implements IListener all it has to do to receive messages is to call Broadcaster.AddListener(Me), passing a reference to self to the AddListener method. To stop listening, the Listener simply calls Broadcaster.RemoveListener(Me). Each time any code calls Broadcaster.Broadcast(string) every Listener is notified of the broadcast message.
Using an interface to define a broadcaster and listeners allows you to write a more loosely coupled code. Notice that none of the listeners is referring to a specific object, neither are any of the listeners referring to any of the controls on those objects. You could devise a listener that updated a database, or use some new control to display status on the main form. In both cases you would not have to change any of the existing code between the forms and broadcaster.
An alternate scenario for using TopMost is to create a Splash screen. If you show a Splash screen as a modal dialog then your splash screen has to perform initialization of your application, but such a use for a splash screen is counter intuitive. With the TopMost property you can display the splash form as a modeless form and initialize your application using some other, more appropriate object.
Using Opacity to Create Form Affects
The Opacity property of a Form works very simply. If Opacity is 0 then the object, usually a form, is transparent. If the Opacity is 1, representing 100% or Opaque, then the form is not transparent. You might use the Opacity property to fade in a splash form. Each tick of a timer control will increase the Opacity of the splash form until it is 100%. (Unfortunately I cannot show you the form fading in. Using Alt+Print Screen shows the form as 100% opaque and Collage Complete was unable to capture the form until it was Opaque.)
The code sample is quite straight forward. Add an enabled timer to the Splash form and have the timer increase the Opacity until it is greater than or equal to 100%
Private Sub Timer1_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Timer1.Tick Opacity += 0.02 If (Opacity >= 1) Then Close() End Sub
Alternatively you could use a Thread and have the thread increase the Opacity a little and sleep a little until the Opacity is 100%. The Threaded version follows.
Public Sub FadeIn() While (Opacity < 1) Opacity += 0.02 Sleep(175) Text = "Thread: " & Opacity End While End Sub
To create and use the Threaded version create an instance of a Thread and pass it the Address of the FadeIn method as follows.
Dim Splash As New FormSplash() Splash.Opacity = 0 Dim Thread As New Threading.Thread(AddressOf Splash.FadeIn) Splash.Show() Thread.Start()
The listing creates a Thread passing the AddressOf SplashForm.FadeIn. The form is shown and the thread is started.
Finally, at Tech Ed Microsoft presenters stated that Windows Forms are not Thread-safe. I am still experimenting with Threads and forms. The multithreaded version of the fade-in form seems to work okay, but further experimentation and information might be necessary to determine how reliable code like the preceding will work in VB.NET. In the mean time the Timer version seems to work reasonably well.
About the Author
Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. He is the founder of Software Conceptions, Inc, founded in 1990. Paul Kimmel performs contract software development services in North America and can be contacted at email@example.com