Module templates - arklumpus/TreeViewer GitHub Wiki

The Module creator window provides templates for 9 kinds of TreeViewer modules. This page will discuss the elements that are necessary to properly define a new module of each kind, as well as elements that are common to all kinds of modules.

Common elements

Each module is defined as a static class named MyModule contained in a unique namespace. The properties, fields and methods within this static class define the module's behaviour. The following members are common to all kinds of modules:

namespace a8317b82b174a4942aa128e85b98ed720
{
    public static class MyModule
    {
        public const string Name = "A name for your module.";
        public const string HelpText = "A very short description for your module.";
        public const string Author = "Your name";
        public static Version Version = new Version("1.0.0");
        public const ModuleTypes ModuleType = ModuleTypes.LoadFile;

        public const string Id = "8317b82b-174a-4942-aa12-8e85b98ed720";

        public static List<(string, string)> GetGlobalSettings()
        {
            return new List<(string, string)>()
            {
                
            };
        }

        // ...

    }
}
  • public const string Name: this defines the name of the module, which is shown in the module manager, in the windows used to add the module to the plot, and as the header of the module parameter section (if applicable).

  • public const string HelpText: this defines a short description for the module, which is shown in the module manager, in the windows used to add the module to the plot, and as a tooltip on the "question mark" button used to open the full module help.

  • public const string Author: this identifies the author of the module.

  • public static Version Version: this field defines the version of the module. If the user has update notifications enabled, when a new version of the module (with a higher version number) is uploaded on the module repository, they get a notification prompting them to open the Module repository window.

  • public const ModuleTypes ModuleType: this field defines the kind of module represented by the code. The available values are:

    • ModuleTypes.FileType for File type modules;
    • ModuleTypes.LoadFile for Load file modules;
    • ModuleTypes.Transformer for Transformer modules;
    • ModuleTypes.FurtherTransformation for Further transformation modules;
    • ModuleTypes.Coordinate for Coordinates modules;
    • ModuleTypes.Plotting for Plot action modules;
    • ModuleTypes.Action for Action modules;
    • ModuleTypes.MenuAction for Menu action modules;
    • ModuleTypes.SelectionAction for Selection action modules.
  • public const string Id: this should be a string representation of a Globally Unique IDentifier (GUID), made up of five groups of hexadecimal characters (0-9, a-f) separated by dashes, comprising 8, 4, 4, 4, and 12 digits, respectively. A Guid value can be obtained in C# by using invoking System.Guid.NewGuid().ToString(), or by using an online service such as Waste-A-GUID. The Module creator window will automatically generate a GUID for your module when you choose a template.

  • public static List<(string, string)> GetGlobalSettings(): this optional method returns a list of global settings that are used by the method (see Defining parameters for how to define them). These settings can be changed by the user in the Preferences window accessible from the Preferences... menu item of the Edit menu; they persist even after the program closes.

    Note that in the Preferences window global settings are not grouped according to which module defined them; furthermore, if multiple modules define a global setting with the same name, then only one parameter will be shown in the Preferences window, and the same value will be provided to all modules (for example, the Large file threshold parameter is used by all the Load file modules).

    If the module does not require any global settings, this method can be omitted.

The other members that must be defined in the MyModule class depend on the module type.

File type modules

File type modules are used to provide support for various types of tree files. In addition to the common members described above, File type modules must also define the following members:

  • public static string[] Extensions { get; }

  • public static double IsSupported(string fileName)

  • public static IEnumerable<TreeNode> OpenFile(string fileName, List<(string, Dictionary<string, object>)> moduleSuggestions, Action<double> progressAction, Func<RSAParameters?, bool> askForCodePermission)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

Code in the OpenFile method of the module should open the file and read it (ideally, tree-by-tree, returning trees as requested without loading them all in memory, as this will avoid out-of-memory exceptions with large files).

If the tree file contains TreeViewer module data serialised as a string, the modules can be deserialised using the TreeViewer.ModuleUtils.DeserializeModules method, which takes as a first argument the string containing the serialised modules and the askForCodePermission parameter that was passed to the OpenFile method.

Load file modules

Load file modules are used to make the trees read by File type modules accessible to TreeViewer. In addition to the common members described above, Load file modules must also define the following methods:

  • public static double IsSupported(FileInfo fileInfo, string filetypeModuleId, IEnumerable<TreeNode> treeLoader)

  • public static TreeCollection Load(Window parentWindow, FileInfo fileInfo, string filetypeModuleId, IEnumerable<TreeNode> treeLoader, List<(string, Dictionary<string, object>)> moduleSuggestions, ref Action<double> openerProgressAction, Action<double> progressAction)

Descriptions of the function of these methods and of their arguments are available in the comments in the template source code.

The Load method should return a TreeCollection object containing the trees that were read from the file by the File type module. This can be achieved e.g. by transforming the treeLoader object into a list (using its .ToList() method), which means that all the trees from the file will be loaded into memory and is only advisable for files with a small number of trees.

Alternatively, the trees from the treeLoader could be read one at a time and stored in a stream in binary format, so that the TreeCollection can be created using the constructor that uses a Stream argument. This is more suitable with large files that risk using up too much RAM if loaded directly.

The openerProgressAction and progressAction can be used to provide feedback to the user as to the progress of the file being opened. The openerProgressAction should be created by the Load file module, and it will be invoked by the File type module, while instead the progressAction is provided externally and should be invoked by the Load file module.

Depending on the approach being used to load the file, the progress from the openerProgressAction could be passed directly to the progressAction using code like openerProgressAction = progress => progressAction(progress);. Alternatively, the Load file module may decide when to invoke the progressAction method based on other information.

Transformer modules

Transformer modules are used to convert the TreeCollection object returned by the Load file module into a single TreeNode object that will be plotted. In addition to the common members described above, Transformer modules must also define the following methods:

  • public static List<(string, string)> GetParameters(TreeCollection trees)

  • public static bool OnParameterChange(object tree, Dictionary<string, object> previousParameterValues, Dictionary<string, object> currentParameterValues, out Dictionary<string, ControlStatus> controlStatus, out Dictionary<string, object> parametersToChange)

  • public static TreeNode Transform(TreeCollection trees, Dictionary<string, object> parameterValues, Action<double> progressAction)

Descriptions of the function of these methods and of their arguments are available in the comments in the template source code.

The GetParameters method returns a list of settings that can be changed by the user and affect the behaviour of the module (see Defining parameters for how to define them). If the module does not use any of these settings, this method must still be present (and return an empty list).

The OnParameterChange method is called every time a parameter value changes. Therefore, care should be taken to ensure that this method executes quickly.

Further transformation modules

Further transformation modules are used to alter the tree before it is plotted. In addition to the common members described above, Further transformation modules must also define the following members:

  • public static bool Repeatable { get; }

  • public static Page GetIcon(double scaling)

  • public static List<(string, string)> GetParameters(TreeNode tree)

  • public static bool OnParameterChange(object tree, Dictionary<string, object> previousParameterValues, Dictionary<string, object> currentParameterValues, out Dictionary<string, ControlStatus> controlStatus, out Dictionary<string, object> parametersToChange)

  • public static void Transform(ref TreeNode tree, Dictionary<string, object> parameterValues, Action<double> progressAction)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

The GetParameters method returns a list of settings that can be changed by the user and affect the behaviour of the module (see Defining parameters for how to define them). If the module does not use any of these settings, this method must still be present (and return an empty list).

The OnParameterChange method is called every time a parameter value changes. Therefore, care should be taken to ensure that this method executes quickly.

If an exception occurs while running the code for your module and you do not catch and handle it properly, an error message will be generated in the interface. This will appear in the interface as a yellow warning sign next to the module's name in the module list; when the user moves the mouse over this icon, the exception's Message is shown. This means, for example, that if the user selects an invalid combination of parameters for your module, you can safely throw an Exception and use the Message to explain what happened.

However, if you just wish to communicate something to the user (e.g., if the settings they selected may produce unexpected results, but you can still do something with them), you can issue a warning message instead. This is only shown when the module's settings are expanded, and can be disabled by the user for a single session or forever.

To issue a warning, you should call a method that is provided within the parameterValues dictionary, under the Modules.WarningMessageControlID key. Note that this is only present in the GUI version of TreeViewer, and not in the command-line version, thus you should always check whether this is available. This method accepts two string parameters: the first one is the text of the warning, while the second one should be a unique string identifying the warning. This is used to determine which warnings are supposed to be shown or hidden based on the user's preferences.

Here is an example:

// ...

private const string MyWarningID = "5e632fa8-0b0b-42af-8775-faf769ec9a0e"; // Use a different ID for each kind of warning you produce.

public static void Transform(ref TreeNode tree, Dictionary<string, object> parameterValues, Action<double> progressAction)
{
    if (parameterValues.TryGetValue(Modules.WarningMessageControlID, out object action) && action is Action<string, string> setWarning)
    {
        setWarning("This is a warning message!", MyWarningID);
    }

// ...

If a warning or error has been issued by any of the modules that are currently in use in the plot, a button will be shown in the status bar at the bottom of the plot interface, which can be clicked to open a window showing a summary of all the errors and warnings.

Coordinates modules

Coordinates modules are used to compute the coordinates for each node in the tree that needs to be plotted. In addition to the common members described above, Coordinates modules must also define the following members:

  • public static List<(string, string)> GetParameters(TreeNode tree)

  • public static bool OnParameterChange(object tree, Dictionary<string, object> previousParameterValues, Dictionary<string, object> currentParameterValues, out Dictionary<string, ControlStatus> controlStatus, out Dictionary<string, object> parametersToChange)

  • public static Dictionary<string, Point> GetCoordinates(TreeNode tree, Dictionary<string, object> parameterValues)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

The GetParameters method returns a list of settings that can be changed by the user and affect the behaviour of the module (see Defining parameters for how to define them). If the module does not use any of these settings, this method must still be present (and return an empty list).

The OnParameterChange method is called every time a parameter value changes. Therefore, care should be taken to ensure that this method executes quickly.

The GetCoordinates method returns a Dictionary associating the Id of every node in the tree to a Point corresponding to its coordinates. In addition to the nodes defined in the tree, the dictionary should also contain an entry for key Modules.RootNodeId (which is a constant equal to "650a0ef5-5322-4511-ae86-68bd87b47ecd"), that defines the position of the ancestor of the root node of the tree. This is useful e.g. to display the root branch for a rooted tree. The dictionary can also contain additional entries that may be used by Plot action modules to derive information about the Coordinates module that has been used.

Plot action modules

Plot action modules are used to draw the actual elements of the plot. In addition to the common members described above, Plot action modules must also define the following members:

  • public static Page GetIcon(double scaling)

  • public static List<(string, string)> GetParameters(TreeNode tree)

  • public static bool OnParameterChange(object tree, Dictionary<string, object> previousParameterValues, Dictionary<string, object> currentParameterValues, out Dictionary<string, ControlStatus> controlStatus, out Dictionary<string, object> parametersToChange)

  • public static Point[] PlotAction(TreeNode tree, Dictionary<string, object> parameterValues, Dictionary<string, Point> coordinates, Graphics graphics)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

The GetParameters method returns a list of settings that can be changed by the user and affect the behaviour of the module (see Defining parameters for how to define them). If the module does not use any of these settings, this method must still be present (and return an empty list).

The OnParameterChange method is called every time a parameter value changes. Therefore, care should be taken to ensure that this method executes quickly.

The coordinates passed to the PlotAction method are the ones that were computed by the Coordinates module.

More information about the how to use the graphics object to draw on the plot can be found in the documentation and demo for the VectSharp library.

Plot action modules can also show warnings and error messages, like Further transformation modules.

Action modules

Action modules are used to perform specific actions on the plot, if necessary invoking and enabling other modules. In addition to the common members described above, Action modules must also define the following members:

  • public static bool IsAvailableInCommandLine { get; }

  • public static string GroupName { get; }

  • public static double GroupIndex { get; }

  • public static List<(string, Func<double, Page>)> SubItems { get; }

  • public static List<(Avalonia.Input.Key, Avalonia.Input.KeyModifiers)> ShortcutKeys { get; }

  • public static bool TriggerInTextBox { get; }

  • public static string ButtonText { get; }

  • public static bool IsLargeButton { get; }

  • public static Page GetIcon(double scaling)

  • public static void PerformAction(MainWindow window, InstanceStateData stateData)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

The GetIcon method should return a Page object containing the icon for the button that will be associated to the module. This method is invoked with a parameter that gives information about the DPI scaling factor used by the system. If IsLargeButton is true, the icon should be 32x32 device independent pixels (which corresponds to 32x32 pixels at a scaling of 1, to 48x48 at a scaling of 1.5 and 64x64 at a scaling of 2). If IsLargeButton is false, the icon should be 16x16 device-independent pixels.

It is possible to use vector or raster images as icons by embedding them as base64 strings. For example, if you wish to use a PNG icon, you could create a private const string IconBase64 somewhere, whose value is the base64-encoding of the icon file. Then, in the GetIcon method, you can do something like this:

// Convert the base64 string into a byte array.
byte[] bytes = Convert.FromBase64String(IconBase64);

// Copy the image bytes to unmanaged memory.
IntPtr imagePtr = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, imagePtr, bytes.Length);

// Declare the object that will contain the icon.
RasterImage icon;

try
{
    // Initialise the icon as a raster image stream from the specified bytes in unmanaged memory.
    icon = new VectSharp.MuPDFUtils.RasterImageStream(imagePtr, bytes.Length, MuPDFCore.InputFileTypes.PNG);
}
catch (Exception ex)
{
    // Rethrow the error.
    throw ex.InnerException;
}
finally
{
    // Free the unmanaged memory that was allocated.
    Marshal.FreeHGlobal(imagePtr);
}

// Create a new Page with the same size as the icon.
Page pag = new Page(icon.Width, icon.Height);

// Draw the raster icon on the Page's graphics.
pag.Graphics.DrawRasterImage(0, 0, icon);

// Return the Page.
return pag;

Note that you will need a using System.Runtime.InteropServices; statement at the top of the file. Alternatively, if you wish to use a vector image in SVG format, you can (after creating the private const string IconBase64 containing a base64 representation of the SVG file) do the following:

// Set up the URI parser so that it can handle raster images embedded in the SVG file.
VectSharp.SVG.Parser.ParseImageURI = VectSharp.MuPDFUtils.ImageURIParser.Parser(VectSharp.SVG.Parser.ParseSVGURI);

// Convert the base64 string into a byte array.
byte[] bytes = Convert.FromBase64String(IconBase64);

// Declare the page that will contain the icon.
Page iconPage;

// Wrap the byte array in a Stream.
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(bytes))
{
    // Parse the SVG file from the Stream.
    iconPage = VectSharp.SVG.Parser.FromStream(ms);
}

// Return the icon.
return iconPage;

To obtain the base64 representation of a file, you can use the buttons in the ribbon tab of the Module creator window. Make sure to use the appropriate option for the kind of file you are encoding (e.g. binary for PNG files, text for SVG files).

The PerformAction method performs the module's action. If you need to invoke asynchronous code within this method, you should use the ContinueWith method of the Task object returned by the asynchronous method. You can add or remove modules and change module parameters by using the methods of the stateData object (these are described in the section about Custom scripts).

If your module applies any changes to the plot (e.g., by enabling other modules or changing their settings), you should ensure that the changes are stored in the Undo/Redo stack, so that users can undo them if they wish to do so (e.g., by pressing CTRL+Z). To do this, you can use the PushUndoFrame method of the MainWindow class. This method stores the current state of the plot so that it can be later restored in order to undo the change; it should be invoked before performing any changes.

This method has three arguments:

  • UndoFrameLevel level: the value of this parameter should correspond to the most upstream kind of change that is being performed. For example, if your module is going to update the Coordinates module and some Plot action modules, this should be set to UndoFrameLevel.CoordinatesModule.
  • int moduleIndex: the value of this parameter should correspond to the index of the first module that is being changed, within the list of modules of that kind. For example, if your module will update the parameters for the third Further transformation module and the first Plot action module, the value of this parameter should be 2 (index of the third Further transformation module). Downstream Further transformation modules will automatically be updated as necessary.
  • IEnumerable<int> plotLayersToUpdate = null: if you module is updating multiple Plot action modules, this parameter should be some kind of list containing all the indices of the affected Plot action modules. This is not necessary if a Further transformation or Coordinates module is being changed (because all Plot action will be refreshed anyways), or if a single Plot action module is being modified.

Selection action modules

Selection action modules are used to perform actions on the node that is currently selected on the tree. In addition to the common members described above, Selection action modules must also define the following members:

  • public static bool IsAvailableInCommandLine { get; }

  • public static Avalonia.Input.Key ShortcutKey { get; }

  • public static Avalonia.Input.KeyModifiers ShortcutModifier { get; }

  • public static bool TriggerInTextBox { get; }

  • public static string ButtonText { get; }

  • public static string GroupName { get; }

  • public static double GroupIndex { get; }

  • public static bool IsLargeButton { get; }

  • public static List<(string, Func<double, Page>)> SubItems { get; } = new List<(string, Func<double, Page>)>();

  • public static Page GetIcon(double scaling)

  • public static bool IsAvailable(TreeNode selection, MainWindow window, InstanceStateData stateData)

  • public static void PerformAction(TreeNode selection, MainWindow window, InstanceStateData stateData)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

The information about the GetIcon method of Action modules above apply to this module's GetIcon method as well.

The IsAvailable method will be invoked once every time the selected node changes, therefore care should be taken to make sure that it does not take too long to execute.

If your module applies any changes to the plot, please see the remarks above for Action modules in order to allow users to undo the changes.

Menu action modules

Menu action modules are similar to Action modules, but, instead of being triggered by clicking on a button, they are triggered by clicking on a menu item. In addition to the common members described above, Menu action modules must also define the following members:

  • public static bool TriggerInTextBox { get; }

  • public static string ItemText { get; }

  • public static string ParentMenu { get; }

  • public static string GroupName { get; }

  • public static double GroupIndex { get; }

  • public static bool IsLargeButton { get; }

  • public static List<(string, Func<double, Page>)> SubItems { get; } = new List<(string, Func<double, Page>)>();

  • public static List<(Avalonia.Input.Key, Avalonia.Input.KeyModifiers)> ShortcutKeys { get; }

  • public static Page GetIcon(double scaling)

  • public static Avalonia.AvaloniaProperty PropertyAffectingEnabled { get; }

  • public static bool IsEnabled(MainWindow window)

  • public static Task PerformAction(MainWindow window)

Descriptions of the function of these members and of their arguments are available in the comments in the template source code.

The GroupName property should return a string corresponding to the menu item group to which the item belongs. Multiple Menu action modules that return the same group name will be placed close together in the ribbon, in a section with the specified name.

The PerformAction method performs the module's action. This method returns a Task, and can be marked async to allow the execution of asyncrhonous code. If the method does not invoke asynchronous code, it should return Task.CompletedTask.

If your module applies any changes to the plot, please see the remarks above for Action modules in order to allow users to undo the changes.

⚠️ **GitHub.com Fallback** ⚠️