C# SDI/MDI Application wizards

Supports VC#2002, 2003 and 2005


Two new C# project templates

 

It's a fact of life that C# project templates provided by the VS.NET IDE lack SDI/MDI support. Since SDI/MDI fit many real-world requirements when writing applications, especially those being document-centric, I thought that would be nice and hopefully useful to share this stuff.

In the remainder of this article, I'll show :
  • what is brought in the zip files
  • how to install the application wizard
  • the meaning of SDI/MDI, huh ?
  • how it was built

 

1. Installing the wizard

The zip files has everything you need to get the wizards installed on either VC#2002 or VC#2003. The package can be broken down as follows :

  • SDIMDIwizardinstaller.exe, command-line installer
  • CSharpSDIWiz folder, templates files for the SDI wizard
  • CSharpMDIWiz folder, templates files for the MDI wizard
  • *.vsz files, parent files for the wizard, IDE version specific

If you only want to install the stuff and are not very much interested in the details, then extract all the stuff in some folder, and then double-click on SDIMDIwizardinstaller.exe if you are using VC#2005. If you are using VC#2002, bring a command-line up, and type SDIMDIwizardinstaller.exe 2002. If you are using VC#2003, bring a command-line up, and type SDIMDIwizardinstaller.exe 2003. Once the wizards are installed, you can delete the temporary extraction folder.

The installer does the following job :

  1. check-out the command-line, whether 2002 or 2003 is being passed
  2. according to the command-line, retrieve the install directory of VC# from the registry
  3. recurse-copy all template files in the <installdir> / VC#Wizards subfolder
  4. copy all *.vsz files in the <installdir> / CSharpProjects subfolder
  5. update the <installdir> / CSharpProjects / CSharp.vsdir file and add two entries if they don't exist yet.

The code for the installer is reproduced below :

static void Main(string[] args)
{
 // installation sequence
 // 1- get VC# install dir (regkey)
 // 2- copy template files in the VC# subfolder for files
 // 3- copy vsz files in the VC# subfolder for project wizards
 // 4- add two entries in the VC# vsdir file so that the IDE sees them

 // check out cmdline (type 2002 for VC#2002, 2003 for VC#2003 none or everything else for VC#2005)
 bool bIsFor2002 = args.GetLength(0)>0 && args[0] == "2002";
 bool bIsFor2003 = args.GetLength(0)>0 && args[0] == "2003";

 // 1- regkey
 RegistryKey k;
 if (bIsFor2002)
  k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\VisualStudio\7.0", true);
 else if (bIsFor2003)
  k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\VisualStudio\7.1", true);
 else
  k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\VisualStudio\8.0", true);

 String szWizardDir = (String) k.GetValue("InstallDir") + @"..\..\VC#\";

 if ( !Directory.Exists(szWizardDir) )
 {
  System.Console.WriteLine("Make sure to install VC# first");
  return;
 }

 // 2- template files
 String szSrcDir = AppDomain.CurrentDomain.BaseDirectory;
 RecurseCopyFiles(szSrcDir + @"\CSharpSDIWiz", szWizardDir + @"VC#Wizards\CSharpSDIWiz");
 RecurseCopyFiles(szSrcDir + @"\CSharpMDIWiz", szWizardDir + @"VC#Wizards\CSharpMDIWiz");

 // 3- vsz files
 if (bIsFor2002) // VC# 2002
 {
  File.Copy(szSrcDir + @"\CSharpSDI_VS2002.vsz", szWizardDir + @"CSharpProjects\CSharpSDI.vsz",true);
  File.Copy(szSrcDir + @"\CSharpMDI_VS2002.vsz", szWizardDir + @"CSharpProjects\CSharpMDI.vsz",true);
 }
 else if (bIsFor2003)// VC# 2003
 {
  File.Copy(szSrcDir + @"\CSharpSDI_VS2003.vsz", szWizardDir + @"CSharpProjects\CSharpSDI.vsz",true);
  File.Copy(szSrcDir + @"\CSharpMDI_VS2003.vsz", szWizardDir + @"CSharpProjects\CSharpMDI.vsz",true);
 }
 else // VC# 2005
 {
  File.Copy(szSrcDir + @"\CSharpSDI_VS2005.vsz", szWizardDir + @"CSharpProjects\CSharpSDI.vsz",true);
  File.Copy(szSrcDir + @"\CSharpMDI_VS2005.vsz", szWizardDir + @"CSharpProjects\CSharpMDI.vsz",true);
 }

 // 4- update vsdir file (append 2 entries if they don't exist yet)
 bool bAlreadyInstalled = false;
 bool bFileExists = false;

 if (File.Exists( szWizardDir + @"CSharpProjects\CSharp.vsdir" ))
 {
   bFileExists = true;

   StreamReader sr = new StreamReader( szWizardDir + @"CSharpProjects\CSharp.vsdir" );
   String line;
   while ((line = sr.ReadLine()) != null) 
   {
     if (line.IndexOf("CSharpSDI.vsz") > -1)
     {
       bAlreadyInstalled = true;
       break;
     }
   }
   sr.Close();
 }

 if (!bFileExists || !bAlreadyInstalled)
  {
   using (StreamWriter sw = File.AppendText( szWizardDir + @"CSharpProjects\CSharp.vsdir" )) 
   {
    sw.WriteLine("CSharpSDI.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|SDI Application|11|" +
                 "Builds a Windows single document interface (SDI) application|" +
                 "{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4554| |SDIApplication");
    sw.WriteLine("CSharpMDI.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|MDI Application|12|" +
                 "Builds a Windows multiple document interface (MDI) application|" +
                 "{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4554| |MDIApplication");
    sw.Close();
   }
  }
 }

}

static void RecurseCopyFiles(String szSrcDir, String szDestDir)
{
 if ( !Directory.Exists(szDestDir) )
  Directory.CreateDirectory(szDestDir);

 string [] fileEntries = Directory.GetFiles(szSrcDir);
 foreach(string fileName in fileEntries)
  File.Copy(fileName, szDestDir + fileName.Substring(fileName.LastIndexOf('\\')),true);

 // recurse
 string [] subdirectoryEntries = Directory.GetDirectories(szSrcDir + @"\");
 foreach(string subdirectory in subdirectoryEntries)
 {
  String szSrcNextDir = subdirectory;
  String szDestNextDir = szDestDir + subdirectory.Substring(subdirectory.LastIndexOf('\\'));
  RecurseCopyFiles(szSrcNextDir, szDestNextDir);
 }

}

The MDI application is derived from a sample provided by MS in the VS.NET CDs, called "Scribble".

 

2. What is SDI/MDI ?

SDI/MDI is short for Simple Document Interface / Multiple Document Interface.

Those words are used by VC++ MFC programmers to refer to application wizards that are built within the VC++ IDE and which provide rich and useful application skeleton code. We could also speak about MVC (Model View Controller). In fact, the document-view paradigm is a way to provide functionalities to an application such like the ability to create a new document of a given type (file extension), open an existing document, and save a working document. Each document is rendered, whether on screen or not, using one or more views. The document orders the views to update themselves based on events or other application logic. Each view implements an OnPaint method to display the content purposedly. For instance, if a document stores the bits for a 3D graphic model, then one view could be used to display a front view of it, while another view could be used to display a left view of it. And so on.

A SDI application is a one document - one view application. Depending on requirements, one can add views to it. Notepad is a SDI application.

A MDI application is a multiple document - multiple view application. Microsoft Word is a MDI application.


Sample MDI application

 

 

3. Views and documents

Both SDI and MDI wizards share a common "architecture". The main form acts like a container. One or more documents (instances of the SDIDoc class) hold all the documents life-cycle (open, save, print, is it dirty?, ...) as well as the application logic itself. One or more views for each document (instances of the SDIView class) hold what's draw on screen or being sent to the printer or any other device.

In the MDI wizard, the main form not only acts like a container, it is really a container and uses the Winforms MDI properties associated to each class derived from System.Windows.Forms.Form, namely IsMdiChild (false for the container), IsMdiContainer (true for the container), MdiChildren (collection of Form-derived classes) and MdiParent (null for the container).

In the SDI wizard, the main form is a container but is mostly a simple form by itself.

In both the SDI and MDI wizards, the drawing and printing are delegated to views. Each view knows its "parent" document, and can use the properties from that document to draw itself, etc.

By analogy, the serialization (load / save) is delegated to the document. Like in the MFC wizard, a document is being associated a file extension, .doc by default. Each document knows all the views being attached, and this custom mechanism is used to delegate the calls.

 

4. Inside the application wizard

Installing the application wizard allows to create projects using either of two new project templates. Once installed, the 2 new project templates appear with a clearly identifiable icon and label from the VS.NET project wizard. Both can be used like if you were using the default Windows Application project template. Below are explanations on how to install the application :

Below are the steps to add both SDI and MDI application wizards to the list of known C# project wizards.

4.1 Find the VC# installation folder. On my machine, it's c:\program files\VC#, and it has subfolders like VC#Wizards and CSharpProjects. Let's call it <VC#dir> for the remainder of the article.

4.2 When you bring up the VS.NET project box, VS.NET lists all existing .vsdir files, including those found in particular <VC#dir> subfolders. Each .vsdir file is a category of project templates. The <VC#dir> \ CSharpProjects \ CSharp.vsdir lists all standard C# project templates. The definition of the .vsdir file format is detailed here[^]. Entries in a .vsdir file are referenced .vsz files which in turn are small project template identifiers. Among those identifiers is CSharpEXE.vsz, the project which helps build simple C# Windows Applications. What we are going to do is add two entries to the CSharp.vsdir file, one for the SDI project template, one for the MDI project template. So, edit the CSharp.vsdir and clone twice the line referencing CSharpExe.vsz, so as to make sure our two projects are built from this starting point. The documentation for the .vsz file can be found here[^].

Those two files must be edited, and the reference to the CSharpEXEWiz folder must be replaced with corresponding references to CSharpSDIWiz and CSharpMDIWiz. Those are references to subfolders we are going to create in a while. The two .vsz files should look like this :

CSharpSDIWiz.vsz // VC# 2002
VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine
Param="WIZARD_NAME = CSharpSDIWiz"
Param="WIZARD_UI = FALSE"
Param="PROJECT_TYPE = CSPROJ"
CSharpMDIWiz.vsz // VC# 2002
VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine
Param="WIZARD_NAME = CSharpMDIWiz"
Param="WIZARD_UI = FALSE"
Param="PROJECT_TYPE = CSPROJ"

CSharpSDIWiz and CSharpMDIWiz are meant to be <VC#dir> \ VC#Wizards subfolders. Let's create them. Each of these folders must follow the project template guideline, ie have a predefined structure. The structure for a simple project template is straight forward : there must be two subfolders, namely Scripts and Templates. In each of these 2 subfolders must be a localized folder whose name depends on the language version of your VS.NET installation. We assume in the remainder of this article that it's 1033 (US english).

4.3 Filling the Scripts \ 1033 folder : this folder is supposed to contain a default.js script file. The implementation in default.js is what differentiates this project template from another and brings Javascript application code aimed to build the actual .csproj file based on the core Javascript source code from <VC#dir> \ VC#Wizards \ 1033 \ Common.js and the actual VS.NET IDE extensibility object model[^]. The default.js file assumes that Form1.cs is the application mainform. So to make things easier, both SDI and MDI projects are such that Form1.cs is the mainform. Otherwise, default.js would have to be manually edited. Just copy the default.js file found in the CSharpEXEWiz \ Scripts \ 1033 folder.

4.4 Filling the Templates \ 1033 folder : this folder is supposed to contain a Templates.inf file, and all files that are to be included in the target .csproj project file. Things get easy since the Templates.inf file simply lists all files in this folder, one per line.

In short, here is how the file hierarchy should look like for each WIZ subfolder :

CSharpSDIWiz
 + Scripts
   + 1033
     + default.js  (a copy from the one existing in the CSharpEXEWiz folder)
 + Templates
   + 1033
      + Templates.inf
      + Form1.cs
      + Form1.resx
      + SDIDoc.cs
      + SDIView.cs
      + SDIView.resx
      + New.bmp
      + Open.bmp
      + Preview.bmp
      + Print.bmp
      + Save.bmp
      + App.ico
Where Templates.inf constains this :
Form1.cs
Form1.resx
SDIDoc.cs
SDIView.cs
SDIView.resx
New.bmp
Open.bmp
Preview.bmp
Print.bmp
Save.bmp
App.ico
Last note, to make sure namespaces are properly transferred to the target project, we have to change the content of the Form1.cs file from the original stand-alone project :
namespace SDIApp
{
   ...
with this (in the Templates \ 1033 folder) :
namespace [!output SAFE_NAMESPACE_NAME]
{
   ...

What is just done and finished for CSharpSDIWiz must be also done for CSharpMDIWiz.

 

5. Unifying both project templates ?

I have to confess that, for trivial reasons, I have brought two wizards up instead of only one. Actually there is no need to have two additional icons in the project box, one for SDI and one for MDI. The idea is that, in the .vsz file, it's possible to let the Wizard engine know about a HTML UI which, in turn, would let the user choose SDI or MDI, get the return value, and act accordingly. Although this is certainly doable, this is not what I have done for this release.

For those interested in HTML UI-based wizards, just check out the undocumented wizard named CSharpIndexerWiz

 

 

History :
  • first time published - May 3rd, 2003.
  • updated - Nov 30, 2003 : added a user-friendly installer ; the SDI wizard really creates a SDI app.
  • updated - Nov 14, 2004 : added support for VC# 2005

 

 

Stéphane Rodriguez

Home
Blog