This article is designed to give you a simple understanding of how to
marshaling operations between child forms in an MDI application.
From the outset I would like to say that this is my preferred way, it may
not be the 'correct' way for some technical reason which I am not aware of
but I know that it works efficiently enough to use in my applications.
Whilst in most cases MDI's generally deal with the same document type but
here I am allowing for forms of different document types or forms.
In my FormsFrameWork I have created a class of forms; this class has an
Object array which will store a reference to the forms created by the MDI
parent.
See forms.cs.
Object[]
formObjects = new Object[0];
This
creates an Object array of zero length, we do not need a size when first
instantiated.
To make the formObjects available we declare a getter and setter for the
form objects.
// allow access to the forms
public Object[]
Forms
{
get{return(formObjects);}
set{formObjects
= value;}
}
So that we
can use the getter and setter we need to be able to add forms to the array
//add a new form to the object array
public int AddForm(Form f)
{
Array.Resize(ref formObjects,
formObjects.Length +1);
f.Tag =
formObjects.Length-1;
formObjects[formObjects.Length-1] = (Object)f;
return(formObjects.Length-1);
}
Notice I did
not specify the actual type of form to add, only that it is a Form and I am
storing a reference to that form, it can be any type of form you design.
In order to add the form to the array, we need to re dimension the array,
Array.Resize(ref formObjects,
formObjects.Length +1);
Before
going any further set the Tag property of the form to the index of the array
element
f.Tag = formObjects.Length-1;
Now we can add the form to the object array casting f as an Object so that
no exceptions are thrown
formObjects[formObjects.Length-1] = (Object)f;
The last thing to do is return the index into the array, this is needed
elsewhere.
Other things we need to do is to be able to do the following
Set the parent,
Set the Caption,
Show the Form,
Close a form by its index
Remove forms from the object array.
In every case we do not need to know what type of form it is only that it is
a form and to do that we need to cast the object back to a form, needless to
say that without casting to it's original form type we cannot get public
members or methods that are exclusive to the particular form type to call so
you can only call those methods that are of the base type.
We can call Close, Show and set Captions etc.
In order to remove a form from the object array we need to be able to re
designate the index into the array of the forms in the array so that you do
not get an exception trying to index a form that does not exist, this is
done using the following function which is called by the MDI parent AFTER
the FormClosed event is fired.
//-------------------------------------------------------------------------------------
// remove form from forms array
//-------------------------------------------------------------------------------------
public void RemoveForm(int frm_index)
{
//
temporary form objects (tfo), visible only in this function
Object[] tfo= new Object[0];
//rebuild
array leaving out form at frm_index
for(int i
= 0; i < formObjects.Length; i++)
{
if(i
!= frm_index ) //if
its not the form index
{
Form f
= (Form)formObjects[i];
Array.Resize(ref tfo,
tfo.Length +1); //add form to temp forms array
f.Tag = tfo.Length-1;
tfo[tfo.Length -1] = f;
}
}
formObjects
= tfo; //assign f to formObjects
}
Moving on to
the MDI parent, we instantiate the forms class
private forms fms
= new forms();
This is done before the constructor so that its scope is form wide; another
form wide variable is a current form index
private int childFormNumber
= 0;
This is
used by the MDI parent to marshal operations to the active form.
Creating new forms of the type needed is no big deal but we do need to keep
a handle on them so this is how it is done
private void
cSVFormToolStripMenuItem_Click(object sender, EventArgs e)
{
//create
the CSV form
createCSVForm();
}
//------------------------------------------------------------------------------------
// create a new document
form
//-------------------------------------------------------------------------------------
private void
createCSVForm()
{
//local variable, can use a global one
int frm_index = -1;
// form management, only needs to be
created once for the various forms that may be used in an application
// and should be placed before the form
constructor
// this is an Objects array so it does not
care what type of form you send it
//*********************************
// forms fms = new forms(); //uncomment if you want to use it here.
//*********************************
// creates an instance of form frmCSV (any form you
want to create)
frmCSV f = new frmCSV();
// add to the forms management array
returning the index into the array
// Note: you can get the index of the form
into the array from the forms Tag property
frm_index = fms.AddForm(f);
//make sure that parent is the MDI
otherwise will not show as MDI child.
fms.SetParent(this, frm_index);
//set the forms caption
fms.SetCaption(frm_index, "This is a
new form managed by the forms array | index = " +
frm_index.ToString());
//now we can show the form
fms.ShowForm(frm_index);
}
This is about as hard as it gets to initiate a new form and be able to manage it by its index.
To remove a form from the array, we use the form closed event to tell the MDI parent that we are now closed and that it should discard the form from the object array
//----------------------------------------------------------------------------
private void
frmCSV_FormClosed(object sender, FormClosedEventArgs e)
{
// tell parent to remove this form from
forms array
f.Discard((int)this.Tag);
}
//---on MDI parent --------------------------------------------------------
public void
Discard(int formTag)
{
//remove a form from the array
fms.RemoveForm(formTag);
}
To ensure that the MDI parent knows who to marshal operations to we need to
let it know who the active form is, in the form Activated event we tell the
parent the index into the array to set the current form to.
//------------------------------------------------------------------------------
private void
frmCSV_Activated(object sender, EventArgs e)
{
//let the parent know we are
the current active form.
if(f != null)
f.MeActiveForm((int)this.Tag);
}
//-on MDI parent------------------------------------------------------------
public void
MeActiveForm(int formTag)
{
//set the active form index
childFormNumber = formTag;
}
I said earlier
that the MDI does not need to know what to do on any given form, the only
thing it needs to know is who to marshal the operation to for example we
have a number of forms open (can be any type) and we want to open a document
or files that only one particular form knows how to open.
private void
openACSVToolStripMenuItem_Click(object sender, EventArgs e)
{
// identify the form type, if it is a csv
form issue the openFile() instruction.
// MDI does not need to know how to do this
or what to do with it once open.
// the child form does.
if(fms.Forms[childFormNumber].GetType().Name == "frmCSV")
{
frmCSV f = (frmCSV)fms.Forms[childFormNumber];
f.openFile();
}
}
In this case if
you tried to open a document that only an frmCSV form can open in any other
form , the function above would not even try to marshal the operation to the
form because it is not of the type required.
Yes I know we could have set the enabled state of the menu item to stop it
from happening but this is a lesson on how to marshal operations.
As I said the MDI parent is just like a traffic cop directing traffic, it is
hoped that the driver knows how to interpret the traffic cops instruction so
it is your job to design your forms to communicate with the parent and let
the parent communicate with it's children.
One of the best examples of what I am attempting to get across is an
application that shows a tree view of client nodes, supplier nodes and say
entities, each of these nodes has, based on the node type, transactions etc.
If you were to click on say a client node you may want to show the clients
address details, this would then call a form to show those details from here
you could call a form to show the client transactions and further, a form
that shows a digital image of the when a row on the transactions grid is
clicked.
In this case, the MDI parent marshals operations based on who has the focus,
if we decide to change the client by clicking on the tree view, we can
either initiate new client forms or update the forms already created for
clients.
If you re-use the client forms, the transactions and image forms are
instructed by the MDI parent to update themselves to reflect the new client
details.
Hope this helps some of you design better applications.