Tuesday, November 13, 2018

Go-Lang Learn to Code GR



A while back (last summer) a coding group member recommended Go as a new language.  Since I have now run out of things to do regarding the Expense It application, I decided to start looking into Go.

At the end of July I had downloaded version 1.10.3 and recently I downloaded 1.11.2 to a different PC that seems to be the latest version.  So these comments will mainly be about 1.10.3 but could also apply to 1.11.2 since it seems to have the same problems.

The comments that I make here will be about writing an application in the Go language as well as a lesson learned about the Learn to Code GR application of a number of posts from September 2017 thru February 2018 mostly under titles such as Max Column Sum by Key.

Comments concerning Go

First off, upon starting to use the Go compiler the second of this month (it is now the twelfth) I tried the hello.go project as instructed in How to Write Go Code as found online (golang.org/doc/code.html).  This site provides the code example
package main


func main() {
}

Except that it wouldn't build using the 1.10.3 version of the compiler on a Windows PC.

I guess I should back track a bit.  Like many of the compilers that I used for the Learn to Code GR Max Column Sum by Key, the compiler is not visual.  So an editor, such as UltraEdit, has to be used and then the compiler has to be run from a DOS command window the old fashion way.  (My preferred way is a visual compiler such as those of GNAT or Microsoft C#, etc where code can be entered in a window of the compiler and then run with errors and warnings identified and located to the file being compiled.  And a clean compile run via the debugger of the visual compiler.)

Go didn't provide any of that.  First the hello.go file had to be formatted as a MAC file – that is, with only Line Feed characters at the end of lines.  Luckily this conversion is no big deal with UltraEdit and once the file is converted it can remain in that format.

I then got the error
C:\Go\src>go build hello.go
# command-line-arguments
.\hello.go:4:30: newline in string
.\hello.go:4:30: syntax error: unexpected newline, expecting comma or )
And noticed that line 4 column 30 was just past the end of the
      fmt.Println("Hello, world.)
line.  That was when I switched to MAC formatting.  Then I got
C:\Go\src>go build hello.go
can't load package: package main:
hello.go:1:14: expected ';', found 'import'

How to Write Go Code had no ';' in the code that it specified as the sample to try.  Checking further Wikipedia had that semi-colons still terminate lines but are implicit.  I added one anyway to see what happens.  Then I got
C:\Go\src>go build hello.go
# command-line-arguments
.\hello.go:1:28: syntax error: unexpected func, expecting semicolon or newline
.\hello.go:1:53: string not terminated

This result appeared to be that the separate lines of the hello.go file were all strung together as if all were on line 1.  Not very helpful if the file had been of greater length.  However, I added a semi-colon at the end of the
import "fmt"
line and got
C:\Go\src>go build hello.go
# command-line-arguments
.\hello.go:1:54: string not terminated
.\hello.go:1:74: syntax error: unexpected EOF, expecting comma or )
so I put all the code on one line and saw that I had a missing closing double quote after "world\n" of the line
package main; import "fmt"; func main() { fmt.Printf("Hello, world.\n); }
from entering the example into UltraEdit.  So I added it to complete the Printf statement.

It then compiled.  So I had managed to find my coding error in spite of the inadequate error output.  And also that I needed a semi-colon where Go wasn't supposed to need them.  Note, by the time that I finished, the code was
package main;
import "fmt";
func main() {
fmt.Printf("Hello, world.\n");
}
with semi-colons after the first, second, and fourth lines. 

To double check for this post I removed them again and got the error message
can't load package: package main:
hello.go:1:14: expected ';', found 'import'
The same is true if the file is all on one line
package main import "fmt" func main() { fmt.Printf("Hello, world.\n") }
where position 14 is the 'i' of import.  That is, assuming that position 1 is the p of package.

Note:  The size of hello.go text file is 74 bytes in Windows and hello.exe is 2,058,752 bytes.  Quite a difference.

Before continuing I captured the contents of a number of Go / Golang internet site into Word documents and read them.  I also tried their Rectangle struct example where I discovered the need to also put semi-colons after all the trailing } brackets (except the last).

I then began writing a somewhat larger package to redo the Learn to Code GR application in go.  This resulted in harder to locate errors due to the inadequate identification of the location of the errors.  Not only were the errors all specified as being on line 1 but many didn't identify the reason that the code was in error or provide any position whatsoever (other than line 1).  This would be a significant handicap in writing an application of any size and was problematic even with such a small application.

Not only did I find that semi-colons were needed in the expected places but also following the trailing } brackets except for the very last closing bracket.

Other differences I found writing the Learn to Code GR package are
  • // comments couldn't be used.  Instead they had to be /* */ comments.
  • The construct
      import (
           "bufio"
           "fmt"
           "io"
           "io/ioutil"
           "os"
      ); that an example gave didn't work.  Separate import lines with trailing semi-colons were needed.
  • Variables had to have a leading var keyword and the type had to be supplied even though the Go documentation says that Go will recognize what the type has to be from its use.

Comments concerning Max Column Sum by Key

After the reading of the web sites and doing the two simple examples I began the new version of Max Column Sum by Key on Nov 8 (after starting looking into Go on the 2nd).

I found an example for reading a text file (this Go package needs to read the max-col-sum-by-key.tsv file) at https://gobyexample.com.  Using this example, the entire file was read into a data buffer and could be displayed via
    /* Open and read the entire file into data */
    data, err := ioutil.ReadFile("C:/Source/LearnToCodeGR-Ada/max-col-sum-by-key.tsv");
    check(err);
    fmt.Print(string(data));
    fmt.Print("\n");
so that use of Go went quickly.  Note: check is a function to determine if an error occurred.

Like in past implementations I then invoked a parse function to obtain the key and values from the file so that the key with the associated value with the most references could be found and reported.

At this point I found a helpful Scanner function of the Go programming language.  Where
  var s scanner.Scanner;
  fset := token.NewFileSet();
  file := fset.AddFile("", fset.Base(), len(data));
  s.Init(file, data, nil, scanner.ScanComments);
initializes the scanner for the data passed to my parse function via the Init function.  Note: public functions have a leading upper case letter in Go while private functions have a lower case letter.

Then I added a for loop to scan each line of the data from the file.  That is, each line is terminated by a new line character.  Each line is decoded in the loop via
    position, tokenFound, literal := s.Scan();
    if tokenFound == token.EOF {
      };
      break;
    };
where the Scan function requires that three variables be declared for the function to return. 

This is where a rule of Go comes into play.  While debugging via Print output I had used all three variables to check what was happening.  But when I was satisfied with my code I no longer needed the position of the location of the token and the literal.  So I stopped referencing position in my debug output.  This resulted in a Go error because Go doesn't allow a variable to be declared and not used.  This is the reason for the dummy use of the position variable in
      if position > 0 { /* just to use position to avoid error for non-use */
above.  Although, I would think that since the Scan function requires that all three variables be declared (I did try the use of s.Scan() returning only tokenFound and literal and it failed to compile) that the use by Scan could be interpreted as satisfying the Go requirement.

While I still had the debugging output a portion of the sample results were
%!s(token.Pos=400)      ;       "\n"
%!s(token.Pos=401)      INT     "0"
%!s(token.Pos=402)      ,       ""
%!s(token.Pos=403)      INT     "912"
%!s(token.Pos=406)      IDENT   "_NUM"
%!s(token.Pos=411)      INT     "3000"
%!s(token.Pos=416)      INT     "1"
%!s(token.Pos=418)      INT     "1"
%!s(token.Pos=420)      ;       "\n"
where the positions in the data are 400, 401, etc; the tokens are ';', ',', INT, and IDENT; and the literals are strings of \n, 0, an empty string, 912, _NUM, 3000, and 1.  These results are from the file record
0,912_NUM       3000    1       1
where there is a \t (horizontal tab) character after "0,912_NUM", "3000", and the first "1" and a \n (new line) after the second "1".

From this output I could tell that the literals that I needed were those of the last three INT tokens of each line/file record.  It's a mystery to me why \n was returned as a token but the three instances of  \t were not.  But since the INT tokens were associated with the needed items the Go Scanner is quite usable and limits what has to be done.

Therefore, in the for loop, I counted the number of times the token was INT.  Then, if the third time, the associated literal was captured as the key; if the fourth time, it was captured as the first value of the key; and if the fifth time, as the second value of the key.

Prior to this I had, following previous Learn to Code GR examples in other languages, created a struct of
type KeyValues struct {
   key string;
   count1 int;
   value2 string;
   count2 int;
};
while preparing to get all the results
type keys [30]KeyValues;
type Keys struct {
  count int;
  keys;
};
var items Keys;

While beginning to write the captureKeyAndValue function to keep track of the results I suddenly had an epiphany.  I was again adding the extra code to determine whether the second value was the same or different from the first with the extra code necessary to handle each such case and whether another array entry was going to need to be added.  And it suddenly occurred to me that all I had to do was call the function twice; once for the key and the first value and again for the key and the second value.  A neat solution that hadn't occurred to me when I was doing one language example after another back at the end 2017 and the beginning of this year. 

At that time I was on many occasions illustrating the use of a class (which Go doesn't have) as well as language constructs of yet another language so not really looking for a better way rather than how the same concept could be written in the language.  But now, about a year later, the fact that the extra code wasn't necessary suddenly occurred to me.  An example, I suppose, of the importance of code reviews.

Hence the first struct is reduces to
type KeyValues struct {
   key string;
   value string;
   count int;
};
and, with the help of the scanner functions, the parse function is reduced to
/* This function scans each line and finds the key at the 3rd INT token.
   There are 5 INT tokens in all per line where the line ends in \n.  It
   then finds the two values for the key in the next two INT tokens. */
func parse(data []byte) {
  var s scanner.Scanner;
  fset := token.NewFileSet();
  file := fset.AddFile("", fset.Base(), len(data));
  s.Init(file, data, nil, scanner.ScanComments);
   
  var intCount int = 0;
  var key string = "";
  var value1 string = "";
  var value2 string = "";
    
  for {
    position, tokenFound, literal := s.Scan();
    if tokenFound == token.EOF {
      if position > 0 { /* just to use position to avoid error for non-use */
      };
      break;
    };
    if tokenFound == token.INT {
      intCount++; /* count INT tokens found */
    };
    /* capture literals found for 3rd, 4th, and 5th INT tokens */
    if intCount == 3 {
      key = literal;
    };
    if intCount == 4 {
      value1 = literal;
    };
    if intCount == 5 {
      value2 = literal;
      captureKeyAndValue(key, value1); /* capture the two    */
      captureKeyAndValue(key, value2); /*   key, value pairs */
      intCount = 0;
      key = "";
      value1 = "";
      literal = "";
    };
    if literal == "\n" { /* end of the line */
    }
  };
}; /* end parse */

Of course, clearing key, value1, and literal isn't really necessary since they will be captured again the next time through the loop.

With this change, captureKeyAndValue is reduced to
/* Capture the key and value pair as data is parsed */
func captureKeyAndValue(key string, value string) {
  /* Capture initial entry */
  if items.count == 0 {
    items.keys[items.count].key = key;
    items.keys[items.count].value = value;
    items.keys[items.count].count = 1;
    items.count++;
    return;
  };
  /* Capture item with duplicate entry */
  for i := 0; i < items.count; i++ {
    if items.keys[i].key == key { /* then key already captured */
       if items.keys[i].value == value {
         items.keys[i].count++;
         return;
       };
    };
  };
  /* Capture new key, value pair */
  if items.count < 30 {
     items.keys[items.count].key = key;
     items.keys[items.count].value = value;
     items.keys[items.count].count = 1;
     items.count++;
  };
}; /* end captureKeyAndValue */

Note: The above illustrates one advantage of Go that is actually met.  That is, unlike C and C# the function doesn't need to declare void as the return value when no value is to be returned.

Thus the entire code is
package main;
import "fmt";
import "go/scanner";
import "go/token";
import "io/ioutil";
func check(e error) {
    if e != nil
    {  panic(e) };
};
/* Struct to save parsed data */
type KeyValues struct {
   key string;
   value string;
   count int;
};
/* Array and struct to save parsed data */
type keys [30]KeyValues;
type Keys struct {
  count int;
  keys;
};
/* Saved data */
var items Keys;
/* Capture the key and value pair as data is parsed */
func captureKeyAndValue(key string, value string) {
  /* Capture initial entry */
  if items.count == 0 {
    items.keys[items.count].key = key;
    items.keys[items.count].value = value;
    items.keys[items.count].count = 1;
    items.count++;
    return;
  };
  /* Capture item with duplicate entry */
  for i := 0; i < items.count; i++ {
    if items.keys[i].key == key { /* then key already captured */
       if items.keys[i].value == value {
         items.keys[i].count++;
         return;
       };
    };
  };
  /* Capture new key, value pair */
  if items.count < 30 {
     items.keys[items.count].key = key;
     items.keys[items.count].value = value;
     items.keys[items.count].count = 1;
     items.count++;
  };
}; /* end captureKeyAndValue */
/* This function scans each line and finds the key at the 3rd INT token.
   There are 5 INT tokens in all per line where the line ends in \n.  It
   then finds the two values for the key in the next two INT tokens. */
func parse(data []byte) {
  var s scanner.Scanner;
  fset := token.NewFileSet();
  file := fset.AddFile("", fset.Base(), len(data));
  s.Init(file, data, nil, scanner.ScanComments);
   
  var intCount int = 0;
  var key string = "";
  var value1 string = "";
  var value2 string = "";
    
  for {
    position, tokenFound, literal := s.Scan();
    if tokenFound == token.EOF {
      if position > 0 { /* just to use position to avoid error for non-use */
      };
      break;
    };
    if tokenFound == token.INT {
      intCount++; /* count INT tokens found */
    };
    /* capture literals found for 3rd, 4th, and 5th INT tokens */
    if intCount == 3 {
      key = literal;
    };
    if intCount == 4 {
      value1 = literal;
    };
    if intCount == 5 {
      value2 = literal;
      captureKeyAndValue(key, value1); /* capture the two    */
      captureKeyAndValue(key, value2); /*   key, value pairs */
      intCount = 0;
      key = "";
      value1 = "";
      literal = "";
    };
    if literal == "\n" { /* end of the line */
    }
  };
}; /* end parse */
/* main entry point */
func main() {
    /* Open and read the entire file into data */
    data, err := ioutil.ReadFile("C:/Source/LearnToCodeGR-Ada/max-col-sum-by-key.tsv");
    check(err);
    fmt.Print(string(data));
    fmt.Print("\n");
  
    /* Parse the data read from the file */
    parse(data);
   
    /* Display parsed results */
    fmt.Printf("Table values count %d\n", items.count);
    for i := 0; i < items.count; i++ {
       fmt.Printf("%d\t%s\t%s\t%d\n", i,items.keys[i].key,items.keys[i].value,items.keys[i].count);
    };
   
    /* Determine key value combination with largest count and display */
    var max KeyValues;
    max.key = "";
    max.value = "";
    max.count = 0; /* how to initialize in the declaration? */
    for i := 0; i < items.count; i++ {
       if items.keys[i].count > max.count {
         max = items.keys[i];
       };
    };
    fmt.Printf("Key and Value of maximum references: %s\t%s\t%d\n",max.key,max.value,max.count);
   
} /* end main */
with the output
Table values count 13
0       1000    1       13
1       1000    2       7
2       2000    1       16
3       2000    2       4
4       3000    1       20
5       4000    1       15
6       4000    3       2
7       4000    2       2
8       4000    4       1
9       5000    2       8
10      5000    1       7
11      5000    3       4
12      5000    4       1
Key and Value of maximum references: 3000       1       20

This code is simpler than before but the past applications could have been as well had the improvement implementing separate function invocations for each of the key, value pairs.  Although each would have needed something to take the place of the Go scanner functions.

What next?

I was going to do some larger application in Go to learn it further.  But, what with the compiler not implementing what the documentation claims and the difficulty in determining where an error actually is located or even what it is, it seems to me that it would be too early to do so.  Of course, I could download a Linux version and see if it is closer to the documentation.  Otherwise, what to do? What to do?



Friday, November 2, 2018



This post will show my attempt at return to using my message framework to support the delivery of messages between components in conjunction with my previous post on the use of Windows panels.  This is a continuation of the ExpenseIt non WPF (Windows Presentation Foundation) application example of Microsoft that has been part of a series of posts.  However, this one is concerned with pairing Windows Forms with the use of the framework to deliver messages.  My previous attempt failed when it seemed to hang after sending a message or, at least, after the response was returned.  (See the previous posts “C# Displays Follow On for ExpenseIt Showing Version of Navigation Bar”, "C# Displays Follow On for ExpenseIt Showing Use of Panels" and those before these two.)

My desire was to be able to use the C# message framework along with Windows Forms to allow framework components, such as that to access a database, to be independent of the Windows Form class.  This would allow such a component to be moved to an entirely different application as well as hiding the workings of the such components from the Windows form class.

I selected ExpenseIt as the application since that is the one that I have been working with recently.  And the database as the interface to be encapsulated in a framework message component since that is the obvious choice and components are needed in order to test whether the framework could be used in conjunction with Windows forms.

This attempt got me involved with my previous C# Display application of a number of years ago (2011) in which it used an A661 like protocol to communicate with framework applications that were written in Ada.  This application could display a number of Windows forms to display A661 widgets (that are just Windows controls) to act upon A661 protocol commands that display different forms depending upon their A661 layer.  Unlike ExpenseIt, these forms were built "on-the-fly" via the program at runtime rather than via the form [Design] window by dragging controls to the window at build-time. 

But the point involved is that there was support to send and receive messages – just that the messages were not communicated via the current C# based framework.  Therefore, I began analyzing the old Display application to find out how I had implemented it that allowed the display of Windows forms while there existed other non-Forms threads.  Among points that seemed pertinent was its use of a loop at the end of the main entry point of
      while(true) // pause this thread to run others
      {
          Application.DoEvents();
          // Sleep for 3 seconds.
          if (System.Threading.Thread.CurrentThread.Name == "MainThread")
          {
              System.Threading.Thread.Sleep(3000);
              System.TimeSpan delay = System.TimeSpan.Zero;
              System.Threading.Thread.Sleep(delay); //.Infinite
          }
      }
where the launch thread was renamed to MainThread.  In addition, the runtime created Windows forms were run via
      try
      {
          if (openMethod == 0) Application.Run(Display.userForm[formIndex]);
          else if (openMethod == 1) Display.userForm[formIndex].ShowDialog();
          else if (openMethod == 2) Display.userForm[formIndex].Show();
          // Note:
          //   There can be no code that gets executed after Run or
          //   ShowDialog or Show since it would run after the form is
          //   closed in the previous instance of a user form.
      }
The relevant line here is that which Runs the particular userForm.

Previous to and in conjunction with this investigation into what I had previously programmed (which incidentally amazed me concerning all that I had developed at the time and had since forgotten about) I had begun creating the C# classes to make use of the code of the previous application in which panels were created for the form via the Form1.cs[Design] window.  As with the Display application of the previous EP (exploratory project), I created a dummy Form1.cs that did nothing as well as using ExpenseItForm.cs (renamed from Form1.cs of the previous post) to be displayed such as was done with the Display application userForm array forms.  But, of course, with no need to create the forms at runtime.  To complete the description of the controls, I also renamed Form1.Designer.cs along with its namespace and class of previous panel application to ExpenseItForm.Designer.cs since it contains the InitializeComponent method needed by the form.

After determining how I had displayed the user forms of the old Display application and kept the Windows events (button clicks and the like) active, I added the DoEvents loop to the end of the C# Program class that's entered first (where this class is a clone of the standard framework application class) and added
            ExpenseItForm expenseItForm;
            expenseItForm = new ExpenseItForm();
            Application.Run(expenseItForm);
to the Install of the application unique classes that is invoked from Program.

I immediately tried running the application and the ExpenseItForm displayed and the button clicks of the Menu panel worked (that is, displayed the requested panel) and the arrow buttons of the Navigation panel also worked.  Somewhat to my surprise that it should be so easy.

Of course, I had yet to implement the use of the framework to send messages to the Employee and Expenses databases as well as obtaining the responses by the form.  When I attempted that I found that the framework components to send an ExpenseItForm request message to the database was never received because their threads had never been created.  Since that is done at the end of the Program class, I moved the three lines to Run the ExpenseItForm to inside the DoEvents loop and found that the form never returns for the Run command.  Recognizing that the loop that contains Application.DoEvents was now superfluous, I removed it.  Therefore, the Windows events were being handled without the use of the invocation of DoEvents and it hadn't been needed in the 2011 Display application.

The application seems to run ok with the framework sending messages to a Database component from an interfacing component that waits for the response and then returns it to the form.  So my entire problem when I first tried to use a form along with the framework may just have been how I displayed the form.  That is, the lack of the use of Application.Run.  Or, perhaps, how I waited for a response in the Windows form rather than in the ComExpenseIt framework component.

This made me wonder whether a rename of ExpenseItForm back to Form1 could be made to work with Application.DoEvents so I created a Try2 folder, changed the ExpenseItForm references back to Form1 references along with the copied and renamed ExpenseItForm.Designer.cs, and created such a Windows Form application (replacing the created Form1.cs file with the copied one).  Then I had its Program.cs do the framework startup along with the Application Run of new Form1() while using the ComExpenseIt component to interface to ComDatabase.

The same hang-up occurred as it had a couple of posts ago (C# Displays Follow On for ExpenseIt Using Access Database) after sending the first request and getting the response back.  The next key click (the Left Arrow button) wasn't treated.  Therefore, it made no difference that I was doing the wait for response in ComExpenseIt rather than in the Form1 event handler as I had tried before.

I then found that Application.DoEvents() wasn't going to do anything (and didn't) since it was to output events not allow them to be received.

Therefore, I wondered if the use of a dummy Form2 could take the place of the dummy Form1.  So I added a Form2 via Project|Add Windows Form...  That was also a no go.  Didn't help at all.

So I thought that the application being described in this post worked because it was created as a Windows Form Application to create the dummy Form1.  I renamed Form1.cs of the Panels application of the previous post to ExpenseItForm.cs in the folder being used to produce the application and used File|Open File to open this renamed file and included it into the project while correcting the class name along with the constructor etc.  As well as doing the same thing with Form1.Designer.cs of the previous application as renamed to ExpenseItForm.Designer.cs.

So this ExpenseItForm class wasn't added via Project|Add Windows Form… but rather just by opening the file and adding it to the application.  This was, in effect, what occurred in the 2011 Display application.  That is, in the 2011 Display application the forms were created at runtime so only the original empty Form1 class would have been created via Project|New Project…|Windows Forms Application.

This seems to be what allows the Windows events to be received in spite of the Project|New Project…|Windows Forms Application created form failing to treat button, etc events since it had no event handlers.  That is, when the Windows Form Application created form contains the interface to the framework to send the request to the component (the Database component in this situation) and wait for the response (either in the event handler of the previous trial or the ComExpenseIt component), the interface to Windows loses its ability to respond to Windows events.  But if the code of the same form is added to the project via File|Open File… and then "File|Move name into" (where name is the opened file) Windows must treat it differently. 

That is, it must be that Windows continues to supply events to the application in spite of the non-Windows threads of the framework components when the use of those threads are not from a Windows Forms Application created form.  Plus it must not distinguish where the event handler is located.  Whereas, for some reason, this is not the case for a Windows Form added to the application via Windows Forms Application of Project|New Project… and, most likely, the add of additional forms via Project|Add Windows Form…

I modified the previous attempt to include the message framework with a Windows form by invoking public methods of a special ComExpenseIt component to have it send the request message to the ComDatabase component and receive the response.  In the previous attempt the Windows form published the request from its event handler and waited for the response to be stored directly to a public string.  In this new application, ComExpenseIt waits for the response from ComDatabase in the normal way and then returns the response to ExpenseItForm.  This means that the ExpenseItForm thread isn't the one waiting for the response as in my first attempt to use a Windows form along with the framework.  The first attempt attempted to Sleep in the Windows form until a response was available so it would have been sleeping the form's thread.  Now each ComExpenseIt interface method sleeps until the response is available in a framework thread.  See the ComExpenseIt code.

Form1

The Form1 class of Form1.cs is a dummy windows form that does nothing except for introducing the use of Windows.Forms and declaring the project as a Windows Forms Application.  It was used because the C# Display application of 2011 used it and had worked while having its own threads to send and receive messages to and from other applications. 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Apps
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

Program.cs

The naming of the CurrentThread was done because it was done in the Exploratory Project of my EP folder for the Display application back in 2011.  In that project there was a forever loop at the end that  invoked Application.DoEvents(); every 3 seconds if the CurrentThread was MainThread and I thought it must have been useful.  However, while doing this project, I found that there was no return from Application.Run so I removed it as unnecessary.

Therefore, the Program class launches the application the same as usual for the framework and then, after the framework has had all its support classes and component classes instantiated and the framework thread pool created, the actual Windows form is run in the launch thread.  That is, the form that is added to the project in the abnormal way.  The now dummy Form1 isn't run at all.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;

namespace Apps
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
            {
                System.Threading.Thread.CurrentThread.Name = "MainThread";
            }
            currentProcess = Process.GetCurrentProcess();

            Component.ApplicationId appId; // the Program.cs version
            appId.name = "App 1";          //   of the Program class
            appId.id = 1;                  //   ids the first application
            App.Launch(appId);

            // Install the components of this application.
            Try1.Install();

            // Now, after all the components have been installed, create the
            // threads for the components to run in.
            //  o In addition, the TimingScheduler thread will be created.
            //  o There will be a return from Create and then the created threads
            //    will begin to execute.  And the main procedure application
            //    thread will no longer be executed -- instead, the created
            //    threads will be the only threads running.
            Threads.Create();

            // Except, that with this framework plus Windows Forms application,
            // this main procedure application thread will continue to be
            // executed in the ExpenseItForm.
            ExpenseItForm expenseItForm;
            expenseItForm = new ExpenseItForm();
            Application.Run(expenseItForm);
       
        } // end Main

        static private Process currentProcess;

    } // end class Program
} // end namespace

Application Specific Components

There are two application specific components that are installed from Try1.Install() that follows the install of the framework via App.Launch(appId) – appId identifies the particular application.  These are ComDatabase and ComExpenseIt. 

ComDatabase is the component that accesses the two Access databases (Employees.mdb and Expenses.mdb) and is a normal framework component.  That is, it can reside in any application and communicates via framework topic messages.  It need not be in the same application as that of the Windows form.

ComExpenseIt is an unusual component since it receives its transactions from the Windows form rather than from a framework message.  This violates the framework constraint that components do not interface with other components.  Thus ComExpenseIt and ExpenseItForm must be treated as a unit and makes, in effect, the Windows form a part of the component. 

This is accomplished by a collection of public methods to be invoked by the event handlers of the form.  Each of these builds a message topic to be treated by the ComDatabase component and publishes the message that will then be delivered to ComDatabase.  They then wait for the response from ComDatabase that is returned via a typical framework callback.  To allow the method invoked by the form to return the response, that method waits for an indication that its response was received.

For instance, to remove an employee from the Employees database, the public RemoveEmployee method is invoked for the employee.  It then builds a framework request containing the Remove keyword and the employee's first and last names, Publishes the request, and awaits the response.  The RemoveEmployee method then has nothing left to do but to return the received response.
        static public string RemoveEmployee(string firstName, string lastName)
        {
            responseReceived = false;
            request = "Remove";
            string message = request + ":" + firstName + ":" + lastName;
            Delivery.Publish(requestTopic, componentKey, message);

            // Wait until response received
            while (true)
            {
                if ((request == "Remove") && (!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response
            responseReceived = false;
            return receivedResponse;
        } // end RemoveEmployee
The common response topic callback moves the data of the response to the static receivedResponse string and sets the static responseReceived boolean to true to allow the particular form invoked method to detect that the response has been received and act upon it.

For ComDatabase and ComExpenseIt to cooperate there, of course, has to be agreement between them as to the format of the messages.

ComDatabase

ComDatabase is larger than the non-component EmployeeDatabase and ExpensesDatabase classes that were invoked directly from the Windows form of the previous post.  Mostly because it treats both the databases but somewhat due the need to parse the received request topic messages to determine what request to process.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;

namespace Apps
{
    static class ComDatabase
    {
        // This class implements a component that interfaces with a database of
        // employees and another of their expenses.
        //
        // It is an on demand component running whenever a message topic is
        // received.
        //
        // This class can be moved to another application if need be to access
        // the databases.

        static string myEmployeesConnectionString =
                          @"Provider=Microsoft.Jet.OLEDB.4.0;" +
                          "Data Source=C:\\Source\\XP2\\Employees.mdb;" +                                   
                          "Persist Security Info=True;"; //+
                      //     "Jet OLEDB:Database Password=myPassword;";
        static OleDbConnection myEmployeesConnection =
                               new OleDbConnection(myEmployeesConnectionString);

        static string myExpensesConnectionString =
                          @"Provider=Microsoft.Jet.OLEDB.4.0;" +
                          "Data Source=C:\\Source\\XP2\\Expenses.mdb;" +
                          "Persist Security Info=True;";

        static OleDbConnection myExpensesConnection =
                               new OleDbConnection(myExpensesConnectionString);


        static private Component.ParticipantKey componentKey;

        static private DisburseForward.DisburseTableType forward =
                       new DisburseForward.DisburseTableType();
        static DisburseForward queue;

        static private Topic.TopicIdType requestTopic;
        static private Topic.TopicIdType responseTopic;

        static public void Install()
        {
            Console.WriteLine("in Try1 ComDatabase Install");

            // Build the Disburse forward list
            forward.count = 1;
            forward.list[0].topic.topic = Topic.Id.DATABASE;
            forward.list[0].topic.ext = Topic.Extender.REQUEST;
            forward.list[0].forward = DatabaseRequestCallback;

            // Instantiate the queue with the forward list
            queue = new DisburseForward("ComDatabase", forward);

            // Register this component
            Component.RegisterResult result;
            result = Component.Register
                     ("ComDatabase", Threads.ComponentThreadPriority.NORMAL,
                      MainEntry, queue);
            componentKey = result.key;

            if (result.status == Component.ComponentStatus.VALID)
            {
                Library.AddStatus status;
                requestTopic.topic = Topic.Id.DATABASE; // EMPLOYEEADD;
                requestTopic.ext = Topic.Extender.REQUEST;

                // Register to consume EMPLOYEE ADD topic via a callback
                status = Library.RegisterTopic
                         (requestTopic, result.key,
                          Delivery.Distribution.CONSUMER, null);
                Console.WriteLine("ComDatabase EMPLOYEE ADD Register {0}", status);

                responseTopic.topic = Topic.Id.DATABASE; // EMPLOYEEADD;
                responseTopic.ext = Topic.Extender.RESPONSE;
                status = Library.RegisterTopic
                         (responseTopic, result.key,
                          Delivery.Distribution.PRODUCER, null);
            }

        } // end Install

        // Periodic entry point
        static void MainEntry()
        {
            while (true) // loop forever
            {
                // Wait for event.
                string xxx = queue.queueName;
                queue.EventWait();

            } // end forever loop

        } // end MainEntry

        static Component.ParticipantKey from; // component that sent request

        // Callback to treat the DATABASE topic 
        static void DatabaseRequestCallback(Delivery.MessageType message)
        {
            Console.WriteLine("ComDatabase DatabaseRequestCallback Read message {0} {1}",
                              message.header.id.topic,
                              message.data);
            from = message.header.from;

            int i = message.data.IndexOf(':');
            int l = 0;
            if (i > 0)
            {
                string command = message.data.Substring(0, i);
                l = message.data.Length - i - 1;
                string rest = message.data.Substring(i + 1, l);
                if (command == "Add")
                {
                    TreatAddEmployee(rest);
                }
                else if (command == "Modify")
                {
                    TreatModifyEmployee(rest);
                }
                else if (command == "Remove")
                {
                    TreatRemoveEmployee(rest+": "); // with blank department
                }
                else if (command == "List")
                {
                    TreatListOfEmployees();
                }
                else if (command == "AddExpense")
                {
                    TreatAddExpense(rest);
                }
                else if (command == "DisplayExpenses")
                {
                    TreatDisplayExpenses(rest + ": "); // with blank department
                }
                else
                {
                    Console.WriteLine("Error: Invalid Request");
                }
            }
        } // end DatabaseRequestCallback

        static string firstName = "";
        static string lastName = "";
        static string department = "";

        static string description = "";
        static decimal amount = 0m;
        static DateTime date = DateTime.Now;

        static void GetNamesAndDepartment(string data)
        {
            firstName = "";
            lastName = "";
            department = "";

            string rest = "";
            int i = data.IndexOf(':');
            int l = 0;
            if (i > 0)
            { // Is substring prior to delimiter the message id?
                firstName = data.Substring(0, i);
                l = data.Length - i - 1;
                rest = data.Substring(i + 1, l);
                i = rest.IndexOf(':');
                if (i > 0)
                {
                    lastName = rest.Substring(0, i);
                }
                else
                { // error for no last name

                }
                l = rest.Length - i - 1;
                if (l > 0)
                {
                    department = rest.Substring(i + 1, l);
                }
                else
                { // error for no department
                }
            }
            else
            {  // error for no first name
            }
            // Note: There should be no errors for lack of names since this should
            //       be checked before a message is sent to the ComDatabase.
        } // end GetNamesAndDepartment

        static void GetNamesAndExpenseData(string data)
        {
            firstName = "";
            lastName = "";
            description = "";
            amount = 0m;
            date = DateTime.Now;

            string rest = "";
            int i = data.IndexOf(':');
            int l = 0;
            if (i > 0)
            { // Is substring prior to delimiter the message id?
                firstName = data.Substring(0, i);
                l = data.Length - i - 1;
                rest = data.Substring(i + 1, l);
                i = rest.IndexOf(':');
                if (i > 0)
                {
                    lastName = rest.Substring(0, i);
                    l = rest.Length - i - 1;
                    rest = rest.Substring(i + 1, l);
                    i = rest.IndexOf(':');
                    if (i > 0)
                    {
                        description = rest.Substring(0, i);
                        l = rest.Length - i - 1;
                        rest = rest.Substring(i + 1, l);
                        i = rest.IndexOf(':');
                        if (i > 0)
                        {
                            string amt = rest.Substring(0, i);
                            bool canConvert = decimal.TryParse(amt, out amount);
                            if (!canConvert)
                            {
                            }
                            l = rest.Length - i - 1;
                            if (l > 0)
                            {
                                string d = rest.Substring(i + 1, l);
                                canConvert = DateTime.TryParse(d, out date);
                                if (!canConvert)
                                {
                                }
                            }
                            else
                            { // error for no date
                            }
                        }
                    }
                    else
                    { // error for no description
                    }
                }
                else
                { // error for no last name

                }
            }
            else
            {  // error for no first name
            }
            // Note: There should be no errors for lack of names, etc since this
            //       should be checked before a message is sent to the ComDatabase.
        }

        // Open Employee Database connection
        static bool OpenEmployeeDatabase()
        {
            bool opened = false;
            try
            {
                // Open OleDb Connection
                myEmployeesConnection.ConnectionString = myEmployeesConnectionString;
                myEmployeesConnection.Open();
                opened = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("OLEDB Employees Connection FAILED: " + ex.Message);
                string responseMessage = "OLEDB Employees Connection FAILED: ";
                Delivery.Publish(responseTopic, componentKey,
                                 from, responseMessage);
            }
            return opened;
        } // end OpenEmployeeDatabase

        // Open Expenses Database connection
        static bool OpenExpensesDatabase()
        {
            bool opened = false;
            try
            {
                // Open OleDb Connection
                myExpensesConnection.ConnectionString = myExpensesConnectionString;
                myExpensesConnection.Open();
                opened = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("OLEDB Expenses Connection FAILED: " + ex.Message);
                string responseMessage = "OLEDB Expenses Connection FAILED: ";
                Delivery.Publish(responseTopic, componentKey,
                                 from, responseMessage);
            }
            return opened;
        } // end OpenExpensesDatabase

        static void TreatAddEmployee(string data)
        {
            string responseMessage = "";

            // Get first and last name and department of request message
            GetNamesAndDepartment(data);

            // Open connection to database
            bool opened = OpenEmployeeDatabase();

            // Add the employee.  An error will occur if a duplicate first
            // and last name.
            if (opened)
            {
                OleDbCommand cmd = myEmployeesConnection.CreateCommand();
                string insertInto = "INSERT INTO Employees (EmployeeID, LastName, FirstName, DepartmentName, Salary) ";
                string values = "VALUES (?, ?, ?, ?, ?)";
                cmd.CommandText = insertInto + values;

                cmd.Parameters.AddWithValue("EmployeeID", 0);
                cmd.Parameters.AddWithValue("LastName", lastName);
                cmd.Parameters.AddWithValue("FirstName", firstName);
                cmd.Parameters.AddWithValue("DepartmentName", department);
                cmd.Parameters.AddWithValue("Salary", 0);
                bool success = true;
                try
                {
                    cmd.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    // return the exception to display as an error message
                    success = false;
                    string exceptionString = ex.ToString();
                    string restOf = "";
                    string except = "";
                    int ii = exceptionString.IndexOf(':');
                    if (ii > 0)
                    {
                        int ll = exceptionString.Length - ii - 1;
                        restOf = exceptionString.Substring(ii + 1, ll);
                        ii = restOf.IndexOf('\r');
                        except = restOf.Substring(0, ii);
                    }

                    responseMessage = "error: " + except;
                    Console.WriteLine("send error message {0}", responseMessage);
                    Delivery.Publish(responseTopic, componentKey,
                                     from, responseMessage);
                }
                if (success)
                {
                    Console.WriteLine("send success: message ");
                    responseMessage = "success";
                    Delivery.Publish(responseTopic, componentKey,
                                     from, responseMessage);
                }

                // Close the connection
                myEmployeesConnection.Close();
            }

        } // AddEmployeeCallback

        static void TreatModifyEmployee(string data)
        {
            string responseMessage = "";

            // Get first and last name and department of request message
            GetNamesAndDepartment(data);

            // Open Connection to database
            bool opened = OpenEmployeeDatabase();

            // Check that name is exists.
            if (opened)
            {
                OleDbCommand cmd = myEmployeesConnection.CreateCommand();

                cmd = new OleDbCommand("SELECT COUNT(*) FROM Employees " +
                                       "WHERE LastName = ? AND FirstName = ?",
                                       myEmployeesConnection);
                cmd.Parameters.AddWithValue("LastName", lastName);
                cmd.Parameters.AddWithValue("FirstName", firstName);

                OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
                dataAdapter.SelectCommand = cmd;

                // Name combination valid?
                int result;
                bool success = true;
                try
                {
                    result = (int)cmd.ExecuteScalar();
                    if (result == 0)
                    {
                        responseMessage = "error: not in database";
                        success = false;
                    }
                }
                catch (Exception ex)
                {
                    // return the exception to display as an error message
                    success = false;
                    string exceptionString = ex.ToString();
                    string restOf = "";
                    string except = "";
                    int ii = exceptionString.IndexOf(':');
                    if (ii > 0)
                    {
                        int ll = exceptionString.Length - ii - 1;
                        restOf = exceptionString.Substring(ii + 1, ll);
                        ii = restOf.IndexOf('\r');
                        except = restOf.Substring(0, ii);
                    }

                    responseMessage = "error: " + except;
                }
                if (success)
                {
                    cmd = new OleDbCommand("UPDATE Employees " +
                                           "SET DepartmentName = ?" +
                                           "WHERE LastName = ? AND FirstName = ?",
                                           myEmployeesConnection);
                    cmd.Parameters.AddWithValue("DepartmentName", department);
                    cmd.Parameters.AddWithValue("LastName", lastName);
                    cmd.Parameters.AddWithValue("FirstName", firstName);

                    try
                    {
                        cmd.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        // return the exception to display as an error message
                        success = false;
                        string exceptionString = ex.ToString();
                        string restOf = "";
                        string except = "";
                        int ii = exceptionString.IndexOf(':');
                        if (ii > 0)
                        {
                            int ll = exceptionString.Length - ii - 1;
                            restOf = exceptionString.Substring(ii + 1, ll);
                            ii = restOf.IndexOf('\r');
                            except = restOf.Substring(0, ii);
                        }

                        responseMessage = "error: " + except;
                    }
                }
                if (success)
                {
                    responseMessage = "success";
                }

                Delivery.Publish(responseTopic, componentKey,
                                 from, responseMessage);

                // Close the connection and return the response message.
                myEmployeesConnection.Close();

            } // opened
           
        } // end TreatModifyEmployee

        static void TreatRemoveEmployee(string data)
        {
            string responseMessage = "";

            // Get first and last names with unused blank department
            GetNamesAndDepartment(data);
           
            // Open Connection to database
            bool opened = OpenEmployeeDatabase();

            // Check that name is exists.
            if (opened)
            {
                OleDbCommand cmd = myEmployeesConnection.CreateCommand();

                cmd = new OleDbCommand("SELECT COUNT(*) FROM Employees " +
                                       "WHERE LastName = ? AND FirstName = ?",
                                       myEmployeesConnection);
                cmd.Parameters.AddWithValue("LastName", lastName);
                cmd.Parameters.AddWithValue("FirstName", firstName);

                OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
                dataAdapter.SelectCommand = cmd;

                // Name combination valid?
                int result;
                bool success = true;
                try
                {
                    result = (int)cmd.ExecuteScalar();
                    if (result == 0)
                    {
                        responseMessage = "error: not in database";
                        success = false;
                    }
                }
                catch (Exception ex)
                {
                    // return the exception to display as an error message
                    success = false;
                    string exceptionString = ex.ToString();
                    string restOf = "";
                    string except = "";
                    int ii = exceptionString.IndexOf(':');
                    if (ii > 0)
                    {
                        int ll = exceptionString.Length - ii - 1;
                        restOf = exceptionString.Substring(ii + 1, ll);
                        ii = restOf.IndexOf('\r');
                        except = restOf.Substring(0, ii);
                    }

                    responseMessage = "error: " + except;
                }

                if (success)
                {
                    cmd = new OleDbCommand("DELETE * FROM Employees " +
                                           "WHERE LastName = ? AND FirstName = ?",
                                           myEmployeesConnection);
                    cmd.Parameters.AddWithValue("LastName", lastName);
                    cmd.Parameters.AddWithValue("FirstName", firstName);
                    dataAdapter.DeleteCommand = cmd;

                    try
                    {
                        cmd.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        // return the exception to display as an error message
                        success = false;
                        string exceptionString = ex.ToString();
                        string restOf = "";
                        string except = "";
                        int ii = exceptionString.IndexOf(':');
                        if (ii > 0)
                        {
                            int ll = exceptionString.Length - ii - 1;
                            restOf = exceptionString.Substring(ii + 1, ll);
                            ii = restOf.IndexOf('\r');
                            except = restOf.Substring(0, ii);
                        }

                        responseMessage = "error: " + except;
                    }
                }

                if (success)
                {
                    responseMessage = "success";
                }

                Delivery.Publish(responseTopic, componentKey,
                                  from, responseMessage);

                // Close the connection and return the response message.
                myEmployeesConnection.Close();

            } // end opened

        } // end TreatRemoveEmployee

        private struct EmployeeDataType
        {
            public string firstName;
            public string lastName;
            public string department;
        }

        private class EmployeeListType
        {
            public int count;
            public EmployeeDataType[] list =
                       new EmployeeDataType[50];
        }

        static void TreatListOfEmployees()
        {
            // Initialize list to return in response
            EmployeeListType employeeData = new EmployeeListType();
            employeeData.count = 0;

            string responseMessage = "";

            // Open Connection to database
            bool opened = OpenEmployeeDatabase();

            if (opened)
            {
                string select = "SELECT * FROM Employees";
                OleDbCommand cmd = new OleDbCommand(select, myEmployeesConnection);

                using (OleDbDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        employeeData.list[employeeData.count].lastName =
                            reader["LastName"].ToString();
                        employeeData.list[employeeData.count].firstName =
                            reader["FirstName"].ToString();
                        employeeData.list[employeeData.count].department =
                            reader["DepartmentName"].ToString();
                        employeeData.count++;
                    }
                }

            }

            // Close the connection and return the response message.
            myEmployeesConnection.Close();

            if (employeeData.count > 0)
            {
                responseMessage = "success:";
                responseMessage += employeeData.count + ":";
                for (int i = 0; i < employeeData.count; i++)
                {
                    responseMessage += employeeData.list[i].lastName + "\t";
                    responseMessage += employeeData.list[i].firstName + "\t";
                    responseMessage += employeeData.list[i].department + "\n";
                }
            }
            else
            {
                responseMessage = "No employees found";
            }
            Delivery.Publish(responseTopic, componentKey,
                             from, responseMessage);

        } // end TreatListOfEmployees

        static void TreatAddExpense(string data)
        {
            string responseMessage = "";

            // Get first and last names, description, amount, and date
            GetNamesAndExpenseData(data);

            // Open connection to database
            bool opened = OpenExpensesDatabase();

            if (opened)
            {
                OleDbCommand cmd = myExpensesConnection.CreateCommand();
                string insertInto =
                    "INSERT INTO Expenses (LastName, FirstName, Description, AmountSpent, DatePurchased)";
                string values = "VALUES (?, ?, ?, ?, ?)";
                cmd.CommandText = insertInto + values;

                cmd.Parameters.AddWithValue("LastName", lastName);
                cmd.Parameters.AddWithValue("FirstName", firstName);
                cmd.Parameters.AddWithValue("Description", description);
                cmd.Parameters.AddWithValue("AmountSpent", amount);
                cmd.Parameters.AddWithValue("DataPurchased", date);

                bool success = true;
                try
                {
                    cmd.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    // return the exception to display as an error message
                    success = false;
                    string exceptionString = ex.ToString();
                    string restOf = "";
                    string except = "";
                    int ii = exceptionString.IndexOf(':');
                    if (ii > 0)
                    {
                        int ll = exceptionString.Length - ii - 1;
                        restOf = exceptionString.Substring(ii + 1, ll);
                        ii = restOf.IndexOf('\r');
                        except = restOf.Substring(0, ii);
                    }

                    responseMessage = "error: " + except;
                }
                if (success)
                {
                    responseMessage = "success";
                }

                // Close the connection and return the response message.
                myExpensesConnection.Close();

                Delivery.Publish(responseTopic, componentKey,
                     from, responseMessage);

            } // end if opened

        } // end TreatAddExpense

        public struct ExpensesDataType
        {
            public string description;
            public string date;
            public string amount;
        }

        public class ExpensesListType
        {
            public int count;
            public ExpensesDataType[] list =
                       new ExpensesDataType[30];
        }

        static void TreatDisplayExpenses(string data)
        {
            string responseMessage = "";

            // Get first and last names with unused blank department
            GetNamesAndDepartment(data);

            // Initialize list to return in response
            ExpensesListType expenseData = new ExpensesListType();
            expenseData.count = 0;

            // Open Connection to database
            bool opened = OpenExpensesDatabase();

            if (opened)
            {
                string select = "SELECT * FROM Expenses";
                OleDbCommand cmd = new OleDbCommand(select, myExpensesConnection);

                using (OleDbDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        if ((reader["LastName"].ToString() == lastName) &&
                            (reader["FirstName"].ToString() == firstName))
                        {
                            expenseData.list[expenseData.count].description =
                                reader["Description"].ToString();
                            expenseData.list[expenseData.count].date =
                                reader["DatePurchased"].ToString();
                            expenseData.list[expenseData.count].amount =
                                reader["AmountSpent"].ToString();
                            expenseData.count++;
                        }
                    }
                }
            }

            // Close the connection and return the response message.
            // Note: The response can't use ":" separators as usual since the
            //       date also has time and it uses ":" between the time fields.
            myExpensesConnection.Close();

            if (expenseData.count > 0)
            {
                responseMessage = "success#";
                responseMessage += expenseData.count + "#";
                for (int i = 0; i < expenseData.count; i++)
                {
                    responseMessage += expenseData.list[i].description + "#";
                    responseMessage += expenseData.list[i].amount + "#";
                    responseMessage += expenseData.list[i].date + "#";
                }
            }
            else
            {
                // No expenses for the employee
                responseMessage = "success#";
                responseMessage += expenseData.count + "#";
            }
            Delivery.Publish(responseTopic, componentKey,
                             from, responseMessage);

        } // end TreatDisplayExpenses

    } // end class ComDatabase

} // end namespace

ComExpenseIt

The complete ComExpenseIt class follows.  There is one public method for each database query needed by ExpenseItForm.  And one common response topic callback to be run when a response is forwarded to the class's queue.  As previously noted, the receive of the response message triggers the particular ExpenseItForm interface method to return the response.  Each ExpenseItForm interface method has a wait loop to await the receive of its response.  At times, as can be seen, the framework response is reformatted for return to ExpenseItForm.

The need for this component is, of course, extra code from directly accessing the particular database access class of the previous post.  But is necessary to separate the database access to allow it to be moved to a different application.  And, of course, as a test of whether Windows forms can be used in conjunction with the framework.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;

namespace Apps
{
    public class ComExpenseIt
    {
        // This class implements a component that interfaces with ExpenseItForm
        // which does a Windows Form with panels.  It must be in the same
        // application as ExpenseItForm.
        //
        // It is an on demand component running whenever a message topic is
        // received and passes the response topic back to ExpenseItForm for
        // display.

        static private Component.ParticipantKey componentKey;

        static private DisburseForward.DisburseTableType forward =
                       new DisburseForward.DisburseTableType();
        static DisburseForward queue;

        static private Topic.TopicIdType requestTopic;
        static private Topic.TopicIdType responseTopic;

        static private string request; // request being sent to ComDatabase
        static private bool responseReceived = false;
        static private string receivedResponse; // response received

        static public void Install()
        {
            Console.WriteLine("in Try1 ComExpenseIt Install");

            // Build the Disburse forward list
            forward.count = 1;
            forward.list[0].topic.topic = Topic.Id.DATABASE;
            forward.list[0].topic.ext = Topic.Extender.RESPONSE;
            forward.list[0].forward = DatabaseResponseCallback;

            // Instantiate the queue with the forward list
            queue = new DisburseForward("ComExpenseIt", forward);

            // Register this component
            Component.RegisterResult result;
            result = Component.Register
                     ("ComExpenseIt", Threads.ComponentThreadPriority.LOWER,
                      MainEntry, queue);
            componentKey = result.key;

            if (result.status == Component.ComponentStatus.VALID)
            {
                Library.AddStatus status;
                requestTopic.topic = Topic.Id.DATABASE;
                requestTopic.ext = Topic.Extender.REQUEST;

                // Register to produce the EMPLOYEE ADD topic and consume the response
                status = Library.RegisterTopic
                         (requestTopic, result.key,
                          Delivery.Distribution.PRODUCER, null);
                Console.WriteLine("ComDatabase DATABASE Register {0}", status);

                responseTopic.topic = Topic.Id.DATABASE;
                responseTopic.ext = Topic.Extender.RESPONSE;
                status = Library.RegisterTopic
                         (responseTopic, result.key,
                          Delivery.Distribution.CONSUMER, null);
            }

        } // end Install
           
        // Entry point
        static void MainEntry()
        {
            while (true) // loop forever
            {
                // Wait for event.
                string xxx = queue.queueName;
                queue.EventWait();

            } // end forever loop

        } // end MainEntry

        // Callback to treat the response from ComDatabase
        static void DatabaseResponseCallback(Delivery.MessageType message)
        {
            receivedResponse = message.data;
            responseReceived = true;
        } // end AddEmployeeResponseCallback

        static public string TreatAddEmployee(string firstName, string lastName,
                                              string department)
        {
            responseReceived = false;
            request = "Add";
            string message = request + ":" + firstName + ":" + lastName + ":" + department;
            Delivery.Publish(requestTopic, componentKey, message);

            // Wait until response received
            while (true)
            {
                if ((request == "Add") && (!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response
            responseReceived = false;
            return receivedResponse;

        } // end TreatAddEmployee

        static public string ModifyEmployeeData(string firstName, string lastName,
                                                string department)
        {
            responseReceived = false;
            request = "Modify";
            string message = request + ":" + firstName + ":" + lastName + ":" +
                                             department;
            Delivery.Publish(requestTopic, componentKey, message);

            // Wait until response received
            while (true)
            {
                if ((request == "Modify") &&(!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response
            responseReceived = false;
            return receivedResponse;
        } // end ModifyEmployeeData

        static public string RemoveEmployee(string firstName, string lastName)
        {
            responseReceived = false;
            request = "Remove";
            string message = request + ":" + firstName + ":" + lastName;
            Delivery.Publish(requestTopic, componentKey, message);

            // Wait until response received
            while (true)
            {
                if ((request == "Remove") && (!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response
            responseReceived = false;
            return receivedResponse;
        } // end RemoveEmployee

        static public string AddExpense(string firstName, string lastName,
                                        string description, string amt, string date)
        {
            responseReceived = false;
            request = "AddExpense";
            string message = request + ":" + firstName + ":" + lastName + ":" +
                             description + ":" + amt + ":" + date;
            Delivery.Publish(requestTopic, componentKey, message);

            // Wait until response received
            while (true)
            {
                if ((request == "AddExpense") && (!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response
            responseReceived = false;
            return receivedResponse;
        } // end AddExpense

        public struct EmployeeDataType
        {
            public string employee;
        }

        public class EmployeeListType
        {
            public int count;
            public EmployeeDataType[] list =
                       new EmployeeDataType[50];
        }

        static public void ListOfEmployees(out string result,
                                           out EmployeeListType listOut)
        {
            responseReceived = false;
            request = "List";
            string message = request + ":";
            Delivery.Publish(requestTopic, componentKey, message);

            EmployeeListType list = new EmployeeListType();
            list.count = 0;

            // Wait until response received
            while (true)
            {
                if ((request == "List") && (!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }

            result = "error"; // overwrite with parsed result
            responseReceived = false;
            // Decode the response
            int i = receivedResponse.IndexOf(':');
            int l = 0;
            if (i > 0)
            {
                result = receivedResponse.Substring(0, i);
                l = receivedResponse.Length - i - 1;
                string data = receivedResponse.Substring(i + 1, l);
                if (result != "success")
                {
                    // Return the negative response
                }
                // Decode the count
                int ii = data.IndexOf(':');
                if (ii > 0)
                {
                    int ll = data.Length - ii - 1;
                    Int16 count = Convert.ToInt16(data.Substring(0, ii));
                    string rest = data.Substring(ii+1,ll);
                    for (int j = 0; j < count; j++)
                    {
                        ii = rest.IndexOf('\n');
                        string item = rest.Substring(0, ii);
                        list.list[j].employee = item;
                        ll = rest.Length - ii - 1;
                        rest = rest.Substring(ii + 1, ll);
                    }
                    list.count = count;
                    listOut = list;
                }
            }
            else
            {
                // Return the negative response
                listOut = list;
            }
            listOut = list;

         } // end ListOfEmployees

        public struct ExpensesDataType
        {
            public string description; //expenses;
            public string amount;
            public string date;
        }

        public class ExpensesListType
        {
            public int count;
            public ExpensesDataType[] list =
                       new ExpensesDataType[30];
        }

        static public void ListOfExpenses(string firstName, string lastname,
                                          out string result,
                                          out ExpensesListType expenses)
        {
            responseReceived = false;
            request = "DisplayExpenses";
            string message = request + ":" + firstName + ":" + lastname;
            Delivery.Publish(requestTopic, componentKey, message);

            ExpensesListType expense = new ExpensesListType();
            expense.count = 0;

            // Wait until response received
            while (true)
            {
                if ((request == "DisplayExpenses") && (!responseReceived))
                {
                    Thread.Sleep(250); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }

            result = "error"; // overwrite with parsed result
            responseReceived = false;
            // Decode the response
            // Note: The response can't use ":" separators as usual since the
            //       date also has time and it uses ":" between the time fields.
            int i = receivedResponse.IndexOf('#');
            int l = 0;
            if (i > 0)
            {
                result = receivedResponse.Substring(0, i);
                l = receivedResponse.Length - i - 1;
                string data = receivedResponse.Substring(i + 1, l);
                if (result != "success")
                {
                    // Return the negative response
                }
                else
                {
                    // Decode the count
                    int ii = data.IndexOf('#');
                    if (ii > 0)
                    {
                        int ll = data.Length - ii - 1;
                        Int16 count = Convert.ToInt16(data.Substring(0, ii));
                        expense.count = count;
                        string rest = data.Substring(ii + 1, ll);
                        for (int j = 0; j < count; j++)
                        {
                            ii = rest.IndexOf('#');
                            string item = rest.Substring(0, ii);
                            expense.list[j].description = item;
                            ll = rest.Length - ii - 1;
                        
                            rest = rest.Substring(ii + 1, ll);
                            ii = rest.IndexOf('#');
                            item = rest.Substring(0, ii);
                            expense.list[j].amount = item;
                            ll = rest.Length - ii - 1;

                            rest = rest.Substring(ii + 1, ll);
                            ii = rest.IndexOf('#');
                            item = rest.Substring(0, ii);
                            expense.list[j].date = item;
                            ll = rest.Length - ii - 1;
                            rest = rest.Substring(ii + 1, ll);
                        }
                    }
                }
            }
            else
            {
            }
            // Return parsed expenses
            expenses = expense;
        } // end ListOfExpenses

    } // end ComExpenseIt class

} // end namespace

ExpenseItForm

ExpenseItForm is the same as the previous post's Form1 except for when there was an invocation of one of the two database classes.  For instance, the constructor now instantiates
           databaseInterface = new ComExpenseIt();
although there is no reference to databaseInterface so this line isn't needed. 

Getting an employee's expenses to display, for instance, is now done by
                        // Obtain employee's expenses
                        ComExpenseIt.ExpensesListType employeeExpenses =
                            new ComExpenseIt.ExpensesListType();

                        string response = "";
                        ComExpenseIt.ListOfExpenses(listFirstName, listLastName,
                                                    out response,
                                                    out employeeExpenses);
etc where the structure was provided by ComExpenseIt. 

In the previous attempt to use the message framework, the publish to the database component and the wait for the response was done from within the Windows form.  For instance, to add an employee
            ComDatabaseAdd.AddEmployeePublish(firstName, lastName, department);

            while (true)
            {
                if (interClassComm == "success")
                {
                    ClearTextBoxes();
                    interClassComm = "";
                    break; // exit loop
                }
                else if (interClassComm != "")
                {
                    errorMessageBox.Text = interClassComm;
                    interClassComm = "";
                    break; // exit loop
                }
                Thread.Sleep(100);
            }
That is, following the publish of the request a while loop was entered to await the response.  The ComDatabaseAdd component put the response into the public string of the form and the form event handler cycled until it contained a non-null value.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Apps
{
    public partial class ExpenseItForm : Form
    {
        private ComExpenseIt databaseInterface;

        private Navigation navigation;

        public enum PanelSelection
        {
            NONE,
            MENU,
            ADDEMPLOYEE,
            UPDATEEMPLOYEE,
            ENTEREXPENSES,
            DISPLAYEXPENSES,
            UPDATEEMPLOYEELIST,
            ENTEREXPENSESLIST,
            DISPLAYEXPENSESLIST
        }

        public SelectionQueue.ParentDataType[] parentData =
            new SelectionQueue.ParentDataType[SelectionQueue.size];

        public enum SelectionDirection
        {
            LEFT,
            RIGHT
        }

        // Employee selected from ListPanel
        private string listFirstName = "";
        private string listLastName = "";
        private string listDepartment = "";

        public ExpenseItForm() // constructor
        {
            InitializeComponent();

            // Allow access to the ComExpenseIt class to communicate with the
            // databases
            databaseInterface = new ComExpenseIt();

            // Activate the Navigation Panel
            navigation = new Navigation(this, this.leftArrow, this.rightArrow);

            // Select the Menu Panel as the first panel below the Navigation Panel
            navigation.SelectPanel(PanelSelection.MENU);

            // Build the structure of the panels
            SupplyPanelStructure();

        } // end constructor

        /**********************************************************************
         * Navigation panel actions
         */
        private void leftArrow_Click(object sender, EventArgs e)
        {
            navigation.ArrowClicked(SelectionDirection.LEFT);

        } // end leftArrow_Click

        private void rightArrow_Click(object sender, EventArgs e)
        {
            navigation.ArrowClicked(SelectionDirection.RIGHT);

        } // end rightArrow_Click

        private void SupplyPanelStructure()
        {
            parentData[(int)PanelSelection.NONE].panel = null;
            parentData[(int)PanelSelection.NONE].parent = PanelSelection.NONE;
            parentData[(int)PanelSelection.NONE].child = PanelSelection.NONE;
            parentData[(int)PanelSelection.NONE].displayFirst = true;
           
            parentData[(int)PanelSelection.MENU] =
                parentData[(int)PanelSelection.NONE];
            parentData[(int)PanelSelection.MENU].panel = MenuPanel;
           
            parentData[(int)PanelSelection.ADDEMPLOYEE] =
                parentData[(int)PanelSelection.NONE];
            parentData[(int)PanelSelection.ADDEMPLOYEE].panel = AddEmployeesPanel;
           
            parentData[(int)PanelSelection.UPDATEEMPLOYEE] =
                parentData[(int)PanelSelection.NONE];
            parentData[(int)PanelSelection.UPDATEEMPLOYEE].panel = UpdateEmployeesPanel;
            parentData[(int)PanelSelection.UPDATEEMPLOYEE].child =
                PanelSelection.UPDATEEMPLOYEELIST;
           
            parentData[(int)PanelSelection.ENTEREXPENSES].parent =
                PanelSelection.NONE;
            parentData[(int)PanelSelection.ENTEREXPENSES].panel = EnterExpensesPanel;
            parentData[(int)PanelSelection.ENTEREXPENSES].child =
                PanelSelection.ENTEREXPENSESLIST;
            parentData[(int)PanelSelection.ENTEREXPENSES].displayFirst = false;

            parentData[(int)PanelSelection.DISPLAYEXPENSES] =
                parentData[(int)PanelSelection.ENTEREXPENSES];
            parentData[(int)PanelSelection.DISPLAYEXPENSES].panel = DisplayExpensesPanel;
            parentData[(int)PanelSelection.DISPLAYEXPENSES].child =
                PanelSelection.DISPLAYEXPENSESLIST;

            parentData[(int)PanelSelection.UPDATEEMPLOYEELIST].panel = ListPanel;
            parentData[(int)PanelSelection.UPDATEEMPLOYEELIST].parent =
                PanelSelection.UPDATEEMPLOYEE;
            parentData[(int)PanelSelection.UPDATEEMPLOYEELIST].child =
                PanelSelection.NONE;
            parentData[(int)PanelSelection.UPDATEEMPLOYEELIST].displayFirst = false;
           
            parentData[(int)PanelSelection.ENTEREXPENSESLIST].panel = ListPanel;
            parentData[(int)PanelSelection.ENTEREXPENSESLIST].parent =
                PanelSelection.ENTEREXPENSES;
            parentData[(int)PanelSelection.ENTEREXPENSESLIST].child =
                PanelSelection.NONE;
            parentData[(int)PanelSelection.ENTEREXPENSESLIST].displayFirst = true;

            parentData[(int)PanelSelection.DISPLAYEXPENSESLIST].panel = ListPanel;
            parentData[(int)PanelSelection.DISPLAYEXPENSESLIST].parent =
                PanelSelection.DISPLAYEXPENSES;
            parentData[(int)PanelSelection.DISPLAYEXPENSESLIST].child =
                PanelSelection.NONE;
            parentData[(int)PanelSelection.DISPLAYEXPENSESLIST].displayFirst = true;
        } // end SupplyPanelStructure

        public void SwitchPanels(ExpenseItForm.PanelSelection to)
        {
            MenuPanel.Visible = false;
            AddEmployeesPanel.Visible = false;
            UpdateEmployeesPanel.Visible = false;
            EnterExpensesPanel.Visible = false;
            DisplayExpensesPanel.Visible = false;
            ListPanel.Visible = false;

            // Note: Special initializations needed for particular panels are
            //       done here since that allows them to be done in one place.
            //       They are needed when there are modifications to a panel
            //       rather than have multiple versions of the panel.
            switch (to)
            {
                case PanelSelection.NONE:
                    {
                        break;
                    }
                case PanelSelection.MENU:
                    {
                        Controls.Add(MenuPanel);
                        MenuPanel.Visible = true;
                        break;
                    }
                case PanelSelection.ADDEMPLOYEE:
                    {
                        Controls.Add(AddEmployeesPanel);
                        Department.Visible = true;
                        AddEmployeesPanel.Visible = true;
                        break;
                    }
                case PanelSelection.UPDATEEMPLOYEE:
                    {
                        Controls.Add(UpdateEmployeesPanel);

                        // Modify visibility of selected panel controls
                        Modify.Visible = true;
                        Remove.Visible = true;
                        DepartmentLabel.Visible = false;
                        SelectDepartment.Visible = false;
                        UpdateButton.Visible = false;
                        ErrorMessage.Visible = false;
                        SelectErrorMessage.Visible = false; // because have an extra box

                        // Display ListPanel selected employee
                        if ((listFirstName != "") && (listLastName != ""))
                        {
                            SelectFirstName.Text = listFirstName;
                            SelectLastName.Text = listLastName;
                            SelectDepartment.Text = listDepartment;
                        }

                        // Make panel visible
                        UpdateEmployeesPanel.Visible = true;
                        break;
                    }
                case PanelSelection.UPDATEEMPLOYEELIST:
                    {
                        Controls.Add(ListPanel);
                        // Display the particular ListPanel title
                        ListPanelTitle.Text = "Update Employee";
                        ListPanel.Visible = true;

                        LoadEmployeeList();
                        break;
                    }
                case PanelSelection.ENTEREXPENSES:
                    {
                        Controls.Add(EnterExpensesPanel);
                        EnterExpensesPanel.Visible = true;

                        // Display ListPanel selected employee
                        if ((listFirstName != "") && (listLastName != ""))
                        {
                            EnterExpensesFirstName.Text = listFirstName;
                            EnterExpensesLastName.Text = listLastName;
                            EnterExpensesDepartment.Text = listDepartment;
                         }

                        break;
                    }
                case PanelSelection.ENTEREXPENSESLIST:
                    {
                        Controls.Add(ListPanel);
                        ListPanelTitle.Text = "Enter Expenses";
                        ListPanel.Visible = true;

                        LoadEmployeeList();
                        break;
                    }
                case PanelSelection.DISPLAYEXPENSES:
                    {
                        Controls.Add(DisplayExpensesPanel);
                        DisplayExpensesPanel.Visible = true;

                        // Display ListPanel selected employee
                        if ((listFirstName != "") && (listLastName != ""))
                        {
                            DisplayExpensesFirstName.Text = listFirstName;
                            DisplayExpensesLastName.Text = listLastName;
                            DisplayExpensesDepartment.Text = listDepartment;
                        }

                        DescListBox.Items.Clear();
                        DateListBox.Items.Clear();
                        AmtListBox.Items.Clear();

                        // Obtain employee's expenses
                        ComExpenseIt.ExpensesListType employeeExpenses =
                            new ComExpenseIt.ExpensesListType();

                        string response = "";
                        ComExpenseIt.ListOfExpenses(listFirstName, listLastName,
                                                    out response,
                                                    out employeeExpenses);

                        // Display employee's expenses
                        string description, date, amount;
                        string datetime;
                        for (int i = 0; i < employeeExpenses.count; i++)
                        {
                            description = employeeExpenses.list[i].description;
                            DescListBox.Items.Add(description);
                            datetime = employeeExpenses.list[i].date;
                            int ii = datetime.IndexOf(' '); // find " 12:00:00 AM"
                            if (ii > 0)
                            {
                                date = datetime.Substring(0, ii);
                            }
                            else
                            {
                                date = datetime;
                            }
                            DateListBox.Items.Add(date);
                            amount = employeeExpenses.list[i].amount;
                            AmtListBox.Items.Add(amount);
                        }
                        DescListBox.Height = DescListBox.PreferredHeight;
                        DateListBox.Height = DateListBox.PreferredHeight;
                        AmtListBox.Height = AmtListBox.PreferredHeight;

                        break;
                    }
                case PanelSelection.DISPLAYEXPENSESLIST:
                    {
                        Controls.Add(ListPanel);
                        ListPanelTitle.Text = "Display Expenses";
                        ListPanel.Visible = true;


                        LoadEmployeeList();

                        break;
                    }
            } // end switch
        } // end SwitchPanels

        // Common method for UpdateEmployee, EnterExpenses, and UpdateExpenses
        private void LoadEmployeeList()
        {
            // Clear the list box
            employeeList.Items.Clear();

            // Obtain the list of employees
            ComExpenseIt.EmployeeListType employees =
                new ComExpenseIt.EmployeeListType();
            string response = "";
            ComExpenseIt.ListOfEmployees(out response, out employees);

            // Transfer to the list box
            if (response == "success")
            {
                for (int i = 0; i < employees.count; i++)
                {
                    employeeList.Items.Add(employees.list[i].employee);
                }
            }
            // Display the list box
            employeeList.Visible = true;   

        } // end LoadEmployeeList

        /**********************************************************************
         * Menu panel actions
         */
        private void AddEmployee_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.ADDEMPLOYEE);
        } // AddEmployee_Click

        private void UpdateEmployee_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.UPDATEEMPLOYEE);
        } // end UpdateEmployee_Click

        private void Expenses_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.ENTEREXPENSESLIST);
        } // end Expenses_Click

        // Note: Due to problems getting the panels laid out using
        //       Form1.cs[Design], when I went to use the Display_Click
        //       I was prevented from doing so although I don't see an
        //       entry for it in Form1.Designer.cs as it became finalized.
        private void Display_Click_1(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.DISPLAYEXPENSESLIST);
        }

        /**********************************************************************
         * AddEmployees panel actions
         */
        private void AddEnteredEmployee_Click(object sender, EventArgs e)
        {
            // Clear any error message
            ErrorMessageBox.Clear();
            // Obtain entered data
            string firstName, lastName, department;
            firstName = FirstName.Text;
            lastName = LastName.Text;
            department = Department.Text;

            // Check that values supplied
            if ((firstName == "") || (lastName == "") || (department == ""))
            {
                ErrorMessageBox.Text = "Error: all fields must be supplied";
                return;
            }

            // Update database
            string response = ComExpenseIt.TreatAddEmployee
                              (firstName, lastName, department);

            if (response == "success")
            {
                FirstName.Clear();
                LastName.Clear();
                Department.Clear();
            }
            else
            {
                ErrorMessageBox.Text = response;
            }
        } // end AddEnteredEmployee_Click

        /**********************************************************************
         * UpdateEmployees panel actions
         */
        private void ShowList_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.UPDATEEMPLOYEELIST);
        } // end ShowList_Click

        private void UpdateButton_Click(object sender, EventArgs e)
        {
            // Clear any error message
            ErrorMessage.Clear();

            // Obtain entered data
            string firstName, lastName, department;
            firstName = SelectFirstName.Text;
            lastName = SelectLastName.Text;
            department = SelectDepartment.Text;

            // Check that values supplied
            if ((firstName == "") || (lastName == "") || (department == ""))
            {
                ErrorMessage.Text = "Error: all fields must be supplied";
                ErrorMessage.Show();
                return;
            }

            // Update database
            string response = ComExpenseIt.ModifyEmployeeData
                                           (firstName, lastName, department);
            if (response == "success")
            {
                ErrorMessage.Clear();
                ErrorMessage.Hide();
                SelectFirstName.Clear();
                SelectLastName.Clear();
                DepartmentLabel.Hide();
                SelectDepartment.Clear();
                SelectDepartment.Hide();
                UpdateButton.Hide();
                Modify.Show();
                Remove.Show();
            }
            else
            {
                ErrorMessage.Text = response;
                ErrorMessage.Show();
            }
        } // end UpdateButton_Click

        private void Remove_Click(object sender, EventArgs e)
        {
            // Clear any error message
            ErrorMessage.Clear();
            ErrorMessage.Hide();

            DepartmentLabel.Hide();
            Department.Hide();

            // Obtain entered data
            string firstName, lastName;
            firstName = SelectFirstName.Text;
            lastName = SelectLastName.Text;

            // Check that values supplied
            if ((firstName == "") || (lastName == ""))
            {
                ErrorMessage.Text = "Error: all fields must be supplied";
                ErrorMessage.Show();
                return;
            }

            // Update database
            string response = ComExpenseIt.RemoveEmployee
                              (firstName, lastName);

            if (response == "success")
            {
                SelectFirstName.Clear();
                SelectLastName.Clear();
                ErrorMessage.Hide();
            }
            else
            {
                ErrorMessage.Text = response;
                ErrorMessage.Show();
            }
        } // end Remove_Click

        private void Modify_Click(object sender, EventArgs e)
        {
            Modify.Visible = false;
            Remove.Visible = false;
            DepartmentLabel.Visible = true;
            SelectDepartment.Visible = true;
            UpdateButton.Visible = true;

        } // end Modify_Click

        /**********************************************************************
         * EnterExpenses panel actions
         */
        private void EnterExpensesButton_Click(object sender, EventArgs e)
        {
            // Capture data
            string firstName = EnterExpensesFirstName.Text;
            string lastName = EnterExpensesLastName.Text;
            string department = EnterExpensesDepartment.Text;
            string description = EnterExpensesDescription.Text;
            string amount = EnterExpensesAmount.Text;
            string date = EnterExpensesDate.Text;

            // Check that values supplied
            if ((firstName == "") || (lastName == "") || (department == "") ||
                (description == "") || (amount == "") || (date == ""))
            {
                ErrorExpensesErrorMessage.Text = "Error: all fields must be supplied";
                ErrorExpensesErrorMessage.Show();
                return;
            }
            // Check that amount is decimal and date is formatted correctly
            Decimal amt;
            bool canConvert = decimal.TryParse(amount, out amt);
            if (!canConvert)
            {
                ErrorExpensesErrorMessage.Text = "Error: Amount must be xx.xx format";
                ErrorExpensesErrorMessage.Show();
                return;
            }
            DateTime d;
            canConvert = DateTime.TryParse(date, out d);
            if (!canConvert)
            {
                ErrorExpensesErrorMessage.Text = "Error: Date must be mm/dd/yy format";
                ErrorExpensesErrorMessage.Show();
                return;
            }

            // Add expense to database
        //    string response = ExpensesDatabase.AddExpense(firstName, lastName, description, amt, d);
            string response = ComExpenseIt.AddExpense
                              (firstName, lastName, description, amount, date);
            if (response == "success")
            {
                ErrorExpensesErrorMessage.Hide();
                EnterExpensesDescription.Clear();
                EnterExpensesAmount.Clear();
                EnterExpensesDate.Clear();
            }
            else
            {
                ErrorExpensesErrorMessage.Text = response;
                ErrorExpensesErrorMessage.Show();
            }

        } // end EnterExpensesButton_Click

        private void EnterExpensesDone_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.MENU);
        } // end EnterExpensesDone_Click

        /**********************************************************************
         * DisplayExpenses panel actions
         */
        private void DisplayExpensesDone_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(PanelSelection.MENU);
        } // end DisplayExpensesDone_Click

        /**********************************************************************
         * List panel actions
         */
        private void CancelEmployeeList_Click(object sender, EventArgs e)
        {
            navigation.SelectPanel(navigation.SelectParentPanel());
        } // end CancelEmployeeList_Click

        private void employeeList_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Clear the employee identification that is to be used by the
            // parent panel
            listFirstName = "";
            listLastName = "";
            listDepartment = "";

            // Get currently selected entry
            string curItem = employeeList.SelectedItem.ToString();
            int index = employeeList.FindString(curItem);

            // If the item was not found in employeeList display a message box,
            // otherwise select it in employeeList.
            if (index == -1)
                MessageBox.Show("Error: Item is not available in employee list");
            else
            {
                int i = curItem.IndexOf('\t');
                if (i > 0)
                {
                    listLastName = curItem.Substring(0, i);
                    int l = curItem.Length - i - 1;
                    string restOf = curItem.Substring(i + 1, l);
                    i = restOf.IndexOf('\t');
                    if (i > 0)
                    {
                        listFirstName = restOf.Substring(0, i);
                        l = restOf.Length - i - 1;
                        listDepartment = restOf.Substring(i + 1, l);
                    }
                }

                // Select the parent panel to use the selected employee.
                navigation.SelectPanel(navigation.SelectParentPanel());
            }
        } // end employeeList_SelectedIndexChanged

    } // end class ExpenseItForm
} // end namespace

Navigation and SelectionQueue are unchanged from the previous "C# Displays Follow On for ExpenseIt Showing Use of Panels" post except for changing references to ExpenseItForm from Form1. 

Try1

Try1 is similar to the previous App1, App2, etc classes to install the application specific components.  For this application, there are only the two such components.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Apps
{
    static class Try1 //App1
    {
        // Install the components of Application 1
        static public void Install()
        {
            // Install into the framework each of the components
            // of the application.
            ComDatabase.Install();
            ComExpenseIt.Install();

        } // end Install

    } // end class Try1
} // end namespace

Otherwise, the framework classes of Delivery, Disburse, Format, Library, NamedPipe, Receive, ReceiveInterface, Remote, Threads, Topic (except for the addition of the DATABASE topic), and Transmit are unchanged and can be located in previous posts.