SharpDevelop Community

Get your problems solved!
Welcome to SharpDevelop Community Sign in | Join | Help
in Search

Matt Ward

  • Debugging IronPython Code in SharpDevelop

    With SharpDevelop 3.1 you can now debug IronPython code with the IronPython Interpreter (ipy.exe).

    Before you start make sure the debugger is set to use the Just My Code feature. From the Tools menu select Options and then click the Debugging category.

    Debugger options for debugging IronPython code

    Ensure that the Just My Code feature is checked and that the Step over code without symbols is not checked. If the Step over code without symbols option is selected then stepping will not work properly and lines of code will be skipped over.

    There are two ways to debug your code. You can use the Python menu or modify the project options. We will look at both of these alternatives. First open your IronPython project into SharpDevelop. Open your main file and make sure it is the active text editor window. Set a breakpoint somewhere in your code. Then from the Python menu select Run.

    Python menu option to run with debugger

    This will start ipy.exe which will run your code and the debugger should stop the execution at the breakpoint.

    Debugging IronPython code

    From this point you can do the usual debugging activities such as stepping through your code, viewing the callstack, adding items to the watch window, etc.

    If you want to use a different ipy.exe then this can be specified in the Python Options dialog (Tools menu | Options).

    Python options dialog

    To enable debugging when you press F5 or select the Debug Run menu option you can modify the project options. From the Projects menu select Project Options and then open the Debug tab. Here you should change the Start Action to Start external program and use the browse button to locate ipy.exe. In the Start Options add the following command line arguments, changing the name of your main file as required.

    -D ${ProjectDir}\Program.py

    Once these changes are saved you can then press F5 and ipy.exe will be run under the debugger instead of running the compiled executable.3

    Issues

    • No support for debugging the executable produced by the IronPython compiler since it does not produce debug symbols (i.e. .pdb files).
    • When using ipy.exe you need to add references to .NET assemblies explicitly in your code except for System which is included by default. For example:
      import clr 
      clr.AddReference("System.Windows.Forms")

    Thanks

    Thanks to David Srbecky, SharpDevelop's debugger expert and maintainer, for reviewing the code changes I wanted to make to the debugger and making sure nothing was broken. Adding support for debugging IronPython was straightforward and required 10-15 lines of new code thanks to the code already written by David.

    Thanks also to Harry Pierson (IronPython Program Manager at Microsoft) who has written a great set of blog posts on creating an IronPython debugger in IronPython which gave me the reason why SharpDevelop's debugger was not working when debugging IronPython code.

    Posted Mai 30 2009, 01:40 PM by MattWard with 2 comment(s)
    Filed under:
  • IronPython 2.0 Forms Designer

    Support for designing Windows Forms in IronPython is now available in SharpDevelop 3.1. The original IronPython forms designer was removed when SharpDevelop 3.0 began supporting IronPython 2.0 which had removed support for generating IronPython code from Microsoft's CodeDOM. The forms designer has now been re-implemented to use the IronPython abstract syntax tree (AST) and no longer relies on the CodeDOM.

    Creating a Windows Application

    To create a Windows Application open up the new project dialog by selecting New then Solution from the File menu. Select the Python category to show the available project templates. Select the Windows Application project template, enter a name and location and click the Create button.

    New Python Project Dialog

    Designing Windows Forms

    The Windows Forms designer is not yet complete so be warned that it could generate form code that will no longer compile.

    The designer can be opened by opening a form in the text editor and selecting the Design tab at the bottom of the editor.

    Python main form before opening the designer

    Once open in the designer you can add controls to the form by dragging the controls from the Tools window. In the screenshot below a label, text box and a button have been added.

    Main form designed in designer

    Click the Source tab at the bottom of the editor to view the generated code in the InitializeComponents method.

    Generated form code

    Limitations

    The IronPython forms designer is not yet complete and the following are some of the known limitations.

    1. No support for project or local form resources.
    2. No support for icons.
    3. Incomplete support for ToolStripItems and menu strips.
    4. Incomplete support for ListViewItems.
    5. No support for TreeViewItems.
    6. Incomplete support for non-visual components (e.g. Timers).
    7. Controls needed to be fully namespace qualified.

    Forms Designer Internals

    For those interested in how the forms designer actually works at a high level we will now look at what the IronPython forms designer does when loading and then generating code for a form.

    To show the form in the designer the following steps are executed.

    1. The form's code is parsed and an IronPython AST (PythonAst object) is created.
    2. The AST is then visited and each control is added to the forms designer and the control's properties are set.
    3. The form's properties are set in the designer and the form is displayed.

    To generate the code after the form has been designed the following steps are executed.

    1. The form is obtained from the forms designer.
    2. Each of the child components of the form have their properties checked to see if they need to be serialized. This can be done by getting all the property descriptors and then checking the ShouldSerializeValue method. If they do need to be serialized then code is generated for them and added to a StringBuilder.
    3. After all the child components are added the code for the form is generated.
    4. Finally the generated code is inserted into the text editor inside the InitializeComponent method, replacing any existing code.
    Posted Mai 12 2009, 08:50 PM by MattWard with no comments
    Filed under:
  • Converting C# and VB.NET Code to IronPython

    SharpDevelop 3.1 now supports converting C# and VB.NET code to IronPython. It can convert a single file or an entire project. The code to convert between these languages is still under development and has some limitations.

    Converting an Individual File

    To convert a C# or VB.NET file, open it in SharpDevelop's text editor, then from Tools menu select Convert code to Python.

    Convert code to Python menu option.

    The code conversion is limited to converting classes so it will not convert an arbitary piece of code that is not inside a class.

    C# code before conversion.

    C# code after conversion to Python

    Converting a Project

    To convert a C# or VB.NET project, open it in SharpDevelop, then from the Project menu select Convert From C# to Python.

    Convert from C# project to Python project menu option.

    Once converted the project will most likely not compile straight away due to limitations in the implementation. At the time of writing converting a project has the following limitations:

    • Project's Main File is not set.
    • No code generated to call the project's Main entry method.
    • Namespace imports do include all the used classes.

    Code Conversion Internals

    Converting code to IronPython was originally supported in SharpDevelop 2.2 and was based on converting code to a Microsoft CodeDOM and then getting IronPython 1.0 to generate the Python code. In IronPython 2.0 this CodeDOM support was removed so the code conversion feature was removed from SharpDevelop 3.0 since that was using IronPython 2.0. In SharpDevelop 3.1 the code conversion has been rewritten to no longer use the CodeDOM support. It now works by executing the following simple steps:

    1. The C# or VB.NET code is parsed using SharpDevelop's parsing library NRefactory and an abstract syntax tree (AST) is generated.
    2. A visitor class then walks this AST and generates Python code which is added to a StringBuilder.
    3. Once the visit is complete the generated Python code is then displayed or saved to disk.
  • NUnit 2.5 Support

    SharpDevelop 3.1 now supports NUnit 2.5.

    A summary of which NUnit version is supported by SharpDevelop is shown in the table below.

    SharpDevelop 3.1 NUnit 2.5
    SharpDevelop 3.0 NUnit 2.4.8
    SharpDevelop 2.2.1 NUnit 2.4.7
    SharpDevelop 1.1 NUnit 2.2

    NUnit 2.5 Changes

    NUnit 2.5 has changed quite substantially compared with the previous 2.4.8 release, as outlined in the NUnit 2.5 release notes. The problems that we had when migrating SharpDevelop's unit tests to NUnit 2.5 were as follows.

    1. Assert.IsInstanceOfType has been replaced by Assert.IsInstanceOf.

      Your code will still compile and work if Assert.IsInstanceOfType is used but you will get compiler warnings.

    2. NUnit.Framework.SyntaxHelpers namespace no longer exists.

      All classes that were in this namespace have been moved to the NUnit.Framework namespace.

    3. The Has.Count constraint no longer takes an integer parameter.

      To fix this problem replace code such as:

      Assert.That(classesCollection, Has.Count(1));

      With the following:

      Assert.That(classesCollection.Has.Count.EqualTo(1));
    4. Overriding a [TestFixtureSetUp] method in a derived class using the new keyword no longer works.

      Some of the SharpDevelop unit tests were overriding an abstract base test class [TestFixtureSetUp] method in a derived class by using the new keyword, as shown below.

      Base class:

      [TestFixtureSetUp] 
      public void SetUpFixture()
      {
      // Setup code.
      }

      Derived class:

      [TestFixtureSetUp] 
      public new void SetUpFixture()
      {
      // Extra setup code.
      base.SetUpFixture();
      }

      In NUnit 2.4.8 the SetUpFixture method in the derived class would be called when running the tests allowing it to execute some extra setup steps. In NUnit 2.5 the base class SetUpFixture method is called instead and the derived class method is never called. To resolve the problem we changed the base class so it used a virtual method and allowed the derived class to override this to execute its extra setup steps.

      Base class:

      [TestFixtureSetUp] 
      public void SetUpFixture()
      {
      BeforeSetUpFixture();
      // Setup code.
      }

      public virtual void BeforeSetUpFixture()
      {
      }

      Derived class:

      public override void BeforeSetUpFixture() 
      {
      // Extra setup code.
      }
    Posted Mai 10 2009, 05:46 PM by MattWard with no comments
    Filed under:
  • Using the Python Standard Library

    Here is a short walkthrough on how to use the Python Standard Library with SharpDevelop 3.0 and IronPython 2.0.

    Prerequisites

    You will need to have SharpDevelop 3.0 and Python 2.5 installed on your machine. These can be downloaded from the following locations.

    Note that using Python 2.6 is not supported. The following section assumes that Python 2.5 was installed into the C:\Python25 folder.

    Using the Python Standard Library

    First we will create an IronPython console application in SharpDevelop. From the File menu select New and then Solution. In the New Project window select the Python category and select the Console Application template.

    New Python Console Application template

    Give the project a name, select its location and click the Create button.

    To use the Python Standard Library the project needs a reference to IronPython.dll, which should be added by default, and a reference to IronPython.Modules.dll. Open the Projects window, if it is not already open, by selecting Projects from the View menu. Right click the project's references and select Add Reference. In the Add Reference dialog first add a reference to mscorlib, this reference is needed since we are going to use the System.Console class to pause the console output. Then select the .NET Assembly Browser tab and click the Browse button. Locate the IronPython.Modules.dll file and select it. This file should be in the following folder:

    C:\Program Files\SharpDevelop\3.0\AddIns\AddIns\BackendBindings\PythonBinding

    Click OK to close the Add Reference dialog.

    In the Program.py file change the code to the following:

    # Add Python Standard Library to search path. 
    import sys
    sys.path.append("c:\python25\lib")

    # Use Python Standard Library os module.
    import os
    print os.getcwd()

    # Wait for a key press before closing the console window.
    import System
    print "Press any key to continue..."
    System.Console.ReadKey(True)

    The sys.path.append line adds the Python Standard Library to the search path. After that the os module is imported and the os.getcwd method is called to get the current working directory and this is output to the console window. The last three lines of code are just used to pause the console window so we can see the output.

    Compile the above code by selecting Build Solution from the Build menu.

    Finally run the application by selecting Run from the Debug menu.

    Output from Python Console application.

    Posted Mrz 01 2009, 02:43 PM by MattWard with 2 comment(s)
    Filed under:
  • WiX 3.0 AddIn for SharpDevelop 2.2

    If you want to use WiX 3.0 with SharpDevelop 2.2 now you can. SharpDevelop 2.2 will still ship with WiX 2.0 support however the WiX 3.0 addin has been backported. This WiX 3.0 addin for SharpDevelop 2.2 can be downloaded at the end of this post. It was built and tested using WiX 3.0.4714 which is the most recent release at the current time.

    Installing

    The simplest way to use the addin is as follows.

    1. Download the wix3-binaries.zip file from SourceForge.
    2. In SharpDevelop's WiX addin folder SharpDevelop\2.2\AddIns\AddIns\BackendBindings\WixBinding rename the original WixBinding.addin file to WixBinding.addin-bak.
    3. Copy the contents of the bin folder in the Wix3Binding.zip file to SharpDevelop\2.2\AddIns\AddIns\BackendBindings\Wix3Binding.
    4. Copy the contents of the doc folder in the wix3-binaries.zip file to the SharpDevelop\2.2\data\schemas folder. This updates the WiX schemas for use in the XML editor.
    5. Rename the folder SharpDevelop\2.2\Tools\Wix to SharpDevelop\2.2\Tools\Wix-bak
    6. Copy the contents of the wix3-binaries.zip file to the SharpDevelop\2.2\Tools\Wix folder.
    7. Restart SharpDevelop.

    Updating to Newer WiX Versions

    1. Download the wix3-binaries.zip file from wix.sf.net
    2. Copy the contents of the wix-binaries.zip file to the folder SharpDevelop\2.2\Tools\Wix.
    3. Copy the contents of the doc folder to the SharpDevelop\2.2\data\schemas folder.
    4. If there are any problems try re-compiling the source code and check the unit tests still work.

    Compiling the Source Code

    1. Copy the contents of the Wix3Binding zip file into the folder SharpDevelop\2.2\src\AddIns\BackendBindings\Wix3Binding.
    2. Run msbuild WixBinding.sln.

    Download Wix3Binding.zip

    Posted Nov 18 2008, 07:53 PM by MattWard with no comments
    Filed under:
  • XML Editor Reuse

    It is always good to see that someone else finds the code that you have written useful enough to be reused in another application. Here we take a look at where SharpDevelop's XML Editor has been reused. The XML Editor was originally added to SharpDevelop 1.0 back in April 2005.

    MonoDevelop

    Some time ago I ported SharpDevelop's XML Editor so it could be used from inside MonoDevelop. Currently there is an addin available for MonoDevelop 1.0. MonoDevelop 2.0 now ships with this XML Editor after Michael Hutchinson from Novell integrated it in March this year. I also believe that it is being used to help provide at least some part of the autocompletion for ASP.NET. It will be interesting to see how Michael builds on and improves the XML Editor code.

    Kaxaml

    Kaxaml is a lightweight XAML Editor written by Robby Ingebretsen. Kaxaml version 1.0 and 2.0 use a modified version of SharpDevelop's XML Editor. Robby has replaced the user interface part so the autocompletion popup window now uses WPF. He has also modified it so the autocompletion popup window behaves the same as Visual Studio's XML Editor. For example, SharpDevelop automatically inserts the equals sign and double quotes an attribute name is autocompleted whilst Visual Studio will autocomplete just the attribute name and then automatically insert the double quotes after the equals sign is typed in.

    Intellisense for Microsoft Expression Blend 2.5

    Stefan Dobrev has written an addin to provide XML autocompletion for the as yet unreleased Expression Blend 2.5. This addin uses the XML Editor code from Kaxaml to provide the autocompletion. Stefan has modified this code slightly to add support for the Expression Blend's code editor.

  • IronPython 2.0 Beta Integration

    Support for IronPython 2.0 Beta 4 is now available with SharpDevelop 3.

    Missing Features

    Some of the features have been disabled compared to the IronPython integration in SharpDevelop 2.

    • Forms designer
    • C# and VB.NET code conversion to Python

    Both of the above features involve converting code to and from a Code DOM. Support for the Code DOM is reduced in IronPython 2 so the above features have been temporarily disabled.

    Compiling

    IronPython 2.0 beta 4 re-introduced support for compiling python code to a .NET executable or dll and so SharpDevelop supports this. There is however one limitation. The working folder needs to be set to the folder containing the compiled dll or executable otherwise it will not be able to locate any local assembly references that are not in the GAC, for example IronPython.dll.

    IronPython Console

    There's now an IronPython console which can be used to type in IronPython expressions and have them evaluated interactively. It is currently missing code completion which will be implemented shortly. From the View menu select Tools and then Python Console.

    IronPython Console Window

    Posted Aug 20 2008, 09:27 PM by MattWard with no comments
    Filed under:
  • Attach to Process

    SharpDevelop 3 supports attaching the debugger to a running process.

    From the Debug menu select Attach to Process.

    Attach to Process menu item

    The Attach to Process dialog will show the managed processes by default. Select the process and then either double click or click the Attach button to attach to the process.

    Attach to Process dialog

    When you have finished debugging you can detach from the process by selecting Detach from the Debug menu.

    Detach menu item

    Posted Aug 20 2008, 09:16 PM by MattWard with no comments
    Filed under:
  • WiX 3.0 Integration

    SharpDevelop 3.0 now supports WiX 3.0.

    There are some changes to how things work compared to the WiX integration in SharpDevelop 2. The differences will be covered in the following sections.

    WiX Project Templates

    There are some new WiX project templates available. A basic empty project template and a template for each of the standard WiX UI library dialog sequences.

    Adding WiX Extensions

    WiX extensions are now displayed in the project browser instead of in the project options. They can be added by right clicking the WiX Extensions folder and selecting Add WiX Extension.

    .

    Project Options

    The Library and Linking tabs have been removed from the project options since the WiX extensions can now be added from the project browser.

    The Compiling tab has some new options as shown below.

    The WiX Variables field can be used to override the standard WiX UI library settings. In the screenshot above the standard licence agreement and dialog background bitmap are being replaced with new ones.

    The Suppress ICEs field is used to stop WiX from showing errors or warnings for particular Internal Consistency Evaluators (ICEs). After building your installer WiX now validates it against a standard set of rules which saves you from having to use another validation tool such as Orca.

    Localized string files are no longer specified in the Application's tab. Instead add the file to the project and change its Build action to Embedded Resource.

    Other Editors

    If you want integration with Visual Studio 2005 or 2008 then please check out Justin Rockwood's Votive.

    Rob Mensching has a list of WiX editors, including commerical ones, on his blog.

    It also looks like a future version of Visual Studio will ship with WiX.

     

    Posted Jan 02 2008, 11:28 PM by MattWard with no comments
    Filed under:
  • Porting an AddIn from SharpDevelop 2.2 to 3.0

    There have been some fairly large code changes on moving from SharpDevelop 2.2 to 3.0 and addins written for 2.2 are unlikely to work without some modification. We will look at the changes in the core parts of SharpDevelop and then look at one way to do the porting.

    Not all the differences are covered in the next section just those types and methods that are likely to have been used by an addin.

    ICSharpCode.Core

    TypeDescription
    ICSharpCode.Core.AddInReference
    bool RequirePreload New property that specifies that when a type from an addin is created any addins that are needed by this addin will be preloaded if this flag is set to true. This corresponds to the new requirePreload attribute in an .addin file.

    <Dependency addin="ICSharpCode.XmlEditor" requirePreload="true"/>

    ICSharpCode.Core.AddInTreeNode.TopologicalSortType is no longer public.
    ICSharpCode.Core.FileUtility
    bool IsValidPath(string path)New method that determines whether a full or relative path is valid. This replaces the IsValidFileName method.
    bool IsValidFileName(string fileName)Renamed to IsValidPath.
    static string NormalizePath(string fileName)New method that gets the normalized version of the filename. Slashes are replaced with backslashes, backreferences "." and ".." are 'evaluated'.
    ICSharpCode.Core.Properties
    void ReadProperties(XmlReader reader, string endElement)Method is no longer public but internal.

    NRefactory

    TypeDescription
    ICSharpCode.NRefactory.Ast.ArrayInitializerExpressionReplaced by the CollectionInitializerExpression type.
    ICSharpCode.NRefactory.Ast.BlockStatement
    BlockStatement NullThe Null property now returns a BlockStatement instead of a NullStatement type.
    ICSharpCode.NRefactory.Ast.FieldReferenceExpressionReplaced by the MemberReferenceExpression type.
    ICSharpCode.NRefactory.Ast.MemberReferenceExpressionReplaces the FieldReferenceExpression type.
    ICSharpCode.NRefactory.Ast.InvocationExpression
    List<TypeReference> TypeArgumentsThis property has been removed.
    ICSharpCode.NRefactory.Ast.NullArrayInitializerExpressionThis type has been removed.
    ICSharpCode.NRefactory.Ast.NullBlockStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullConstructorInitializerNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullEventAddRegionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullEventRaiseStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullEventRemoveStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullExpressionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullPropertyGetRegionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullPropertySetRegionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.IAstVisitorVarious new methods on this interface to support new types.
    object VisitArrayInitializerExpression(...)Method removed along with the ArrayInitializerExpression type. Replaced by the VisitCollectionInitializerExpression method.
    object VisitFieldReferenceExpression(...)Method removed along with the ArrayInitializerExpression type. Replaced by the VisitMemberReferenceExpression method.
    ICSharpCode.NRefactory.Visitors.NodeTrackingAstVisitorAll TrackedVisit methods have been renamed and now include the name of the type being tracked (e.g. TrackedVisitAddHandlerStatement).

    ICSharpCode.SharpDevelop.Dom

    TypeDescription
    ICSharpCode.SharpDevelop.Dom.AbstractAmbience
    bool IncludeBodiesReplaced by the new IncludeBody property.
    bool IncludeBodyReplaces the IncludeBodies property.
    bool IncludeHTMLMarkupReplaced by the new IncludeHtmlMarkup property.
    bool IncludeHtmlMarkupReplaces the old IncludeHTMLMarkup property.
    bool UseFullyQualifiedMemberNamesReplaced by the new UseFullyQualifiedMemberTypeNames property.
    bool UseFullyQualifiedMemberTypeNamesReplaces the old UseFullyQualifiedMemberNames property.
    String Convert(ModifierEnum)Method removed.
    ICSharpCode.SharpDevelop.Dom.AbstractReturnType
    int TypeArgumentCountReplaces the old TypeParameterCount property.
    int TypeParameterCountReplaced by the new TypeArgumentCount property.
    ICSharpCode.SharpDevelop.Dom.AttributeArgumentType has been removed.
    ICSharpCode.SharpDevelop.Dom.ClassFinderThe constructors now take a ParseInformation type instead of a filename.
    ICSharpCode.SharpDevelop.Dom.ConversionFlags
    IncludeBodiesReplaced by IncludeBody.
    IncludeBodyReplaces IncludeBodies.
    IncludeHTMLMarkupReplaced by IncludeHtmlMarkup.
    IncludeHtmlMarkupReplaces IncludeHTMLMarkup.
    QualifiedNamesOnlyForReturnTypesEnum value removed.
    UseFullyQualifiedNamesReplaced by two new enums UseFullyQualifiedMemberNames and UseFullyQualifedTypeNames.
    UseFullyQualifiedMemberNamesReplaces the UseFullyQualifiedNames enum value.
    UseFullyQualifiedTypeNamesReplaces the UseFullyQualifiedNames enum value.
    ICSharpCode.SharpDevelop.Dom.CSharpExpressionFinder
    int LastExpressionStartPositionProperty has been removed.
    string FindExpressionInternal(string inText, int offset)Method has been removed.
    ctor(string fileName)Constructor now takes a ParseInformation type instead of the filename.
    ICSharpCode.SharpDevelop.Dom.CtrlSpaceResolveHelper
    ResolveResult GetResultFromDeclarationLine(IClass callingClass, IMethodOrProperty callingMember, int caretLine, int caretColumn, string expression)Obsolete method removed. Should use the GetResultFromDeclarationLine method that takes an ExpressionResult instead of a string.
    ICSharpCode.SharpDevelop.Dom.DefaultAttribute
    int CompareTo(IAttribute attribute)Method removed.
    string NameProperty has been removed. The attribute name can now be obtained from the Name property of the AttributeReturnType.
    ICSharpCode.SharpDevelop.Dom.DefaultCompilationUnit
    List<IClass> GetOuterClasses(int caretLine, int caretColumn)Method removed.
    ICSharpCode.SharpDevelop.DomRegion
    int CompareTo(DomRegion region)Method removed and replaced by the Equals method.
    ctor(Location start, Location end)Replaced by the static FromLocation method.
    ICSharpCode.SharpDevelop.ExpressionContext
    bool IsAttributeContextProperty has been removed. Instead compare the ExpressionContext against the ExpressionContext.Attribute.
    ExpressionContext GetAttribute(IProjectContent projectContent)Method removed. To indicate that an expression is an attribute use the ExpressionContext.Attribute type.
    ExpressionContext TypeDerivingFrom(IClass baseClass, bool isObjectCreation)Method now requires an IReturnType instead of an IClass type.
    ICSharpCode.SharpDevelop.ExpressionResult
    ctor(string expression, ExpressionContext context, object tag)Replaced by the new constructor that also takes a DomRegion.
    ctor(string expression, object tag)Replaced by the new constructor that also takes a DomRegion and ExpressionContext.
    ICSharpCode.SharpDevelop.GacAssemblyNameType removed. Code should use the DomAssemblyName instead.
    ICSharpCode.SharpDevelop.GacInterop
    GacAssemblyName FindBestMatchingAssemblyName(GacAssemblyName name)Method now uses DomAssemblyName types.
    GacAssemblyName FindBestMatchingAssemblyName(string name)Method now returns a DomAssemblyName type.
    ICSharpCode.SharpDevelop.GacInterop.AssemblyListEntryType has been removed. Code should now use a DomAssemblyName type.
    ICSharpCode.SharpDevelop.Dom.IAmbience
    String Convert(ModifierEnum)Method removed.
    ICSharpCode.SharpDevelop.Dom.IAttribute
    string NameProperty has been removed. The attribute name can now be obtained from the Name property of the AttributeReturnType.
    ICSharpCode.SharpDevelop.Dom.ICompilationUnit
    List<IClass> GetOuterClasses(int caretLine, int caretColumn) Method removed. Code should now use the IClass's DeclaringType property. For example:
        IClass type = callingClass.DeclaringType; 
    while (type != null) {
    ...
    type = type.DeclaringType;
    }
    ICSharpCode.SharpDevelop.Dom.IProjectContent
    IClass GetClass(string typeName)Method removed. Code should now use GetClass(string typeName, int typeParameterCount) and pass 0 for the typeParameterCount.
    ICSharpCode.SharpDevelop.Dom.IResolver
    ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context)Method changed to use a ParseInformation type instead of a filename.
    ResolveResult Resolve(ExpressionResult expressionResult, int line, int col, string fileName, string fileContent)Method changed to take a ParseInformation type instead of a filename and the line and column parameters have been removed.
    ICSharpCode.SharpDevelop.Dom.IReturnType
    int TypeArgumentCountReplaces the old TypeParameterCount property.
    int TypeParameterCountReplaced by the new TypeArgumentCount property.
    ICSharpCode.SharpDevelop.Dom.MemberLookupHelper
    IMethod FindOverload(IList<IMethod> methods, IReturnType[] typeParameters, IReturnType[] arguments)Method now has an extra out parameter called resultIsAcceptable. This parameter is true if the resulting method is an acceptable match, false if the resulting method is just a guess and will lead to a compile error.
    ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
    bool Initialize(string fileName, int line, int column)Method now takes a ParseInformation type instead of a filename.
    IClass SearchClass(string name)Method now takes an extra Location parameter which is used to search for a class at a particular location in the file. Code that does not need to specify a location should use Location.Empty.
    IReturnType DynamicLookup(string identifier)Method now takes an extra Location parameter. Use Location.Empty if no location can be specified.
    IReturnType SearchMember(IReturnType type, string memberName)Method removed. Instead of using this method call GetMember and then use the IMember's ReturnType.
    IReturnType SearchType(string name)Method now takes an extra Location parameter. Use Location.Empty if no location can be specified.
    ResolveResult Resolve(ExpressionResult expressionResult, int line, int column, string fileName, string fileContent)Method now takes a ParseInformation type instead of the filename. The line and column parameters are no longer required.
    ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context)Method changed to use a ParseInformation type instead of a filename.
    ICSharpCode.SharpDevelop.Dom.ProjectContentRegistry
    IProjectContent GetExistingProjectContent(AssemblyName assembly)Obsolete method has been removed. Use the overloaded method that takes a DomAssemblyName type instead.
    IProjectContent GetExistingProjectContent(string itemInclude, string itemFileName)Method removed. Use the overloaded method that takes a single string parameter.
    ICSharpCode.SharpDevelop.Dom.ReflectionProjectContent
    AssemblyName[] ReferencedAssembliesProperty removed and replaced by the ReferencedAssemblyNames property which returns a list of DomAssemblyNames.
    IList<DomAssemblyName> ReferencedAssemblyNamesReplaces the old ReferencedAssemblies property.

    ICSharpCode.SharpDevelop.Widgets

    TypeDescription
    ICSharpCode.SharpDevelop.Widgets.SideBar.SideTab
    bool IsClipboardRingProperty removed.

    ICSharpCode.SharpDevelop

    TypeDescription
    ICSharpCode.SharpDevelop.ClassBrowserIconService
    int CombineIndexField removed and replaced with SolutionIndex.
    ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.CtrlSpaceCompletionDataProvider
    bool ForceNewExpressionProperty removed.
    ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.SharpDevelopTextEditorProperties
    bool CreateBackupCopyProperty removed.
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.SharpDevelop.FileService
    IWorkbenchWindow GetOpenFile(string fileName)Method now returns an IViewContent type.
    IWorkbenchWindow NewFile(string defaultName, string language, string content)Method now returns an IViewContent type and the language parameter has been removed.
    IWorkbenchWindow OpenFile(string fileName)Method now returns an IViewContent type.
    ICSharpCode.SharpDevelop.Gui.AbstractBaseViewContentClass has been removed.
    ICSharpCode.SharpDevelop.Gui.AbstractSecondaryViewContent
    void NotifyAfterSave(bool successful)Method removed.
    void NotifyBeforeSave()Method removed.
    void NotifyFileNameChanged()Method removed. Code should either use the TitleNameChanged event or the FileNameChanged event on the PrimaryFile.
    ICSharpCode.SharpDevelop.Gui.AbstractViewContent
    event DirtyChangedEvent renamed to IsDirtyChanged.
    string FileNameProperty renamed to PrimaryFileName.
    bool IsUntitledProperty removed. The property has been moved to the OpenedFile type which can be accessed via the view content's PrimaryFile property.
    void Load(string fileName)Method replaced by the Load method that takes an OpenedFile and a Stream.
    void Load(OpenedFile file, Stream stream)New method that is used to open a file in a view. The OpenedFile type contains information about the file, whilst the stream is the actual file data.
    event SavedEvent removed.
    event SavingEvent removed.
    string UntitledNameProperty removed. The untitled name is now stored as the filename.
    ICSharpCode.SharpDevelop.Gui.DefaultWorkbenchClass is now private and no longer publicly accessible.
    ICSharpCode.SharpDevelop.Gui.DriveObjectClass is now private and no longer publicly accessible.
    ICSharpCode.SharpDevelop.Gui.ExtTreeView
    void ApplyViewStateString(string state, TreeView tree)Method moved to the TreeViewHelper class.
    string GetViewStateString(TreeView tree)Method moved to the TreeViewHelper class.
    ICSharpCode.SharpDevelop.Gui.FileListClass is now private and no longer publicly accessible.
    ICSharpCode.SharpDevelop.Gui.IBaseViewContent Interface has been removed. Most of the interface has been moved to the IViewContent interface apart from the following methods which no longer exist.
    void Deselected()
    void Deselecting()
    void Selected()
    void SwitchedTo()
    ICSharpCode.SharpDevelop.Gui.ICanBeDirty
    event DirtyChangedEvent renamed to IsDirtyChanged.
    bool IsDirtyProperty setter is no longer defined in the interface. Only the getter is defined.
    ICSharpCode.SharpDevelop.Gui.IParseableContentInterface removed.
    ICSharpCode.SharpDevelop.Gui.ISecondaryViewContentInterface removed. All secondary view contents now implement the IViewContent interface.
    ICSharpCode.SharpDevelop.Gui.IViewContent
    bool IsDisposedNew property that indicates whether the view has been disposed.
    bool IsUntitledProperty removed. Code should check the PrimaryFile's Untitled property instead.
    event SavedEvent removed.
    bool SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView)New method. Determines whether switching without a Save/Load is supported when switching from this view to another view.
    bool SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView)New method. Determines whether switching without a Save/Load is supported when switching to this view from another view.
    IWorkbenchWindow WorkbenchWindowProperty moved from the now obsolete IBaseViewContent interface. Gives access to the workbench window associated with this view.
    List<ISecondaryViewContent> SecondaryViewContentsProperty now returns an ICollection instead of a list.
    event FileNameChangedEvent moved to the OpenedFile class which is accessible via the PrimaryFile property.
    event SavingEvent removed.
    OpenedFile PrimaryFileNew property that gives access to information about the primary file associated with this view.
    IList<OpenedFile> FilesNew property that returns a list of files that are associated with this view.
    event DisposedNew event raised when the view is disposed.
    event TabPageTextChangedNew event raised when the tab page text at the bottom of the window is changed.
    string FileNameThe filename associated with a view is now accessible from the PrimaryFileName property.
    string TabPageTextGets or sets the tab page text at the bottom of the window. This property was originally on the now obsolete IBaseViewContent interface.
    string UntitledNameProperty removed. The UntitledName is now the same as the PrimaryFileName.
    Control ControlGets or sets the control associated with this view. Originally part of the IBaseViewContent interface.
    void Load(string fileName)Replaced by the Load method that takes an OpenedFile and a Stream.
    void Load(OpenedFile file, Stream stream)New method that is used to open a file in a view. The OpenedFile type contains information about the file, whilst the stream is the actual file data..
    void Save()Replaced by the Save method that takes an OpenedFile and a Stream.
    void Save(string fileName)Replaced by the Save method that takes an OpenedFile and a Stream.
    void Save(OpenedFile file, Stream stream)New method that is used to save a file in a view.
    ICSharpCode.SharpDevelop.Gui.IViewContentMementoClass removed.
    ICSharpCode.SharpDevelop.Gui.IViewContentMementoCreatorClass removed.
    ICSharpCode.SharpDevelop.Gui.IWorkbenchWindow
    IBaseViewContent ActiveViewContentThe ActiveViewContent property is now an IViewContent.
    IViewContent ViewContentProperty removed. Use the ActiveViewContent property instead.
    ICSharpCode.SharpDevelop.IDisplayBinding
    bool CanCreateContentForFile(string fileName)Method now takes an OpenedFile type instead of a string.
    bool CanCreateContentForLanguage(string language)Method removed.
    ICSharpCode.SharpDevelop.ISecondaryDisplayBinding
    ISecondaryViewContent[] CreateSecondaryViewContent(IViewContent view)Method now returns an IViewContent array since the ISecondaryViewContent interface is obsolete.

    ICSharpCode.TextEditor

    TypeDescription
    ICSharpCode.TextEditor.Caret
    Point PositionThe Position property now uses the ICSharpCode.TextEditor.TextLocation type instead of the System.Drawing.Point type.
    Point ValidatePosition(Position pos)The method now returns and uses the ICSharpCode.TextEditor.TextLocation type instead of the System.Drawing.Point type.
    ICSharpCode.TextEditor.Document.Bookmark
    event LineNumberChangedEvent removed.
    ICSharpCode.TextEditor.Document.BookmarkManager
    event ChangedEvent removed.
    ICSharpCode.TextEditor.Document.DefaultFormattingStrategy
    int FormatLine(TextArea textArea, int line, int caretOffset, char charTyped)Method now no longer returns anything.
    ICSharpCode.TextEditor.Document.DefaultTextEditorProperties
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.TextEditor.Document.IDocument
    LineSegment GetLineSegment(int line)The default implementation in the DefaultLineSegment class no longer allows the line number to be equal to the number of items in the IDocument's LineSegment collection.
    Point OffsetToPosition(int offset)Method now returns a TextLocation type.
    int PositionToOffset(Point point)Method now takes a TextLocation type.
    ICSharpCode.TextEditor.Document.IFormattingStrategy
    int FormatLine(TextArea textArea, int line, int caretOffset, char charTyped)Method now no longer returns anything.
    ICSharpCode.TextEditor.Document.ILineManagerInterface has been removed.
    ICSharpCode.TextEditor.Document.ISelection
    bool ContainsPosition(Point point)Method now uses a TextLocation type.
    Point EndPositionProperty now uses a TextLocation type.
    Point StartPositionProperty now uses a TextLocation type.
    ICSharpCode.TextEditor.Document.ITextEditorProperties
    bool CreateBackupCopyProperty removed.
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.TextEditor.Document.LineLengthEventArgsReplaced by the LineLengthChangedEventArgs type.
    ICSharpCode.TextEditor.Gui.InsightWindow.IInsightDataProvider
    char CharTypedProperty removed.
    ICSharpCode.TextEditor.TextEditorControlBase
    event ChangedEvent renamed to TextChanged.
    bool CreateBackupCopyProperty removed.
    bool IsUpdatingProperty removed.
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.TextEditor.ToolTipRequestEventArgs
    Point LogicalPositionProperty now uses a TextLocation type.
    ICSharpCode.TextEditor.Undo.UndoStack
    void CombineLast(int actionCount)Method removed. Similar functionality can be obtained by using the StartUndoGroup and EndUndoGroup method.
    void UndoLast(int actionCount)Method removed. Similar functionality can be obtained by using the StartUndoGroup and EndUndoGroup method.

    How to Port

    This section looks at one way to port an addin. This is the way that the IronPython addin was ported from SharpDevelop 2.2 to SharpDevelop 3.

    Download the source code for SharpDevelop 3.0 and 2.2.

    Copy the source code of your addin to a folder inside SharpDevelop 3.0.

    Run three copies of SharpDevelop, one to view SharpDevelop 2.2's source, one to view SharpDevelop 3.0's source, one with your addin. In the other copies of SharpDevelop you should open SharpDevelop.sln for 2.2 and 3.0. This will allow you to quickly search for SharpDevelop classes when you find your addin code will not compile.

    Compile your code and fix the code or comment it out as you try to work out what is wrong.

    You do not have to convert the project to use .NET 3.5. SharpDevelop itself uses it, but some of the addins do not. With the IronPython addin I needed to change it to use .NET 3.5 since there were some assembly conflicts between what SharpDevelop was using and what the addin was using. Obviously changing to use .NET 3.5 will allow you to use any new features of this framework.

    .NET Framework Differences

    One change in .NET 3.5 caused me a few problems when porting the IronPython addin. This was a change in MSBuild.

    After finally getting the IronPython addin to compile with the modified SharpDevelop 3 assemblies there was an assembly conflict for MSBuild. The ICSharpCode.SharpDevelop assembly now uses MSBuild 3.5 assemblies whilst the IronPython build task project and test project were using MSBuild 2.0. This was fixed by converting these two projects so they use the .NET Framework 3.5. This can be done by opening the project's properties, selecting the Compiling tab, then clicking the Convert Project to C# 3.0 button. In the dialog that opens we select the "Change target framework to .NET 3.5" and click the OK button. After making this change the addin compiled and worked however all the unit tests for the IronPython build tasks were failing with the error:

    System.ArrayTypeMismatchException : Attempted to access an element as a type incompatible with the array.

    The test code that was failing:

    PythonCompilerTask compiler = new PythonCompilerTask(); 
    TaskItem sourceTaskItem = new TaskItem("test.py");
    compiler.Sources = new ITaskItem[] {sourceTaskItem};

    The last line was failing when the unit test was run. The TaskItem type implements the ITaskItem interface so the code should have worked. The reason for this error is that both the build task and build task tests project were referencing the MSBuild libraries just using the reference name.

        <Reference Include="Microsoft.Build.Framework" /> 
    <Reference Include="Microsoft.Build.Tasks" />
    <Reference Include="Microsoft.Build.Utilities" />

    Running MSBuild from the command line the actual assemblies being used were a mix of 3.5 and 2.0 versions.

    "Python.Build.Tasks.csproj": 
    /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\Microsoft.Build.Framework.dll"
    /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Tasks.dll
    /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Utilities.dll

    Printing out the code base of the TaskItem and ITaskItem types whilst running the unit tests showed that the TaskItem was being taken from Microsoft.Build.Utilities assembly version 2.0 and the ITaskItem was being taken the Microsoft.Build.Framework 3.5 assembly. The TaskItem we actually needed was in the new Microsoft.Build.Utilities assembly version 3.5. Changing the project references so they used this new 3.5 assembly fixed the problem.

    Posted Dez 09 2007, 05:28 PM by MattWard with no comments
    Filed under:
  • IronPython AddIn Internals

    This is a tutorial about how to create a language binding for SharpDevelop using the IronPython addin as an example. As well as covering how to create a language binding it will also look at how the addin used IronPython. The source code for the IronPython addin is available at the end of this tutorial.

    The tutorial will cover the following.

    Before You Begin

    The starting point of this tutorial is a basic addin project. Daniel Grunwald created a video tutorial on how to create an addin for SharpDevelop, a transcript of this video is available on the wiki. The information presented in those two links will not be repeated here beyond the following summary.

    • Two copies of SharpDevelop. One for developing and one for testing the addin.
    • A new addin project called PythonBinding. Create this project using the SharpDevelop addin template, remove the TestPad and MyUserControl classes since you will not be using them, and modify the .addin file as shown below.
    • In the project options change the output path for the addin so it builds into a subfolder inside SharpDevelop's AddIn Folder. This will be the copy of SharpDevelop that you are using to test with.
    • Add references to ICSharpCode.SharpDevelop and ICSharpCode.Core. Set Local Copy to false for both of these references.
    <AddIn name="Python Binding" 
    author=""
    description="Backend binding for IronPython">

    <Manifest>
    <Identity name="ICSharpCode.PythonBinding"/>
    </Manifest>

    <Runtime>
    <Import assembly=":ICSharpCode.SharpDevelop"/>
    <Import assembly="PythonBinding.dll"/>
    </Runtime>
    </AddIn>

    Syntax Highlighting

    For syntax highlighting we need a highlighting definition file (.xshd). A good starting point for writing one of these is to look at the existing files provided with SharpDevelop and create your own based on these. One thing that is worth noting is that if you want to be able to comment and uncomment a block of code using SharpDevelop's comment region toolbar button you will need to define a LineComment property in your .xshd file as shown below.

    <Properties> 
    <Property name="LineComment" value="#"/>
    </Properties>

    This .xshd file needs to be added to the PythonBinding project and embedded as a resource in the addin. First create a new folder in the project called Resources and add the .xshd file to the project inside this folder. You can use the .xshd that is provided with the IronPython addin source code or create your own. In the Projects window, right click the added .xshd file, select Properties and change the Build action to EmbeddedResource.

    To tell SharpDevelop about this highlighting definition file we need to add a SyntaxMode element to the PythonBinding.addin file.

    <Path name="/SharpDevelop/ViewContent/DefaultTextEditor/SyntaxModes"> 
    <SyntaxMode id="Python.SyntaxMode"
    extensions=".py"
    name="Python"
    resource="ICSharpCode.PythonBinding.Resources.Python.xshd"/>
    </Path>

    Depending on what the root namespace of your addin project is you may need to modify the value of the resource attribute in the SyntaxMode element. In the example shown above the root namespace is ICSharpCode.PythonBinding.

    File Filters

    When the open file dialog is displayed we want to provide a filter to show only Python files (.py). This is done by adding the following FileFilter element to the PythonBinding.addin file.

     <!-- Add the "Python" entry to the Open File Dialog --> 
    <Path name="/SharpDevelop/Workbench/FileFilter">
    <FileFilter id="Python"
    insertbefore="Resources"
    insertafter="Icons"
    name="Python Files (*.py)"
    extensions="*.py"/>
    </Path>

    The insertbefore and insertafter attributes determine where in the list of file filters the new file filter will be shown.

    Similary when we open a project we want to provide a filter to show only Python project files (.pyproj). As before we add a FileFilter element to the .addin file.

     <!-- Add the "Python" entry to the Open Project Dialog --> 
    <Path name = "/SharpDevelop/Workbench/Combine/FileFilter">
    <FileFilter id="PythonProject"
    insertbefore="AllFiles"
    name="Python Project Files (*.pyproj)"
    class="ICSharpCode.SharpDevelop.Project.LoadProject"
    extensions="*.pyproj"/>
    </Path>

    Project and File Templates

    Create a new folder in your addin project called Templates. Inside here you put file templates (.xft) and project templates (.xpt). For each file you add make sure the Copy to output directory property is set to Always. To change this select the file in the Projects window, right click, select Properties and change the drop down value. When you build your addin the Templates folder and all the templates should be copied to a subdirectory where the addin is built. In order to tell SharpDevelop about these new templates we add the following to the PythonBinding.addin file.

     <!-- File templates --> 
    <Path name="/SharpDevelop/BackendBindings/Templates">
    <Directory id="Python" path="./Templates" />
    </Path>

    Compiling a Project

    SharpDevelop uses MSBuild to compile projects so in order to support compiling a Python project we need to create an MSBuild task and associated .targets file. With the IronPython addin there is a separate Python.Build.Tasks project contains a PythonCompilerTask class and a SharpDevelop.Build.Python.targets file. The Python.Build.Tasks project gets built into the same folder as the addin. The .targets file has its Copy to output directory property set to Always.

    The Python.Build.Tasks project also contains an IPythonCompiler interface and a PythonCompiler class which are essentially empty. These are used to unit test the PythonCompilerTask making sure it correctly sets properties on the compiler.

    The PythonCompilerTask's job is to map from MSBuild types to types the IronPython's PythonCompiler can handle and compile the Python code to an assembly. Compiling with IronPython's PythonCompiler is straightforward, as shown below.

       using (compiler) { 
    // Set what sort of assembly we are generating
    // (e.g. WinExe, Exe or Dll)
    compiler.TargetKind = PEFileKinds.WindowApplication;

    compiler.SourceFiles = new string[] { "Program.py";
    compiler.MainFile = "Program.py";
    compiler.OutputAssembly = "test.exe";

    // Compile the code.
    compiler.Compile();
    }

    The most important part is telling the compiler which file contains the main entry point into the assembly.

    In order to get SharpDevelop to use this new build task we need to do two things, tell SharpDevelop how to find the .targets file and implement a language binding class. The language binding class is used to create the Python project file (.pyproj) which is an MSBuild file. The created .pyproj file references the .targets file via a PythonBinPath property as shown below.

      <Import Project="$(PythonBinPath)\SharpDevelop.Build.Python.targets" /> 

    This PythonBinPath needs to be defined so SharpDevelop can set it before building the project with MSBuild. Otherwise the build will fail. To do this we add a String element to the PythonBinding.addin file.

     <!--  
    Register path to SharpDevelop.Build.Python.targets for the MSBuild engine.
    SharpDevelop.Build.Python.targets is in the PythonBinding AddIn directory
    -->
    <Path name="/SharpDevelop/MSBuildEngine/AdditionalProperties">
    <String id="PythonBinPath" text="${AddInPath:ICSharpCode.PythonBinding}"/>
    </Path>

    Here the PythonBinPath is set so it points to the addin's build folder which is also where the .targets file is stored.

    The language binding is defined by adding a LanguageBinding element to the PythonBinding.addin file.

     <!-- Register Python MSBuild project (.pyproj) --> 
    <Path name="/SharpDevelop/Workbench/LanguageBindings">
    <LanguageBinding id="Python"
    guid="{FD48973F-F585-4F70-812B-4D0503B36CE9}"
    supportedextensions=".py"
    projectfileextension=".pyproj"
    class="ICSharpCode.PythonBinding.PythonLanguageBinding" />
    </Path>

    This defines the Project Guid that will be inserted into the Python MSBuild file (.pyproj), it defines the project file extension (.pyproj) and the class that implements the ILanguageBinding interface (PythonLanguageBinding).

    When you open a Python project SharpDevelop looks through the list of language bindings for one that supports the .pyproj file extension. It finds that the PythonBinding.addin has defined such a language binding and SharpDevelop returns an instance of the PythonLanguageBinding class. This class is responsible for loading an existing Python project and creating a new one. The two main methods of this class are LoadProject and CreateProject. The PythonLanguageBinding implementation of these are shown below.

      public IProject LoadProject(IMSBuildEngineProvider engineProvider, string fileName, string projectName) 
    {
    return new PythonProject(engineProvider, fileName, projectName);
    }

    public IProject CreateProject(ProjectCreateInformation info)
    {
    return new PythonProject(info);
    }

    Both of these methods create a new instance of the PythonProject class which represents the MSBuild project file (.pyproj). Most of the implementation for the PythonProject class is provided for you in by its base class which is SharpDevelop's CompilableProject class. All the PythonProject needs to do is add the import statement for the SharpDevelop.Build.Python.targets file when a new project is created, and when a new file is added to the project its type is set to Compile if its extension is .py. Setting the type to Compile means that a new Python file added to the project will be added inside a Compile element as shown below.

        <Compile Include="Class1.py" /> 

    Only files defined inside a Compile element will be compiled by the Python build task.

    To do all this two methods, GetDefaultItemType and Create, need to be overridden in the PythonProject class as shown below.

      public const string DefaultTargetsFile =
    @"$(PythonBinPath)\SharpDevelop.Build.Python.targets";

    /// <summary>
    /// Returns ItemType.Compile if the filename has a
    /// python extension (.py).
    /// </summary>
    public override ItemType GetDefaultItemType(string fileName)
    {
    if (fileName != null) {
    string extension = Path.GetExtension(fileName);
    if (extension.ToLowerInvariant() == ".py") {
    return ItemType.Compile;
    }
    }
    return base.GetDefaultItemType(fileName);
    }

    protected override void Create(ProjectCreateInformation information)
    {
    base.Create(information);
    AddImport(DefaultTargetsFile, null);
    }

    You will also need to add a reference to ICSharpCode.SharpDevelop.Dom in order to compile the PythonProject class.

    Project Options

    Just adding a language binding and project class there are no project options defined. Some of the existing project options (e.g. BuildEvents, DebugOptions) can be re-used but the Python project still needs custom project options for Application Settings and its Compiling options.

    A custom project option consists of a class derived from the AbstractBuildOptions class and an XML form (.xfrm). The XML form needs to be embedded as a resource and can be designed with SharpDevelop's forms designer. A slightly simplified version of the CompilingOptionsPanel class is shown below.

     public class CompilingOptionsPanel : AbstractBuildOptions 
    {
    public override void LoadPanelContents()
    {
    string resourceName = "ICSharpCode.PythonBinding.Resources.CompilingOptionsPanel.xfrm";
    SetupFromXmlStream(typeof(CompilingOptionsPanel).Assembly.GetManifestResourceStream(resourceName));
    InitializeHelper();

    ConfigurationGuiBinding b = helper.BindString("outputPathTextBox", "OutputPath", TextBoxEditMode.EditRawProperty);
    b.ConnectBrowseFolder("outputPathBrowseButton", "outputPathTextBox", "Choose Folder", TextBoxEditMode.EditRawProperty);

    helper.AddConfigurationSelector(this);
    }
    }

    This class takes the CompilingOptionsPanel XML form, embedded as a resource, reads it and adds any controls defined in it to the options panel control. It then uses a helper class to bind an MSBuild property called OutputPath to a text box. After that it connects a button on the XML form to a browse folder dialog. Finally it adds a configuration selector which means the options dialog will have the Debug/Release drop down at the top allowing the user to configure the MSBuild property in the Release or Debug builds of the project.

    The PythonBinding.addin file then needs to be changed so SharpDevelop is made aware of these new options dialogs for a Python project.

     <!-- Project options panels --> 
    <Path path="/SharpDevelop/BackendBindings/ProjectOptions/Python">
    <DialogPanel id="Application"
    label="Application"
    class="ICSharpCode.PythonBinding.ApplicationSettingsPanel"/>
    <DialogPanel id="BuildEvents"
    label="Build Events"
    class="ICSharpCode.SharpDevelop.Gui.OptionPanels.BuildEvents"/>
    <DialogPanel id="CompilingOptions"
    label="Compiling"
    class="ICSharpCode.PythonBinding.CompilingOptionsPanel"/>
    <DialogPanel id="DebugOptions"
    label="Debug"
    class="ICSharpCode.SharpDevelop.Gui.OptionPanels.DebugOptions"/>
    </Path>

    Note that above we are re-using some of the existing options panels as well as including our custom ones.

    Code Folding

    To support code folding we need to parse the Python code and determine where the folds need to be placed. To parse the code we use the parser provided by IronPython as shown below.

    PythonCompilerSink sink = new PythonCompilerSink(); 
    CompilerContext context = new CompilerContext(fileName, sink);
    Parser parser = Parser.FromString(null, context, fileContent);
    Statement statement = parser.ParseFileInput();

    Here we are using a custom compiler sink (PythonCompilerSink) which is only used to suppress some of the exceptions thrown by IronPython's parser. If no sink is passed into the CompilerContext then IronPython's SimpleParserSink which throws an exception if the parser comes across anywhere errors. If an error occurs whilst parsing a class the parser will not return any class information. Suppressing the exception will mean at least some class information is returned from the parser.

    Now that we have a parser we need to use it from inside SharpDevelop. This is done by implementing the IParser interface. In the IronPython AddIn this interface is implemented in the PythonParser class. The one IParser method that needs to be implemented for folding is the Parse method.

    ICompilationUnit Parse(IProjectContent projectContent, string fileName, string fileContent);

    This method takes the Python source code, parses it and returns a code compile unit, which is a container for the code document object model that SharpDevelop understands.

    The IronPython parser returns an AST statement which needs to be converted to SharpDevelop's code document object model. This is then done by walking the AST using a custom AST walker (PythonAstWalker).

    PythonAstWalker walker = new PythonAstWalker(projectContent); 
    walker.Walk(statement);
    walker.CompilationUnit.FileName = fileName;
    return walker.CompilationUnit;

    The PythonAstWalker walks the AST looking for classes and methods and their positions in the source code which it records. The positions are recorded so SharpDevelop can create folds at the correct locations in the code. Part of the PythonAstWalker class is shown below. Here a class and its location is recorded.

     public class PythonAstWalker : AstWalker 
    {
    DefaultCompilationUnit compilationUnit;
    DefaultClass currentClass;

    public PythonAstWalker(IProjectContent projectContent)
    {
    compilationUnit = new DefaultCompilationUnit(projectContent);
    }

    /// <summary>
    /// Walks the python statement returned from the parser.
    /// </summary>
    public void Walk(Statement statement)
    {
    statement.Walk(this);
    }

    /// <summary>
    /// Walks a class definition.
    /// </summary>
    public override bool Walk(ClassDefinition node)
    {
    DefaultClass c = new DefaultClass(compilationUnit, node.Name.ToString());
    c.Region = new DomRegion(node.Start.Line,
    node.Start.Column + 1,
    node.Start.Line,
    node.Body.Start.Column + 1);
    Location start = node.Body.Start;
    Location end = node.Body.End;
    c.BodyRegion = new DomRegion(start.Line, start.Column + 2, end.Line, end.Column);

    // Save the class.
    compilationUnit.Classes.Add(c);

    // Walk through all the class items.
    currentClass = c;
    node.Body.Walk(this);
    currentClass = null;

    return false;
    }

    SharpDevelop needs to be told about the PythonParser so we add a new Parser element to the PythonBinding.addin.

     <Path name="/Workspace/Parser"> 
    <Parser id="Python"
    supportedextensions=".py"
    projectfileextension=".pyproj"
    class="ICSharpCode.PythonBinding.PythonParser"/>
    </Path>

    We have made our first use of the IronPython assembly so we need to add a reference to it in our project.

    Class View

    Implementing the parser has the side effect that classes and methods now appear in the Class View. The Class View can also display properties, fields, events, base types and method parameters. Information about these items need to be extracted from the AST and added to the code document object model. In the IronPython addin, the current implementation of the PythonAstWalker will add the base type of a class and method parameters to the code dom. Base types are needed for the forms designer when checking if a form can be designed so we will take a quick look at how to do this before moving on to the forms designer itself.

    First we need to modify the PythonAstWalker class. In the Walk(ClassDefinition node) method, shown in the previous section, we add a call to an AddBaseTypes method just before the compilationUnit.Classes.Add.

       AddBaseTypes(c, node.Bases); 

    The AddBaseTypes method looks at each expression in the Bases collection and adds the name of the type to the class.

      void AddBaseTypes(IClass c, IList<Expression> baseTypes) 
    {
    foreach (Expression expression in baseTypes) {
    NameExpression nameExpression = expression as NameExpression;
    FieldExpression fieldExpression = expression as FieldExpression;
    if (nameExpression != null) {
    AddBaseType(c, nameExpression.Name.ToString());
    } else if (fieldExpression != null) {
    AddBaseType(c, fieldExpression.Name.ToString());
    }
    }
    }

    /// <summary>
    /// Adds the named base type to the class.
    /// </summary>
    void AddBaseType(IClass c, string name)
    {
    c.BaseTypes.Add(new SearchClassReturnType(c.ProjectContent, c, 0, 0, name, 0));
    }

    If the base class is fully qualified (i.e. System.Windows.Forms.Form) in the source code then the IronPython parser will return a FieldExpression otherwise it will return a NameExpression.

    Creating a Forms Designer

    Writing a forms designer is fairly complicated mainly because of the number of classes that you need to implement before you can see the designer working in SharpDevelop. The classes needed and their relationships are shown below.

    Forms designer classes

    The secondary display binding is responsible for creating the designer window when a form is open in the text editor. It also creates the forms designer, designer loader provider and designer generator.

    The designer loader provider's only job is to create the designer loader. The designer loader is responsible for reading the form's source code, generating a code DOM from it so the forms designer can display the form.

    The designer generator is responsible for updating the form's InitializeComponent method and generating event handlers.

    Now we will look at creating the Python forms designer. The approach we will take is to do the bare minimum to get things working, using dummy values where required and then building on this base to get everything properly. The first step is to get the loading working.

    First we tell SharpDevelop about our new secondary display binding by adding the following to the PythonBinding.addin file.

     <!-- Python display binding --> 
    <Path name="/SharpDevelop/Workbench/DisplayBindings">
    <DisplayBinding id="PythonDisplayBinding"
    type="Secondary"
    fileNamePattern="\.py$"
    languagePattern="^Python$"
    class="ICSharpCode.PythonBinding.PythonFormsDesignerDisplayBinding" />
    </Path>

    You will need to add a reference to the FormsDesigner.dll and ICSharpCode.TextEditor.dll in order to compile the project. Both of these references should have Local copy set to false.

    You should also indicate to SharpDevelop that the IronPython addin depends on the FormsDesigner addin so if that is missing or disabled the IronPython addin will not be loaded. This is done by adding a Dependency element to the PythonBinding.addin file.

     <Manifest> 
    <Identity name="ICSharpCode.PythonBinding"/>
    <Dependency addin="ICSharpCode.FormsDesigner"/>
    </Manifest>

    You will also need to add the FormsDesigner.dll to the Runtime section of the PythonBinding.addin file.

     <Runtime> 
    <Import assembly=":ICSharpCode.SharpDevelop"/>
    <Import assembly="$ICSharpCode.FormsDesigner/FormsDesigner.dll"/>
    <Import assembly="PythonBinding.dll"/>
    </Runtime>

    The dollar sign indicates to SharpDevelop that it should look in the folder containing the ICSharpCode.FormsDesigner addin.

    Now we need to create four classes: PythonFormsDesignerBinding, PythonDesignerLoaderProvider, PythonDesignerLoader and the PythonDesignerGenerator.

    Our first pass at the PythonFormsDesignerBinding is shown below.

     public class PythonFormsDesignerDisplayBinding : ISecondaryDisplayBinding 
    {
    public PythonFormsDesignerDisplayBinding()
    {
    }

    public bool ReattachWhenParserServiceIsReady {
    get { return false; }
    }

    public bool CanAttachTo(IViewContent content)
    {
    return true;
    }

    public ISecondaryViewContent[] CreateSecondaryViewContent(IViewContent viewContent)
    {
    IDesignerLoaderProvider loader = new PythonDesignerLoaderProvider();
    IDesignerGenerator generator = new PythonDesignerGenerator();
    return new ISecondaryViewContent[] { new FormsDesignerViewContent(viewContent, loader, generator) };
    }

    There are several things wrong with the above class but it is enough to get us started. The CanAttachTo method should only attach to a Python file that contains a form or a user control. At the moment it attaches to any open file. The ReattachWhenParserServiceIsReady should return true. This is because when a large project is opened the parser may take a few seconds to finish parsing and so we may not know if the currently open file actually contains a form or user control. Returning false means we may not be able to get to the design view for a file without closing it and re-opening it. For the current implementation this is not a problem. The CreateSecondaryViewContent method is responsible for creating the forms designer, loader provider and generator. It should also make sure that it has not already created a secondary display binding for the current view but it in the above code this is not done. This is more important when ReattachWhenParserServiceIsReady returns true since we may end up with two designer windows shown. We will come back to these problems later.

    The designer loader provider's only job is to create a designer loader.

     public class PythonDesignerLoaderProvider : IDesignerLoaderProvider 
    {
    public DesignerLoader CreateLoader(IDesignerGenerator generator)
    {
    return new PythonDesignerLoader();
    }
    }

    Our first pass at a designer loader is shown below.

     public class PythonDesignerLoader : CodeDomDesignerLoader 
    {
    PythonProvider codeDomProvider = new PythonProvider();

    public PythonDesignerLoader()
    {
    }

    protected override CodeDomProvider CodeDomProvider {
    get { return codeDomProvider; }
    }

    protected override ITypeResolutionService TypeResolutionService {
    get { return null; }
    }

    protected override CodeCompileUnit Parse()
    {
    return new CodeCompileUnit();
    }

    protected override void Write(CodeCompileUnit unit)
    {
    }
    }
    }

    There are some things wrong with this. The Parse method should parse the source code and generate a code DOM that can be loaded by the forms designer. The Write method should generate the Python code and update the form's source code. Generally this is done by calling the designer generator's MergeFormChanges method. Again we will address these problems later on.

    The PythonDesignerLoader is derived from Microsoft's CodeDomDesignerLoader so you will need to add a reference to System.Design.

    The designer generator implements the IDesignerGenerator interface. For now we implement empty methods.

     public class PythonDesignerGenerator : IDesignerGenerator 
    {
    PythonProvider pythonProvider = new PythonProvider();
    FormsDesignerViewContent viewContent;

    public PythonDesignerGenerator()
    {
    }

    public CodeDomProvider CodeDomProvider {
    get { return pythonProvider; }
    }

    public void Attach(FormsDesignerViewContent viewContent)
    {
    }

    public void Detach()
    {
    }

    public void MergeFormChanges(CodeCompileUnit unit)
    {
    }

    public bool InsertComponentEvent(IComponent component, EventDescriptor edesc, string eventMethodName, string body, out string file, out int position)
    {
    position = 0;
    file = String.Empty;
    return true;
    }

    public ICollection GetCompatibleMethods(EventDescriptor edesc)
    {
    return new ArrayList();
    }

    [Obsolete("This method is not used by the forms designer.")]
    public ICollection GetCompatibleMethods(EventInfo edesc)
    {
    return new ArrayList();
    }
    }

    The MergeFormChanges method should update the form's source code with any changes made in the designer. This method is called when the forms designer is open and the form is saved or when the source code is switched to.

    The InsertComponentEvent method is used to create an empty event handler in the form's source code.

    The GetCompatibleMethods method is used to return methods that are compatible with an event. These methods will then be shown in the Properties window in the drop down list for an event.

    If the IronPython addin is now built you can switch to the forms designer and back again but you will get an error saying that nothing can be designed. To get the forms designer to work we need to return a CodeCompileUnit that contains a form. As a quick test we can use the IronPython's PythonProvider to create a CodeCompileUnit from a hard coded Python source code string.

      protected override CodeCompileUnit Parse() 
    {
    string source = "class MainForm(System.Windows.Forms.Form):\r\n" +
    " def __init__(self):\r\n" +
    " self.InitializeComponent()\r\n" +
    "\r\n" +
    " def InitializeComponent(self):\r\n" +
    " pass\r\n";

    PythonProvider provider = new PythonProvider();
    return provider.Parse(new StringReader(source));
    }

    The above code should be added to the designer loader. Note that we are using the fully qualified name for the base class (System.Windows.Forms.Form) otherwise the designer will not be able to create a form instance to display in the designer. This is something that we will need to fix later.

    Now we will get the generator working. The generator's MergeFormChanges method needs to be called from the loader's Write method. The loader will also need to be told about the generator so we need to change the loader provider as shown below.

     public class PythonDesignerLoaderProvider : IDesignerLoaderProvider 
    {
    public DesignerLoader CreateLoader(IDesignerGenerator generator)
    {
    return new PythonDesignerLoader(generator);
    }
    }

    The loader can now call the generator's MergeFormChanges method.

      IDesignerGenerator generator; 

    public PythonDesignerLoader(IDesignerGenerator generator)
    {
    this.generator = generator;
    }

    protected override void Write(CodeCompileUnit unit)
    {
    generator.MergeFormChanges(unit);
    }

    The generator's MergeFormChanges method needs to get access to the text editor, find the InitializeComponent method, generate the new python source code from the CodeCompileUnit and then update the source code in the text editor.

      FormsDesignerViewContent viewContent; 

    public void Attach(FormsDesignerViewContent viewContent)
    {
    this.viewContent = viewContent;
    }

    public void Detach()
    {
    this.viewContent = null;
    }

    public void MergeFormChanges(CodeCompileUnit unit)
    {
    GeneratedInitializeComponentMethod generatedInitalizeComponentMethod = GeneratedInitializeComponentMethod.GetGeneratedInitializeComponentMethod(unit);
    if (generatedInitalizeComponentMethod == null) {
    throw new InvalidOperationException("InitializeComponent not found in generated code.");
    }

    TextEditorControl textEditor = viewContent.TextEditorControl;
    ParseInformation parseInfo = ParserService.ParseFile(textEditor.FileName, textEditor.Text);
    generatedInitalizeComponentMethod.Merge(textEditor.Document, parseInfo.BestCompilationUnit);
    }

    The MergeFormChanges method locates the InitializeComponent method in the generated code dom and then updates the source code.

      public void Merge(IDocument document, ICompilationUnit compilationUnit) 
    {
    // Get the document's initialize components method.
    IMethod documentInitializeComponentsMethod = GetInitializeComponents(compilationUnit);

    // Generate source code from the code DOM.
    string generatedCode = GenerateCode();
    Console.WriteLine("GeneratedCode: " + generatedCode);

    // Parse the generated source code so we can
    // find the InitializeComponent method. We can
    // only generate code for the entire form.
    IMethod generatedCodeInitializeComponentMethod = GetInitializeComponentFromGeneratedCode(generatedCode);
    string generatedInitializeComponentsMethodBody = GetMethodBody(generatedCodeInitializeComponentMethod, generatedCode);

    // Merge the code.
    DomRegion methodRegion = GetBodyRegionInDocument(documentInitializeComponentsMethod);
    int startOffset = document.PositionToOffset(new Point(methodRegion.BeginColumn - 1, methodRegion.BeginLine - 1));
    int endOffset = document.PositionToOffset(new Point(methodRegion.EndColumn - 1, methodRegion.EndLine - 1));
    document.Replace(startOffset, endOffset - startOffset, generatedInitializeComponentsMethodBody);
    }

    The GeneratedInitializeComponentMethod's Merge method is shown above, for the other methods take a look at the actual class since there is too much code to go through all of it here. The Merge method locates the InitializeComponent method in the text editor, generates the new Python code, locates the InitializeComponent method in the generated code and then updates the source code. The Python code is generated using the IronPython's PythonProvider's GenerateCodeFromType method.

    The Python forms designer will now generate the correct code and update the text editor so let us return to the loader and get it working with the actual source code. First we need to pass the source code document to the designer loader provider so we update the PythonFormsDesignerDisplayBinding class.

      public ISecondaryViewContent[] CreateSecondaryViewContent(IViewContent viewContent) 
    {
    TextEditorControl textEditor = ((ITextEditorControlProvider)viewContent).TextEditorControl;
    IDesignerLoaderProvider loader = new PythonDesignerLoaderProvider(textEditor.Document);
    IDesignerGenerator generator = new PythonDesignerGenerator();
    return new ISecondaryViewContent[] { new FormsDesignerViewContent(viewContent, loader, generator) };
    }

    The designer loader provider just passes the document onto the designer loader as shown below.

     public class PythonDesignerLoaderProvider : IDesignerLoaderProvider 
    {
    IDocument document;

    public PythonDesignerLoaderProvider(IDocument document)
    {
    this.document = document;
    }

    public DesignerLoader CreateLoader(IDesignerGenerator generator)
    {
    return new PythonDesignerLoader(document, generator);
    }
    }

    Now we can get the source code from the document in the designer loader.

     public class PythonDesignerLoader : CodeDomDesignerLoader 
    {
    PythonProvider codeDomProvider = new PythonProvider();
    IDesignerGenerator generator;
    IDocument document;

    public PythonDesignerLoader(IDocument document, IDesignerGenerator generator)
    {
    this.document = document;
    this.generator = generator;
    }

    protected override CodeCompileUnit Parse()
    {
    PythonProvider provider = new PythonProvider();
    return provider.Parse(new StringReader(document.TextContent));
    }

    The designer loader will work with simple forms as long as the base class is fully qualified so let's fix that.

      protected override CodeCompileUnit Parse() 
    {
    PythonProvider provider = new PythonProvider();
    CodeCompileUnit unit = provider.Parse(new StringReader(document.TextContent));
    FixCompileUnit(unit);
    return unit;
    }

    void FixCompileUnit(CodeCompileUnit unit)
    {
    CodeTypeDeclaration formClass = FindForm(unit);
    FullyQualifyBaseType(formClass);
    }

    static void FullyQualifyBaseType(CodeTypeDeclaration type)
    {
    CodeTypeReference reference = type.BaseTypes[0];
    if (reference.BaseType == "Form") {
    reference.BaseType = "System.Windows.Forms.Form";
    }
    }

    The code above finds the form and then changes the base type so it is fully qualified.

    If we add a control to the form, such as a button, and close and re-open the designer, the loader will fail with a CodeDomSerializerException saying that the variable 'button1' is undeclared. The problem here is that the forms designer needs the button1 field to be added to the code DOM. This is not done by IronPython's PythonProvider class. In the IronPython addin the code DOM generated by the PythonProvider is altered so it can be loaded by the forms designer in the PythonDesignerCodeDomGenerator class. We will not look at that class in detail here but instead return to the other problems we skipped over at the start with the PythonFormsDesignerDisplayBinding class. First the CanAttach method needs to attach only to Python files that are designable. This is done by using the IsDesignable method of the forms designer.

      public bool CanAttachTo(IViewContent content) 
    {
    ITextEditorControlProvider textEditorControlProvider = content as ITextEditorControlProvider;
    if (textEditorControlProvider != null) {
    string fileName = GetFileName(content);
    if (fileName != null && IsPythonFile(fileName)) {
    ParseInformation parseInfo = ParserService.ParseFile(fileName, textEditorControlProvider.TextEditorControl.Text, false);
    return FormsDesignerSecondaryDisplayBinding.IsDesignable(parseInfo);
    }
    }
    return false;
    }

    /// <summary>
    /// Gets the filename from the view content. This method
    /// takes into account the fact that the view content may
    /// be untitled.
    /// </summary>
    string GetFileName(IViewContent viewContent)
    {
    if (viewContent.IsUntitled) {
    return viewContent.UntitledName;
    }
    return viewContent.FileName;
    }

    /// <summary>
    /// Checks the file's extension represents a python file.
    /// </summary>
    static bool IsPythonFile(string fileName)
    {
    string extension = Path.GetExtension(fileName);
    if (extension != null) {
    return extension.ToLower() == ".py";
    }
    return false;
    }

    If we set ReattachWhenParserServiceIsReady to true then the CreateSecondaryViewContent method will need to make sure that it has not already added a forms designer window otherwise we may get two windows added. This can be done by adding the following code at the start of the CreateSecondaryViewContent method.

       foreach (ISecondaryViewContent existingView in viewContent.SecondaryViewContents) { 
    if (existingView.GetType() == typeof(FormsDesignerViewContent)) {
    return new ISecondaryViewContent[0];
    }

    That finishes the overview of how to implement a forms designer, now onto code completion.

    Code Completion

    Code completion is a huge area so we will not cover everything here. The IronPython addin itself only has limited support for code completion, so we will limit ourselves to looking at code completion for import statements and static types. The main classes involved when implementing code completion are shown below.

    Code completion classes diagram

    The DefaultCodeCompletionBinding is the starting point for code completion support. It is responsible for enabling and disabling different types of code completion (e.g. method completion, comment completion). It handles key presses in the text editor, calls the appropriate completion data provider and shows the code completion window.

    The PythonCodeCompletionBinding derives from the DefaultCodeCompletionBinding and adds support for the keywords "from" and "import". This support means that when a space character is typed in after a keyword the CtrlSpaceCompletionProvider is used to show a completion list.

    The CodeCompletionDataProvider returns a list of code completion items at the current location.

    The MethodInsightDataProvider provides information about overloaded methods when the opening bracket of a method is typed in.

    The IndexerInsightDataProvider provides a list of items, for example attribute names, after an opening square bracket is typed in.

    The CtrlSpaceCompletionDataProvider is derived from the CodeCompletionDataProvider class and provides completion when the user presses Ctrl+Space or types in a keyword followed by a space character.

    The ExpressionFinder locates the expression before and around the current cursor position. By default the text editor provides a basic expression finder which can be used whilst you are first implementing code completion. Often you will find you need more control over finding the expression so a custom expression finder can be created. The IronPython addin implements its own custom expression finder.

    The Resolver takes the expression returned from the ExpressionFinder and tries to determine what the expression actually is (e.g. is it a method, a class or a namespace). When it has worked this out it returns a resolve result which can then be used to generate a list of completion data. There is no default implementation for the resolver so the IronPython addin implements its own in the PythonResolver class.

    The starting point for adding code completion is to create a class that implements the ICodeCompletionBinding interface.

     public interface ICodeCompletionBinding 
    {
    bool HandleKeyPress(SharpDevelopTextAreaControl editor, char ch);
    }

    The DefaultCodeCompletionBinding class implements that interface but it also implements a lot of code completion functionality which saves us from doing the same. In the IronPython addin's case we have the PythonCodeCompletionBinding class that derives from the DefaultCodeCompletionBinding. Before we look at this class it is worth noting that it might have been possible to just create a class that implements the ICodeCompletionBinding interface and uses Python's dir function to get basic code completion working. When you are using the interactive interpreter you can type in things like "dir()" or "dir(System.Console)" to see the items that can be used at that point. For now this is left as an exercise for the reader.

    To tell SharpDevelop that we have a code completion binding we add a new CodeCompletionBinding element to the PythonBinding.addin file.

     <!-- The Python code completion binding --> 
    <Path name = "/AddIns/DefaultTextEditor/CodeCompletion">
    <CodeCompletionBinding id="Python"
    extensions=".py"
    class="ICSharpCode.PythonBinding.PythonCodeCompletionBinding"/>
    </Path>

    The first thing we will do is look at how to get code completion when you type in an import statement. To do this the PythonCodeCompletionBinding needs to handle the import keyword, generate the list of imports and then show the list in a window.

     public class PythonCodeCompletionBinding : DefaultCodeCompletionBinding 
    {
    public PythonCodeCompletionBinding()
    {
    }

    /// <summary>
    /// Shows the code completion window if the keyword is handled.
    /// </summary>
    /// <param name="word">The keyword string.</param>
    /// <returns>true if the keyword is handled; otherwise false.</returns>
    public override bool HandleKeyword(SharpDevelopTextAreaControl editor, string word)
    {
    if (word != null) {
    switch (word.ToLowerInvariant()) {
    case "import":
    case "from":
    CtrlSpaceCompletionDataProvider dataProvider = new CtrlSpaceCompletionDataProvider(ExpressionContext.Importable);
    editor.ShowCompletionWindow(dataProvider, ' ');
    return true;
    }
    }
    return false;
    }
    }

    The CtrlSpaceCompletionDataProvider will ask SharpDevelop for the parser that handles the current file extension and then it will ask the parser for a resolver. The CtrlSpaceCompletionDataProvider will then call the resolver's CtrlSpace method. So our PythonParser needs to return a resolver.

      public IResolver CreateResolver() 
    {
    return new PythonResolver();
    }

    The PythonResolver class implements the IResolver interface and for now we will return a array of strings back from the CtrlSpace method.

     public class PythonResolver : IResolver 
    {
    public PythonResolver()
    {
    }

    public ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent)
    {
    return null;
    }

    public ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context)
    {
    ArrayList results = new ArrayList();
    results.Add("a");
    results.Add("b");
    results.Add("c");
    return results;
    }
    }

    If the IronPython addin is now compiled you will see the list a, b, c is displayed when typing in the space character after an import statement. To get the correct items in the list we can use the IProjectContent's AddNamespaceContents method as shown below.

      public ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context) 
    {
    ArrayList results = new ArrayList();
    ParseInformation parseInfo = ParserService.GetParseInformation(fileName);
    ICompilationUnit compilationUnit = parseInfo.MostRecentCompilationUnit;
    compilationUnit.ProjectContent.AddNamespaceContents(results, String.Empty, compilationUnit.ProjectContent.Language, true);
    return results;
    }

    When we now press the space character we get better results. Now let us look at getting simple code completion when you type in the dot character after the namespace in an import statement.

    If you look at the DefaultCodeCompletionBinding's HandleKeyPress method you will see that by default when the dot character is typed in the CodeCompletionDataProvider is used to get a list of items for code completion. This code is shown below.

        case '.': 
    if (enableDotCompletion) {
    editor.ShowCompletionWindow(new CodeCompletionDataProvider(), ch);
    return true;
    } else {
    return false;
    }

    The CodeCompletionDataProvider will look for an ExpressionFinder for the current file extension, this will fail since we do not currently have one for Python files, then it will use the TextUtilities GetExpressionBeforeOffset method instead to get the expression before the cursor. This saves us from writing our own expression finder initially but you may well find that it is not good enough and you will probably want to write your own. The IronPython addin has its own custom expression finder because this is the only way to set the expression context type (i.e. is the expression an import or namespace) is at creation in the expression finder class. The expression returned from the TextUtilities class is then passed to the resolver's Resolve method. The resolver then needs to work out what the expression refers to and return code completion information. For now we will cheat and pretend that the expression resolves to the System namespace.

      public ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent) 
    {
    return new NamespaceResolveResult(null, null, "System");

    The NamespaceResolveResult used above will return a list of code completion items for the specified namespace. Now when we type in "import System" followed by the dot character we get code completion for the System namespace. What we need to do is check that the expression is an import statement and work out the namespace being used. The correct way of doing this is shown below.

      public ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent) 
    {
    // Search for a namespace.
    string ns = GetNamespaceExpression(expressionResult.Expression);
    if (!String.IsNullOrEmpty(ns)) {
    return new NamespaceResolveResult(null, null, ns);
    }
    return null;
    }

    static string GetNamespaceExpression(string expression)
    {
    string ns = GetNamespaceExpression("import ", expression);
    if (ns == null) {
    ns = GetNamespaceExpression("from ", expression);
    }

    if (ns != null) {
    return ns;
    }
    return expression;
    }

    /// <summary>
    /// Removes the "import " or "from " part of a namespace expression if it exists.
    /// </summary>
    static string GetNamespaceExpression(string importString, string expression)
    {
    int index = expression.IndexOf(importString, StringComparison.InvariantCultureIgnoreCase);
    if (index >= 0) {
    return expression.Substring(index + importString.Length);
    }
    return null;
    }

    Now we get completion results for namespaces after an import statement. A side effect of this code is that we also get code completion inside a class when we type in the dot character after a namespace. Let'ss take a look at getting a list of methods for a type such as System.Console. To do this we need to alter the Resolve method so that it looks for a type.

       // Search for a class name. 
    ParseInformation parseInfo = ParserService.GetParseInformation(fileName);
    ICompilationUnit compilationUnit = parseInfo.MostRecentCompilationUnit;
    IClass matchingClass = compilationUnit.ProjectContent.GetClass(expressionResult.Expression);
    if (matchingClass != null) {
    return new TypeResolveResult(null, null, matchingClass);
    }

    First we look for the parse information for the current file, then from the project content we try to match the expression against a class. If this matches we return a TypeResolveResult which will provide the completion data. Note that the code above will only work with fully qualified class names, so typing in Console followed by the dot character will not work, but typing in System.Console will work.

    Well, that is a quick introduction to code completion, for more information take a look at the code for the addins that support code completion (e.g. IronPython addin, Boo Binding addin code, and CSharpBinding addin).

    Code Conversion

    How you implement code conversion will depend on what functionality the parser you are using can give you. With IronPython we can generate code from Microsoft's code DOM so we need a way of converting from SharpDevelop's code DOM to Microsoft's. Microsoft's code DOM does not support all the features of many languages, Python included, so the code conversion will be limited in what it can produce.

    The IronPython addin currently only supports converting code from C# and VB.NET to Python. When the menu option to convert code to Python is selected, the code in the text editor is parsed using the C# or VB.NET parsers, a SharpDevelop code DOM generated, this code DOM is converted to Microsoft's code DOM and then IronPython's PythonProvider is used to generate Python code which is displayed in a new text editor window.

    For now we will look at converting from C# to Python. The code in the IronPython addin will be slightly different since it handles both C# and VB.NET, but since both of these languages will produce a SharpDevelop code DOM the basic concepts will be useful for either of these languages. First we need to add a new menu item for the conversion.

     <Path name="/SharpDevelop/Workbench/MainMenu/Tools/ConvertCode"> 
    <Condition name="ActiveContentExtension" activeextension=".cs" action="Disable">
    <MenuItem id="ConvertToPython"
    insertafter="CSharp"
    insertbefore="VBNet"
    label="Python"
    class="ICSharpCode.PythonBinding.ConvertToPythonMenuCommand"/>
    </Condition>
    </Path>

    This menu item is only enabled when the open file has a C# file extension. The menu class is straightforward. It takes the code, creates a CSharpToPythonConverter class and shows the generated Python code.

     public class ConvertToPythonMenuCommand : AbstractMenuCommand 
    {
    public override void Run()
    {
    // Get the code to convert.
    IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveWorkbenchWindow.ViewContent;
    IEditable editable = viewContent as IEditable;

    // Generate the python code.
    CSharpToPythonConverter converter = new CSharpToPythonConverter();
    string pythonCode = converter.Convert(editable.Text);

    // Show the python code in a new window.
    FileService.NewFile("Generated.py", "Python", pythonCode);
    }
    }

    The CSharpToPythonConverter class uses the NRefactory library so the addin project needs a reference to ICSharpCode.NRefactory. The CSharpToPythonConverter class is fairly large so we will only look at a few of the methods. It works by creating a SharpDevelop code DOM from the source code, walking this code DOM and converting it to Microsoft's code DOM, and then generating Python code using IronPython's PythonProvider.

     public class CSharpToPythonConverter : IAstVisitor 
    {
    public CSharpToPythonConverter()
    {
    }

    /// <summary>
    /// Converts the C# source code to Python.
    /// </summary>
    public string Convert(string source)
    {
    CodeCompileUnit pythonCodeCompileUnit = ConvertToCodeCompileUnit(source);

    // Convert to Python source code.
    return ConvertCodeCompileUnitToPython(pythonCodeCompileUnit);
    }

    /// <summary>
    /// Converts the C# source code to a code DOM that
    /// the PythonProvider can use to generate Python. Using the
    /// NRefactory code DOM (CodeDomVisitor class) does not
    /// create a code DOM that is easy to convert to Python.
    /// </summary>
    public CodeCompileUnit ConvertToCodeCompileUnit(string source)
    {
    // Convert to code DOM.
    CompilationUnit unit = GenerateCompilationUnit(source);

    // Convert to Python code DOM.
    return (CodeCompileUnit)unit.AcceptVisitor(this, null);
    }

    public CompilationUnit GenerateCompilationUnit(string source)
    {
    using (IParser parser = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StringReader(source))) {
    parser.Parse();
    return parser.CompilationUnit;
    }
    }

    public static string ConvertCodeCompileUnitToPython(CodeCompileUnit codeCompileUnit)
    {
    PythonProvider pythonProvider = new PythonProvider();
    StringWriter writer = new StringWriter();
    CodeGeneratorOptions options = new CodeGeneratorOptions();
    options.BlankLinesBetweenMembers = false;
    options.IndentString = "\t";
    pythonProvider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, options);
    return writer.ToString().Trim();
    }

    The code DOM conversion is initiated in the ConvertToCodeCompileUnit method. The AcceptVisiter call causes the SharpDevelop code DOM to be visited part by part. The CSharpToPythonConverter class implements the IAstVisiter interface so as each part of the code DOM is visited the corresponding method on the IAstVisiter class is called.

    The NRefactory library already provides a CodeDomVisitor class that will generate a Microsoft code DOM from SharpDevelop's code DOM. Unfortunately the code DOM generated by this class needed some changes before the PythonProvider would generate good Python code so a custom converter class was created.

    That is the end of this tutorial on language bindings. The full source code for the IronPython addin is available at the end of this tutorial. Instructions on how to compile the addin are in the next section.

    Compiling the IronPython AddIn from Source

    1. Download SharpDevelop's source code.
    2. Extract SharpDevelop's source code from the zip file.
    3. Build SharpDevelop by running either src\debugbuild.bat or src\releasebuild.bat.
    4. Extract the IronPython addin's source code from IronPythonAddIn-0.2.1.src.zip to the folder src\AddIns\BackendBindings.
    5. Open a command prompt and navigate to the Python addin folder src\AddIns\PythonBinding containing the source code just extracted.
    6. Run msbuild PythonBinding.sln to compile the code.
    Posted Nov 18 2007, 03:54 PM by MattWard with no comments
    Filed under:
  • IronPython Integration In SharpDevelop 2.2

    Support for IronPython 1.1 is now available for SharpDevelop 2.2.1.2648. The IronPython addin is an early alpha release and is not an official part of SharpDevelop 2.2.1 so it is available as a separate download at the end of this post.

    The addin will not work with SharpDevelop 3.0 nor IronPython 2.0.

    Features

    • Code folding
    • Syntax highlighting
    • File and project templates for Console and Windows Forms applications
    • Code completion (limited)
    • Windows Forms designer
    • C# and VB.NET code conversion to Python

    Please note that code completion, the forms designer and code conversion all need a lot more work.

    Creating a Windows Application

    Open up the new project dialog by selecting New then Solution from the File menu. Selecting the Python category will show two project templates. One will create a Windows console application and the other will create a Windows Forms application.

    New Python Project Dialog

    Once you have created a new project you will then need to add a reference to the IronPython assembly. Open the Projects window by selecting Projects from the View menu. In the Projects window, right click the project and select Add Reference. Select the .NET Assembly Browser tab and click the Browse button. Then browse to IronPython.dll. The addin includes this file so can either extract it from there or if you installed it from the sdaddin file then you should be able to find the IronPython.dll in the folder:

     

    C:\Documents and Settings\[YourUserName]\Application 
    Data\ICSharpCode\SharpDevelop2.1\AddIns\ICSharpCode.PythonBinding

    To build the application select Build Solution from the Build menu.

    The built executables cannot be run with the debugger so instead select Run without debugger from the Debug menu. If you are running a windows app and nothing seems to happen then open a command line window and run it from there. This way you should see any errors reported from the IronPython runtime.

    There are a few file templates which can be added to the project as shown below.

    Designing Windows Forms

    The Windows Forms designer is still in its early stages so please be warned that it may break the form's code or worse. Most of the Windows Forms controls work with the designer but things like adding columns to a list view will generate code that does not compile.

    The designer can be opened in the usual way by opening the form in the text editor and selecting the Design tab at the bottom of the text editor.

    Python main form before opening the designer

    Once open in the designer you can add controls to the form in the usual way from the Tools window. In the screenshot below a label, text box and a button have been added.

    Main form designed in designer

    Click the Source tab to view the generated code in the InitializeComponents method.

    Generated form code

    Code Folding

    Code folding allows you to collapse regions of a class.

    Folded python code

    Code Completion

    Code completion is very limited currently. If you type a space after an import statement then you will get a list of namespaces available.

    Import code completion

    Code completion for classes is one area which has the most limited support. If you open Program.py you can get code completion for static classes such as System.Console but it does not work everywhere.

    Console.WriteLine code completion

    Method insight for Python

    Code Conversion

    To convert VB.NET or C# to Python open the file you want to convert and then select Convert code to Python from the Tools menu.

    Convert code to Python menu item

    The code conversion is limited to classes so it will not convert an arbitary piece of code that is not inside a class. A C# class being converted to Python is shown below.

    C# code before converting to Python

    Converted Python code

    The code conversion is still at an early stage of development so it will fail on complicated classes.

    Class View

    Classes in the open solution will be displayed in the Class browser (Select Classes from the View menu).

    Python class in Classes window

    From there you can double click a class or method and the text editor will display the corresponding code.

    Standalone Python Files

    The addin has support for standalone Python files. If you open a file with a .py file extension then a Python menu will appear.

    Python menu items

    From this menu you can run ipy.exe and have it execute the file. Any output from the Python script will be shown in the Output window.

    By default the ipy.exe run is the one that ships with the addin. You can choose another IronPython console by select Options from the Tools menu. Selecting the Python option allows you to choose another IronPython console.

    Python options dialog

    Installing the IronPython AddIn

    1. Rename the IronPythonAddIn-0.2.1.zip file to IronPythonAddIn-0.2.1.sdaddin.
    2. From the Tools menu select AddIn Manager .

      Tools AddIn Manager menu item

    3. Click the Install AddIn button.

      AddIn Manager dialog

    4. In the Open File Dialog browse to the IronPythonAddIn-0.2.1.sdaddin file and click the Open button.

      AddIn installed confirmation dialog.

    5. Click the Close button.
    6. Restart SharpDevelop.

    Future Work

    Since SharpDevelop 2.2.1.2648 is the last release in the 2.x branch the next release of the IronPython addin will be for SharpDevelop 3.0 and it will support IronPython 2.0.

    Python Links

    Some of the Python tutorials and links used whilst creating the IronPython addin.

    1. Python documentation by Guido van Rossum the author of Python.
    2. Dive into Python book by Mark Pilgrim. The full text is available online.
    3. IronPython homepage.

     

     

    Posted Okt 21 2007, 05:02 PM by MattWard with 10 comment(s)
    Filed under:
  • New Features in SharpDevelop 2.1

    Here is the list of features that have been added to SharpDevelop 2.1.

    FeatureDescription
    Code Analysis with FxCopAnalyse your code with FxCop inside SharpDevelop.
    Code Completion for Different Frameworks
    When a particular .NET framework is targeted by a project you will get code completion for that framework. Currently .NET 1.1, Mono 2.0 and Compact Framework 2.0 are supported.
    Code Navigation HistoryAllows you to quickly navigate backwards and forwards through visited code.
    Compact Framework SupportNew templates and code completion for the Compact Framework have been added.
    Component InspectorSharpDevelop includes the .NET Component Inspector created by Oakland Software. The Component Inspector allows you to explore any type in an assembly or COM component, create an instance of that type, execute its methods, change its properties, and monitor its events.
    Custom Tool SupportA custom tool is a component that is run every time a particular file is saved. SharpDevelop ships with a ResXFileCodeGenerator custom tool that can be used to automatically generate a class that provides strongly typed access to a resource file. An addin can also extend SharpDevelop with its own custom tools.
    Edit Solution ConfigurationYou can now add, remove and rename solution build configurations.
    Go to XML Schema DefinitionWhilst editing an XML document with an associated schema you can quickly navigate to the schema definition of the currently selected element, attribute or attribute value.
    Hosting SharpDevelop - SharpDevelop for ApplicationsYou can now host SharpDevelop inside your own application. This allows you to create your own IDE with support for addins without having to write the code from scratch.
    Incremental SearchYou can now search the active document incrementally. As you type in each character the document is searched from the current cursor position and the first match is highlighted.
    Resource ToolkitThe resource toolkit provides code completion and tooltips for string resources when localizing an application.
    SQL QueriesSharpDevelop includes a simple SQL query tool that allows you to run SQL queries and view the results.
    Standalone SharpDevelop ReportsSharpDevelop Reports is now available as a standalone application as well as being integrated inside SharpDevelop.
    Subversion IntegrationSharpDevelop integrates with TortoiseSVN to provide support for Subversion.
    Testing with .NET 1.1If your unit test project targets .NET 1.1 then the tests will be run under this framework.
    WiX IntegrationYou can create WiX setup packages and design WiX dialogs with SharpDevelop's forms designer.
    XML Tree EditorYou can view and edit XML in a tree based editor similar to Microsoft's XML Notepad.
    XPath QueriesYou can run xpath queries on the active XML document and the matched items will be highlighted in the text editor.
  • Creating an Installer with SharpDevelop

    This tutorial will walk you through creating an installer from scratch using the integrated WiX support included with SharpDevelop 2.1.

    The installer that will be created is a real-world example and was used by Christoph Wille as the starting point for his Code Comment Checking Policy for Visual Studio Team System installer.

    Please note that you will need a recent version of SharpDevelop 2.1 to follow this walkthrough. In beta 3 any changes made to the WiX project's options are incorrectly encoded on saving. This prevents the project from being compiled.

    Installer Requirements

    First, what does our installer need to do?

    ItemDescription
    cccppol.dll (.NET assembly) To be installed in Program Files.
    ICSharpCode.CCCPLib.dll (.NET assembly) To be installed in the GAC
    ICSharpCode.NRefactory.dll (.NET assembly) To be installed in the GAC
    ICSharpCode.SharpDevelop.Dom.dll (.NET assembly) To be installed in the GAC
    Registry key: HKLM \ SOFTWARE \ Microsoft \ VisualStudio \ 8.0 \ TeamFoundation \ SourceControl \ Checkin Policies

    Registry value name: cccppol

    The registry value cccppol should contain the full path to the cccppol.dll assembly.
    License Information The installer should show the license information in one of its dialogs.
    Installation Directory The installer should allow the user to select where the cccppol file is to be installed. The default should be "C:\Program Files\Cccp Policy"

    It has four .NET assemblies to install. Three of which are to be registered in the GAC. It needs to create one registry key that points to the cccppol.dll file. The installer UI needs to show license information and allow you to choose the directory where the cccppol.dll file will be installed. Now let's create our installer.

    Creating a New Setup Project

    To create a new WiX setup project, from the File menu, select New and then select Solution....

    Menu option to create a new solution

    This opens the New Project dialog box.

    New Project dialog

    Select the Setup category and then select Empty Setup Project from the list of available templates. We are going to use the empty project and try to keep the setup as simple as possible using as much of the functionality that WiX provides for us. Give the setup project a name and choose a location for it to be saved. Finally click the Create button.

    The empty setup project contains very little, just one Setup.wxs file, which is where we will start. At the moment the project will not compile, so let us fix that. First give your product a name and a manufacturer.

     <Product Id="8F3A52FE-BB54-4BC9-953C-7173D16AA96D" 
    Name="Code Comment Checking Policy"
    Language="1033"
    Version="1.0.0.0"
    Manufacturer="ic#code">

    Then give your package a description.

     <Package Id="A970C90A-3EFD-4121-B92D-2D0454643B38" 
    Description="Installs Code Comment Checking Policy for VSTS"
    Comments="Comments"
    InstallerVersion="200"
    Compressed="yes"
    />

    Preparing to Add Files

    Before we add the four files to the installer we need to add a few extra bits to Setup.wxs. First we add a media element to the product.

    <!-- 
    Source media for the installation.
    Specifies a single cab file to be embedded in the installer's .msi.
    -->
    <Media Id="1" Cabinet="contents.cab" EmbedCab="yes" CompressionLevel="high"/>

    Here we are specifying that the files should be embedded in the installer (.msi) and we want the compression to be high. Now we create a directory element, as highlighted below, so you should end up with a Setup.wxs file that looks like:

    <Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi"> 
    <Product Id="8F3A52FE-BB54-4BC9-953C-7173D16AA96D"
    Name="Code Comment Checking Policy"
    Language="1033"
    Version="1.0.0.0"
    Manufacturer="ic#code">
    <Package Id="A970C90A-3EFD-4121-B92D-2D0454643B38"
    Description="Installs Code Comment Checking Policy for VSTS"
    Comments="Comments" InstallerVersion="200"
    Compressed="yes" />
    <!--
    Source media for the installation.
    Specifies a single cab file to be embedded in the installer's .msi.
    -->
    <Media Id="1" Cabinet="contents.cab" EmbedCab="yes" CompressionLevel="high" />
    <!-- Installation directory and files are defined in Files.wxs -->
    <Directory Id="TARGETDIR" Name="SourceDir"/>

    </Product>
    </Wix>

    The SourceDir directory does not get created on installation. It is just the name used to identify where in the WiX XML to look for the files and directories that will be installed. Which is what we will now look at.

    We are going to create a new WiX file (Files.wxs) that will contain information about the files to be installed. Open the Project Explorer, if it is not already open, by selecting Projects from the View menu.

    View projects menu option

    In the Projects Explorer select the name of your project, right click, select Add and then New Item....

    Menu option to add a new file to a project

    This opens the New File dialog box.

    New File Dialog

    Select the Setup category, and then select Empty Setup Document from the list of available templates. Type in the name Files.wxs and then click the Create button to add the new file to your project.

    Now in order to make SharpDevelop aware that we want to put the file information in Files.wxs we need to make a reference to the SourceDir directory element that we added to Setup.wxs. If we do not do this then when we add files using the Setup Files Editor the file information will be added to the Setup.wxs file inside the SourceDir directory element. So modify Files.wxs so it looks like:

    <?xml version="1.0"?> 
    <Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
    <Fragment>
    <DirectoryRef Id="TARGETDIR">
    </DirectoryRef>
    </Fragment>
    </Wix>

    Here we have referenced the SourceDir directory element by referring to its Id TARGETDIR. Now save the Files.wxs file and close it. We are now ready to add some files.

    Adding Files

    Open up the Setup Files editor, from the View menu select Setup and then select Files.

    We first need to create the Program Files directory. The directories, when we create them, will be displayed in the left hand window. Right click in the left hand window, select Add and then select Directory.

    On the right hand side of the Setup Files window, fill in the details as shown below.

    Here we have specified the Program Files folder by using the special ProgramFilesFolder id. In this case the Name and Long Name of the directory do not matter and are not used when installing. If you are creating a custom folder, which is what we will do next, you will need to specify these values.

    Now we need a Cccp Policy directory. This could be added as we did previously, but we can add a new directory and the files in one go to save us a lot of time. Here I made a bin folder and copied into it the four files that will be a part of our installer.

    From the Setup Files window, we can add the bin folder to the installer by right clicking Program Files and selecting Add Directory....

    Browse to the bin folder, select it and click the OK button.

    If you expand the bin folder just added you will see that the files have been added each inside their own component.

    .

    Select the bin folder and change the name by modifying the properties on the right hand side as shown below.

    The files now need to be configured. The Cccppol is a .NET assembly which does not need to be installed in the GAC. The modified properties for this file are shown below.

    Here I have updated the Assembly, AssemblyApplication, AssemblyManifest and KeyPath properties. These need to be set for a .NET assembly.

    The Assembly property indicates that the file is a .NET assembly.

    The AssemblyManifest property is set to the id of the assembly manifest file. In general the manifest file is the assembly itself.

    Setting the KeyPath to true means that the installer will use the existence of this file to indicate that the component is installed. It is a Windows Installer guideline that this is set to true for a .NET assembly.

    The AssemblyApplication property is set to the id of the file so it is not installed in the GAC. If the assembly needs to be registered in the GAC then the AssemblyApplication property should be left blank. If it is blank then the files will not be installed into Program Files and will only exist in the GAC. If you need these files to be installed into Program Files aswell you will need to create a separate component and fill in the AssemblyApplication property.

    The other files should be configured in a similar way, the only difference that the AssemblyApplication should not be set since they are to be registered in the GAC. As an example, the properties for one file are shown below.

    Adding a Registry Value

    Now that the files have been added we need to add the registry value. The registry value will point to the installed cccppol.dll so we will put it inside the same component. Select the CccppolDll component, right click, select Add and then select Registry.

    Enter the registry properties as shown below.

    This will create a registry value cccppol under the HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies key. The value is set to the full path to the cccppol.dll. This is done using a value of the form [#FileId] which will expand out when installed to the full path of the file. A value of the form [!FileId] would expand to the short name of the file (i.e. it includes ~ characters).

    We have now finished with the Setup Files editor so you can close it. If you want you can open up the Files.wxs file and see the WiX XML that has been generated.

    Adding Features

    Now that we have added the files to the installer we need to associate these files with features so they will be installed. Currently SharpDevelop does not have a GUI editor for features so you will have to add them by hand. Open Setup.wxs and inside the Product element add the following:

      <!-- Features to install --> 
    <Feature Id="Complete" Level="1">
    <Feature Id="CodeCommentCheckingPolicyBinaries" Level="1">
    <ComponentRef Id="CccppolDll"/>
    <ComponentRef Id="ICSharpCode.CCCPLibDll"/>
    <ComponentRef Id="ICSharpCode.NRefactoryDll"/>
    <ComponentRef Id="ICSharpCode.SharpDevelop.DomDll"/>
    </Feature>
    </Feature>

    Here we have created two features, one called CodeCommentCheckingPolicyBinaries that references all the components we are going to install, and one parent feature called Complete. We could have just used the one feature to install everything. We have put the files into a separate feature so in the future we could an add extra feature containing documentation that could be optionally installed.

    Adding Dialogs

    Now we need to show some dialogs when the installer is run. WiX ships with a dialog library which saves you the time and effort of creating new dialogs. The dialog library supports several different dialog sequences. We want to show a license and allow the user to choose the installation folder and for this we can use the WixUI_InstallDir sequence of dialogs. In Setup.wxs insert the following xml inside the Product element.

      <!-- Use Wix UI library --> 
    <Property Id="WIXUI_INSTALLDIR">INSTALLDIR</Property>
    <UIRef Id="WixUI_InstallDir"/>

    The WIXUI_INSTALLDIR property is used to tell the WiX dialog library the id of the directory that the user is allowed to configure. In our case it is INSTALLDIR.

    Now we need to add the WiX dialog library to our project. In the Project Browser, select WiX Libraries, right click and select Wix Library.

    Now browse to the file wixui.wixlib, which is shipped with SharpDevelop in the subfolder bin\Tools\Wix folder. Select the file and click the Open button. The WiX dialog library should then be added to your project.

    Now we need to add the WiX localisation file (.wxl) to the project. A localisation file contains all the strings that are displayed in the dialogs. We are going to use the English localisation file since this is complete. For information on the status of other localisations refer to the WiX localisation project page. From the Project menu select Project Options.

    In the Application tab, locate the Localisation string file field, click the Browse button to open the Open File dialog, locate the WixUI_en-us.wxl file inside the bin\Tools\Wix folder, select it and click the Open button. The localisation file should then be added to the project options.

    Save the changes you have made to the project options. We have not quite finished, but you can test the dialogs if you want. First build the project. From the Build menu, select Build Cccp.Setup.

    Once the installer has built successfully, run it by selecting Run from the Debug menu.

    You should then see the first setup dialog.

    Showing the License

    If you tried out all the setup dialogs you will see that an end user license is displayed. In order to get a custom license shown we need to create a rich text document called License.rtf and put it into the same folder as the WiX project file (Cccp.Setup.wixproj). Then on rebuilding the project the custom license will taken from this file, embedded into the installer and displayed when installing.

    Installation Conditions

    Now we want the installer to inform the user that the installer can only be run under the following conditions:

    • .NET 2.0 is installed.
    • The OS is at least Windows 2000.
    • User has Administrator privileges.

    To do this we add three Condition elements to Setup.wxs inside the Product element.

      <!-- 
    Check for .NET 2.0
    -->
    <Condition Message="This setup requires the .NET Framework 2.0 or higher.">
    Installed OR MsiNetAssemblySupport &gt;= "2.0.50727"
    </Condition>

    <!--
    Check for the operating system is at least Windows 2000 (VersionNT = 500).
    -->
    <Condition Message="The operating system you are using is not supported (95/98/ME/NT3.x/NT4.x).">
    Installed OR VersionNT &gt;= 500
    </Condition>

    <!-- Check for admin rights -->
    <Condition Message="Administrator rights are required to install the Code Comment Checking Policy.">
    Privileged
    </Condition>

    If a condition fails then a message will be displayed. Some of the conditions also use have an Installed OR part which means the installer will never prevent the user from repairing or uninstalling the package if it is already installed.

    Now all that is left to do is run the installer and check that it actually installs everything correctly.

    Validating the Installer

    After you have tested out your installer, making sure it creates the correct registry key, and copies the files to the correct location, you should validate the created msi file.

    To validate the installer you can use one of the validation tools provided by Microsoft. One such tool is Orca which is available as part of the Windows 2003 R2 Platform SDK. Orca was originally created by Rob Mensching and as well as allowing you to validate your installer it also allows you to view and edit the various tables inside your .msi file.

    To validate the installer with Orca.exe, use the File menu to open the .msi file, then from the Tools menu select Validate.

    From the dialog that opens we can choose various validation suites.

    Validating the installer we just created against the Full MSI Validation Suite we see three warnings.

    Error IDDescription
    ICE74 The UpgradeCode property is not authored in the Property table. It is strongly recommended that authors of installation packages specify an UpgradeCode for their application.
    ICE82 This action WelcomeDlg has duplicate sequence number 1298 in the table InstallUISequence
    ICE82 This action MaintenanceWelcomeDlg has duplicate sequence number 1298 in the table InstallUISequence

    The two ICE82 warnings are from the WiX UI dialog library so we shall ignore them. The other error is about upgrades. If you are never going to support upgrades then you can ignore it, but if you are or might do in the future we can fix the warning by adding an UpgradeCode attribute to the Product element.

    <Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi"> 
    <Product Id="8F3A52FE-BB54-4BC9-953C-7173D16AA96D"
    Name="Code Comment Checking Policy"
    Language="1033"
    Version="1.0.0.0"
    Manufacturer="ic#code"
    UpgradeCode="">

    This value for this attribute is a GUID which can be quickly generated either by using the keyboard shortcut Ctrl+Shift+G or from the Edit menu, selecting Insert and then Insert New GUID.

    Other WiX Tutorials

    That's the end of this tutorial. If you want more information about WiX than is given here, one of the best WiX tutorials available is that created by Gábor DEÁK JAHN which can be found at http://www.tramontana.co.hu/wix/

    Posted Jan 08 2007, 09:30 PM by MattWard with no comments
    Filed under:
More Posts Next page »
Powered by Community Server (Commercial Edition), by Telligent Systems
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.