Exploratory
Project with Linux C# MonoDevelop Widget Events
Background:
It's been a while in coming but the light dawned yesterday
and now widget events (e.g., button clicks) are received by the MonoDevelop
WinForms solution that was imported from the Microsoft Visual C# Express
project.
This is the C# code to implement a PC based ARINC-661
Cockpit Display System (CDS) application without the "Cockpit". Call it a PCDS maybe - Personal Computer
Display System. Now it runs
A) under Windows via the GNAT Ada user applications that
communicate via the A661 protocol with the C# display application and
B) under Linux Mint via the same Ada application (with a
Named Pipe interface package replacing the Microsoft MS Pipe interface package
and the use of special packages to interface to Linux replacing Win32Ada). The display application runs via
MonoDevelop. Presumably the MonoDevelop
display application could also be used with Windows.
Initially I used Fedora Linux. The MonoDevelop that I used with Fedora couldn't handle the .NET
4.0 that I was using with C#. But very
little adjustment was necessary to display the WinForm that took the place of
an A661 layer. However, the application
failed to receive widget (Microsoft control) events such as button clicks except
for the form close (X) button.
I then tried MonoDevelop with a Windows download with the
same result.
Next I installed Linux Mint Cinnamon version 14 as mentioned
in the previous blog post (Exploratory Project with Linux C# MonoDevelop). With it I was able to revert back to the
WinForm .NET 4.0 code (only a change to how the queue was inherited that
allowed the display threads to obtain A661 commands read from the named pipe
for the particular layer that ran, of course, in their own receive threads -
this to satisfy Windows problems with needing the same thread to paint the
display form as to get the events).
Again the widget events weren't received by the MonoDevelop
display application.
Trying to figure out what to do I had done a number of internet
searches and had come up with various bits of information. One saying that widgets had to be wrapped
inside a special event container for GTK to receive them.
I had also been playing with GTK and so got sample code
running this way. (See Linux Mint of
the previous post.) However, in
attempting that I ran into the wall that WinForms and GTK shall never mix. So I couldn't put a GTK event container such
as VBox around the widgets of the form.
Finally:
Finally I found a "First steps in Mono Winforms"
post (zetcode.com/gui/csharpwinforms/firststeps/)
that said, yes indeedy, it is possible to get click and other events using a
WinForms MonoDevelop application. I
made myself a test solution using the supplied sample code and got the OnEnter
and OnClick events that it illustrated.
Now I knew there had to be a solution to my problem other
than rework the display application into a Gtk# application.
The Solution:
I first tried various versions of the sample code with the
form of the Exploratory Project display app.
What had worked in the standalone sample MonoDevelop solution with its
MForm class derived from the Windows Form class just didn't seem to work with
my form.
After tearing out a little more of my hair, the light
suddenly dawned, the epiphany arrived.
In the sample the form was just created. That is,
class MForm
: Form {
public
MForm() {
. . .
}
void
OnClick(object sender, EventArgs e) {
Close();
}
void
OnEnter(object sender, EventArgs e) {
Console.WriteLine("Button
Entered");
}
}
public
static void Main() {
Application.Run(new MForm());
}
Whereas in the A661 Exploratory Project case it was created
and then set (temporarily) to disabled.
public
class UserForm : Form
{
. . .
public UserForm(int formIndex)
{
initializeForm(formIndex);
} // end
UserForm constructor
private
void initializeForm(int formIndex)
{
. . .
if
(!Display.widgetsCreated[formIndex])
{
widgetIndex = formIndex;
widgetId = Display.formDesc[formIndex].formId;
layerId = Display.formDesc[formIndex].layerId;
layerIndex = Display.formDesc[formIndex].layerIndex;
buttonCount = 0;
checkBoxCount = 0;
listBoxCount = 0;
labelCount = 0;
textBoxCount = 0;
myControl.count = 1;
myControl.depth = 0;
myControl.controls = new Control.ControlCollection[6];
for
(int i = 0; i < 6; i++)
{
myControl.controls[i] = this.Controls;
}
containerCount = 0;
panelCount = 0;
Display.widgetsCreated[formIndex] = true;
}
this.SuspendLayout();
// Set
widget index and id of form for lookup as another kind of widget.
widgetIndex
= formIndex;
widgetId = Display.formDesc[formIndex].formId;
//
// Form
//
string
formId, formDesc, formParentId, formBorder;
float
scaleWidth, scaleHeight;
int
sizeWidth, sizeHeight;
int
parentFormIndex = -1;
FormDisplay.formScale scale;
FormDisplay.formSize size;
formId
= Display.formDesc[formIndex].formId;
formDesc = Display.formDesc[formIndex].formDesc;
formParentId = Display.formDesc[formIndex].parentId;
formBorder = Display.formDesc[formIndex].formBorder;
scale =
Display.formDesc[formIndex].formDimension;
size =
Display.formDesc[formIndex].formSize;
scaleWidth = scale.width;
scaleHeight = scale.height;
sizeWidth = size.width;
sizeHeight
= size.height;
int x,
y;
x =
Display.formDesc[formIndex].xGet(Display.formDesc[formIndex]);
y =
Display.formDesc[formIndex].yGet(Display.formDesc[formIndex]);
if (x
!= 0 || y != 0)
{
this.Location = new System.Drawing.Point(x, y);
}
else
{
this.Location = new System.Drawing.Point(50, 220);
}
this.AutoScaleDimensions = new System.Drawing.SizeF(scaleWidth,
scaleHeight);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(sizeWidth, sizeHeight);
this.Name = formId;
this.Text = formDesc;
if
(formBorder == null || formBorder.Equals("NONE"))
{
this.FormBorderStyle = FormBorderStyle.None;
}
this.Load += new System.EventHandler(this.Form_Load);
createWidgets(formIndex);
//
determine if top level form or child
parentFormIndex = Display.widgetLoad.getFormIndexForId(formParentId);
if
(parentFormIndex >= 0)
{
this.TopLevel = false; // indicate that a child
//
add child form to parent form
Display.userForm[parentFormIndex].Controls.Add(this);
}
else
{
this.TopLevel = true; // indicate parent form
}
this.ResumeLayout(false);
this.PerformLayout();
this.Enabled = Display.formDesc[formIndex].enabled;
} // end
method initializeForm
Most of the various information used above comes from
parsing the xml of the A661 Definition File that specifies what the layer (and
hence the Form) is to look like. At the
end of the above code, this.Enabled was set to false since Enabled was False in
the Definition File - that is,
<A661_LAYER
DESCRIPTION="GUI1 Layer"
LAYER_IDENT="3002"
NET_PORT="/home/clayton/Source/EP/DisplayLayer"
POS_X="0"
POS_Y="0"
SIZE_X="800"
SIZE_Y="800"
UNIT_NAME="GUI1"
FORM_IDENT="3000"
ENABLE="A661_FALSE"
VISIBLE="A661_TRUE"
SCALE_WIDTH="6"
SCALE_HEIGHT="13"
FORM_BORDER="FixedDialog">
so the form, as initialized in the call from the
constructor, is initially disabled.
(Note: This xml is used to create the form that takes the place of the
layer in the PC implementation.)
The form becomes enabled when the display app (the PC
version of the A661 CDS) receives the A661 Enable Layer command.
I changed the statement at the end of initializeForm to
this.Enabled = true;
to have the form enabled from the start.
With this change, the button clicks were received and hence
were forwarded as Event Messages to the user app (app1) via the named
pipe. The user app then routed them to
the Widget Action (WA) component associated with the widget identifier as
illustrated in the diagram of the last week's post. The Widget Action component then created the command to modify
the display and it was transmitted back to the display app and the display was
modified.
Therefore, MonoDevelop is only allowing widget events from a
form (except for the form close widget in the upper right corner) when the form
is enabled when it is created and isn't recognizing a later enable of the form.
Consequences:
I had set ENABLED in the xml to FALSE for the form so that
all the widgets of the layer (that is, form) would be disabled until the user
application sent the command to enable the layer. (Note: Under A661, to be enabled, a child widget of a container
must be enabled and its parent container must be enabled all the way back to
the top level container which in this case is the form. Therefore, with the form disabled, all the
widgets were disabled.)
Needing to have the form enabled from the beginning for the
widget events to be received meant I had to find a different way to have the
upper level container disabled until the layer is commanded to be enabled.
Volia! I just
happened to have the solution designed in from the start. That is, my Definition File xml has just
such a container as follows.
<A661_LAYER
DESCRIPTION="GUI1 Layer"
LAYER_IDENT="3002"
NET_PORT="/home/clayton/Source/EP/DisplayLayer"
POS_X="0"
POS_Y="0"
SIZE_X="800"
SIZE_Y="800"
UNIT_NAME="GUI1"
FORM_IDENT="3000"
ENABLE="A661_FALSE"
VISIBLE="A661_TRUE"
SCALE_WIDTH="6"
SCALE_HEIGHT="13"
FORM_BORDER="FixedDialog">
<WIDGET
DESCRIPTION="GUI1 Layer"
WIDGET_TYPE="A661_BASIC_CONTAINER"
WIDGET_IDENT="3101"
PARENT_IDENT="3000"
ENABLE="A661_FALSE"
VISIBLE="A661_TRUE"
POS_X="0"
POS_Y="0">
That is, I have had a container as the direct child of the
form that represents the layer. This
container has widget 3000 as its parent which is the widget that has that
identifier. No other widget of the xml
references 3000 as its parent. The
direct children all reference the layer container widget 3101. Just what is needed.
So all it took was to change the ENABLE of this container
widget to FALSE in the Definition File as shown above. This made all the child widgets disabled
(grayed out) when the form is first displayed.
The receive of the Make Layer Enabled command was already implemented to
make this widget enabled along with the form.
So the solution to the lack of receiving widget events was
quite simple once I figured out the reason that the events weren't being
received. Just, oh the hide and seek
game in the meantime.
Now the xml can be changed once more to set ENABLE of widget
3000 (the form) to TRUE and restore the initializeForm statement back to using
the value from the Definition File.
This was done and all is happy. The MonoDevelop now sets the initial form enable as before but
the xml has that enable should true.
What wouldn't work before now works with only these two changes to the
Definition File.