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 :
- check-out the command-line, whether
2002
or2003
is being passed - according to the command-line, retrieve the install directory of VC# from the registry
- recurse-copy all template files in the
<installdir> / VC#Wizards
subfolder - copy all
*.vsz
files in the<installdir> / CSharpProjects
subfolder - 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 :
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.icoWhere 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.icoLast 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