Integrating an MDI application using C#
Written by Herb Fickes   
Wednesday, 10 February 2010 12:47

The OpenSpan training includes a series of labs working with a sample CRM application.  This article demonstrates one of those labs but instead of using graphical automations to work with OpenSpan adapters, it uses C# within a standard Visual Studio project to perform what the automation did in the labs.

The application we are integrating is a demonstration of a CRM application running as a Windows client and using MDI windows to display information about customers.  The adapter is the same in the training lab since we are using the same application.  The application is part of the OpenSpan training files which can be optionally installed when installing the Visual Studio Plug-in.  The install for the demo CRM application is at:

C:\Program Files\OpenSpan\OpenSpan Plug-in for Visual Studio 2008\Extras\Crm Setup.msi

The entire solution with .Net code is attached to the article and you may modify and reuse it.

The differences between this and the training lab is instead of using OpenSpan studio to create a toolbar and integrate it with the application, we will use Visual Studio to create the toolbar and the integration code will be in C# rather than with graphical automations.

The main points of using OpenSpan with .Net demonstrated here are:

  • Starting and stopping an application using an adapter
  • Handling events from an application
  • Working with MDI Child windows in an application
  • Dealing with multi-threading issues from application events

First, two projects need to be created in Visual Studio.  One is a WinForm project, the other is an OpenSpan project.

CRM_1b

The OpenSpan adapter is created by right-clicking the OpenSpan project (here names, OpenSpanIntegration) and selecting Add and then New Windows Application.

CRM_2c

Once you have done this, you set the path to the application and interrogate it.

CRM_3b

Now for the interesting changes using the OpenSpan Visual Studio plug-in.

Instead of creating a application bar across the top of the screen using drag-n-drop tools in OpenSpan Studio, you do this using Visual Studio.  However, there is no application bar class in Visual Studio unlike in OpenSpan Studio.  You could still do this with the OpenSpan tools, however, for this example you can use a C# class that creates a WinForm that acts as an application bar.  This code is in the attached solution for your use.  It is not copied into the text here since it is straight C# and pinvoke code.

You need to add references to the .Net project to work with the OpenSpan libraries.  This is done by right-clicking References and selecting Add Reference.  You need to add a project reference to the OpenSpan project you’ve already created.  You must also add .Net references to:

  • OpenSpan.dll
  • OpenSpan.Adapters.dll
  • OpenSpan.Adapters.Windows.dll

CRM_4b

You should also set the OpenSpan adapter to not auto-start when the project runs since we will start it from code.  This is done by setting StartOnProjectStart to false.

CRM_5b

Before we dive into the code, the application bar and external CRM application appear as below when run.  Everything on the application bar was drawn using Visual Studio 2008.

CRM_6c

and the code for creating and starting the toolbar is:

public partial class Toolbar : OpenSpan.Utilities.ApplicationBar
{
protected OpenSpanIntegration.CRM Crm;

private void Toolbar_Load( object sender, EventArgs e )
{
Crm = new OpenSpanIntegration.CRM();

// Connect events
//  handle when the login button is first matched by the adapter
Crm.btnLogin.Created += new EventHandler( btnLogin_Created );
//  event fires when a new record is shown in an MDI child window
//  in the CRM application
Crm.CallRecord.Created += new EventHandler( CallRecord_Created );
//  event fires when a MDI child window is closed for any reason
Crm.CallRecord.Destroyed += new EventHandler( CallRecord_Destroyed );
//  event fires when a MDI child window is given the focus
Crm.CallRecord.GotFocus += new EventHandler( CallRecord_GotFocus );

// Starts the external application
Crm.Start();
}

The first part to notice is that Toolbar inherits not from System.Windows.Form but from ApplicationBar.

The second is that we created an instance of the OpenSpan adapter in code within the Toolbar_Loadevent handler.  Next we created handlers for a couple of the events.  These events are fired when a CRM MDI child window is created, gets the focus, or is closed.  Finally, we start the CRM demonstration application.

You’ll notice that on the application bar there is an Exitbutton.  This will shut down the application bar and the CRM application.  This is not necessary depending on your requirements.  By default, since the CRM application was already started, it will keep running after the solution is ended since it is after all an external application to the solution.  The adapter will stop running but not the application.

The following code handles the Exit button being pressed and stops the CRM application:

private void Exit_Click( object sender, EventArgs e )
{
if ( Crm != null && Crm.IsRunning )
{
// If the app has some items that will fire events when the app
//   is closed, we need to first stop watching for those events
//   since if they happen as the applications
//   are closing we can get an exception since some objects
//   will no longer exist.
// In this case, the toolbar may be disposed before the
//    CRM applications open MDIChild windows are closed.
Crm.CallRecord.Destroyed -= new EventHandler( CallRecord_Destroyed );

Crm.Stop();
}
Close();
}

The code first checks to see if the CRM application is running since a user might have manually closed it.  It then ‘unwires’ the Destroyed event for the MDI child windows since there might still be a child window visible and we don’t want to handle its being closed while the entire application is exiting.  Finally, the adapter is stopped which also closes the application according to the StopMethod property of the adapter.

Handling MDI Child Windows

The demonstration CRM application uses MDI (Multiple Document Interface) windows to display the records for each customer.  You can have more than one of these displayed at a time and the adapter creates a ‘clone’ of a matched form for each one.  The adapter use keys to identify them uniquely.  A full explanation of how to handle them is in the OpenSpan training.

First when a new record is created, we need to assign it a key so we can identify it later.

void CallRecord_Created( object sender, EventArgs e )
{
OpenSpan.Adapters.Controls.MdiChild currentCallRecord = sender as OpenSpan.Adapters.Controls.MdiChild;
if ( currentCallRecord != null )
{
OpenSpan.Adapters.Controls.Label account =
currentCallRecord.GetDescendant( Crm.Account.Name ) as OpenSpan.Adapters.Controls.Label;
if ( account != null )
{
currentCallRecord.Key = account.Text;
handleToolbarUpdate( currentCallRecord.Key );
}
}
}

The bolded sections of the code are the important ones discussed here.

When the record is created, we cast the calling object to an MdiChild window.  We then get the account field from it since we know that the account code is unique on this child window for our example.  For another application you might use the Window Text or some other unique string.

Next we set the Keyproperty to the account number found in the label’s text property.  Once this is done, we can later find this particular child form even when there are others like it.

Threading

Microsoft Windows requires that all changes made to a user interface control be done by the thread that created the control.  This is simply how Win32 works.  However, it is something you need to be aware of since it will happen when handling events from external application adapters.

OpenSpan adapters will fire their events on different threads than your .Net applications UI thread.  This keeps your application’s UI responsive however it also means that you need to marshal events to the UI thread to work with controls like textboxes and labels.

This is not needed if your event handler does not touch a UI control.

Below is a sample pattern for handling an UI control safely from different threads.  There are many patterns you can use; this one is self-contained.  You can find out more by searching MSDN.  For instance the article, http://blogs.msdn.com/csharpfaq/archive/2004/03/17/91685.aspx describes this.

The important thing is to use Invoke()if updating anything on a UI control.  Some examples are changing Text, clearing a listbox, or working with a DataGridView.

public delegate void updateUI( string key );

///


/// Handles updating the appliation bar's values. It’s done here so we can ensure its
/// thread safe for calls from the OpenSpan adapter.
///
/// account number for the window we are working with

public void handleToolbarUpdate( string keyText )
{
if ( InvokeRequired )
{
    // Call self but on the correct thread
    Invoke( new updateUI( handleToolbarUpdate ), keyText );
    return;
}

… the rest of your code…

Working with controls on Keyed or Cloned MDI child window

You can see the full code in the attached solution, the following is a section from the handToolbarUpdate() method shown when discussing threads.

// Copy all the other values we want to the application bar.  In each case, we
// need to get the proper clone using the GetClone() method and the key.
City.Text = Crm.txtCity.GetClone<OpenSpan.Adapters.Controls.TextBox>( key ).Text;

This code has to be thread-safe  because of the assignment to City.Text.  The other notable feature of this is the use of a C# generic in GetCloneType> to return a properly typed object reference.  The type in this case is a OpeSpan.Adapters.Controls.TextBox which is a bit wordy  but fortunately intellisense helps here.  It is not a System.Windows.Forms.TextBox even though that is what the adapter is working with in the CRM application.

This means you can’t use GetClone<TextBox>( key ) because your list of using statements at the beginning will point this to the Form’s one.

You can simplify the typing with a statement such as this at the head of the C# module:

using OS = OpenSpan.Adapters.Controls;

Then you can make  the earlier line of code easier to read by using OS as the namespace rather than the full path as:

// Copy all the other values we want to the application bar.  In each case, we
// need to get the proper clone using the GetClone() method and the key.
City.Text = Crm.txtCity.GetClone<OS.TextBox>( key ).Text;

Here is another use of keys.  In this example, the application bar’s combobox lists all the opened account numbers.  When the combox is changed to another account number, we change which MDI child is displayed.

This also assumes that a using statement is present of:

using OS = OpenSpan.Adapters.Controls;


///


/// Fires when the combo selection has changes
|///
private void GetAccount_SelectedIndexChanged( object sender, EventArgs e )
{
    string account = GetAccount.SelectedItem as string;
    if ( account != null && Crm.CallRecord.IsCreated )
    {
        OS.MdiChild callRecord = Crm.CallRecord.GetClone( new OpenSpan.Adapters.KeyContext( account ) );

        if ( callRecord != null && !callRecord.Focused )
            callRecord.Activate();   // make this child the one with the focus
    }
}

This shows a few techniques used when using OpenSpan’s integration within a .Net project.  The attached solution can be used to experiment with this further.