Creating a Markdown Monster Add-in

To facilitate creation of Add-ins Markdown Monster provides two installable addin templates:

Both of these create a ready-to-run starter Addin project for you. They each perform the following steps:

  • Creates an SDK style .NET 4.7.2 Class Library Project
  • Creates a class that inherits from MarkdownMonsterAddin
  • Implements OnApplicationStart() to configure the Addin
  • Stubs out a few common event handlers for MM life time event handling

Once the base is installed you can then:

  • Implement the OnExecute() and OnExecuteConfiguration() handlers
  • Optionally you can hook into many other addin events
  • Compile project output into %appdata%\Markdown Monster\addins\YourAddin

Let's go through these steps in detail.

Create an Addin with the dotnet new Template

The dotnet new template is an installable template that can be used with dotnet new. You can install the Nuget package after which you can then use the template to create a new Markdown Monster Addin project. All of this is done from the command line.

To use the dotnet new template is a two step process:

  • Install the Markdown Monster Addin Template from NuGet
  • Create the Addin Project

The template is available via NuGet and you can install it using dotnet new -i CLI SDK tooling. You need to have a recent .NET Core SDK installed to run dotnet new.

To install the template you'll use the dotnet new -i CLI command from the Terminal:

dotnet new -i MarkdownMonster.AddinProject.Template

You can then create a new project like this:

# Create a folder for your project and change to it
md \projects\SampleAddin
cd \projects\SampleAddin

# Create the new Project - make sure the name ends in 'Addin'
dotnet new markdownmonsteraddin -n SampleAddin --company "West Wind Technologies"

# Build the project - should create a placeholder addin
dotnet build 

# Run Markdown Monster - placeholder Addin should be loaded (bullhorn icon on toolbar)
mm

You can then open the project in your .NET IDE of choice by clicking on the .csproj file.

Create an Addin Project with the Visual Studio Extension

You can install the Markdown Monster Addin Project Extension from the Visual Studio extension MarketPlace. You can use Extensions -> Manage Extensions and search for Markdown Monster and install the Project template from there.

Start by creating a new Markdown Monster Addin Project called SampleAddin:

The project type created is a .NET Framework 4.7.2 Class Library. Next specify the project name.

Important: Make sure the project name ends in Addin (ie. SampleAddin or RefactoringAddin). The addin manager looks for files that end in Admin.dll to ensure your addin is found in the common Addins folder.

What's created by the Project Templates

The project is created as a .NET Framework 4.7.2 Class Library project, which builds its output into the MM Common Addins folder (by default %appdata\Markdown Monster) so that MM can find the addin and load it when it starts. If you want to use Visual Studio or another IDE with the CLI generated project, simply open the SampleAddin.csproj file in your IDE and you're good to go.

Note that building and running the project as-is assumes that Markdown Monster is installed in its default %LocalAppData%\Markdown Monster location. If MM is installed in a different location, you'll need to replace the $(LocalAppData) and $(AppData) references in the .csproj file to point at your install and common folder locations.

For reference or if you want to manually create your project without the template, here's what the generated project file looks like:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <Version>1.0.0</Version>
    <TargetFramework>net472</TargetFramework>
    <UseWPF>true</UseWPF>

    <!--<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>-->
    <OutDir>$(appdata)\Markdown Monster\Addins\SampleAddin</OutDir>    
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Windows.Forms" />        
    <Reference Include="System.Data" />
    <Reference Include="System.Xaml" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
    <Reference Include="WindowsBase" />
  </ItemGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net472'">
    <Reference Include="$(localappdata)\Markdown Monster\MarkdownMonster.exe">
      <Private>false</Private>
    </Reference>
    <Reference Include="$(localappdata)\Markdown Monster\MahApps.Metro.dll">
      <Private>false</Private>
    </Reference>
    <Reference Include="$(localappdata)\Markdown Monster\FontAwesome.WPF.dll">
      <Private>false</Private>
    </Reference>
    <Reference Include="$(localappdata)\Markdown Monster\Westwind.Utilities.dll">
      <Private>false</Private>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <Resource Include="icon.png" />
    <None Update="version.json">
	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net472'">
    <DefineConstants>NETFULL</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DebugType>embedded</DebugType>
    <DebugSymbols>true</DebugSymbols>    
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
    <StartAction>Program</StartAction>
    <StartProgram>$(localappdata)\Markdown Monster\MarkdownMonster.exe</StartProgram>
  </PropertyGroup>
</Project>

Additionally the addin creates an Addin.cs, AddinConfiguration.cs source files which hold the addin implementation and an optional configuration class. A build.ps1 file can be used to package up your Addin into a Build folder and zip file that can be shared in the Markdown Monster Addin registry.

Here's what the project should look like in Visual Studio:

Working with your Addin Class

If you need to do this manually create a new C# class that inherits from the MarkdownMonsterAddin class. The template creates a SampleAddin class in Addin.cs and provides the base infrastructure to load the addin on MM startup. It also sets up a toolbar button and OnExecute() handler you can implement to quickly add behavior to your addinand or you can choose to hook up and respond to additional events that are fired throughout the application lifetime.

This is a link

The basic process is:

  • Override the OnApplicationStart() method
  • In that method hook up the Menu handler to invoke your add-in
  • Override OnExecute() to handle the toolbar click

For reference, here is the default add-in implementation that gets generated, with the SampleAddin as the new project name:

using FontAwesome.WPF;
using MarkdownMonster;
using MarkdownMonster.AddIns;

namespace MarkdownMonsterSampleAddin
{
    public class SampleAddin : MarkdownMonster.AddIns.MarkdownMonsterAddin

    {
        public override Task OnApplicationStart()
        {
            base.OnApplicationStart();

            // Id - should match output folder name. REMOVE 'Addin' from the Id
            Id = "SampleAddin";

            // a descriptive name - shows up on labels and tooltips for components
            // REMOVE 'Addin' from the Name
            Name = "SampleAddin";


            // by passing in the add in you automatically
            // hook up OnExecute/OnExecuteConfiguration/OnCanExecute
            var menuItem = new AddInMenuItem(this)
            {
                Caption = Name,

                // if an icon is specified it shows on the toolbar
                // if not the add-in only shows in the add-ins menu
                FontawesomeIcon = FontAwesomeIcon.Bullhorn
            };

            // if you don't want to display config or main menu item clear handler
            //menuItem.ExecuteConfiguration = null;

            // Must add the menu to the collection to display menu and toolbar items            
            MenuItems.Add(menuItem);
            
            return Task.CompletedTask;
        }
        

        public override Task OnExecute(object sender)
        {
            MessageBox.Show("Hello from your sample Addin","Markdown Addin Sample",
                            MessageBoxButton.OK, MessageBoxImage.Information);
                            
            return Task.CompletedTask;                            
        }

        public override async Task OnExecuteConfiguration(object sender)
        {
            // MessageBox.Show("Configuration for our sample Addin","Markdown Addin Sample",
            //                 MessageBoxButton.OK, MessageBoxImage.Information);
            
            // if you created a configuration you can open the file
            await OpenTab(Path.Combine(mmApp.Configuration.CommonFolder,"SampleAddinConfig.json"));
        }

        public override bool OnCanExecute(object sender)
        {
            return true;
        }
        
        /// <summary>
        /// Fired after the model has been loaded. If you need model access during load
        /// this is the place to hook up your code.
        /// </summary>
        public override Task OnModelLoaded(AppModel model)
        { 
            return Task.CompletedTask;
        }

        /// <summary>
        /// If you add UI elements as part of your Addin, this is the
        /// place where you can hook them up.
        /// </summary>
        public override void OnWindowLoaded()
        { 
            return Task.CompletedTask;
        }
    }
}

This add-in simply displays a MessageBox() when you click the Toolbar button, which is a placeholder for any other type of operation you'd like to perform. More on that in a minute.

Build the Project

With the Project Template and assuming standard install folders, the project should be ready to build and run. You can also start debugging by running the application in Debug mode, which should launch Markdown Monster as the startup executable.

Build and Debug Failures

If the project does not build or run, make sure your paths are correct. The new Add-in assumes you installed Markdown Monster in the default %localappdata%\Markdown Monster location and build your addin to the common folder location at %appdata%\Markdown Monster\Addins - if you used a different install folder or the portable installer, you'll have to adjust the project paths in the .csproj file by replacing all references to $(localappdata) and $(appdata) folders with your actual MM install and common folder paths.

When the project builds the the output is generated into the Markdown Monster Shared Settings folder and the Addins folder below that. The default build location is: %appdata%\Addins\SampleAddin.

Default Toolbar and Menu Items

The template generated code creates the default toolbar button and menu item in OnApplicationStart(). That code sets up the Toolbar menu items for the toolbar icon as well as a menu option on the Tools -> Addins menu popup.

If you don't want to display the menu item or configuration option - because your add-in maybe doesn't need UI - simply disable the event handlers on the menu item explicitly:

// don't show Configuration drop down button
menuItem.ExecuteConfiguration = null;

When you do this the toolbar button (as well as the dropdown next to it) is not displayed.

By default the toolbar button handler is routed to the OnExecute() handler, which is a good starting point for most addins to test and interactively launch behavior.

What can you do with your Addin?

Add-ins can be as simple or as complex as you want to make them. The template only sets up the OnExecute() click handler routing but there are many other things you can do in an Addin.

Addins may intercept document update operations and insert or modify content of the document. Others yet may pop up their own Forms and perform many user interface or conversion operations. Yet other ones like the KavaDocs add-in hook up entire documentation management system hooked in as an add-in.

To give you some ideas, here are a few things you can do from within an Add-in:

  • Manipulate the active document or editor
  • Insert or update text in a document
  • Activate an open editor
  • Load and save a document from disk
  • Select a folder or file in the Folder Browser
  • Monitor document updates and manipulate the text before saving/updating

The Addin Model

An top level addin instance, exposes a Model property that gives access to a host of powerful, top level properties that allow you to perform the above tasks and more. Here's what the live Model object looks like in the debugger:

Here are what some of the more common properties you might use:

  • ActiveDocument The document holds the active document's content, as well as information about the file, document format and statistics and so on. It's also used to load and save a document.

  • ActiveEditor
    Using the editor instance you can manipulate the editor's operation by selecting, inserting and removing text, searching etc. A huge number of editor commands are accessible both for the editor wrapper as well as the core editor surfaces (JavaScript). The most common editor operations revolve around selecting and updating or inserting text into the document.

  • OpenEditors/OpenDocuments
    You can also access all the Open editors or Documents as a collection to find a specific open editor or document to work with rather than the active one.

  • Window (Main MM Window)
    This object gives access to all of Markdown Monster's UI. You can gain access to the menu to add items for example, as well as gain access to the open editor tabs, the file and folder browser, the bookmarks window and so on. This is the entry point to the entire MM UI.

  • Configuration
    This object holds all of Markdown Monster's many configuration settings. There are a number of nested objects that separate out the configuration functionality into things like Editor, Markdown, System etc.

There are a lot more Model properties available and you can find out more about these objects and their sub-objects in the Class Reference or by browsing the configuration file as JSON (go to Settings, then click on Edit JSON).

Implementing generic Addin Configuration

By default the Addin also creates a configuration class that you can use to hold configuration data specific to your addin. The configuration class can be easily persisted and is configured to handle loading and saving autmatically. You can add any properties to the configuration class and those properties are then accessible via SampleAddinConfiguration.Current.Property.

The .Write() method can then persist the configuration data to a JSON config file, or you can use .Read() to re-load configuration data from disk. The latter is useful if you edit the configuration setting as a JSON file, you can then update the current settings by reloading the settings from disk. The configuration data is stored in a JSON file in the common settings folder (ie. %appdata%\Markdown Monster by default) and is not deleted when the add-in is removed.

Simple Configuration Management via JSON File Editing

Most add-ins require some sort of configuration. For example, the Azure Blob Storage add-in needs to store account information for any remembered blob stores and you'll need a place to hold this configuration.

Your addin project automatically generated a Configuration.cs class that you can use to add custom properties that can be persisted to disk. A very common way to handle add-in configuration is to write configuration settings to file when the add-in is shut down (OnApplicationShutdown()). To modify settings you can then simply open and edit the JSON configuration file in an MM tab and save it when you're done - intercepting the save operation to re-read the updated changes.

To put this all together in the Add-in looks like this:

// Opens configuration editing
public override void OnExecuteConfiguration(object sender)
{
    // save current settings to file before editing
    SampleAddinConfiguration.Current.Write();

    // open the add-in config file in the editor
    var path = Path.Combine(Model.Configuration.CommonFolder,@"SampleAddin.json");
    Model.Window.OpenFile(path);  // opens file in a tab for editing
}

// detect when we made a change to the configuration file in the editor
public override void OnAfterSaveDocument(MarkdownDocument doc)
{
    base.OnAfterSaveDocument(doc);

    // when saving check for our config file
    if (doc.Filename.Contains("SampleAddin.json"))
    {
        // re-read the configuration to update with changes
        SampleAddinConfiguration.Current.Read();
    }
}

If your add-in makes interactive changes to the configuration (like in a custom window) you should also write it out when the addin shuts down or when you exit your UI or other change operation.

public override void OnApplicationShutdown()
{
    base.OnApplicationShutdown();
    SampleAddinConfiguration.Current.Write();
}

Addin Binaries Location

Addins run out of the %appdata%\Markdown Monster\Addins\SampleAddin folder by default. Each addin gets its own folder which contains the binaries to load the add-in. By default the addin doesn't output assemblies that are already in used by Markdown Monster and simply uses the top level project reference to ensure addin sizes don't ship redundant assemblies:

<ItemGroup Condition=" '$(TargetFramework)' == 'net472'">
    <Reference Include="$(localappdata)\Markdown Monster\MarkdownMonster.exe">
      <Private>false</Private>
    </Reference>
    <Reference Include="$(localappdata)\Markdown Monster\MahApps.Metro.dll">
      <Private>false</Private>
    </Reference>
    ...
</ItemGroup>    

The project template automatically points the build output to this folder in the project settings:

<OutDir>$(appdata)\Markdown Monster\Addins\SampleAddin</OutDir>    

The Addins folder is where Markdown Monster looks for user add-ins. It then scans all folders for any files that end in Addin.dll. Those that match are loaded as add-ins.

If MM is running and the addin is loaded you will have to stop all instances of Markdown Monster before you can recompile the files.

Test your Add in

At this point you should also be able to run your add-in. When you do, you should now see something like this:

Note the bullhorn icon in the toolbar on the right which is the default icon for this add-in as specified in the initialization code but you can change that easily to another FontAwesome or a real image icon. If you click the Toolbar button, you should see the dialog box as shown which verifies the add-in works.

The dropdown to the right provides for executing the addin, executing the configuration handler or uninstalling the add-in.

Debug your Add in

Nothing special is required to debug your add-in. By default Markdown Monster points at $(localappdata)\Markdown Monster\MarkdownMonster.exe as the startup executable and if you use this default install location the debugger will just work. If you run in Debug mode the debugger starts Markdown Monster and you can simply set a breakpoint in your add-in code.

If you set a breakpoint, the debugger stops at the appropriate place in the code:

And you're off to the Races!

And voila with that you've hooked up your Add-in. You can now run any .NET code necessary to manipulate the markdown content, bring up your own UI and or transfer rendered output to a server for example.

We'll look at some of the things you can do in the next topics.


© West Wind Technologies, 1996-2021 • Updated: 08/02/21
Comment or report problem with topic