Click here to Skip to main content
Click here to Skip to main content

Building Applications with the SharpDevelop Core

By , 3 Jan 2006
 

Introduction

This article presents the AddIn architecture used in the IDE SharpDevelop [^], and how you can use it in your own Windows applications. The Addin infrastructure is LGPL-licensed, and so can be used in arbitrarily-licensed applications, ranging from GPL to commercial closed-source solutions. It has proven to scale for a 300 KLOC project such as SharpDevelop, so take a look at it to see if it fits your needs too.

Most of the applications use some sort of AddIn architecture. But most of the time AddIns are limited to do only a few specific jobs - like extending a certain menu or handling a new file format.

The goal of the SharpDevelop AddIn architecture is to make it easy to provide "extension points" in the application that can be extended. In fact, we wanted to make it so easy that you could use it all the time, thus allowing AddIns to extend nearly everything.

In this article, we'll build a small text editor application. Here is a screenshot of the finished application:

Features

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

Let’s start with a view on the assemblies of our sample application:

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).

In this article's example application, Base contains a Notepad-like application. The download includes two AddIns – AddInManager and RichTextEditor. AddInManager allows the user to install packaged AddIns. For more information, watch the AddIn Manager Video Tutorial [^].

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. For example, if we want to access SubNode2, we have to specify the location as /Path1/SubPath1/Node1/SubNode2.

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. In the case of SharpDevelop, the path "/SharpDevelop/MainMenu" contains the items of the main menu, the path "/SharpDevelop/Browser/Toolbar" contains the toolbar of the browser (in SharpDevelop, the browser is used for the start page and the integrated help).

So, how do you load such a toolbar? Thanks to the ToolbarService provided by the core:

toolStrip = ToolbarService.CreateToolStrip(this, 
                         "/SharpDevelop/Browser/Toolbar");
toolStrip.GripStyle = ToolStripGripStyle.Hidden;
this.Controls.Add(toolStrip);

As you can see, creating extensible toolbars is extremely easy.

Where did this toolbar get loaded from? Of course, from an XML definition of that path in the AddIn file:

<Path name = "/SharpDevelop/Browser/Toolbar">
  <ToolbarItem id      = "Back"
               icon    = "Icons.16x16.BrowserBefore"
               tooltip = "${res:AddIns.HtmlHelp2.Back}"
               class   = " SharpDevelop.BrowserDisplayBinding.GoBack"/>
  <ToolbarItem id      = "Forward"
               icon    = "Icons.16x16.BrowserAfter"
               tooltip = "${res:AddIns.HtmlHelp2.Forward}"
               class   = " SharpDevelop.BrowserDisplayBinding.GoForward"/>
  [...]
  <ToolbarItem id = "Separator1" type  = "Separator"/>
  <ToolbarItem id      = "GoHome"
               icon    = "Icons.16x16.BrowserHome"
               tooltip = "${res:AddIns.HtmlHelp2.Homepage}"
               class   = "SharpDevelop.BrowserDisplayBinding.GoHome"/>
  [...]

This piece of XML defines the path "/SharpDevelop/Browser/Toolbar". It contains sub-nodes "/SharpDevelop/Browser/Toolbar/Back" etc. 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. But these are just the special cases for the "ToolbarItem", you can use the AddInTree to store any information.

The important fact about the AddIn tree is that it is constructed by combining the AddIn definitions from all AddIns. For example, the HtmlHelp2.addin file from the help AddIn contains this:

<Path name = "/SharpDevelop/Browser/Toolbar">
  <Condition name = "BrowserLocation" urlRegex = "^ms-help:">
    <ToolbarItem id      = "SyncHelpTopic"
                 icon    = "Icons.16x16.ArrowLeftRight"
                 tooltip = "${res:AddIns.HtmlHelp2.SyncTOC}"
                 class   = "HtmlHelp2.SyncTocCommand"
                 insertafter = "Separator1"/>
    [...]

You can see that AddIns can add new items into the existing paths and use the special attributes insertafter and insertbefore to specify the location of the inserted elements.

You can also see that Codons can have conditions assigned to them; I'll explain the conditions (in detail) in a later article.

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. Custom doozers will be covered in another article.

However, using Class will be sufficient in most cases. It allows you to put absolutely any object in the AddIn tree. If all of the classes put into a path implement a specific interface (e.g. IMyInterface), you can use:

foreach (IMyInterface obj in AddInTree.BuildItems(
                           "/Path/SubPath", this, false)) {
  // the third parameter means that no exception should 
  // be thrown if the path doesn’t exist
  obj.SomeMethod(…);
}

This makes it possible to use the AddInTree to define things like file type handlers or a set of commands being run on some action.

In 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.

The example application

Now let’s get back to our sample application, the little text editor. The main form is called "Workbench" and shows the main menu, a toolbar and the ViewContent. A ViewContent can be anything that can behave remotely like a document. Our example application can only display one ViewContent at a time.

The "Edit" and "Format" menus are intentionally missing: in the next article, we'll add them as AddIn.

The application is just meant to demonstrate what you can do with ICSharpCode.Core; it is not usable as a full-blown text editor because it doesn't support encodings (only UTF-8). As an example AddIn, the download includes a "RichTextEditor" AddIn that enables simple rich text editing.

We will look at building this application in the following steps:

  1. The Startup code required to set up the Core.
  2. The code required to set up the application window.
  3. Loading and saving application settings using the PropertyService.
  4. Implementing menu commands.
  5. Opening files using extendable "display bindings".
  6. Using localizable resources.

To give you a better overview of the project, here is a screenshot of the project explorer:

Startup

Let us take a look at the Startup code (file Start.cs in the project "Startup", method Start.Main) and the features of ICSharpCode.Core used in it:

// The LoggingService is a small wrapper around log4net.
// Our application contains a .config file telling log4net to write
// to System.Diagnostics.Trace.
LoggingService.Info("Application start");

// Get a reference to the entry assembly (Startup.exe)
Assembly exe = typeof(Start).Assembly;

// Set the root path of our application. 
// ICSharpCode.Core looks for some other
// paths relative to the application root:
// "data/resources" for language resources, 
// "data/options" for default options
FileUtility.ApplicationRootPath = Path.GetDirectoryName(exe.Location);

LoggingService.Info("Starting core services...");

// CoreStartup is a helper class 
// making starting the Core easier.
// The parameter is used as the application 
// name, e.g. for the default title of
// MessageService.ShowMessage() calls.
CoreStartup coreStartup = new CoreStartup("Test application");
// It is also used as default storage 
// location for the application settings:
// "%Application Data%\%Application Name%", but you 
// can override that by setting c.ConfigDirectory

// Specify the name of the application settings 
// file (.xml is automatically appended)
coreStartup.PropertiesName = "AppProperties";

// Initializes the Core services 
// (ResourceService, PropertyService, etc.)
coreStartup.StartCoreServices();

// Registeres the default (English) strings 
// and images. They are compiled as
// "EmbeddedResource" into Startup.exe.
// Localized strings are automatically 
// picked up when they are put into the
// "data/resources" directory.
ResourceService.RegisterNeutralStrings(
  new ResourceManager("Startup.StringResources", exe));
ResourceService.RegisterNeutralImages(
  new ResourceManager("Startup.ImageResources", exe));

LoggingService.Info("Looking for AddIns...");
// Searches for ".addin" files in the 
// application directory.
coreStartup.AddAddInsFromDirectory(
  Path.Combine(FileUtility.ApplicationRootPath, "AddIns"));

// Searches for a "AddIns.xml" in the user 
// profile that specifies the names of the
// AddIns that were deactivated by the 
// user, and adds "external" AddIns.
coreStartup.ConfigureExternalAddIns(
  Path.Combine(PropertyService.ConfigDirectory, "AddIns.xml"));

// Searches for AddIns installed by the 
// user into his profile directory. This also
// performs the job of installing, 
// uninstalling or upgrading AddIns if the user
// requested it the last time this application was running.
coreStartup.ConfigureUserAddIns(
  Path.Combine(PropertyService.ConfigDirectory, "AddInInstallTemp"),
           Path.Combine(PropertyService.ConfigDirectory, "AddIns"));

LoggingService.Info("Loading AddInTree...");
// Now finally initialize the application. 
// This parses the ".addin" files and
// creates the AddIn tree. It also 
// automatically runs the commands in
// "/Workspace/Autostart"
coreStartup.RunInitialization();

LoggingService.Info("Initializing Workbench...");
// Workbench is our class from the base 
// project, this method creates an instance
// of the main form.
Workbench.InitializeWorkbench();

try {
  LoggingService.Info("Running application...");
  // Workbench.Instance is the instance of 
  // the main form, run the message loop.
  Application.Run(Workbench.Instance);
} finally {
  try {
    // Save changed properties
    PropertyService.Save();
  } catch (Exception ex) {
    MessageService.ShowError(ex, "Error storing properties");
} }
LoggingService.Info("Application shutdown");

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.

// restore form location from last session
FormLocationHelper.Apply(this, "StartupFormPosition");

contentPanel = new Panel();
contentPanel.Dock = DockStyle.Fill;
this.Controls.Add(contentPanel);

menu = new MenuStrip();
MenuService.AddItemsToMenu(menu.Items, 
             this, "/Workbench/MainMenu");

toolbar = ToolbarService.CreateToolStrip(this, 
                          "/Workbench/Toolbar");

this.Controls.Add(toolbar);
this.Controls.Add(menu);

// Start with an empty text file
ShowContent(new TextViewContent());

// Use the Idle event to update the 
// status of menu and toolbar items.
Application.Idle += OnApplicationIdle;

The FormLocationHelper is not provided by the Core, but by a helper class in the "Base" project. It makes use of the PropertyService to load and store the location of the main window.

The PropertyService

The Core contains a class called "PropertyService" that can be used to store application settings. Take a look at the code used to save and restore the location of a Form to get an idea of how easy it is to use:

public static void Apply(Form form, string propertyName)
{
  form.StartPosition = FormStartPosition.Manual;
  form.Bounds = Validate(
    PropertyService.Get(propertyName, GetDefaultBounds(form)));
  form.Closing += delegate {
    PropertyService.Set(propertyName, form.Bounds);
  };
}

The Get and Set methods of the PropertyService are generic methods:

public static T Get<T>(string property, T defaultValue)
public static void Set<T>(string property, T value)

The C# compiler infers the type from GetDefaultBounds, which just returns the bounds of the Form centered on the active screen, and reads the property. The Validate method ensures that the position is valid; we do not want to show the Form on a no-longer-existing secondary monitor. When the Form is closed, the new location is saved. 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

You have already seen that menu commands are declared in the .addin file. Here are the commands specific to our text editor application:

<Path name = "/Workbench/MainMenu">
    <MenuItem id = "File"
             type = "Menu"
             label = "${res:Demo.Menu.File}">
        <MenuItem id = "New"
                 label = "&New"
                 shortcut = "Control|N"
                 icon = "Icons.New"
                 class = "Base.NewFileCommand"/>

Now take a look at the NewFileCommand class (which obviously wasn’t provided by the core itself):

public class NewFileCommand : AbstractMenuCommand
{
    public override void Run()
    {
        Workbench workbench = (Workbench)this.Owner;
        if (workbench.CloseCurrentContent()) {
                workbench.ShowContent(new TextViewContent());
}   }   }

The "Owner" workbench is passed automatically while creating the menu or toolbar:

ToolbarService.CreateToolStrip(this, "/Workbench/Toolbar");

The first parameter is the owner of the toolstrip, all commands created for the toolbar will have their Owner property set to the owner passed while creating the tool strip. This is useful while creating context menus for items.

Opening files

Now move on to opening the existing files. We do not know what kind of file the user will try to open, and we want to give AddIn authors the possibility to add support for more file types. Therefore, the file filter used in the OpenFileDialog must be extensible, and the AddIns should be able to create custom view contents for the file chosen by the user:

using (OpenFileDialog dlg = new OpenFileDialog()) {
    dlg.CheckFileExists = true;
    dlg.DefaultExt = ".txt";
    dlg.Filter = FileViewContent.GetFileFilter("/Workspace/FileFilter");
    if (dlg.ShowDialog() == DialogResult.OK) {
        IViewContent content = 
          DisplayBindingManager.CreateViewContent(dlg.FileName);
        if (content != null) {
            workbench.ShowContent(content);
}   }   }

First look at how the file filter is constructed:

<Path name = "/Workspace/FileFilter">
<FileFilter id = "Text" name = "Text files" extensions = "*.txt"/>
<FileFilter id = "LogFiles" name = "Log files" extensions = "*.log"/>
</Path>

And the GetFileFilter method:

public static string GetFileFilter(string addInTreePath)
{
    StringBuilder b = new StringBuilder();
    b.Append("All known file types|");
    foreach (
     string filter in AddInTree.BuildItems(addInTreePath, null, true)) {
        b.Append(filter.Substring(filter.IndexOf('|') + 1));
        b.Append(';');
    }
    foreach (
     string filter in AddInTree.BuildItems(addInTreePath, null, true)) {
        b.Append('|');
        b.Append(filter);
    }
    b.Append("|All files|*.*");
    return b.ToString();
}

As you can see, the BuildItems method returns an ArrayList of strings in this case. For the file filter, we do not need any "owner", that’s why the second argument to BuildItems is null.

The FileFilter doozer returns strings in the form "name|extensions"; this is used to concatenate the complete filter string.

Now we'll take a look at the creation of the view content. As already said in the "Features" section, the Core does not provide you with predefined classes for this task, but the DisplayBindingManager is easy to write.

We will define a new interface IDisplayBinding and a new path in the AddIn tree. AddIns will be able to add instances of classes implementing the interface to the AddIn tree using the <Class> element. Our DisplayBindingManager constructs those objects and asks each of them to create a view content for the file. The first object that is able to open the file will be used.

Implementing this behavior is easy:

/// <summary>
/// Interface for classes that are able to open a file 
/// and create a <see cref="IViewContent"/> for it.
/// </summary>
public interface IDisplayBinding
{
    /// <summary>
    /// Loads the file and opens a <see cref="IViewContent"/>.
    /// When this method returns <c>null</c>, 
    /// the display binding cannot handle the file type.
    /// </summary>
    IViewContent OpenFile(string fileName);
}

public static class DisplayBindingManager
{
    static ArrayList items;

    public static IViewContent CreateViewContent(
                                  string fileName)
    {
        if (items == null) {
            items = AddInTree.BuildItems(
                     "/Workspace/DisplayBindings", null, true);
        }
        foreach (IDisplayBinding binding in items) {
            IViewContent content = binding.OpenFile(fileName);
            if (content != null) {
                return content;
            }
        }
        return null;
}   }

As you can see, we are simply constructing all the DisplayBinding classes from the AddIn tree. The first display binding that is able to open the file will be used.

The AddIn definition from the "Base" project, Base.addin, tries to open everything as text file. Our "RichTextEditor" example AddIn has to use "insertbefore" to make sure it is used first:

<Path name = "/Workspace/DisplayBindings">
    <Class id = "RTF"
           class = "RichTextEditor.DisplayBinding"
           insertbefore = "Text"/>
</Path>

If we don't use "insertbefore", the base text editor would display the rich text source code, and our rich text editor would never be asked to open the file.

Here is the code for the display binding class:

public class DisplayBinding : IDisplayBinding
{
    public IViewContent OpenFile(string fileName)
    {
        if (Path.GetExtension(fileName).ToLowerInvariant() == ".rtf") {
            return new RichTextViewContent(fileName);
        }
        return null;
}   }

This approach will cause all the display binding AddIns to be loaded when a file is opened for the first time; in a later article, I will show you a better approach by moving the file extension check into the XML.

Including items from other AddIn tree paths

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

<Path name = "/Workbench/MainMenu">
  <MenuItem id = "Tools"
            type = "Menu"
            label = "&Tools">
    <Include id = "ToolList" path = "/Workspace/Tools"/>
  </MenuItem>
</Path>

This will take all the elements from /Workspace/Tools and insert them at the position of the "Include" node. /Workspace/Tools is a kind of "standardized" path for the AddIns that are not tightly coupled with the base application, but just open a new window when they are called in the "Tools" menu. The AddInManager AddIn included in the download uses this path, so you can start the AddInManager and use it to disable and enable RTF integration.

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.

However, it is also possible that AddIns supply their own localized string resources. The AddInManager for example (included in the download) comes with English and German resource files. Both are set to "EmbeddedResource", so the English resources are included in the AddIn assembly and the German resources are put into a satellite assembly.

When the AddInManager is started, it calls:

ResourceService.RegisterStrings(
    "ICSharpCode.AddInManager.StringResources", 
                               typeof(ManagerForm).Assembly);

The ResourceService will load the string resources from the AddIn assembly and it will look for a satellite assembly for the current language. ResourceService.GetString() will probe all the registered resources and return the string for the current language.

However, we cannot simply call a method in the XML files, so we have to use something different there.

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.

<MenuItem id = "File" type = "Menu" label = "${res:Demo.Menu.File}">

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}.

License

ICSharpCode.Core and AddInManager are licensed under the terms of the GNU Lesser General Public License. In short, you can use the libraries in commercial projects, but you have to publish the source code for any modifications done to the libraries.

The example core from this article "Base" and "Startup" can be used freely, it is BSD-licensed.

Summary

This article shows you how you can use ICSharpCode.Core for your own applications. We discussed the services provided by the core and showed what you need to implement for your applications. We used the AddInTree to store menu items, toolbar items, file filter entries and our own custom objects. Lazy-loading, custom doozers and conditions will be explained in the next article.

History

  • 3rd January, 2005: Article published (based on SharpDevelop 2.0.0.962).

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

Daniel Grunwald

Germany Germany
I am currently studying for my CS master in Karlsruhe, Germany.
In my free time, I am the lead developer on the SharpDevelop open source project.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
BugA Warning To EveryonememberZac Greve6-Dec-12 9:05 
There is a major bug in this sample:
 
        public static string GetFileFilter(string addInTreePath)
        {
            StringBuilder b = new StringBuilder();
            b.Append("All known file types|");
            foreach (string filter in AddInTree.BuildItems<string>(addInTreePath, null, true)) { // HERE!
                b.Append(filter.Substring(filter.IndexOf('|') + 1));
                b.Append(';');
            }
            foreach (string filter in AddInTree.BuildItems<string>(addInTreePath, null, true)) { // AND HERE!!
                b.Append('|');
                b.Append(filter);
            }
            b.Append("|All files|*.*");
            return b.ToString();
        }
 
This code will throw an InvalidCastException as the type FileFilterDescriptor (the type of object the addInTreePath refers to) cannot be converted to a string.
 
The following code is the fix for this:
 
        public static string GetFileFilter(string addInTreePath)
        {
            StringBuilder b = new StringBuilder();
            b.Append("All known file types|");
            foreach (FileFilterDescriptor filter in AddInTree.BuildItems<FileFilterDescriptor>(addInTreePath, null, true)) {
                b.Append(filter.ToString().Substring(filter.ToString().IndexOf('|') + 1));
                b.Append(';');
            }
            foreach (FileFilterDescriptor filter in AddInTree.BuildItems<FileFilterDescriptor>(addInTreePath, null, true)) {
                b.Append('|');
                b.Append(filter.ToString());
            }
            b.Append("|All files|*.*");
            return b.ToString();
        }
 
I hate samples that have critical bugs like this!

Bob Dole
The internet is a great way to get on the net.

D'Oh! | :doh: 2.0.82.7292 SP6a

GeneralMy vote of 5memberMember 784605218-May-12 4:37 
nice post,tks
General[My vote of 2] This article is less than helpfulmemberBrian C. Hart, Ph.D.18-Jan-11 4:13 
I am a newbie to add-ins and MVC and I would really have found a more step-by-step article useful, where you say, ok, create this form, now code this class etc etc etc...
Sincerely Yours,
Brian Hart

GeneralNon-UI based addins [modified]memberdelamaine23-Nov-10 22:33 
Hi, thanks for a great addin solution I hope to utilise it and have spent a bit of time going through the article and demo project.
 
I am interested in utilising this addin framework in a windows service environment which obviously would not require a user interface. Does this framework support addins that do not require an owner such as a form. The addins themselves would autostart or be invoked from a daemon as is required. If so would you mind pointing me in the right direction;)
 
Thanks again.
 
So it must have been late at night cause I must have missed the fact that a class codon could be created and probably would suit my need...

modified on Wednesday, November 24, 2010 4:29 PM

GeneralMy vote of 5memberSandeep Datta16-Oct-10 23:02 
Nice article Daniel...a small question is doozer a real word?
The best way to accelerate a Macintosh is at 9.8m/sec-sec - Marcus Dolengo

GeneralRe: My vote of 5memberDaniel Grunwald16-Oct-10 23:13 
Thanks!
 
'Doozer' is from the TV series Fraggle Rock[^]
QuestionSecuritymemberPaul Sanders3-Sep-07 18:09 
Hi, I must say that I think the design of the core is excellent, and I'm looking forward to using it more often when I can.
 
I was just wondering if/how you would implement security?
 
Originally, I was thinking of adding a IsInRole condition as part of the default conditions, but then maybe if it was implemented as another add-in, then this could include this condition as well as provide a login UI...
 
I've already written the IsInRole condition, and tested it with the AddInManager - which was suprisingly simple with your model Big Grin | :-D
 
///
/// Uses Thread.CurrentPrincipal.IsInRole to check if a user in in any specified roles
///

public class IsInRoleConditionEvaluator : IConditionEvaluator
{
public bool IsValid(object caller, Condition condition)
{
string roles = condition.Properties["roles"];
System.Security.Principal.IPrincipal principal = System.Threading.Thread.CurrentPrincipal;

foreach (string role in roles.Split(',')) {
if(principal.IsInRole(role))
return true;
}
return false;
}
}
 
It could then be used with something like this...
 
< Path name = "/Workspace/Tools" >
 <Condition name="IsInRole" roles="Administrator" action="Exclude" >
  <MenuItem id = "ShowAddInManager"
   label = "${res:AddInManager.Title}"
   class = "ICSharpCode.AddInManager.ShowCommand"/ >
 </Condition >
</Path>
 
At this point, I'm starting to go round in circles on what should be done next...
 
Where would I display the login window and how would it display the UI of the add-in?
Maybe the login window should not be an add-in at all, but instead be part of the Base, which I shown in the private constructor of the workbench.
If I do that, then the role check condition does not need to be in an add-in, but would be better suited in either the core default conditionals or as an exposed class in the Base. Confused | :confused:
 
Any thoughts on all of this would be much appreciated.
 
Thanks,
 
Paul

AnswerRe: SecuritymemberDaniel Grunwald4-Sep-07 7:19 
You can add a command to /Workspace/Autostart that displays the login window as a dialog.
 
If all code depends on the IsInRole condition, you could put it in Base. If you can prevent modifying ICSharpCode.Core, you should do so; it'll make upgrading to future ICSharpCode.Core versions easier.
 

GeneralRe: SecuritymemberPaul Sanders4-Sep-07 13:47 
Thanks for the reply Daniel
 
My version of "Dissecting a C# application" ebook states to add an extension (see below). I found out how to do under the Path schema, but was wondering if there is a more recent version of the ebook I don't know about...
 
This throws an exception.

<Extension path = "/Workspace/Autostart">
     <Class id = "LoginCommand" class = "Base.LoginCommand"/>
</Extension>

 
I've changed it as you recommended, very neet. Smile | :)
 
< Path name="/Workspace/Autostart">
    <Class id="LoginCommand" class="Base.LoginCommand" />
</Path>


Thanks
 
Paul

QuestionNew Icon in AddIn [modified]memberPablo8516-Aug-07 23:35 
I have a problem with BitmapResources. I want to add in my AddIn a ToolBarItem with my own icon.
<ToolbarItem id = "SomeID"
icon = "myIcon.png"
class = "..."/>

But I don't know, where I must put this icon, in what resources...

-- modified at 10:36 Monday 20th August, 2007
 

-- modified at 3:24 Wednesday 19th September, 2007
 
.. still go on...

QuestionHow are conditions called?memberkkkwj2-Feb-07 9:50 
I've spent hours and hours trying to understand this framework, and have made some progress. I think I understand how addin.xml files are FIRST read into a flat arraylist of addin objects. The addin objects each contain a list of extension path objects, which in turn contain codon objects. Codon objects contain the xml attributes and xml conditions that were read from the addin.xml file for particular items such as menu items, toolstrip items, etc.
 
To actually build "the addin tree", InsertAddin creates AddinTreeNodes and copies the codon objects from the flat list of addin objects into the new tree node objects (actually copying from the ExtensionPath objects held by the Addin objects). The treenode objects are actually structured into a tree (which the flat list of addin objects is not).
 
So now we have an Addin tree that contains TreeNodes and Codons (the codons contain Properties--the xml attributes such as label, icon, etc--and Conditions, which are used to enable/disable various codons under various state conditions at runtime.)
 
When the workbench is created, it calls methods (which ultimately call "doozers") to build various menu items, toolbars, etc. These doozers use the codon properties (xml attributes) to actually construct the UI elements.
 
Ok so far...
 
But I cannot seem to find out how or where the conditions inside the codons in the addin tree get used. Who calls them? What is the calling chain? How is the calling chain (of events?) initatiated? Maybe OnApplicationIdle?
 
I've searched the SD doc, read the Dissecting book, and have spent hours with the code, to "do my homework" before posting a request to bother you busy SD guys. Perhaps you could give me a thorough answer? Thanks in advance...

AnswerRe: How are conditions called?memberDaniel Grunwald11-Feb-07 10:09 
How conditions are evaluated depends on the doozer. If the doozer returns false for its HandleConditions property, ICSharpCode.Core evaluates the condition before building the item and excludes any items for which the condition is not met. This happens when items are built from the AddInTree.
Obviously this is not the case with toolbar and menu items - they remember their conditions and can reevaluate them. This works like this: the MenuItemDoozer class returns true for HandleConditions. This tells the core that it should not exclude items where the condition is not met but instead let the Doozer handle conditions. So excluded menu items still get created.
The created menu item is (for <MenuItem type="Item"/type="Command"/> ) of type ICSharpCode.Core.MenuCommand (deriving from ToolStripMenuItem). It remembers the Codon instance used to create it. Whenever its UpdateStatus() method is called, it uses the codon.GetFailedAction(caller) method to evaluated all conditions of the codon and return an enumeration value (ConditionFailedAction.Nothing, ConditionFailedAction.Exclude or ConditionFailedAction.Disabled). The MenuCommand class then sets Visible to false for ConditionFailedAction.Exclude (otherwise Visible=true), and Enabled=false for ConditionFailedAction.Disabled.
 
So who calls UpdateStatus()?
For context menus, it is called automatically whenever the menu opens. For main menus (and toolbars), it must be called manually. In the case of SharpDevelop, there is a timer that calls it regularly, though you could also use Application.Idle.
 
So here is an example stacktrace of a condition being updated:
 
   at ICSharpCode.SharpDevelop.ActiveWindowStateConditionEvaluator.IsValid(Object caller, Condition condition) in d:\SD\2.1\SharpDevelop\src\Main\Base\Project\Src\Internal\ConditionEvaluators\ActiveWindowStateEvaluator.cs:line 29
   at ICSharpCode.Core.Condition.IsValid(Object caller) in d:\SD\2.1\SharpDevelop\src\Main\Core\Project\Src\AddInTree\AddIn\Condition.cs:line 59
   at ICSharpCode.Core.Condition.GetFailedAction(IEnumerable`1 conditionList, Object caller) in d:\SD\2.1\SharpDevelop\src\Main\Core\Project\Src\AddInTree\AddIn\Condition.cs:line 145
   at ICSharpCode.Core.Codon.GetFailedAction(Object caller) in d:\SD\2.1\SharpDevelop\src\Main\Core\Project\Src\AddInTree\AddIn\Codon.cs:line 94
   at ICSharpCode.Core.ToolBarCommand.UpdateStatus() in d:\SD\2.1\SharpDevelop\src\Main\Core\Project\Src\AddInTree\AddIn\DefaultDoozers\ToolBarItem\Gui\ToolBarCommand.cs:line 55
   at ICSharpCode.Core.ToolbarService.UpdateToolbar(ToolStrip toolStrip) in d:\SD\2.1\SharpDevelop\src\Main\Core\Project\Src\Services\ToolBarService\ToolBarService.cs:line 81
   at ICSharpCode.SharpDevelop.Gui.DefaultWorkbench.UpdateToolbars() in d:\SD\2.1\SharpDevelop\src\Main\Base\Project\Src\Gui\Workbench\DefaultWorkbench.cs:line 624
   at ICSharpCode.SharpDevelop.Gui.DefaultWorkbench.UpdateMenu(Object sender, EventArgs e) in d:\SD\2.1\SharpDevelop\src\Main\Base\Project\Src\Gui\Workbench\DefaultWorkbench.cs:line 607
   at System.Windows.Forms.Timer.OnTick(EventArgs e)
 

My other article "Line Counter - Writing a SharpDevelop AddIn[^]" contains a section "Adding new counting algorithms" where a custom doozer is created.
 

GeneralVisual Studio 2005memberAdriM26-Mar-06 21:54 
Hello,
 
I want to know if someone as already use SharpDevelop Core in a VS 2005 project (My team project is using Team Foundation Server), and if the MicroSoft Add-In Manager support the integration of tha add-ins.
 
Thanks (and sorry for my bad english)
GeneralRe: Visual Studio 2005memberkkkwj23-Apr-06 9:57 
I use the SD core in a vs2005 project, no problem.
 
No, Microsoft addins have nothing to do with SD addins.

GeneralBuilding Console Applications with the SharpDevelop Corememberesteewhy16-Mar-06 10:19 
I'd like to know how well the SD Core is suited for Console apps development? Namely, i'd be happy to utilise such services as property management, wrapped logging services, AddIn tree, whatever.
 
(Some time ago i've came up with an idea of how to quickly build an interactive console apps. In brief: expose classes' public members as a command for the built-in shell. Having added solid SD's core services would result a powerfull console framework.)
QuestionDocking?memberFundamentalDiscord2-Feb-06 19:53 
Curiosity... How would one add a Docking Manager to this project?
I'm not entirely sure what the bare necessities are to implement docking
Via DockingPanel Suite, for example (like #D)...
 

AnswerRe: Docking?memberkkkwj5-Feb-06 6:52 
I don't know about DockingPanel Suite, but I added a docking manager to this project as follows:
 
1. Where Startup/Start.cs calls the initial form Workbench, you have to call a Workbench.cs form that builds a docking manager (really, just a layout manager that does docking) into the form. Here's my code for this (the same as the original code, really):
 
try {
LoggingService.Info ("Running application...");
// Workbench.Instance is the instance of the main form, run the msg loop.
Application.Run (Workbench.Instance);
}
 
2. In workbench.cs, where it builds the form to display, use a docking manager control of some kind to manage the docking. Here's a summary of my code in the constructor of Workbench.Instance:
 
// build menus
// build toolbars
// build status bar
// add controls to the app form, the last guy in the list wins his position
appwin.Controls.Add (toolcon);
appwin.Controls.Add (status_strip);
appwin.Controls.Add (tbar1);
appwin.Controls.Add (menu);
 
// create a docking manager to manage the toolstripcontainer center panel
appwin.dockman = new DockingManager (toolcontainer.ContentPanel);
 
// and now you have a docking manager going in your form.
 
It's taken me weeks of pretty constant effort to understand enough of this project in order to get docking working, but I'm a newbie. I didn't even understand what docking _was_, when I started. And as you can see from the forum entries at #d and here, Daniel Grunwald was kind enough to provide quite a bit of help in the form of answers to my questions, which I greatly appreciated. Hopefully it won't take you as long as me.

GeneralRe: Docking?memberFundamentalDiscord10-Feb-06 19:39 
So is this giving you persistence in the docking manager?
I am hoping to give an interface which is customizable
And obviously the customizations should persist...
 
I got lost in the #D code so am unsure of how that works.
 

GeneralRe: Docking?memberkkkwj13-Feb-06 5:33 
The code bit that I showed merely hooked up a docking manager. You are right, you need separate code to store the positions of the docked windows and so on. So no, the specific code I showed doesn't give you persistence.
 
But the docking manager code itself usually provides interfaces to load and save particular configurations. Each docking manager is different, so each docking manager library would have its own way of storing/retrieving window positions, etc. Or you could get the positions, and store them in the #d core properties file, like the Core does for the basic window position.
 
#d does it's own persistence, I imagine. I got lost in the #d code too, so many times. But I could understand the core code posted here. Good luck.
QuestionCan't compile on VS2005memberAndrés Villanueva1-Feb-06 11:42 
Hi! VS2005 is giving me the weirdest error...
 
Error 1 Could not write to output file 'C:\pruebas\ICSharpCodeCoreDemo\Startup\obj\Debug\Startup.exe' -- 'Acceso denegado. ' Startup
 
What could be the reason for this? I'm not on a network, this is my personal PC and I'm not running with a restricted user (I have admin privileges)
 
Note:
(Acceso denegado == Access denied)
 
Thanks for posting this code!!
 
Andrés Villanueva
AnswerRe: Can't compile on VS2005memberDaniel Grunwald2-Feb-06 5:12 
Really weird.
I guess the VS debugger is locking the file, did you try restarting VS? (and maybe you need to kill the .vshost process)
GeneralRe: Can't compile on VS2005memberAndrés Villanueva2-Feb-06 7:30 
Ok I solved it!! Sorry I didn't answer before.
 
I tried running the line the output gave me (you know, csc.exe.........). It didn't work. Only one time I could make it work if I used c:\startup.exe as output.
 
Really weird...
 
Anyway, apparently it had something to do with my antivirus... o_0
I disabled it and everything worked as it should...
 
Thanks!!
QuestionAddin resources not totally self contained?memberkkkwj24-Jan-06 11:50 
It seems to me that using a localized string on a menu label that points to the initial invokation of an addin method is not possible, as explained below. Here I use the resource string ${res:AddInManager.Title} as an example. It is the menu label for the Tools -> Addin Manager menu choice.
 
1. Suppose ${res:AddInManager.Title} = "AddIn Manager", but is stored as an embedded resource in the ICSharpCode.AddInManager.dll assembly.
 
2. Suppose ${res:AddInManager.Title} is used in the AddInManager.addin file, as follows:
 



 
3. The menu builder doozer that assigns the label to the Tools->AddIn Manager menu choice is not able to access the embedded resource definition in the AddInManager.dll file. This is because the SD core has had no reason to load the resource manager for the AddInManager addin yet.
 
If you remove the ${res:AddInManager.Title} resource definition from Startup/StringResources, the core will not be able to find a corresponding resource value, and so it will display "${res:AddInManager.Title}" as the menu label.
 
The core will only register the addin resources when the addin is first run, as follows from AddInManager/commands.cs:
 
public override void
Run () {
if (!resourcesRegistered) {
resourcesRegistered = true;
ResourceService.RegisterStrings("AddInManager.StringResources",
typeof (ShowCommand).Assembly);
}
 
So it seems to me that the "top" localized menu labels of all addins must be stored in the Startup/StringResources file of the main startup assembly in order to be found. (If they are not localized, the core uses the labels from the addin file itself, so resource loading is not a problem.)
 
This implies that a main application must know in advance the localized string names of all addins that will be added in to the core framework.
 
Is this correct? Thanks
AnswerRe: Addin resources not totally self contained?memberDaniel Grunwald27-Jan-06 2:48 
ResourceService.RegisterStrings can be called by a /Workspace/Autostart command, then the resources will be ready before the menu is created. This isn't optimal because it will cause the AddIn assembly to be loaded when the application starts.
GeneralRe: Addin resources not totally self contained?memberkkkwj27-Jan-06 19:18 
Good idea, quite clever. At least you can access a truly standalone addin this way--better to be a bit slower on startup than to break the self-contained boundary, I should think. Thanks

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130617.1 | Last Updated 3 Jan 2006
Article Copyright 2006 by Daniel Grunwald
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid