Marshal Operations Between Child Forms in an MDI Application

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.