An MDI Template for .NET Applications

Introduction

This article presents an assembly that demonstrates and simplifies the production of MDI applications using C#. Although the assembly is written in C#, it can be used in any of the .NET languages. It is loosely based on the MFC document/view structure. It features:

  1. A menu containing the standard file commands (Open, Save, Close, and so forth).
  2. A dynamically updated window menu item allowing quick switching between MDI child forms.
  3. A Help menu item with an automatically updated About box.
  4. A status bar that is updated with text for each of the menu commands.
  5. A document class similar to MFC's CDocument class with associated views.
  6. Automatic detection of document classes using reflection and custom attributes.
  7. Serialization of the main form's size and position between running instances.
  8. A recent files menu item containing the last 10 files opened/saved by the application.
  9. Drag and drop support.

Creating the Solution

To use the MDI template assembly, first create a Windows application as you would normally do. We then need to add the assembly as a project to the solution. To do this, right-click on your new application's solution in Solution view and select 'Add/Existing Project'.

When the Browse dialog appears, go to the directory where the MDI template assembly resides and select the .csproj file. You'll see that the assembly's project has been added to the solution. We now need to add the project as a reference to the Windows application. Right-click on the 'references' item under your application and select 'Add reference'.

This will bring up the following dialog. Go to the 'project's' tab, select the assembly's project, and hit the 'select' button.

Now, click 'OK' and build the solution. The MDI Template assembly can now be used.

Application Attributes

The title of the application is taken from the Assembly Product specificed in the Assembly Info. Set these two lines in your application's AssemblyInfo.cs file:

[assembly: AssemblyCompany("Darwen")]
[assembly: AssemblyProduct("MDI Application Test")]

Application specific data (for example, the last size of the window or the recent files list) are stored in the Registry under HKEY_CURRENT_USER/Software/<Company>/<Product>.

Creating the Main Form

To create the main form of the application, we must create an inherited form that derives from the MdiParentForm in the assembly. First, right-click on your application in the solution explorer. Select 'Add/Add Inherited Form'.

In the following dialog, enter MainForm and click OK. This will bring up the following dialog, requesting which form you want to derive from. Select Darwen.CodeGuru.MdiParentForm and click OK.

This will add a new form called MainForm to your application. We now need to change the main form of the application to our new form. Go to the source code for the initial form (usually Form1) and find the Main() function. Change the 'new Form1()' to 'new MainForm()'. Rebuild the solution and run it. You'll see that MainForm is being used as the main form of the application and not Form1.

Advanced

You can remove Form1 altogether and move the Main() function to the MainForm class instead. If you do this, you need to change the startup object in the properties for your application's project.

Adding Document Types

If you now run the application and select 'file/open', you'll see that only All files is available in the file type combo box. This is because we haven't described any documents yet. The template automatically picks up document types and extensions from the executable assembly by using a combination of attributes and reflection.

To define a document type, first add a class that derives from Darwen.CodeGuru.MDITemplate.MdiDocument. Now, you need to add Darwen.CodeGuru.MDITemplate.DocumentAttributes to this class that gives the name of the document type and its extension. You can add more than one document attribute to a class so that a single document class can process more than one file type. You also need to implement the abstract methods OnCreateView, OnLoadDocument, and OnSaveDocument.

Below is an example of an empty document class that processes text files. Try adding it to your application.

using namespace System;
using namespace Darwen.CodeGuru.MDITemplate;
[Document("Test document files", ".txt")]
public class TestDocument : MdiDocument
{
   public TestDocument()
   {
   }
   protected override MdiViewForm OnCreateView()
   {
      return null;
   }
   protected override bool OnLoadDocument(string sFilePath)
   {
      return false;
   }
   protected override bool OnSaveDocument(string sFilePath)
   {
      return false;
   }
}

Recompile the project and run it. Go to the File/Open menu item and you'll see that "Test document files" with the extension .txt has been added to the file type combo box.

An MDI Template for .NET Applications

Adding a View for the Document

Views are derived from the MdiViewForm class and are created in the same way as the main form—by selecting Add/Inherited Form and selecting the MdiViewForm class as the form to derive from. Try adding a new view form called TestViewForm to your application. Add a multiline text box to it, and make the text boxes docking style 'Fill'. Name the text box m_textBox.

We need to create this form in the OnCreateView() method of the document. We'll also add the data as well as the file saving and loading methods, so the TestDocument class now looks like this:

[Document("Test document files", ".txt")]
public class TestDocument : Darwen.CodeGuru.MDITemplate.MdiDocument
{
   private string m_sText;
   public TestDocument()
   {
      m_sText = "";
   }
   public string Text
   {
      get
      {
         return m_sText;
      }
      set
      {
         m_sText = value;
      }
   }
   protected override MdiViewForm OnCreateView()
   {
      return new TestViewForm();
   }
   protected override bool OnLoadDocument(string sFilePath)
   {
      bool fResult = true;
      try
      {
         System.IO.StreamReader reader = new
            System.IO.StreamReader(sFilePath);
         m_sText = reader.ReadToEnd();
         m_sText = reader.Close();
      }
      catch (System.IO.IOException)
      {
         fResult = false;
      }
      return fResult;
} protected override bool OnSaveDocument(string sFilePath) { bool fResult = true; try { System.IO.StreamWriter writer = new System.IO.StreamWriter(sFilePath); writer.Write(m_sText); writer.Close(); } catch (System.IO.IOException) { fResult = false; } return fResult; } }

We must now add the following methods to the view form, which are called when either the document needs to be updated or when the document changes and the view needs to be updated.

protected override void OnUpdateDocument()
{
   TestDocument document = this.Document as TestDocument;
   document.Text = m_textBox.Text;
}
protected override void OnUpdateView(object update)
{
   TestDocument document = this.Document as TestDocument;
   m_textBox.Text = document.Text;
}
protected override void OnInitialUpdate()
{
   TestDocument document = this.Document as TestDocument;
   m_textBox.Text = document.Text;
}

OnUpdateDocument() is called to transfer data from the view to the document. OnUpdateView() is called to signal that the view should update its data from that in the document. Recompile and run the application. You now have a fully working MDI text editor that you can drag and drop text files onto as well as having a recent files list.

Adding Menu Items

All menu items should be derived from the MdiMenuItem class. This class provides the extra functionality to do the status bar messaging as well as automatic enabling/disabling of the menu item. Try adding a 'Test' menu item thus:

[IMAGE6.jpg]

Now, go to the source code for your main form and change your new menu items so that they are instances of MdiMenuItem. For example:

public class MainForm : Darwen.CodeGuru.MDITemplate.MdiParentForm
{
   private Darwen.CodeGuru.MDITemplate.MdiMenuItem menuItem1;
   private Darwen.CodeGuru.MDITemplate.MdiMenuItem menuItem2;
   // ....
   #region Designer generated code
   private void InitializeComponent()
   {
      this.menuItem1 = new Darwen.CodeGuru.MDITemplate.MdiMenuItem();
      this.menuItem2 = new Darwen.CodeGuru.MDITemplate.MdiMenuItem();

Now, when you go back to editing the menu you'll see that in the properties page two new items have been added.

[IMAGE7.jpg]

If the 'NeedsDocument' property is set to true, this item is greyed out if no document is presently open. The 'StatusMessage' property is the string that is presented in the message bar when the mouse passes over this menu item. MdiMenuItem also has a TestEnabled event that is designed so that the enabled state of the item can be altered dynamically.

Try adding a handler for this event to your main form. With the properties window open and your test menu item selected, click the 'lightning' button.

[IMAGE8.jpg]

When you pres Return, a new method will be added to your main form. Change its definition to the following:

private void m_menuItem2_TestEnabled(Darwen.CodeGuru.MDITemplate.
                                     MdiMenuItem menuItem)
{
   TestDocument document = Darwen.CodeGuru.MDITemplate.MdiDocument.
                           ActiveDocument as TestDocument;

   if (document != null)
   {
      document.UpdateDocument();
      menuItem.Enabled = document.Text.Length > 0;
   }
   else
   {
      menuItem.Enabled = false;
   }
}

This will mean that if there is no document, or if the document text is empty, the item will be greyed out. The call to UpdateDocument() makes sure that any changes in the view of the document are transferred to the document before doing the test.

About Box

The about box is shown by going to Help/About in the menu. You set the image to use by accessing the MdiParentForm's 'ApplicationIcon' property. You do this in the Design view of your application's main form.

Conclusion

We have seen how to use the presented assembly to produce a simple MDI text editor. I have not included a description of the source code because it should be self explanitory. It demonstrates how to do many of the actions that are needed in the production of a professional MDI interface.

This approach could be modified to do Single Document Interfaces or even a tabbed MDI template, but that is left up to the reader. It simply attempts to demonstrate how to use an assembly to standardise and improve the ease of design of user interfaces for .NET applications.



About the Author

David McClarnon

He first encountered Windows programming using Visual C++/MFC version 1.5 on Windows 3.11 a very long time ago. He is now a contract developer specialising in .NET/native interop with p/invoke.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds