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.

No comments: