Team LiB
Previous Section Next Section

The Single-Document Interface

This is the simplest interface you can present to the user. In a complicated program, however, it can also be the most confusing.

In Chapter 2 I showed you an employee editing form. It was a single-screen program where all data was entered on just one screen. Suppose this Employee screen was part of a bigger HR program that also included screens for training, payroll, and scheduling. An SDI program requires that you close one screen before opening another. There would be no way to just flip back and forth between screens. This is the classic definition of an SDI program. Here are some common examples:

In a classic SDI program such as Notepad, if you want to edit two documents at once you need to run two instances of the program. Neither instance of Notepad knows the other exists, and they do not interact. Figure 3-1 shows what a classic SDI HR application that includes an Employee screen might look like.

Click To expand
Figure 3-1: SDI example

The Employee screen in Figure 3-1 is rather simplistic. In order for the user to go to the training screen, the program would have to replace the Employee screen you see here. Although this works, it is limiting.

Another Type of SDI Program

Another type of SDI program is common these days. I call it the scatter effect program. In the case of the hypothetical HR program shown in Figure 3-1, if the user chose the Training module while in the Employee screen, the program would not replace the Employee screen. It would instead bring up another form on the desktop, which would look like a separate program altogether. However, it is not. The different screens can interact with each other and exchange information.

In this scenario, the user can have four forms open on the desktop, one for each module (Employee, Training, Payroll, and Scheduling). Can you imagine a program that could have a dozen or more forms open on the desktop at once? To me, this is chaos, but I bet you have already used a program that does this.

VB 6.0 has an option to work in an SDI environment or an MDI environment. The default is MDI, and this keeps things organized. I have a coworker, though, who likes the SDI environment. This means that each window is a separate screen, including all the forms, classes, and modules he has open. I frequently see him with a few dozen forms scattered all over his desktop.

If you come from the MSVC++ world or you have not programmed yet, you may not have seen this effect in VB 6.0. However, you do see it in Microsoft Word. Word used to have an MDI interface. It used to be that you could open up Word and then open as many documents as you wanted inside Word. Word is arguably the most ubiquitous program in the world, and I bet many people like to edit many documents at once. Word is now an SDI application, which means that each document is in a separate window. In my opinion, all this does is clutter up the taskbar.

The Multiform SDI Example

This next example shows you quite a bit about how SDI programs work. It includes five screens. There is a main screen that lets you choose to edit different parts of an HR program. I will be working from the example shown in Figure 3-1.

Before you attack the coding of an SDI program, you need to come up with a plan for how it will work. My plan for this one is based loosely on how Microsoft Word works:

  • The program has a main form with a menu.

  • The menu allows the user to choose different parts of the HR program to work on.

  • Each part of the HR program is a new form that appears on the desktop in a random place.

  • The main form has a Window menu option that shows all the HR screens the user has open.

  • The Window option has a submenu item called "Close All Windows" that closes all the currently open windows.

  • The Window menu option of the main screen denotes with a check mark the child form that is currently in focus.

  • If the user chooses one of the forms listed under the Window menu option, that form gains focus.

  • Only one instance of any of the HR forms can be running at a time.

  • If the user chooses to edit a form that exists on the desktop, that form will gain focus.

  • If the user closes an existing form, that form is deleted from the Window menu option.

  • If the user closes the main form, the open windows automatically shut down.

This list does not even include any data entry or validation requirements. This example is just to show you what is involved in keeping track of SDI child forms in an SDI environment.

Note 

Because I write examples in this book in both VB and C#, I append my example names with either –vb or –c. You do not have to do this, of course.

Start a new C# or VB Windows project. Mine is called "SDISample." You will need to follow these steps before you add any code:

  1. Add a MainMenu to the form.

  2. Type File in the MainMenu item and call it mnuFile.

  3. Below mnuFile, type in Close and call this item mnuClose.

  4. Next to mnuFile, type in Edit and call this item mnuEdit.

  5. Below mnuEdit, type in Employee and call this item mnuEmp.

  6. Below mnuEmp, type in Training and call this item mnuTrain.

  7. Below mnuTrain, type in Payroll and call this item mnuPayRoll.

  8. Below mnuPayRoll, type in Scheduling and call this item mnuSked.

  9. Next to mnuEdit, type in Window and call this item mnuWindow.

  10. Next to mnuWindow, type in Help and call this item mnuHelp.

  11. Add a status bar to the form.

  12. Add a Panel to the Panels collection in the status bar and make it read Employee Screen. Change the AutoSize property to Spring.

  13. Add a Panel to the Panels collection in the status bar and make it read Operator:. Change the AutoSize property to Contents.

  14. Add a Panel to the Panels collection in the status bar and type in the date. Change the AutoSize property to Contents.

  15. Set the ShowPanels property to true.

  16. Make the form start in the center of the screen.

Your form should look like the one shown in Figure 3-2.

Click To expand
Figure 3-2: The main form for the SDI example

OK, you have this form—now for the others. First up is the Employee form. This one looks just like the form shown in Figure 3-1. Follow these steps:

  1. Add a Label whose text reads Employees. Center the text.

  2. Add a CheckedListBox with several items in the Items collection.

  3. Add a Button whose text reads Add Employee.

  4. Add a Button whose text reads Fire Employee.

  5. Add a Button whose text reads Randomly Demote.

  6. Add a Button whose text reads OK.

  7. Add a Button whose text reads Cancel.

That's it for this form; you will not add any code to this form. The form should look like the one shown in Figure 3-3.

Click To expand
Figure 3-3: The Employee screen for the SDI project

The other three forms are really easy to create. They do not include any controls. Although it may seem boring, this example is meant to show an SDI project as a whole. Besides, as you will see, a form without any controls behaves slightly differently from one with controls.

Perform the following steps to finish the form setup for this project:

  1. Add a Windows form called Payroll and change the text to Payroll.

  2. Add a Windows form called Training and change the text to Training.

  3. Add a Windows form called Scheduling and change the text to Scheduling.

That's it. Now it's time to add the code.

There are a few ways you can accomplish the objectives set out for this SDI project. For instance, you can give each child form a reference to the main form whenever it is called. This way, you can communicate back and forth to see what the state of the child form is. The states you are interested in are as follows:

  • Is the child form active?

  • Is the child form in focus?

  • Is the parent form active?

It would not be too hard to have the child form tell the main form about its state. There is a better way, though. It is possible for the main form to keep track of everything about the child form without having to write any code at all in the child form. You do this using delegates.

Before I get into the code, I want you to look in the online help for the Form class. Look at all the events that can occur when just about anything happens to the form. Decide which events you can use and see if you come up with the same list as I have.

Note 

It is very important that you understand how delegates work. You will see me adding new event handlers to events that happen in the child forms. You must understand that I am not replacing the event handler for these forms with my own delegates. I am extending the list of delegates that get called when this event happens. The base event handler for any event always occurs first, and you can override this if you want to.

There is something else I would like to point out before I start with the code. Usually you will see delegates being assigned as part of the initialization of a form. Most of the time, a programmer knows which delegates need to be assigned to which events at compile time.

I have done things a little differently in this project. The delegates get assigned to events dynamically. Because the main form can call up any child form at any time, this program needs to be a bit more reactive.

Anyway, here is the code. Get into the code pane of the main form. You will need to assign some class local variables, and you will put this section just above the form's constructor. The code for this follows.

C#

    #region Class Local Variables

    Payroll     PayForm;
    Employee    EmpForm;
    Scheduling  SkedForm;
    Training    TrainForm;

    #endregion

VB

#Region "Class Local Variables"

Dim PayForm As Payroll
Dim EmpForm As Employee
Dim SkedForm As Scheduling
Dim TrainForm As Training

#End Region

As you can see, you are keeping track of the forms via these variables. Basically, if the variable is null, the form does not exist—yet.

Before you code the constructor, you will need to add the delegates. Listings 3-1a and 3-1b show the delegate code for this project.

Listing 3-1a: C# Code for Event Delegates
Start example
    #region Menu delegates

    private void CloseMe(object sender, EventArgs e)
    {
      this.Close();
    }

    private void OpenWindow(object sender, EventArgs e)
    {
      MenuItem m;
    if(sender is MenuItem)
      m = (MenuItem)sender;
    else
      return;

    if(m == mnuEmp)
    {
      if(EmpForm == null)
      {
        EmpForm = new Employee();
        EmpForm.Load     += new EventHandler(this.ListWindows);
        EmpForm.GotFocus += new EventHandler(this.ListWindows);
        EmpForm.Disposed += new EventHandler(this.ByByWindow);
        EmpForm.Show();
      }
      else
        EmpForm.Focus();
    }

    if(m == mnuTrain)
    {
      if(TrainForm == null)
      {
        TrainForm = new Training();
        TrainForm.Load     += new EventHandler(this.ListWindows);
        TrainForm.GotFocus += new EventHandler(this.ListWindows);
        TrainForm.Disposed += new EventHandler(this.ByByWindow);
        TrainForm.Show();
      }
      else
        TrainForm.Focus();
    }

    if(m == mnuPayroll)
    {
      if(PayForm == null)
      {
        PayForm = new Payroll();
        PayForm.Load += new EventHandler(this.ListWindows);
        PayForm.GotFocus += new EventHandler(this.ListWindows);
        PayForm.Disposed += new EventHandler(this.ByByWindow);
        PayForm.Show();
      }
      else
        PayForm.Focus();
      }

      if(m == mnuSked)
      {
        if(SkedForm == null)
        {
          SkedForm = new Scheduling();
          SkedForm.Load     += new EventHandler(this.ListWindows);
          SkedForm.GotFocus += new EventHandler(this.ListWindows);
          SkedForm.Disposed += new EventHandler(this.ByByWindow);
          SkedForm.Show();
        }
        else
          SkedForm.Focus();
      }
    }

    private void ListWindows(object sender, EventArgs e)
    {
      mnuWindow.MenuItems.Clear();

      mnuWindow.MenuItems.Add("Close All",
                               new EventHandler(this.CloseAllWindows));
      mnuWindow.MenuItems.Add("-");
      if(EmpForm != null)
        mnuWindow.MenuItems.Add(EmpForm.Text,
                                new EventHandler(this.FocusForm));
      if(PayForm != null)
        mnuWindow.MenuItems.Add(PayForm.Text,
                                new EventHandler(this.FocusForm));
      if(TrainForm != null)
        mnuWindow.MenuItems.Add(TrainForm.Text,
                                new EventHandler(this.FocusForm));
      if(SkedForm != null)
        mnuWindow.MenuItems.Add(SkedForm.Text,
                                new EventHandler(this.FocusForm));
      foreach(MenuItem mnu in mnuWindow.MenuItems)
      {
        if(EmpForm != null && sender == EmpForm &&
           mnu.Text == EmpForm.Text)
        {
          mnu.Checked = true;
          break;
        }
        if(PayForm != null && sender == PayForm &&
           mnu.Text == PayForm.Text)
        {
          mnu.Checked = true;
          break;
        }
        if(TrainForm != null && sender == TrainForm &&
           mnu.Text == TrainForm.Text)
        {
          mnu.Checked = true;
          break;
        }
        if(SkedForm != null && sender == SkedForm &&
           mnu.Text == SkedForm.Text)
        {
          mnu.Checked = true;
          break;
        }
      }
    }

    private void FocusForm(object sender, EventArgs e)
    {
      MenuItem m;

      if(sender is MenuItem)
        m = (MenuItem)sender;
      else
        return;

      foreach(MenuItem mnu in mnuWindow.MenuItems)
        mnu.Checked = false;

      if(EmpForm != null && m.Text == EmpForm.Text)
        EmpForm.Focus();

      if(PayForm != null && m.Text == PayForm.Text)
        PayForm.Focus();

      if(TrainForm != null && m.Text == TrainForm.Text)
        TrainForm.Focus();

      if(SkedForm != null && m.Text == SkedForm.Text)
        SkedForm.Focus();

      m.Checked = true;
    }

    private void CloseAllWindows(object sender, EventArgs e)
    {
      if(EmpForm != null)
        EmpForm.Dispose();
      if(PayForm != null)
        PayForm.Dispose();
      if(TrainForm != null)
        TrainForm.Dispose();
      if(SkedForm != null)
        SkedForm.Dispose();

    }

    private void ByByWindow(object sender, EventArgs e)
    {

      if(sender == EmpForm)
        EmpForm = null;
      if(sender == PayForm)
        PayForm = null;
      if(sender == TrainForm)
        TrainForm = null;
      if(sender == SkedForm)
        SkedForm = null;

      ListWindows(null, null);
    }

    #endregion
End example
Listing 3-1b: VB Code for Event Delegates
Start example

#Region "Menu delegates"

  Private Sub CloseMe(ByVal sender As Object, ByVal e As EventArgs)
    Me.Close()
  End Sub

  Private Sub OpenWindow(ByVal sender As Object, ByVal e As EventArgs)
    Dim m As MenuItem

    If sender.GetType() Is GetType(MenuItem) Then
      m = CType(sender, MenuItem)
    Else
      Return
    End If

    If m Is mnuEmp Then
      If EmpForm Is Nothing Then
        EmpForm = New Employee()
        AddHandler EmpForm.Load, AddressOf Me.ListWindows
        'AddHandler EmpForm.GotFocus, AddressOf Me.ListWindows
        AddHandler EmpForm.Click, AddressOf Me.ListWindows
        AddHandler EmpForm.Disposed, AddressOf Me.ByByWindow
        EmpForm.Show()
      Else
        EmpForm.Focus()
      End If
    End If

    If m Is mnuTrain Then
      If TrainForm Is Nothing Then
        TrainForm = New Training()
        AddHandler TrainForm.Load, AddressOf Me.ListWindows
        'AddHandler TrainForm.GotFocus, AddressOf Me.ListWindows
        AddHandler TrainForm.Click, AddressOf Me.ListWindows
        AddHandler TrainForm.Disposed, AddressOf Me.ByByWindow
        TrainForm.Show()
      Else
        TrainForm.Focus()
      End If
    End If

    If m Is mnuPayroll Then
      If PayForm Is Nothing Then
        PayForm = New Payroll()
        AddHandler PayForm.Load, AddressOf Me.ListWindows
        'AddHandler PayForm.GotFocus, AddressOf Me.ListWindows
        AddHandler PayForm.Click, AddressOf Me.ListWindows
        AddHandler PayForm.Disposed, AddressOf Me.ByByWindow
        PayForm.Show()
      Else
        PayForm.Focus()
      End If
    End If

    If m Is mnuSked Then
      If SkedForm Is Nothing Then
        SkedForm = New Scheduling()
        AddHandler SkedForm.Load, AddressOf Me.ListWindows
        'AddHandler SkedForm.GotFocus, AddressOf Me.ListWindows
        AddHandler SkedForm.Click, AddressOf Me.ListWindows
        AddHandler SkedForm.Disposed, AddressOf Me.ByByWindow
        SkedForm.Show()
      Else
        SkedForm.Focus()
      End If
    End If

  End Sub

  Private Sub ListWindows(ByVal sender As Object, ByVal e As EventArgs)
    Dim mnu As MenuItem

    mnuWindow.MenuItems.Clear()

    mnuWindow.MenuItems.Add("Close All", _
                            New EventHandler(AddressOf Me.CloseAllWindows))
    mnuWindow.MenuItems.Add("-")
    If Not EmpForm Is Nothing Then
      mnuWindow.MenuItems.Add(EmpForm.Text, _
                              New EventHandler(AddressOf Me.FocusForm))
    End If
    If Not PayForm Is Nothing Then
      mnuWindow.MenuItems.Add(PayForm.Text, _
                              New EventHandler(AddressOf Me.FocusForm))
    End If
    If Not TrainForm Is Nothing Then
      mnuWindow.MenuItems.Add(TrainForm.Text, _
                              New EventHandler(AddressOf Me.FocusForm))
    End If
    If Not SkedForm Is Nothing Then
      mnuWindow.MenuItems.Add(SkedForm.Text, _
                              New EventHandler(AddressOf Me.FocusForm))
    End If

    For Each mnu In mnuWindow.MenuItems
      If Not EmpForm Is Nothing And sender Is EmpForm AndAlso _
         mnu.Text = EmpForm.Text Then
        mnu.Checked = True
        Exit For
      End If
      If Not PayForm Is Nothing And sender Is PayForm AndAlso _
         mnu.Text = PayForm.Text Then
        mnu.Checked = True
        Exit For
      End If
      If Not TrainForm Is Nothing And sender Is TrainForm AndAlso _
         mnu.Text = TrainForm.Text Then
        mnu.Checked = True
      Exit For
    End If
      If Not SkedForm Is Nothing And sender Is SkedForm AndAlso _
         mnu.Text = SkedForm.Text Then
        mnu.Checked = True
      Exit For
    End If
  Next
End Sub

Private Sub FocusForm(ByVal sender As Object, ByVal e As EventArgs)
  Dim m As MenuItem
  Dim mnu As MenuItem

  If sender.GetType() Is GetType(MenuItem) Then
    m = CType(sender, MenuItem)
  Else
    Return
  End If
    For Each mnu In mnuWindow.MenuItems
      mnu.Checked = False
    Next

    If Not EmpForm Is Nothing AndAlso m.Text = EmpForm.Text Then
      EmpForm.Focus()
    End If

    If Not PayForm Is Nothing AndAlso m.Text = PayForm.Text Then
      PayForm.Focus()
    End If

    If Not TrainForm Is Nothing AndAlso m.Text = TrainForm.Text Then
      TrainForm.Focus()
    End If

    If Not SkedForm Is Nothing AndAlso m.Text = SkedForm.Text Then
      SkedForm.Focus()
    End If

    m.Checked = True
  End Sub

  Private Sub CloseAllWindows(ByVal sender As Object, ByVal e As EventArgs)

    If Not EmpForm Is Nothing Then
      EmpForm.Dispose()
    End If
    If Not PayForm Is Nothing Then
      PayForm.Dispose()
    End If
    If Not TrainForm Is Nothing Then
      TrainForm.Dispose()
    End If
    If Not SkedForm Is Nothing Then
      SkedForm.Dispose()
    End If
  End Sub
  Private Sub ByByWindow(ByVal sender As Object, ByVal e As EventArgs)

    If sender Is EmpForm Then
      EmpForm = Nothing
    End If

    If sender Is PayForm Then
      PayForm = Nothing
    End If

    If sender Is TrainForm Then
      TrainForm = Nothing
    End If

    If sender Is SkedForm Then
      SkedForm = Nothing
    End If

    ListWindows(Nothing, Nothing)
  End Sub

#End Region
End example

Now that you have entered the delegate code, you can enter the code needed in the constructor.

C#

    public Form1()
    {
      InitializeComponent();

      mnuClose.Click   += new EventHandler(this.CloseMe);
      mnuEmp.Click     += new EventHandler(this.OpenWindow);
      mnuSked.Click    += new EventHandler(this.OpenWindow);
      mnuPayroll.Click += new EventHandler(this.OpenWindow);
      mnuTrain.Click   += new EventHandler(this.OpenWindow);

    }

VB

 Public Sub New()

   MyBase.New()

   'This call is required by the Windows Form Designer.
   InitializeComponent()

   AddHandler mnuClose.Click, New EventHandler(AddressOf Me.CloseMe)
   AddHandler mnuEmp.Click, New EventHandler(AddressOf Me.OpenWindow)
   AddHandler mnuSked.Click, New EventHandler(AddressOf Me.OpenWindow)
   AddHandler mnuPayroll.Click, New EventHandler(AddressOf Me.OpenWindow)
   AddHandler mnuTrain.Click, New EventHandler(AddressOf Me.OpenWindow)

 End Sub

This code is exceeding simple considering what this program can do. As I said before, the bulk of the code is dynamic.

If you can hang on a minute before you run the program, I need to explain what is going on in the code. You will get a much better appreciation of it that way. First, look at the OpenWindow delegate. This method is meant to be called by a menu item only.

Note 

Because I programmed the OpenWindow delegate, I know this method is meant to be called by a menu item only. You cannot always count on this, however. Someone else may accidentally copy and paste a section of code that uses this delegate for a Button Click event. (The delegate signature is the same.) It is best to test the Sender object before you actually do anything.

The first thing I do is test to make sure that the delegate was called by a MenuItem. Once I know that it is, I assign an internal variable that I can work with by casting the Sender object. Here is the code to do this:

    MenuItem m;

    if(sender is MenuItem)
      m = (MenuItem)sender;
    else
      return;
Tip 

This code should never fail. If it does fail, you have a programming error. Instead of quietly exiting like I do here, you should throw an exception for debugging purposes.

Now for the meat of this delegate. The following code is repeated in this method for each child form:

    if(m == mnuEmp)
    {
      if(EmpForm == null)
      {
        EmpForm = new Employee();
        EmpForm.Load     += new EventHandler(this.ListWindows);
        EmpForm.GotFocus += new EventHandler(this.ListWindows);
        EmpForm.Disposed += new EventHandler(this.ByByWindow);
        EmpForm.Show();
      }
      else
        EmpForm.Focus();
    }

I check to see if the user clicked the Employee form menu item. (Reflection in .NET is a wonderful thing.)

Note 

I show the use here of combining several events into a single delegate. In some cases, this is appropriate, but in others, it is not. I prefer to handle each event with its own delegate. If there is common code, I break it out into a helper function that gets called by each delegate. Among other things, doing this makes the delegate code shorter and easier to read.

Once I know this, I test to see if the form already exists on the desktop. If it does, I bring it into focus. This is the first of two ways this example programmatically brings a form into focus.

If the form does not exist yet, I instantiate it, assign a few delegates, and show the form nonmodally. Notice that I use the same delegate for the Load event and the GotFocus event. The ListWindows delegate tears down and reconstructs the window list according to the open forms.

I chain the disposed event of the form so I can reset the form-variable to null. This is how I keep only one instance of a particular form open at a time.

Now let's look at the ListWindows delegate. For clarity's sake, I don't bother testing for the correct Sender object here. The first thing I do is clear the window list of submenu items. I then add the standard Close All menu item to close all windows and a separator for the actual list of windows.

For each of the forms that is open (as determined by the form references), I add a menu item list to the window list. While I do this, I also dynamically assign the FocusForm delegate to each item in the list.

Once I have the list, I check the Sender object to see which form was instantiated (remember, I chain the Load event for each form to this delegate). The form that was instantiated (or received focus) gets its list item checked. This is how most window lists work. It allows you to see which window is currently active.

This delegate is called anytime a form is instantiated via the form's Load event. It is also called each time the form comes in focus via the GotFocus event. So in theory, if I click a form on the desktop, the window list will reflect this by placing a check mark next to that form.

Notice the delegate that I assign to each menu item in the form window list. It is the FocusForm delegate. Here it is again:

    private void FocusForm(object sender, EventArgs e)
    {
      MenuItem m;

      if(sender is MenuItem)
        m = (MenuItem)sender;
      else
        return;

      foreach(MenuItem mnu in mnuWindow.MenuItems)
        mnu.Checked = false;

      if(EmpForm != null && m.Text == EmpForm.Text)
        EmpForm.Focus();

      if(PayForm != null && m.Text == PayForm.Text)
        PayForm.Focus();

      if(TrainForm != null && m.Text == TrainForm.Text)
        TrainForm.Focus();

      if(SkedForm != null && m.Text == SkedForm.Text)
        SkedForm.Focus();

      m.Checked = true;
    }

Read the code and see if you can guess what it does. It is simple enough. This delegate is called when a user clicks a form name in the window list. It unchecks all items in the list, tests which name was clicked, and brings that form into focus. This is the second way to bring an existing form into focus via code.

Running the Multiform SDI Example

Let's recap what happens in this example:

  1. A user brings up a form via the Edit menu.

  2. The form is put into the window list and is defined as being in focus by the check mark next to its name.

  3. The user brings up the rest of the forms via the menu list.

  4. All the open forms are listed in the window list. The last one up is in focus.

  5. The user tries to bring up a form that already exists on the desktop. This is not allowed, but the form the user wanted is brought into focus. This is reflected in the window list.

  6. The user chooses another form in the window list that is not checked. This other form is brought into focus, and the window list now reflects this.

  7. The user clicks a form on the window, bringing it into focus. The window list reflects this.

  8. The user closes a form on the desktop. The window list reflects this, and the user is now able to instantiate this form again via the Edit menu.

  9. The user chooses the Close All menu item and all the open windows close. The window list reflects this and the user is able to instantiate all forms again via the Edit menu.

  10. The user closes the main menu and the open child forms also close.

Step 10 is accomplished via garbage collection because the form reference variables I have get disposed during the Dispose event for the main form.

OK, now you can run the program and try this out. Test all the items in the previous list and see if you can find the bug. Found it yet?

Open all the forms via the Edit menu. Click each form and check the window list on the main form. The window list should reflect the current child form that is in focus. This works for all forms except the Employee form.

What gives? I assigned the GotFocus event to the correct delegate. Every other form works. The problem is one of the chicken or the egg. The only difference between the Employee form and the others is that the Employee form has controls. Try this: Put a button on the Payroll form and run the program again. You will see that now the Payroll form acts like the Employee form.

The reason is that the form never gets focus. As soon as there is a single control on the form, that control receives the focus. This is, of course, provided that the control can receive focus. So how do you get around this?

Replace the code that assigned the GotFocus event of each form to the ListWindows delegate with the form's click event. Your code should look like this:

        EmpForm.Activated += new EventHandler(this.ListWindows);

Do this for each form. This simple change will make the whole system work as it should. Figure 3-4 shows my desktop with all windows open.

Click To expand
Figure 3-4: All forms in the SDI project shown on the desktop

So that is the SDI project. In this section I covered one of the two ways you can present data entry forms to a user. I explain the other way in the next section.

VB and C# Differences

I would like you to note one thing about the VB code as opposed to the C# code. Consider these lines of code:

C#

      if(EmpForm != null && m.Text == EmpForm.Text)
        EmpForm.Focus();

VB

    If Not EmpForm Is Nothing AndAlso m.Text = EmpForm.Text Then
      EmpForm.Focus()
    End If

In standard VB 6.0 there is no such thing as the keyword AndAlso. In fact, most VB 6.0 programmers would use the keyword And instead. But then again, anyone who has programmed in VB knows that using the keyword And in this case creates a bug.

VB has the unfortunate tendency to evaluate a complete If statement before determining its Boolean value. C++, C, and C# do not do this. These languages allow you to short-circuit a multistatement line. What I mean by this is that if you are testing two statements on one line such as "if A is false and B is false," C# will not test for "B is false" if A turns out to be false. This allows the programmer to write code that, if evaluated, would fail under certain circumstances but not others. This is OK if the first test always fails and if the second test cannot be evaluated.

VB tests both statements before deciding if it should go on. So in this case, if EmpForm equaled nothing, the program would crash on testing the text value of EmpForm, even though technically the If statement already failed on the first test.

This was incredibly annoying[2] in VB 6.0 because it forced you to nest If-Then statements. For years I have been waiting for VB to change to the way C++ worked in this manner. Well, the original beta of VB .NET did change the AND keyword to work properly, but VB programmers complained and Microsoft capitulated. So Microsoft added the keyword AndAlso, which I guess satisfies both camps.

Use the AndAlso keyword often. It makes for more compact and faster running code.

[2]Soapbox time!


Team LiB
Previous Section Next Section