Adding a Simple AddIn (Walk-Thru)

While porting some old code into AddIns for YourOtherMind 2013 I prepared this documentation. It should discuss the main details regarding deploying an AddIn for the YourOtherMind writing environment, but it will be worthwhile for the developer to review actual code, such as this example for Random Character Names in Github.

Create Initial Project

Create a class library in the code environment you use. I’ve kept the names in the format AddIn_ for user convenience but there is no built in name enforcement. I usually shorten the default namspace down to the part for readability.

The initial references for this library should be:

  • MEF_INTERFACES. This is the core AddIn library required.
  • LAYOUT> (optional) Most AddIns will need to reference Layout which gives access to the core data lookup methods (through the singletons MasterOfLayouts and LayoutDetails).
  • COREUTILITIES (optional). Many generic routines, that are used elsewhere when YourOtherMind (such as Logging and Localization) exist in CoreUtilities.
  • SYSTEM.WINDOWS.FORMS et cetera. If building a visual interface developer will need to reference Forms and other base .NET packages.

Create main AddIn class

Create a .cs file named something akin to mef_AddIn_.cs. This will be the core class that communicates with the main YourOtherMind application.

This might be how the top of the file looks:

namespace MefAddIns
{

using MefAddIns.Extensibility;
using System.ComponentModel.Composition;
using System;
using System.Windows.Forms;
using CoreUtilities;
using Layout;
using Scene_Shower;

[Export(typeof(mef_IBase))]
public class Addin_Scene_Shower:PlugInBase, mef_IBase
{
public Addin_Scene_Shower ()
{
guid = "Scene_Shower";
}
}

The export is what communicates with the AddIn framework, identifying this as a valid class for reading into the AddIns. Likewise, our class descends from a base class (found in MEF_INTERFACES) and implements the mef_IBase interface.

Fields and Methods

The guid should be set in the constructor, as shown, in the instructor, to a label that reflects the AddIn. This should be unique across all AddIns.

The following methods, should be filled in. These provide additional information to the AddIn system.

public string Author
{
get { return @"Brent Knowles"; }
}
public string Version
{
get { return @"1.0.0.0"; }
}
public string Description
{
get { return "A variety of proofreading tools."; }
}
public string Name
{
get { return @"Proofread"; }
}

The next set of flags that need to be set are in the class CalledFrom. Each plugin has this class associated with them and each plug must assign appropriate values to these flags. Here is an example:

public PlugInAction CalledFrom {
get
{
PlugInAction action = new PlugInAction();
action.MyMenuName = Loc.Instance.GetString("Scene Shower");
action.ParentMenuName = "ToolsMenu";
action.IsOnContextStrip = false;
action.IsOnAMenu = true;
action.IsNoteAction = false;
action.QuickLinkShows = true;
action.IsANote = false;
action.ToolTip =Loc.Instance.GetString("Shows the scene breakdown for the currently selected Text Note.");
action.GUID = GUID;
return action;
}
}

  • MyMenuName = The string desired to appear on the Menu(s) it appears
  • ParentMenuName = This is the menu (from the Main Menu) where the menu item (if there will be) must show up. Valid menu names include NotesMenu, ToolsMenu, AdvancedMenu
  • IsOnContextStrip = If true this would show the AddIn on the right-click invoked menu of a Text Note INSTEAD of on a main menu.
  • IsOnAMenu = If true then the AddIn appears in the main menu, assuming a correct value for ParentMenuName has been set
  • IsNoteAction = If true then this AddIn implements the Note Action system and must override ActionWithParamForNoteTextActions (see below, for more details). This appears as a secondary menu from the right click on a Text Note. A single AddIn may implement both a Menu based features and a Note based feature
  • QuickLInkShows = If true then the developer may implement code that puts a short cut to an invoke AddIn, when it is open (if that AddIn has a form). See below where this is discussed in more detail.
  • IsANote = If true then this AddIn knows this AddIn will create at least one new Note Type.
  • ToolTip = Tooltip to appear when user mouses over the menu item
  • GUID = This passes the GUID set in the AddIn constructor into the PlugInAction

And here is another example with some different fields set.


action.Image = FileUtils.GetImage_ForDLL("camera_add.png");
action.IsANote = true;
action.NoteActionMenuOverride = Loc.Instance.GetString ("Proofread by Color");

This would indicate this AddIn provides a new Note Type and that the menu item should have an icon assigned to it. The final line provides alternative text for the Note Action Menu, using thhis instead of the MyMenuName field. This is used when the Menu should have a different name from the Note Action.

Menu Response

AddIns will generally add a new menu item to the main menu of the application.
They need to implement this method:

public void RespondToMenuOrHotkey(T form) where T: System.Windows.Forms.Form, MEF_Interfaces.iAccess
{
NewMessage.Show("Something");
}

Often this will involve invoking a form for the user to interact with. This method receives the calling form (the T form).

Note Text Actions

Text Notes may be right-clicked and actions selected from the ACTIONS submenu. If this is a Text Action, this is where the code for that action is defined. Just leave the routine blank, if not a note action.

public void ActionWithParamForNoteTextActions (object param)
{
// not used for this addin
}

This example is the Note Text Action for the Proofread AddIn, which does a Proofread by Color, as opposed to the standard Proofread operation (so AddIns may add functionality to menus AND add note text actions, in the same Addin.

public void ActionWithParamForNoteTextActions (object param)
{

if (LayoutDetails.Instance.CurrentLayout != null && LayoutDetails.Instance.CurrentLayout.CurrentTextNote != null) {

ProofreadByColor(LayoutDetails.Instance.CurrentLayout.CurrentTextNote.GetRichTextBox());

} else {
NewMessage.Show (Loc.Instance.GetString ("Please select a note first."));
}
}

Note Text Actions also require an override of BuildFilenameForActionWithParam. This is because the text of the current note is written out into a temporary file for processing. This is how to set this function up:

public static string BuildFileName()
{
return System.IO.Path.Combine (System.IO.Path.GetTempPath (), Guid.NewGuid().ToString () + ".txt");
}

public override string BuildFileNameForActionWithParam ()
{
return BuildFileName();
}

Dependencies

Some AddIns will require a particular version of YourOTherMind to function. To prevent them from being loaded in a version without support for them, the developer overrides the dependencymainapplicationversion property.


public override string dependencymainapplicationversion { get { return "1.1.0.0"; }}

In this case the AddIn would be unable to load in anything less than version 1.1 of the main application.

Developers may also create a dependency requiring another AddIn to be loaded first.

Override dependencyguid to do so. This returns the GUID for the AddIn that is to be the prerequisite of the developers AddIn.

public override string dependencyguid {
get {
return "yourothermindmarkup";
}
}

Registering New Note Type

To make a new note type show up in the Add Note menu on Layouts call the following routine. This registers the new note type.

public override void RegisterType()
{
//NewMessage.Show ("Registering Picture");
Layout.LayoutDetails.Instance.AddToList(typeof(NoteDataXML_Picture.NoteDataXML_Pictures), Loc.Instance.GetString ("Picture"));
}

You can specify a subfolder name if you want. Such as this:

Layout.LayoutDetails.Instance.AddToList(typeof(NoteDataXML_Worklog), Name, "YourFolderName");

Or using the default name for Addins, defined in the constant PlugInBase.AddInFolderName.

Layout.LayoutDetails.Instance.AddToList(typeof(NoteDataXML_Worklog), Name, PlugInBase.AddInFolderName);

Deregistering

If a New Note Type was added, we call

public override bool DeregisterType ()
{
return true;
}


We can also perform any other clean up here, but the main code will do the actual removing of the Note Type as long as this routine returns TRUE.

Quicklinks

The form invoked by this AddIn’s menu response can become a quicklink. These are links to AddIn forms that appear at the bottom of the application. This gives the user a quick glimpse of all active AddIns (those with open forms).capture0000466

To enable quicklinks the developer must do the following:

  • In the PlugInAction CalledFrom “get” accessor, they must specify action.QuickLinkShows=true, as describe above.
  • Override the ActiveForm method to provide a reference to the form they want shown.
  • Have an appropriate FormClosing that removes the quicklink (calling a stock function).

This example demonstrates a simple scenario:

public void RespondToMenuOrHotkey(T form) where T: System.Windows.Forms.Form, MEF_Interfaces.iAccess
{

SceneShower = new Form();
SceneShower.FormClosing+= HandleFormClosing;
}
void HandleFormClosing (object sender, FormClosingEventArgs e)
{
RemoveQuickLinks();
}
public override object ActiveForm ()
{
return SceneShower;
}

Tips

Form not showing up

If you are trying to invoke a form in the RespondToMenu… routine then make sure you are calling .Show() and not .ShowDialog().

How To Add A Table to the System Layout From An AddIn?

Here’s a code example of how to create a table, add default values to it, and include it on the System Layout Page.

string TableName = SYSTEM_GRAMMAR;
LayoutPanels.NoteDataXML_Panel PanelContainingTables = LayoutPanel.GetPanelToAddTableTo (TableName);
if (PanelContainingTables != null) {

// create the note
NoteDataXML_Table randomTables = new NoteDataXML_Table(100, 100 , new ColumnDetails[4]{new ColumnDetails("id",100),
new ColumnDetails("pattern",100), new ColumnDetails("advice",100), new ColumnDetails("overused",100)});

randomTables.Caption = TableName;

PanelContainingTables.AddNote (randomTables);
randomTables.CreateParent (PanelContainingTables.GetPanelsLayout ());

randomTables.AddRow(new object[4]{
"1", "1.0", @"The first row of this table is a version number. Feel free to edit it when major changes are made to this list. On each Layout you can record the last grammar version you have checked it against.", "0"}
);

randomTables.AddRow(new object[4]{
"2", "Among", @"When more than two things or persons are involved, among is usually called for.", "0"}
);
randomTables.AddRow(new object[4]{
"3", "As to whether", @"Whether is sufficient.", "1"}
);

PanelContainingTables.GetPanelsLayout ().SaveLayout ();

// now we reload the system version
LayoutDetails.Instance.TableLayout.LoadLayout (LayoutDetails.TABLEGUID, true, null);

}

The best place to put this code is in the RegisterType method.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to StumbleUpon