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:
the AddIn XML definition (.addin file)
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
).
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:
| Creates object instances by invocating a type's parameterless constructor via |
| Creates file filter entries for the |
| Includes one or multiple items from another location in the addin tree. You can use the attribute " |
| Used to create associations between file types and icons. |
| Creates a |
| Creates a |
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:
Post a Comment