Google
 

Wednesday, May 30, 2007

Building Applications with the SharpDevelop Core

REF: http://www.codeproject.com/csharp/ICSharpCodeCore.asp

The AddIn infrastructure provides the following functionalities:

  • AddIns can extend each other.
  • AddIns can be loaded from multiple locations.
  • AddIns are loaded when they are needed the first time to improve the startup time of the application. If an AddIn only adds menu commands, it is not loaded until the user clicks on the menu item.
  • Contains the basic functionality for enabling + disabling, uninstalling and updating AddIns.
  • A graphical AddInManager is available as AddIn to install new AddIns from "package files" (see screenshot).

What it doesn’t do:

  • Provide an "application platform" with predefined user-interface, pads, docking, file management etc., but it is possible to build that on top of the core (as we have done with SharpDevelop).
  • It does not use AppDomains to load the AddIns, everything is put into the main AppDomain, so uninstalling or disabling AddIns requires a restart of the application.

Components in an application based on the core


The core uses log4net for logging (in the class LoggingService). If you want to use a different logging engine, you only need to modify the file LoggingService.cs.

AddIns must reference ICSharpCode.Core, and also reference Base (except for AddIns that don't need to interact with the host application). AddIns can reference each other and they can also come with additional libraries.

The core

The core is responsible for loading the AddIns and storing a list of extension points and the AddIns extending them. The extension points are stored in a tree structure called AddIn tree.

Moreover, ICSharpCode.Core contains code for:

  • Saving/loading settings,
  • Logging,
  • Showing messages to the user,
  • Reading (localizable) resources,
  • Creating menus and toolbars that can be extended by AddIns.

What’s in the Base?

Base is the base AddIn of the application. To the Core, it's just a normal AddIn, but it provides all the essential functionalities so all other AddIns need references to it.

Base contains the code that controls the main window of the application, the interfaces for the main actions like file management, undo + redo and possibly pads (dockable panels).

The AddIn tree

Compiled AddIns consist of two (or more) files:

  1. the AddIn XML definition (.addin file)

  2. the AddIn library (.dll) and maybe additional files or libraries.

The XML definitions of all AddIns are read when the application is started and combined into a single tree-structure: the AddIn tree.

The AddIn tree is structured like a file system.

A path represents an extension point of the application. A node is some behavior added to the extension point by an AddIn (or the base application). Nodes can have sub-nodes, as presented in this example path.

The most common use of the AddIn tree is to extend menus and tool bars. When some part of the application wants to create a menu or toolbar, it uses a path in the AddIn tree.

Every node has a Codon associated to it. A Codon is the in-memory representation of an AddIn tree node. When the AddIn tree is loaded, an instance of the Codon class is created. Its name property is set to "ToolbarItem", its ID property to "Back". The other attributes are put into a "Properties" container (works like a Hashtable).

The icon attribute refers to an image stored in the ResourceService; the tooltip attribute is parsed using the "StringParser" service to insert localized strings. class is the fully qualified name of the class handling the command. It has to implement the interface ICommand.

The important fact about the AddIn tree is that it is constructed by combining the AddIn definitions from all AddIns.

The following Codon names are supported by the core:

Class

Creates object instances by invocating a type's parameterless constructor via System.Reflection.

FileFilter

Creates file filter entries for the OpenFileDialog or SaveFileDialog.

Include

Includes one or multiple items from another location in the addin tree. You can use the attribute "item" (to include a single item) or the attribute "path" (to include all items from the target path).

Icon

Used to create associations between file types and icons.

MenuItem

Creates a System.Windows.Forms.ToolStrip* item for use in a menu.

ToolbarItem

Creates a System.Windows.Forms.ToolStrip* item for use in a toolbar.


Of course, AddIns (or your base project) can create new element types for other data by adding custom doozers. Doozers are the classes that create the objects from the Codons. ICSharpCode.Core contains the doozer classes for the Codon types mentioned in the table.

However, using Class will be sufficient in most cases. It allows you to put absolutely any object in the AddIn tree.

n case an AddIn needs to run an action on application startup, "/Workspace/Autostart" is a predefined path being run when the core is being initialized (immediately after loading the AddIn tree), the objects stored in it must implement ICommand.

Workbench initialization

The Workbench class in the "Base" project is the main window of our application. In its constructor (called by Workbench.InitializeWorkbench), it uses the MenuService and ToolbarService to create the content of the main window.

The PropertyService

The Core contains a class called "PropertyService" that can be used to store application settings.PropertyService supports the types providing a TypeConverter, so you can use it with most of the .NET's built-in types and adding support for custom types is also easy. Additionally, the PropertyService supports storing one-dimensional arrays if the array element type has a TypeConverter.

Menu commands

Opening files

Including items from other AddIn tree paths

is a very useful element for the .addin XML declarations:





This will take all the elements from /Workspace/Tools and insert them at the position of the "Include" node.

Resources

The core supports localization using resource files for the different languages. The ResourceService reads the resources from multiple locations:

  • The main English StringResources file is usually embedded into the Startup application. It is registered using:
ResourceService.RegisterNeutralStrings(
new ResourceManager("Startup.StringResources", assembly));
  • The ResourceService automatically reads language-specific .resources files from the directory data/resources (relative to the application root). This is the common way for providing localized strings for the application.

The StringParser

The StringParser is a static class in the core that expands "{xyz}"-style property values. The StringParser is used for all labels of menu items in the AddIn tree, so you can use it to include translated strings or other variables.

You can use ${res:ResourceName} to include strings from the ResourceService. You can also use ${property:PropertyName} to include values from the PropertyService, or ${env:VariableName} to include environment variables. Additional variables can be set using StringParser.Properties. Moreover, you can register new prefixes by using PropertyService.PropertyObject. A property object can be any object – the members are accessed using Reflection. ${exe:PropertyName} can be used to access any property of the FileVersionInfo object of the entry assembly, e.g. ${exe:FileVersion}.





No comments: