KnowDotNet Visual Organizer

Doing Things the .NET Way

by William Ryan
Print this Article Discuss in Forums

I think most programmers can admit that when it comes to User Interfaces, everyone is an expert.  Everyone thinks that their ideas, their perspectives and their likes and dislikes are can be said for everyone.  It's hard to read an article or book on the subject without the author pointing out this fact.  So I'm going to make the assumption, just for the sake of this article that, there are a few things everyone should be able to agree upon:

1)    Unnecessary dialog boxes are obnoxious.  If something needs to be filled in before the user presses a button, it's better to have the button disabled until they fill it in correctly than to let them make a mistake and have a dialog box point this out to them.
2)    If there are multiple options that a user can check which aren't mutually exclusive, functionality such as "SELECT ALL" or "CLEAR ALL" should be provided.


If you don't agree with these precepts, that's fine.  You don't need to agree with me in order to learn how to deal with some new .NET Functionality.

In a typical
Windows.Forms.Form, you usually have a Close Windows.Forms.Button Control at a minimum.  Typically, if you have Windows.Forms.TextBox controls you'll have a Submit button of some sort for the user to press when they are done.  If you have multiple Windows.Forms.TextBox controls, you will probably have a Clear All button as well.  Let's assume that this is the case.  So we have x number of Windows.Forms.TextBox controls and three Windows.Forms.Button Controls, btnClose, btnClearAll and btnSubmit.  Behind the Click event of btnClose, you'll want to provide some code to close the form.  Typically, something like me.Dispose in VB.NET or this.Dispose(); in C#.

Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click
        
Me.Dispose()
End Sub

This is pretty standard for the way .Net handles events.  Events are handled via delegates, so the signature of every button.Click event you'll see will look identical to this, with the only difference being the name of the control, in this case, btnClose.  Since Event handling is facilitated through Delegates, and since Delegates don't really care who calls them, this gives us tremendous power.  How?  Well, we can write code for one control, and pass a reference to other controls whos events are the same and have it take care of the processing for multiple controls in one place.  Let's say in this example, I had 20 other buttons and all of them, when clicked, needed to execute Me.Dispose().  In the old days, I'd have to click on all 20 buttons and type in Me.Dispose().  Or, I could make a function and call the function from 20 places.  Yuck!  Instead, I could take advantage of the delegates and append a , textBoxName.TextChanged, textBox2Name.TextChanged etc after the Handles clause in the above snippet.  So I have one handler take care of 20 different events.

Now this isn't a very real example after all, how often do we need 20 places to close one form?  A more real life example is a form that requires the user to fill in each field, and upon fulfilling that requirement, enable the Submit button.  Also, as soon as any text is added to any
Windows.Forms.TextBox, we want to enable the Clear All button.  So when the form is loaded, we have 20 Windows.Forms.TextBox Controls and three Windows.Forms.Button Controls.  Only btnClose is enabled when the form is loaded.

So I could use the same methodology as above and address the TextChanged event.  One routine can handle everything.  But let's say I wanted something that would be able to automatically have the same behavior, even if I add 20 more
Windows.Forms.TextBox Controls.

I could go behind the textChanged handler of textBox1 and add the Handles reference for all the other controls.  Then, I could create a function that's called each time the text changes...

Private Sub ValidateEm
        Dim opt As New Control
        
For Each opt In grpType.Controls
            
btnSubmit.Enabled = CType(opt, TextBox).Text.Length > 0
        
Next
End Sub

This routine would fire at every text change, and even if I added a zillion more textboxes, it would ensure that something was in them before it enabled the button.  Now, there would be one problem in that I only have my Handler referencing 20 controls at first, then 40 leaving a zillion-60 controls without handlers.  Well, you do have to handle them if you want everything to work elegantly, but you'd have to do that anyway in VB6 or using any other methodology.

There is another thing I want to point out.  
Windows.Forms.GroupBox Controls are considered containers.  As such, If I have two Windows.Forms.GroupBox controls each with 50 controls on Form1, and I iterate through each of the controls in Form1's Controls Collection, how many controls do I have?  100?  Good guess but no! I have two.  Each of those two has 50 respectively.  So if you are using Containers like Windows.Forms.GroupBox Controls or Windows.Forms.Panel Controls, keep this in mind.  No big deal though, you can iterate through the Form's Controls collection, then if the control is a container, iterate through it.

One other thing I forgot to discuss.  I said that as soon as a user entered any text, we wanted to enable the Clear All button.

Here is a sample of how I've implemented this with
Windows.Forms.Checkbox controls, on two different Windows.Forms.GroupBox Controls (I separated the group iterations for the sake of clarity, in practice I use the method stated above and call it recursively but I don't want to confuse the goal here).  So I declare a Boolean and set it to false.  If my condition is met, I set it to true.  I then set the Button Control's Enabled state to the boolean.  If it's false, the Windows.Forms.Button Control will be disabled.  If it's true, it's enabled.  This is much more elegant than traditional VB6 methodology wherein you set a me.Dirty flag.  Why?  Because if the user writes some text in a Windows.Forms.TextBox Control, then clears it, the Clear button will be disabled.  Why leave it enabled just because someone typed in it once but deleted what they typed?  There's nothing to Clear!

Private Sub ValidateIt()
        
Dim b As Boolean = False
        Dim opt As New Control
        
For Each opt In grpType.Controls
            
If CType(opt, RadioButton).Checked Then
                b = True
            End If
        Next
        For Each opt In grpStatus.Controls
            
If CType(opt, RadioButton).Checked Then
                b = True
            End If
        Next

        btnRun.Enabled = b
        btnReset.Enabled = b
End Sub

One other point I'd like to make you aware of.  Windows.Forms.MenuItem controls behave the same way as Windows.Forms.Button Controls.  As a matter of fact, most controls do.  So if you have a close button and a Windows.Forms.MenuItem Control called mnuClose you no longer have to write code for both of the items.  You can code it once, and add a handler to the other Windows.Forms.MenuItem.

Private Sub mnuRTimeCard_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                  
Handles mnuRTimeCard.Click, btnFirst.Click

End Sub

If you look at this signature, a
Windows.Forms.MenuItem mnuRTimeCard executes the same code as btnFirst when the user clicks on it.  This is a great feature when your menu items duplicate functionality of other controls.  Moreover, you can have context menus for instance handle the same code as a Windows.Forms.Menu.  This way, features like Undo, Redo, Cut, Paste etc don't have to be coded twice.  

The ultimate point I'm trying to make is this.  Writing less code is better, no doubt about it.  In the past, many times you had to write a lot of code to provide basic functionality or make your software user friendly.  Well, in .NET, thanks to delegates, you can really reuse a lot of features without having to write them over and over.  This allows you to focus on making a UI that is kind to the user.  Moreover, you can accomplish this without having to revert to ugly late bound techniques which were the only way to get things done sometimes in VB6.  And if you are like me, leaving the days where my code looked like this:

Private Sub Clear()

   Textbox1.text = ""
   TextBox2.text = ""
   TextBox3.text = ""
   TextBox4.text = ""
   TextBox5.text = ""
   TextBox6.text = ""
   TextBox7.text = ""
   TextBox8.text = ""
   TextBoxHowManyMoreDoIHaveToHardCode.text = ""

End Sub


For this:

Private Sub Clear()

  Dim opt as New Control

  For Each opt in myForm.Controls
    If opt.GetType = Forms.TextBox then DirectCast(opt, TextBox).Text = String.Empty
  Next
End Sub


The new methodology will come as welcome relief.

Questions or Comments?  I'm new to writing text and working aggressively on refining my skills.
Your feedback, good or bad is welcome.  Furthermore, if you have any questions or need clarification please feel free to contact me at
dotnetguru@comcast.net or bill@knowdotnet.com.

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing