Saturday, March 4, 2017

C# OLEDB Table Number of Rows and Columns


While "translating" an old Visual Basic application to Visual C# there was the need to open particular tables of an Microsoft Access database.  So I had to determine how to open a selected database table.

Then I decided that I wanted to obtain the number of rows and columns in the selected table.  I did numerous internet searches trying to determine how to do this.  And found that getting the number of columns was "deemed" impossible - that there wasn't a way of doing so.

So I was left to find the way on my own. 

The code follows.  This code also includes using an OpenFileDialog with which the user can select the Access .mdb database to be opened.

Each time the database Reader for the EditActions table of the database was Read or Load-ed the reader had to be closed and a new reader used.  Otherwise an error resulted.  That is, while numRows and numCols compiled referencing the DataTable, the ea.Load(reader) statement would result in a run-time error that the reader was closed.  It turned out that this was because the previous reader.Read() loop had exhausted the table contents having read all the rows of the table (even though there wasn't an explicit Close).

If the ea.Load statement was deleted then the numRows  would be 0 due to all the rows having previously been read while numCols would correctly have a value of 7.

Adjusting for these problems, the code first reads through the EditActions table as a result of the query (of queryString) associated with the first reader using the reader.FieldCount statement to obtain the number of columns in the table.

Then that reader is closed so that a new one can be obtained for the DataTable and loaded (i.e., "ea.Load(reader2);").  Using the ea instantiation of the DataTable, the number of rows and columns are obtained with the correct values.

However, I didn't see anyway to use the number of rows.  When I attempted to do a loop over the number of rows I couldn't find a way to read the row so as to loop over the columns.  Therefore, I closed reader2 and created reader3 to be able to do the while reader3.Read() loop once more while using the number of columns obtained via the DataTable instantiation.

I have found that there is a Data Grid View that might be suitable.  It apparently creates a way of looking at the contents of the database table without the necessity of actually reading or loading the table multiple times.  But not the subject of this post since it was about ways to obtain the number of columns in a database table.

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
           // Get an instance of a FileDialog.
           OpenFileDialog openFileDialog = new OpenFileDialog();
           OleDbConnection Config = new OleDbConnection();

           // Use a filter to allow only mdb files.
           openFileDialog.InitialDirectory = "c:\\";
           openFileDialog.Filter = "Edit Actions Access Database (*.mdb)|*.mdb";
           openFileDialog.FilterIndex = 2;
           openFileDialog.RestoreDirectory = true;

           // Show the dialog and get entered database.
           try
           {
              DialogResult result = openFileDialog.ShowDialog();
              if (result == DialogResult.OK) // Test result for success
              {
                if (openFileDialog.FileName != "") // name selected
                {
                    // Open selected database via OLEDB
                    string name = "Provider=Microsoft.Jet.OLEDB.4.0;";
                    name += "Data Source=";
                    name += openFileDialog.FileName;
                    name += ";";
                    OleDbConnection conConfig = new OleDbConnection(name);
                    Config = conConfig;
                    Config.Open();
                }
                else // openFileDialog.FileName = ""
                {
                    MessageBox.Show("Blank.mdb Cannot be Selected");
                    return;
                } // openFileDialog.FileName != ""
           
              }
              else if (result == DialogResult.Cancel)
              { // Cancel button was pressed.
                  return;
              } // (result = DialogResult.OK)

              // Setup a reader of the EditActions table of the Config database
              string queryString = "SELECT * FROM EditActions";
              OleDbCommand command = new OleDbCommand(queryString,Config);
              command.CommandTimeout = 20;
              OleDbDataReader reader = command.ExecuteReader();

              // Read the contents of the EditActions table and allow to be
              // viewed via the debugger.  Shows getting number of columns.
              string valueRead;
              int colCount = reader.FieldCount;
              while (reader.Read())
              {
                  int i = 0;
                  while (i < colCount)
                  {
                      valueRead = reader[i].ToString();
                      i++;
                  }
              }

              // Obtain the EditActions data table of the Config database
              reader.Close();
              OleDbDataReader reader2 = command.ExecuteReader();
              DataTable ea = new DataTable("EditActions");
              ea.Load(reader2);

              // Obtain the number of rows and columns in the selected table
              // where can examine via the debugger
              DataRowCollection rows = ea.Rows;
              DataColumnCollection columns = ea.Columns;
              int numRows = rows.Count;
              int numCols = columns.Count;
              reader2.Close();

              // Read the database table and allow the data to be viewed
              // via the debugger
              OleDbDataReader reader3 = command.ExecuteReader();
              while (reader3.Read())
              {
                  int i = 0;
                  while (i < numCols)
                  {
                      valueRead = reader3[i].ToString();
                      i++;
                  }
              }

           } // try
           finally
           {
               Config.Close();
           }
        } // openToolStripMenuItem_Click


The DialogResult result = openFileDialog.ShowDialog(); statement results in the following Form1 window where the result of selecting the Open option and navigating to the location of the database is also shown.  The result of clicking on the Form1 File control is shown immediately below.



Wednesday, March 1, 2017

C# Example Using Access Database

C# Example Using Access Database

This exploratory project shows two methods of accessing a Microsoft Access database.  One is from a newer example from Microsoft, as modified, and one converted from an old, old Visual Basic example.

They each use a version of the canned Microsoft Northwind database.

The much older database is Nwind.mdb using the OleDb class while the second is the Northwnd.mdf database using different access methods.  (Notice that the 'i' was left out in Microsoft's naming of this database.)

Both databases are accessed from the Microsoft Visual C# 2010 Express that I got from Microsoft years ago and use whatever a recent version of Microsoft Visual Studio that might be necessary (if anything) to interface with the Windows 7 of my PC.

In each case the SQL query is a simple one to read the Customers table of the particular version of the database to return those with an address in London.  The old Visual Basic code that had a fancier query although I don't know as I ever used it since the project got reworked to update a different non-Northwind database to maintain a database of ARINC-661 widgets for a research project for a demo for an Airbus aircraft.

Besides forming the OleDb query after all this time another problem arose.  I initially bypassed the OleDb query and copied the code from the Northwnd.mdf example from an internet search.  It had been for a console application whereas I did a Windows Forms Application.  So the example put the query results to a Console log but I wanted to see them in the Windows form widget of my project.  So I would get the next query result and copy the text to a textbox widget and when nothing appeared, to a label widget.

In neither case did anything appear in the textbox or the label although with the first output to the label's text field the original text would be shortened to fewer characters.  This was a puzzlement since I had had no trouble with other C# applications displaying values to such widgets/controls.  (Note: Widgets is A661 terminology whereas Microsoft refers to a control.)

So to get the value displayed I added a MessageBox where I was going to display the query result – that is, Customer ID and customer City.  But I found all I had to do was request the MessageBox to display some text and the query results appeared in the labels and text boxes.  So again a head scratcher. 

I later found out that to get the screen (i.e., the form) updated a second thread was needed (which the activation of the MessageBox was supplying) or else the use of a Refresh statement for the control – in this case the form.  So I took out the use of the MessageBox and used Refresh instead.  Although the MessageBox had been helpful since the user got to see the query result displayed for each customer that matched the requested city. 

It then occurred to me that the reason I hadn't had such a problem before was because I was displaying data or changing the color of buttons and the like from inside a different event handler.  The event handler, of course, runs in a Windows thread transferring control to the handler when a control is clicked or characters entered into a text box.  In the current application, everything is being done within the same event handler so the form wasn't being redisplayed since the event handler isn't being exited.  Therefore, no other event can be treated.

Without the MessageBox, the query loop runs to completion so only the last result can be seen unless the loop is stepped thru via the debugger.  (In the Microsoft Northwnd example, all the results were to be seen in the Console log that it produced.)  Therefore, I've added a one second delay to allow the user to briefly see the query results as the query proceeds.

This, of course, is only useful for the example database with a small number of matches to be found and displayed.  For a more normal application, a report would be produced and if visual results were wanted, a special control could be used to allow the initial results to be inspected and then continue and produce the report.  Perhaps a text box in which to enter the number of results to display before dispensing with the delay.  Perhaps, two such boxes with one for the delay period desired.  Both to default to just produce the report.

Another problem that occurred was that the Microsoft instructions for using the Northwnd database had the inclusion of the System.Data.Linq.Mapping class.  However, when filling this into my new C# project, the auto selection resulted in my using System.Linq and then attempting to add System.Linq.Mapping.  This stymied me for quite some time attempting to get this unit added to my project when it didn't exist.  Finally the light dawned and I used the correct class.

Below is the form that I used showing the final query result.  It still shows the effects of trying to get the query results to show since I added the label controls to the left when the text boxes weren't showing the results.  The Start button is just to delay until ready.  The searches and the display of the results are done within the event handler of the Start button.


The C# code is as follows without showing the Initialize code within the Form1.Designer.cs file.

Program.cs
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    [Table(Name = "Customers")]
    public class Customer
    {
      /*In this step, you accomplish several tasks.
        · You use the ColumnAttribute attribute to designate CustomerID
          and City properties on the entity class as representing columns
          in the database table.
        · You designate the CustomerID property as representing a primary key
          column in the database.
        · You designate _CustomerID and _City fields for private storage.
          LINQ to SQL can then store and retrieve values directly, instead
          of using public accessors that might include business logic.
        To represent characteristics of two database columns */
        private string _CustomerID;
        [Column(IsPrimaryKey = true, Storage = "_CustomerID")]
        public string CustomerID
        {
            get
            {
                return this._CustomerID;
            }
            set
            {
                this._CustomerID = value;
            }

        }

        private string _City;
        [Column(Storage = "_City")]
        public string City
        {
            get
            {
                return this._City;
            }
            set
            {
                this._City = value;
            }
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e)
        { // Start button

            /* This initial code queries the Nwind.mdb database of Microsoft
             * using OleDb methodology.
             */
            string con = "Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\\Source\\VB\\Nwind.mdb";
            OleDbConnection connection = new OleDbConnection(con);
            string queryString = "SELECT CustomerID, City FROM Customers";
            queryString += " WHERE City = 'London'";
            OleDbCommand command = new OleDbCommand(queryString,connection);
            command.CommandTimeout = 20;

            connection.Open();
            OleDbDataReader reader = command.ExecuteReader();

            string valueRead;
            while (reader.Read())
            {
                valueRead = reader[0].ToString();
                this.label3.Text = valueRead;
                valueRead = reader[1].ToString();
                this.label4.Text = valueRead;
                this.Refresh();
                System.Threading.Thread.Sleep(1000);
            }
//          reader.Close();

            /* This code queries the northwnd.mdf database of Microsoft.
             * A portion of code is in the Customer class in Program.cs.
             */

            // Use a connection string.
            DataContext db = new DataContext
                (@"c:\linqtest5\northwnd.mdf");

            // Get a typed table to run queries - the Customer class.
            Table<Customer> Customers = db.GetTable<Customer>();

            // Attach the log to show generated SQL.
            db.Log = Console.Out;

            // Query for customers in London.
            IQueryable<Customer> custQuery =
                from cust in Customers
                where cust.City == "London"
                select cust;

            foreach (Customer cust in custQuery)
            {
                string val1 = cust.CustomerID;
                this.textBox1.Text = val1;
                this.label3.Text = cust.CustomerID;
                val1 = cust.City;
                this.textBox2.Text = val1;
                this.label4.Text = cust.City;
                this.Refresh();
                System.Threading.Thread.Sleep(1000);
            }

        }

    }

}


Note: The query results could have been directly copied to the label and text box controls.  The use of the extra string values was when I was trying nonsense trying to get the values to display. 

Tuesday, February 21, 2017

Brief Comparison of Four Graphic Compilers

Brief Comparison of Four Graphic Compilers

In the last couple of months I've used four different compilers to create similar applications to interface with Windows to display and interact with forms and their widgets.

I first tried GtkAda.  I discovered that it was difficult to use and also seemed to be need work.  For instance, when I tried a text box it could be displayed but the user (that is, me) couldn't enter anything into it.

I next tried to use the Linux version of visual C# (Mono) using the Window operating system.  This requires the use of CygWin.  I attempted to use instructions from approximately seven years ago that I had saved and then newer ones that I found online.  With none of them could I completely finish the installation to enable me to use Mono to interface to Windows.  I was able to install the Mono that runs under Windows but runs as a DOS-like application in a Linux-like way.  But I couldn't get to the point where I could get Mono that runs as its own Windows application where I could determine if it could create Windows applications.  A number of years ago I did use Mono running under Linux where I could create visual applications.  But as much as I tried to follow the various online instructions of how to do an install for Windows, I wasn't able to succeed.

After giving up on that I went back to the Microsoft Visual Express compilers that I used years ago - Microsoft Visual C++ 2010 Express and Microsoft Visual C# 2010 Express.  My trial usage ran out years ago but I found I could still use them if I first launched the compiler and selected the application rather than trying to launch a particular previously created application.  (Note: Obtaining an updated compiler now costs thousands of dollars which is out-of-range for my recreational use.)

Since I had previously used C# for my exploratory projects, after discovering that I could still use the compilers by launching them, I tried creating Visual C++ application similar to the GtkAda application to see the difference.  That is, an application that displays a few buttons including those inside of a container and a text box and can interact with mouse clicks on these widgets.

The old Microsoft Visual C++ 2010 Express was much, much easier to use than GtkAda and, in addition, the text box could actually be used.  Although I couldn't find a way to know when the user was finished entering into the text box.  Online searches showed that others had the same problem and had devised ways such as using a timer to anticipate that the user was finished with their entry.

I added a Done button instead.   Then, read the entered text via the Done button event handler.  This resulted in finding out that the format of the text was not a C/C++ string but something else entirely that required a conversion to obtain a C/C++ string.

In looking into this I found comments such as "Microsoft's advise would seem to be if you want to do Windows Desktop UI code (with the .Net framework), use C# (or VB.NET)." rather than Visual C++ and, in response to a question, "Looks to me like you're using C++/CLI which is NOT C++. It is a completely different language created by Microsoft to supersede Managed Extensions for C++. (.NET)."

Therefore, I repeated the application in Visual C#.  The general code to create the widgets and the event handlers was much the same.  (Except for using dot notation in C# versus the '->' notation of the Visual C++.)  In each case, the code to initialize the widgets that were placed into the displayed form was created automatically.  Just in it own .cs file in C# versus being in the same file as the event handlers as in Visual C++.

In neither case was a Key Up or Down event handler added automatically when double clicking on the displayed widget.  Versus a handler for when the contents were changed which was.

But I was able to add a Key Down event handler to the C# application and manually add it to the initialization of the text box.  And without problem have it check if the key was the Enter (i.e., Return) key to indicate that the user was finished entering text.  Therefore, although I didn't remove the Done button from the form, it was no longer needed.

Also, the TextChanged event handler that was added by double clicking the text box wasn't needed for this application so it could have been eliminated.  Although it would be needed if trying to restrict the input of certain characters in some way – such as only to digits.

And when getting the entered text (upon detecting the use of the Enter key), a normal C string could be directly used.  Therefore, C# was much more C-like than Visual C++.

In conclusion, Visual C# was easier to use than Visual C++ and the entire application took less than a day.  So much different from GtkAda.  If memory serves at all, Mono under Linux was similar.

To complete this report, the code and the form (as first displayed before buttons or labels were hidden due to mouse clicks on other buttons or the entry of any text) are shown below.  Only the code inside an event handler had to be written along with the line to initialize the use of the KeyDown event handler for the text box.  The rest was generated by the compiler.

Note, button4_Click is the event handler for the Done button.  "Done" is the text displayed on the button via the button's properties.  The name of the button can be changed but I noticed, when I did so for Visual C++, the previously generated code wasn't always automatically updated.  So rather than needing to update the names my hand I didn't bother with the C# application since proper naming wasn't my concern in creating the application.  Thus I don't know if Visual C# would have tracked the property changes better.  And, of course, the compiler is many years out of date and a newer one would need to be used for an actual application.

Note:  In the display of the form, the container panel cannot be seen against the background of the form itself.  However, button3 is a child of the container.  If button1 is clicked button3 is hidden.  As can be seen from the code, the handler for button1 hides the panel as well as label1.  And hiding the panel of which button3 is a child, hides the button as well.


Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.Designer.cs with Initialize
namespace WindowsFormsApplication1
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            this.panel1 = new System.Windows.Forms.Panel();
            this.button3 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.button4 = new System.Windows.Forms.Button();
            this.panel1.SuspendLayout();
            this.SuspendLayout();
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(447, 128);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            //
            // button2
            //
            this.button2.Location = new System.Drawing.Point(484, 259);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(75, 23);
            this.button2.TabIndex = 1;
            this.button2.Text = "button2";
            this.button2.UseVisualStyleBackColor = true;
            //
            // label1
            //
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(434, 223);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(46, 17);
            this.label1.TabIndex = 2;
            this.label1.Text = "label1";
            this.label1.Click += new System.EventHandler(this.label1_Click);
            //
            // panel1
            //
            this.panel1.Controls.Add(this.button3);
            this.panel1.Location = new System.Drawing.Point(114, 111);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(261, 190);
            this.panel1.TabIndex = 3;
            //
            // button3
            //
            this.button3.Location = new System.Drawing.Point(143, 81);
            this.button3.Name = "button3";
            this.button3.Size = new System.Drawing.Size(75, 23);
            this.button3.TabIndex = 0;
            this.button3.Text = "button3";
            this.button3.UseVisualStyleBackColor = true;
            this.button3.Click += new System.EventHandler(this.button3_Click);
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(423, 377);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(100, 22);
            this.textBox1.TabIndex = 4;
            this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged);
            this.textBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBox1_KeyDown);
            //
            // button4
            //
            this.button4.Location = new System.Drawing.Point(538, 376);
            this.button4.Name = "button4";
            this.button4.Size = new System.Drawing.Size(52, 23);
            this.button4.TabIndex = 5;
            this.button4.Text = "Done";
            this.button4.UseVisualStyleBackColor = true;
            this.button4.Click += new System.EventHandler(this.button4_Click);
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(631, 462);
            this.Controls.Add(this.button4);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.panel1);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.panel1.ResumeLayout(false);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.Button button3;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button button4;
    }
}

Form1.cs
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 WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (this.label1.Visible == true)
            {
                this.label1.Visible = false;
                this.panel1.Visible = false;
            }
            else
            {
                this.label1.Visible = true;
                this.panel1.Visible = true;
            }

        }

        private void label1_Click(object sender, EventArgs e)
        {

        }

        private void button3_Click(object sender, EventArgs e)
        {

        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            if (this.button2.Visible == true)
            {
                this.button2.Visible = false;
            }
            else
            {
                this.button2.Visible = true;
            }
        }

        private void textBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
      if (this.label1.Visible == true)
{
   this.label1.Visible = false;
}
else
{
   this.label1.Visible = true;
}
if (e.KeyCode == Keys.Return)
{
   this.button1.Visible = false;
                 string standardString = textBox1.Text;
             }
}

        private void button4_Click(object sender, EventArgs e)
        {
             if (this.label1.Visible == true)
{
   this.label1.Visible = false;
}
else
{
   this.label1.Visible = true;
}
             string standardString = textBox1.Text;
        }    
    }
}


Sunday, February 5, 2017

GtkAda Button Clicks and Containers

GtkAda Button Clicks and Containers

Since I reported that I was beginning to understand GtkAda I've found that I had more to learn.

I've gone further to display a group of buttons at different positions within the display window and to field button clicks to hide and show a button as well as to display text on the button in different fonts, sizes, colors and font styles.

The examples to hide a button all seemed to be to hide the button that was clicked.  So it took a little bit to figure out how to reference a different button instead.  (How can you click on a hidden button to redisplay it?)  And even more time to find out how to select the font, font size, etc.  The examples just didn't seem to compile.  Maybe I've developed old age disease but it took me a while to come up with what would compile.  (The GtkAda code makes use of some esoteric Ada constructs.)  But when I got the solution it became easy.

I then wanted to put the button widgets into a parent container widget since this is standard practice in aircraft ARINC 661 standard.  Thus being able to hide the parent widget to hide all its child widgets.  However, looking at the GtkAda types of containers (vertical boxes, horizontal boxes, grids) they all wanted to place the widget themselves whereas A661 has widgets placed within their container by their x, y position from the container boundary.

So I wrote my own container widget generic package so as to be able to hide or show the container and have its widgets hidden or visible.  After my attempts to use GtkAda this was a snap.  Although it doesn't yet place the widgets relative to the containers position or even specify a position for the container.  So the widgets are still placed relative to the window.

This resulted in the following set of views.

First I have a view with only one button (the one not in the container) visible.  To do this I had to first do a Show All on the window and all its contents and then do a hide of the container.  This doesn't flash the buttons since GtkAda doesn't act on the Show All until the signal handling loop is activated.  View 1 shows the result.

Next I clicked on the Sixth button which is tied to an event handler that toggles the display of the container from hidden to displayed or vice versa.  See View 2 where buttons First thru Fifth are also displayed in their different sizes, fonts and positions.

Next I clicked the First button which is tied to an event handler that toggles the display of the second button.  The results are shown in View 3 where the Second button is no longer displayed.

To illustrate how the Second button will remain hidden when the container is hidden and then redisplayed, the Sixth button was again clicked resulting in View 5.  Then the First button was clicked to redisplay the Second button as in the final view.


View 1

View 2


View 3


View 4



View 5


View 6

For those interested, the applicable code follows.
with Gtk;
with Gtk.Box;
with Gtk.Button;
with Gtk.Button_Box;
with Gtk.Enums;
with Gtk.Handlers;
with Gtk.Fixed;
with Gtk.Font_Button;
with Gtk.Frame;
with Gtk.Hbutton_Box;
with Gtk.Widget;
with Gtk.Window;

package body Layer1 is

  Win
  -- Main Window of layer 1
  : Gtk.Window.Gtk_Window;

  Frame   : Gtk.Frame.Gtk_Frame;
  -- where Gtk_Frame is access all Gtk_Frame_Record'Class;

  Fix     : Gtk.Fixed.Gtk_Fixed;

  FButtonFirst  : Gtk.Button.Gtk_Button;
  FButtonSecond : Gtk.Button.Gtk_Button;
  FButtonThird  : Gtk.Button.Gtk_Button;
  FButtonFourth : Gtk.Button.Gtk_Button;
  FButtonFifth  : Gtk.Button.Gtk_Button;
  FButtonSixth  : Gtk.Button.Gtk_Button;


  function Delete_Event
  -- Treat Delete button of upper right of Window
  ( Object : access Gtk.Widget.Gtk_Widget_Record'Class
  ) return Boolean;
  -- Callback when the main window is killed

  package Container
  --| Instantiate Container for buttons First thru Fifth
  is new Gtk_Container( Size => 10 );

  -----------------------------------------------------------------------------

  package Events is
  -- Treat widget events

    procedure Treat_First_Click_Event
    ( Object : access Gtk.Widget.Gtk_Widget_Record'Class );
    -- Callback when the FButtonFirst button is clicked

    procedure Treat_Second_Click_Event
    ( Object : access Gtk.Widget.Gtk_Widget_Record'Class );
    -- Callback when the FButtonSecond button is clicked

    procedure Treat_Third_Click_Event
    ( Object : access Gtk.Widget.Gtk_Widget_Record'Class );
    --  Callback when the FButtonThird button is clicked

    procedure Treat_Fourth_Click_Event
    ( Object : access Gtk.Widget.Gtk_Widget_Record'Class );
    --  Callback when the FButtonFourth button is clicked

    procedure Treat_Fifth_Click_Event
    ( Object : access Gtk.Widget.Gtk_Widget_Record'Class );
    --  Callback when the FButtonFifth button is clicked

    procedure Treat_Sixth_Click_Event
    ( Object : access Gtk.Widget.Gtk_Widget_Record'Class );
    --  Callback when the FButtonSixth button is clicked

  end Events;

  -----------------------------------------------------------------------------
  -- Window events

  function Delete_Event
  ( Object : access Gtk.Widget.Gtk_Widget_Record'Class) return Boolean
  is
    pragma Unreferenced (Object);
  begin
    return True; -- to avoid killing the Layer1 window when
                 –- the X button is clicked
  end Delete_Event;


  package body Events is separate;

end Layer1;


with Glib;
with Gtk.Font_Chooser;
with Gtk.Font_Chooser_Widget;
with Gtk.Handlers;
with Gtk.Label;
with Pango.Font;

separate( A661.Layer1 )

procedure Initialize is

begin -- Initialize

  -- Install the configuration.
  Configuration.Initialize;

  -- Build the main window.
  Configuration.Build_Window( Window => 100 );

  -- Initializes GtkAda
  Gtk.Main.Init;

  -- Create the basic window
  Gtk.Window.Gtk_New
  ( Window   => Win,
    The_Type => Gtk.Enums.Window_Toplevel );

  -- Position the window in the center of the screen   
  Gtk.Window.Set_Position( Win, Gtk.Enums.Win_Pos_Center );

  -- Set the window size
  Gtk.Window.Set_Default_Size( Win, 800, 500 );

  --  Set a window title
  Gtk.Window.Set_Title
  ( Window => Win,
    Title  => "Layer 1" );

  -- Connect the Delete_Event procedure to the Window Close button
  Return_Window_CB.Connect
  ( Win,
    "delete_event", -- Close button event
    Return_Window_CB.To_Marshaller( Delete_Event'Access) );
 
  --------------------------
  
  -- create a frame
  Gtk.Frame.Gtk_New( Frame );
  Frame.Set_Shadow_Type( Gtk.Enums.Shadow_In );
  Win.Add( Frame );

  Gtk.Fixed.Gtk_New( Fix );
  Gtk.Frame.Add( Frame, Fix );

  Gtk.Button.Gtk_New( FButtonFirst, "First" );
  Gtk.Fixed.Put( Fix, FButtonFirst, 10, 10 );
  Gtk.Label.Set_Markup
  ( Gtk.Label.GTk_Label(Gtk.Button.Get_Child(FButtonFirst)),
    "<span font=""Calibri"" style=""oblique"" weight=""100"" color=""green"" size=""small"">First</span>");  
  Window_CB.Object_Connect
  ( FButtonFirst, "clicked",
    Window_CB.To_Marshaller(Events.Treat_First_Click_Event'Access), Win );
  declare
    Added     : Boolean;
    ButtonRec : Container.Widget_Record;
  begin
    ButtonRec.Widget  := FButtonFirst;
    ButtonRec.Id      := 1001;
    ButtonRec.Visible := True;
    Added := Container.Add( Widget => ButtonRec );
  end;

  Gtk.Button.Gtk_New( FButtonSecond, "Second" );
  Gtk.Fixed.Put( Fix, FButtonSecond, 20, 80 );
  Gtk.Label.Set_Markup
  ( Gtk.Label.GTk_Label(Gtk.Button.Get_Child(FButtonSecond)),
    "<span font=""Arial"" style=""italic"" weight=""900"" color=""blue"" size=""xx-large"">Second</span>"); 
  Window_CB.Object_Connect
  ( FButtonSecond, "clicked",
    Window_CB.To_Marshaller(Events.Treat_Second_Click_Event'Access), Win );
  declare
    Added     : Boolean;
    ButtonRec : Container.Widget_Record;
  begin
    ButtonRec.Widget  := FButtonSecond;
    ButtonRec.Id      := 1002;
    ButtonRec.Visible := True;
    Added := Container.Add( Widget => ButtonRec );
  end;

  Gtk.Button.Gtk_New( FButtonThird, "Third" );
  Gtk.Fixed.Put( Fix, FButtonThird, 80, 30 );
  Gtk.Label.Set_Markup
  ( Gtk.Label.Gtk_Label(Gtk.Button.Get_Child(FButtonThird)),
   "<span font=""Sans"" style=""normal"" weight=""800"" color=""Orange"" size=""medium"">Third</span>"); 
  Window_CB.Object_Connect
  ( FButtonThird, "clicked",
    Window_CB.To_Marshaller(Events.Treat_Third_Click_Event'Access), Win );
  declare
    Added     : Boolean;
    ButtonRec : Container.Widget_Record; --My_Button_Record;
  begin
    ButtonRec.Widget  := FButtonThird;
    ButtonRec.Id      := 1003;
    ButtonRec.Visible := True;
    Added := Container.Add( Widget => ButtonRec );
  end;

  Gtk.Button.Gtk_New( FButtonFourth, "Fourth" );
  Gtk.Button.Set_Size_Request( FButtonFourth, 40, 50 );
  Gtk.Fixed.Put( Fix, FButtonFourth, 100, 150 );
  Gtk.Label.Set_Markup
  ( Gtk.Label.GTk_Label(Gtk.Button.Get_Child(FButtonFourth)),
    "<span font=""Book Antiqua"" style=""normal"" weight=""300"" color=""Red"" size=""xx-small"">Fourth</span>"); 
  Window_CB.Object_Connect
  ( FButtonFourth, "clicked",
    Window_CB.To_Marshaller(Events.Treat_Fourth_Click_Event'Access), Win );
  declare
    Added     : Boolean;
    ButtonRec : Container.Widget_Record;
  begin
    ButtonRec.Widget  := FButtonFourth;
    ButtonRec.Id      := 1004;
    ButtonRec.Visible := True;
    Added := Container.Add( Widget => ButtonRec );
  end;

  Gtk.Button.Gtk_New( FButtonFifth, "Fifth" );
  Gtk.Button.Set_Size_Request( FButtonFifth, 150, 50 );
  Gtk.Fixed.Put( Fix, FButtonFifth, 10, 280 );
  Gtk.Label.Set_Markup
  ( Gtk.Label.GTk_Label(Gtk.Button.Get_Child(FButtonFifth)),
    "<span font=""Courier New"" style=""normal"" weight=""900"" color=""Violet"" size=""medium"">Fifth</span>"); 
  Window_CB.Object_Connect
  ( FButtonFifth, "clicked",
    Window_CB.To_Marshaller(Events.Treat_Fifth_Click_Event'Access), Win );
  declare
    Added     : Boolean;
    ButtonRec : Container.Widget_Record; --My_Button_Record;
  begin
    ButtonRec.Widget  := FButtonFifth;
    ButtonRec.Id      := 1005;
    ButtonRec.Visible := True;
    Added := Container.Add( Widget => ButtonRec );
  end;

  Gtk.Button.Gtk_New( FButtonSixth, "Sixth" );
  Gtk.Button.Set_Size_Request( FButtonSixth, 150, 50 );
  Gtk.Fixed.Put( Fix, FButtonSixth, 200, 280 );
  Gtk.Label.Set_Markup
  ( Gtk.Label.GTk_Label(Gtk.Button.Get_Child(FButtonSixth)),
    "<span font=""Tahoma"" style=""normal"" weight=""900"" color=""Purple"" size=""large"">Sixth</span>"); 
  Window_CB.Object_Connect
  ( FButtonSixth, "clicked",
    Window_CB.To_Marshaller(Events.Treat_Sixth_Click_Event'Access), Win );
  -- Note: Not part of the Container since it must stay visible for alternate
  --       clicks to show the container again.

–-> code from Parse that displays the window when App2
–-  sends the command to do so
        -- Show the window.
        Gtk.Window.Show_All( Win );

        -- Hide the container and its widgets to start
        Container.Hide;

        -- Activate the signal handling loop to allow use of click events
        Gtk.Main.Main;

end Initialize;

package body Events is

  -----------------------------------------------------------------------------
  -- Button click event handlers

  procedure Treat_First_Click_Event
  ( Object : access Gtk.Widget.Gtk_Widget_Record'Class
  ) is
    --  Callback when the FButtonFirst button is clicked
    pragma Unreferenced( Object );
  begin -- Treat_First_Click_Event

    if Container.Is_Visible( Widget => FButtonSecond ) then
      Container.Hide( Widget => FButtonSecond );
    else
      Container.Show( Widget => FButtonSecond );
    end if;
  end Treat_First_Click_Event;

  procedure Treat_Sixth_Click_Event
  ( Object : access Gtk.Widget.Gtk_Widget_Record'Class
  ) is
    --  Callback when the FButtonSixth button is clicked
    pragma Unreferenced( Object );
  begin -- Treat_Sixth_Click_Event
    Console.Write( "Button Sixth clicked" );
    if Container.Is_Visible then
      Container.Hide;
    else -- supposed to be visible, refresh it
      Container.Show;
    end if;
  end Treat_Sixth_Click_Event;

end Events;


with Gtk.Button; -- change to Gtk.Widget

generic

-- Overview:
--   Generic package to group widgets as their parent.
--
--   Widgets can be added to the container and then the widgets
--   can be hidden or made visible as a group by invoking the
--   Hide or Show procedure of the container.
--
--   The container is different from a GtkAda container package
--   since there is no restriction as to how the widgets can be
--   placed in relation to each other.

-- Notes:
--   Parameter to supply when instantiate an instance of a topic.

  Size
  -- Maximum number of widgets allowed to be added to the container.
  : Integer;

package Gtk_Container is
-- Treat a group/array of widgets as being in a container as far as hiding
-- or making visible.

  type Widget_Id_Type
  --| Define 16-bit widget identifier
  is new Integer range 0..32767;
  for Widget_Id_Type'size use 16; -- bits
  -- Note: Needs its own declaration since circularity results if use A661.Types

  type Widget_Record is new Gtk.Button.Gtk_Button_Record with record
    Widget  : Gtk.Button.Gtk_Button;
--<<< will need to generalize to a Gtk Widget >>>
    Id      : Widget_Id_Type;
    Visible : Boolean;
  end record;
  type Widget is access all Widget_Record'Class;

  function Add
  ( Widget : in Widget_Record
    --will want a general type (from Configuration) rather than just for buttons
  ) return Boolean;
  -- Add a widget to the container.
  -- Return True if the Add was successful

  procedure Hide;
  -- Hide all the widgets of the container

  procedure Hide
  ( Widget : in Gtk.Button.Gtk_Button
    -- Widget being hidden
  );
  -- Hide the named widget of the container

  procedure Show;
  -- Show all the widgets of the container

  procedure Show
  ( Widget : in Gtk.Button.Gtk_Button
    -- Widget being shown
  );
  -- Show the named widget of the container

  function Is_Visible
  return Boolean;
  -- Returns whether the container is set to be visible

  function Is_Visible
  ( Widget : in Gtk.Button.Gtk_Button
    -- Widget being checked
  ) return Boolean;
  -- Returns whether the widget is set to be visible

end Gtk_Container;


with Gtk.Widget;

package body Gtk_Container is

  type Widget_Count_Type
  is new Integer range 0..Size; --Max;

  type Widget_Array_Type
  is array (1..Widget_Count_Type'last) of Widget_Record;

  type Widget_List_Type
  is record
    Count   : Widget_Count_Type;
    Visible : Boolean;
    List    : Widget_Array_Type;
  end record;

  type Container_Type
  is record
    Visible : Boolean;
    -- True if container has been requested to be visible
    Data    : Widget_List_Type;
    -- Data concerning the widgets of the container
  end record;

  Container
  -- Container attributes including its widgets
  : Container_Type;

  -----------------------------------------------------------------------------

  function Add
  ( Widget : in Widget_Record
  --will want a general type
  ) return Boolean is

  -- Note: The initialized Visible condition of the Widget is contained in
  --       the record.
  begin -- Add

    if Container.Data.Count < Widget_Count_Type'last then
      Container.Data.Count := Container.Data.Count + 1;
      Container.Data.List(Container.Data.Count) := Widget;
      return True;
    else
      return False; -- widget not added
    end if;

  end Add;

  procedure Hide is
  -- Hide all the widgets of the container but leave the status of the
  -- individual widgets unchanged so when the container is made visible
  -- the widgets that were hidden prior to hiding the container will
  -- remain hidden.
  begin -- Hide
    for I in 1..Container.Data.Count loop
      Gtk.Widget.Hide( Gtk.Widget.Gtk_Widget(Container.Data.List(I).Widget) );
    end loop;
    Container.Visible := False;
  end Hide;

  procedure Hide
  ( Widget : in Gtk.Button.Gtk_Button
  ) is
  -- Hide the Widget
    use type Gtk.Button.Gtk_Button;
  begin -- Hide
    for I in 1..Container.Data.Count loop
      if Widget = Container.Data.List(I).Widget then
        if Container.Data.List(I).Visible then
          Gtk.Widget.Hide( Gtk.Widget.Gtk_Widget(Widget) );
          Container.Data.List(I).Visible := False;
          exit; -- loop
      --else already hidden
        end if;
      end if;
    end loop;
  end Hide;

  procedure Show is
  -- Show all the widgets of the container that weren't hidden before the
  -- Hide of the container was done
  begin -- Show
    if not Container.Visible then
      for I in 1..Container.Data.Count loop
        if Container.Data.List(I).Visible then -- reshow the widget
          Gtk.Widget.Show( Gtk.Widget.Gtk_Widget(Container.Data.List(I).Widget) );
      --else keep hidden
        end if;
      end loop;
    end if;
    Container.Visible := True;
  end Show;

  procedure Show
  ( Widget : in Gtk.Button.Gtk_Button
  ) is
  -- Show the Widget.  Refresh it as supposed to be visible already.
    use type Gtk.Button.Gtk_Button;
  begin -- Show
    for I in 1..Container.Data.Count loop
      if Widget = Container.Data.List(I).Widget then
        Gtk.Widget.Show( Gtk.Widget.Gtk_Widget(Widget) );
        Container.Data.List(I).Visible := True;
        exit; -- loop
      end if;
    end loop;
  end Show;

  function Is_Visible
  return Boolean is
  -- Returns whether the container is set to be visible
  begin -- Is_Visible
    return Container.Visible;
  end Is_Visible;

  function Is_Visible
  ( Widget : in Gtk.Button.Gtk_Button
    -- Widget being checked
  ) return Boolean is
  -- Returns whether the widget is set to be visible
    use type Gtk.Button.Gtk_Button;
  begin -- Is_Visible
    for I in 1..Container.Data.Count loop
      if Widget = Container.Data.List(I).Widget then
        return Container.Data.List(I).Visible;
      end if;
    end loop;
    return False; -- widget isn't in container
  end Is_Visible;

begin -- at instantiation

  Container.Visible := False; -- assume container hidden
  Container.Data.Count := 0;


end Gtk_Container;