Click or drag to resize

Writing your plugin

Minimal plugin example

As a start here is a minimal working plugin, whose function is to add a custom template to a newly created DialogueFragment when it was created as part of a Document. This plugin has no visible command and only works in the background.

Minimal plugin example
using Articy.Api;
using Articy.Api.Plugins;

namespace MyCompany.ArticyPlugin
{
    public class Plugin : MacroPlugin
    {
        // the required display name
        public override string DisplayName
        {
            get { return "Minimal Plugin"; }
        }

        // called when an object was created
        public override bool ObjectCreated(ObjectProxy aObject)
        {
        // If the new object is writable, a DialogueFragment and is a child of a document,
        // set a custom Template named "DialogueLine"
            if ( !aObject.IsReadOnly && aObject.ObjectType == ObjectType.DialogueFragment && aObject.IsInDocumentContext() )
                aObject.SetTemplate("DialogueLine");

            return base.ObjectCreated(aObject);
        }
    }
}
Simple command plugin example

This simple command plugin adds one (nested) entry to the context menu of any object which changes all objects with a color to be Red.

Simple command plugin example
using System.Collections.Generic;
using System.Windows.Media;
using Articy.Api;
using Articy.Api.Plugins;

// To ease working with Loca-Ids use a namespace alias
using Texts = LIds.MyCompany.ArticyPlugin;

namespace MyCompany.ArticyPlugin
{
    public partial class Plugin : MacroPlugin
    {
        public override string DisplayName
        {
            // the namespace alias "Text" makes working with those Ids more convenient
            get { return LocalizeStringNoFormat(Texts.Plugin.DisplayName); }
        }

        public override string ContextName
        {
            get { return LocalizeStringNoFormat(Texts.Plugin.ContextName); }
        }
        // called when an object was created
        public override List<MacroCommandDescriptor>
             GetMenuEntries(List<ObjectProxy> aSelectedObjects, ContextMenuContext aContext )
        {
            var result = new List<MacroCommandDescriptor>();
            switch ( aContext )
            {
                case ContextMenuContext.Global:
                    // entries for the "global" commands of the ribbon menu are requested
                    return result;

                default:
                    // normal context menu when working in the content area, navigator, search
                    if ( aSelectedObjects.Count >= 1)
                    {
                        var setColor = new MacroCommandDescriptor
                        {
                            CaptionLid = "{p:SetColor}Change color\\{p:Red}Set red",
                            ModifiesData = true,
                            Execute = SetColor,
                            UserData = Colors.Red
                        };
                        result.Add(setColor);
                    }
                    return result;
            }
        }

        private void SetColor(MacroCommandDescriptor aDescriptor, List<ObjectProxy> aSelectedObjects)
        {
            var color = (Color)aDescriptor.UserData;
            foreach (var obj in aSelectedObjects)
            {
                if ( obj.HasColor && !obj.IsReadOnly )
                    obj.SetColor(color);
            }
        }

        public override Brush GetIcon(string aIconName)
        {
            switch (aIconName)
            {                
                case "SetColor":
                    return Session.CreateBrushFromFile(Manifest.ManifestPath + "Resources\\SetColor.png");
                case "Red":
                    return new SolidColorBrush(Colors.Red);
            }
            return null;
        }
    }
}

Things of interest for this example:

Defining context menu entries

Each time articy:draft opens a context menu on a single object or object multi-selection, the GetMenuEntries method is called. The aSelectedObjects argument is null if the "global" commands icon from the ribbon bar was clicked or non null if the selection was made within the navigator or content area of articy:draft.
The plugin code can examine the given objects and decide if it wants to add an entry for the context menu.

Using context menu nesting

If you want to create a submenu, please specify the full path to the entry and separate the different levels with a backslash character. The order of the different entries is the same as the entries are added to the result list. Same folders are merged.

Using icons on any level of the context menu hierarchy

You can specify a custom icon for each level of the context menu hierarchy. To use Icons, prefix the text with a {p:<name of icon>} if you want to get an icon from your plugins resources or {0x<Id of project asset>} if you want to reference the id of an asset object within the currently loaded project.

Defining own icons for context menu entries

If you use the {p:<name of icon>} markup you should implement the GetIcon method. This method gets the name as it was written after the "p:" prefix.
It is up to you how to create the Brush object containing the icon. The example uses a SingleColorBrush object or created it from an external file. The Session also has overloads to create Brushes from Streams or from a Bitmap object which you will get if you use the assembly properties to store images.

Using a namespace alias for Loca-Ids

You make use the namespace alias feature of C# to work conveniently with Loca-Ids. Adding the following using clause:

C#
using Text = LIds.MyCompany.ArticyPlugin;

allows you to write

C#
return LocalizeStringNoFormat(Texts.Plugin.DisplayName);

instead of

C#
return LocalizeStringNoFormat(LIds.MyCompany.ArticyPlugin.Plugin.DisplayName);
Plugin example with a background task using a progress window
Background task sample
using System;
using System.Collections.Generic;
using System.Threading;
using Articy.Api;
using Articy.Api.Plugins;

using Texts = LIds.MyCompany.ArticyPlugin;

namespace MyCompany.ArticyPlugin
{
    /// <summary>
    /// public implementation part of plugin code, contains all overrides of the plugin class.
    /// </summary>
    public partial class Plugin : MacroPlugin
    {
        public override string DisplayName
        {
            get { return LocalizeStringNoFormat(Texts.Plugin.DisplayName); }
        }

        public override string ContextName
        {
            get { return LocalizeStringNoFormat(Texts.Plugin.ContextName); }
        }

        public override List<MacroCommandDescriptor> GetMenuEntries(List<ObjectProxy> aSelectedObjects)
        {
            var result = new List<MacroCommandDescriptor>();
            if ( aSelectedObjects != null )
            {
                // normal context menu when working in the content area or navigator
                if (aSelectedObjects.Count == 1 &&
                    (aSelectedObjects[0].IsFolder || aSelectedObjects[0].IsConnectable))
                {
                    var entry = new MacroCommandDescriptor
                    {
                        CaptionLid = Texts.LongRunningTask.Caption,
                        TooltipLid = Texts.LongRunningTask.ToolTip,
                        ModifiesData = true,
                        Execute = ExecuteLongRunning
                    };

                    result.Add(entry);
                }
            }
            return result;
        }

        private void ExecuteLongRunning(MacroCommandDescriptor aDescriptor,
                                      List<ObjectProxy> aSelectedObjects)
        {
            var task = new AsyncTask(aDescriptor, aSelectedObjects)
            {
                Title = Texts.Task.Title,
                Info = Texts.Task.Info,
                BeforeAsyncAction = null,
                AsyncAction = BackgroundTask,
                AfterAsyncAction = null,
                ShowProgressBar = true,
                ShowMessages = true,
                CloseProgressMode = CloseProgressMode.Never,
                UserData = aSelectedObjects[0]
            };
            Session.ExecuteTaskWithProgressDialog(task);
        }

        private bool BackgroundTask(AsyncTask aTask)
        {
            try
            {
                const string query = "SELECT * FROM SELF WHERE ObjectType <> Pin";
                var resultSet = Session.RunQuery(query, aTask.UserData as ObjectProxy);
                aTask.SetMaxProgress(resultSet.Rows.Count);
                aTask.SetProgress(0);
                for (var i = 0; i < resultSet.Rows.Count; i++)
                {
                    var obj = resultSet.Rows[i];
                    aTask.AddMessage(
                        String.Format("{0}: {1}", obj.TypeName, obj.GetDisplayName()), 
                            i % 2 == 0 ? EntryType.Info : EntryType.Warning);
                    aTask.SetProgress(i + 1);
                    Thread.Sleep(10);
                }
            }
            catch (Exception ex)
            {
                // possibly do some cleanup or logging
                throw;
            }
            return true;
        }
    }
}

Things of interest for this example:

Creating a Task for background processing

The ExecuteLongRunning method is called from the context menu command and creates a descriptor that is used to start the execution of a background job. You should check your pre-conditions here before starting the task. For example if all objects that need to be changed are in claimed partitions.

Using the articy:draft Query Language to collect objects

The BackgroundTask method uses the query language to collect objects recursively from a given starting point object.
Take a look at ArticyDraft - Query Language.pdf for more details.

Using the progress bar and adding messages

The task uses SetMaxProgress and SetProgress methods to handle the progress bar.
The AddMessage method is used to add a (pre-localized) string to the log with alternating types (Info, Warning).