Update macros to work with WPF based dialogs

Updated: 5 Apr 2024

The following provides guidance on converting existing macros (and the extensions that use them) to use the latest macro runtimes which support WPF dialogs such as Document Manager in Tekla Structures.

This change applies from Tekla Structures 2020 and will not be released to earlier versions.

What has changed?

The macro recording and playback engine has been updated to support the common WPF controls used in Tekla Structures including ListViews and DataGrids. This means that WPF based dialogs such as Document Manager may now be automated using macros.

Updating Drawing List macros to work with Document Manager

This guide focuses on converting macros (also known as scripts) that previously worked with Drawing List so that they may interact with either Drawing List or Document Manager according to the currently set value of the advanced option XS_USE_OLD_DRAWING_LIST_DIALOG.

Tools to use according to role

All: Environments should be set up so that Document Manager is the default, not Drawing List (check that advanced option XS_USE_OLD_DRAWING_LIST_DIALOG is set to False). At some point, we will discontinue support for the old drawing list.

Localizers: When updating existing scripts/macros you should simply amend the existing code to use Document Manager instead of Drawing List, as that is the future intended behavior. 

Extension Developers: When updating existing code you might want to consider using the MacroBuilder class in the Tekla.Application.Library.dll which has been enhanced to work with the WPF controls used in Document Manager. MacroBuilder automates some of the basic macro operations making the development easier and neater. 

However, if updating existing extensions packaged with TSEP, it might be useful to consider using the MacroBuilderCompatibilityHelper class detailed later, as that makes it possible for a single extension to work in all Tekla Structures versions from 2016i. New extension projects, intended for release to Tekla Structures 2020 and later only, might want to simply use macros or MacroBuilder in their code and avoid that complexity.

Converting Scripts

Scripts are an earlier form of what we now refer to as Macros. The macro engine, as it has been updated, maintains backward compatibility for scripts. However, in order to automate WPF based dialogs, the old scripts do need to be modified. The following is a description of the steps needed to achieve that.

A simple script might look like:

namespace Tekla.Technology.Akit.UserScript
{
    public class Script
    {
        public static void Run(Tekla.Technology.Akit.IScript akit)
        {
            akit.Callback("acmd_rotate_view", "Z180.0", "main_frame");
        }
    }
}

 

The macro to replace it would be:

#pragma warning disable 1633 // Unrecognized #pragma directive
#pragma reference "Tekla.Macros.Akit"
#pragma reference "Tekla.Macros.Runtime"
#pragma warning restore 1633 // Unrecognized #pragma directive

namespace Tekla.Technology.Akit.UserScript
{
    public class Script
    {
        [Tekla.Macros.Runtime.MacroEntryPointAttribute()]
        public static void Run(Tekla.Macros.Runtime.IMacroRuntime runtime)
        {
            var akit = runtime.Get<Tekla.Macros.Akit.IAkitScriptHost>();
            akit.Callback("acmd_rotate_view", "Z180.0", "main_frame");
        }
    }
}

What are the differences?

  • New macros require that the entry point method is marked with MacroEntryPointAttribute and the method parameter is changed to IMacroRuntime. The old IScript compatible interface can be got from the runtime in the same manner as the wpf interface. This generic approach was chosen to make the runtime easily extendable. New macros do not care about namespace, class name or method name so they can be changed after conversion. This allows development of multiple macros in the same solution/project without colliding names.
  • The "#pragma reference" is a non-standard extension. It is recommended that all macros specify their dependencies in the beginning of the file. The string may be either simple assembly name, full assembly name or file name with .exe/.dll extension. Relative file names are relative to the macro location. For common assemblies the recommended way is to use simple names that are bound in the application config (teklastructures.exe.config). This makes it easy to update assembly versions in a single location.
  • Tekla Structures provides also a list of assembly references as a default set of dependencies for all macros. It is a legacy feature that we should try to eliminate with these local definitions in every macro file. Best practice is to reference only those assemblies that are really needed.

Tekla.Structures.MacroBuilder class

MacroBuilder is a class in the Tekla.Structures.Application.Library dll. It provides a toolkit for creating and running macros from C# code and is mainly used to automate the Tekla Structures user interface to carry out tasks that are not possible using the Open API.

In Tekla Structures versions up to and including 2019i it supports only Akit style macros. Starting with Tekla Structures 2020 it provides support for existing akit macros but also support for some WPF controls.

Documentation comments have now been enabled and published with Tekla.Structures.Application.Library dll, so developers will find that MacroBuilder methods support intellisense in Microsoft Visual Studio avoiding the need for additional documentation.

Updating existing extensions

When changing extensions to use the new version of the Macro runtime or the updated MacroBuilder, the developer needs to consider that this does create a breaking change and extensions built against the 2020 version of Tekla.Application.Library.dll and packaged using TSEP would not be compatible with earlier versions of Tekla Structures as their runtimes do not contain the new methods.

The simplest solution to that problem would be to release two TSEP packages, one the same as the original (built against 2019i or older) but with the maximum supported version set to 2019.1, and the second built against Tekla Structures 2020, with a minimum supported version of 2020.0.

If the developer wished to maintain a single version of extensions, it is possible albeit with some additional effort, to create extensions that will detect and adapt to the running TS version so that only one TSEP will be needed. 

Tekla.Application.Library.dll from version 2020.0 contains a class called MacroBuilderCompatibilityHelper which, as the name suggests, overcomes some of the issues of backward compatibility. It does this by using reflection to determine if the Tekla.Application.Library dll in the running Tekla Structures contains the new calls and provides methods to call them only if they exist. It is left to the developer’s ingenuity to work out how to handle the with and without new functionality cases, but the appendix to this document contains details of the class, as well as examples of their use. As with MacroBuilder mentioned above, documentation is provided by Visual Studio Intellisense.

Sorting Document manager’s documents DataGrid by macro

This deserves a comment as this has been a common question. The Document manager documents datagrid’s sort order may be controlled by a macro in a predictable way. The suggested way to get the required result is to first click the header of an unrelated column. That clears any existing sorts in the columns we are interested in.

Next, select the first column to be sorted to put it in ascending order (click a second time to sort in descending order) then, if more columns are to be sorted, hold down the shift key and click the next column (again clicking twice for descending order). This action may be emulated in code by using an overload of the button click method which accepts a modifier key parameter. Please note that the modifier key will only work on the datagrid column header control, other button types do not support this feature.

An example

An example of a macro that sorts by document type and then by creation date:

#pragma warning disable 1633 // Unrecognized #pragma directive
#pragma reference "Tekla.Macros.Wpf.Runtime"
#pragma reference "Tekla.Macros.Runtime"
#pragma warning restore 1633 // Unrecognized #pragma directive

namespace UserMacros {
    public sealed class Macro {
        [Tekla.Macros.Runtime.MacroEntryPointAttribute()]

        public static void Run(Tekla.Macros.Runtime.IMacroRuntime runtime)
        {
            Tekla.Macros.Wpf.Runtime.IWpfMacroHost wpf = runtime.Get<Tekla.Macros.Wpf.Runtime.IWpfMacroHost>();
            wpf.View("DocumentManager.MainWindow").Find("AID_DOCMAN_DataGridControl", "AID_DocMgr_Mark").As.Button.Invoke();
            wpf.View("DocumentManager.MainWindow").Find("AID_DOCMAN_DataGridControl", "AID_DocMgr_DocumentType").As.Button.Invoke();
            wpf.View("DocumentManager.MainWindow").Find("AID_DOCMAN_DataGridControl", "AID_DocMgr_CreationDate").As.Button.Invoke(Tekla.Macros.Runtime.ModifierKeys.Shift);
        }
    }
}

MacroBuilderCompatibilityHelper class

This class contains tools that enable the extension developer to determine if the current MacroBuilder runtime can provide the new methods added in Tekla Structures 2020, and if so invoke them. It does this without containing any direct references to those methods, as doing so would cause runtime exceptions if they don’t exist in the running version.

Many of the methods are paired, one method is used to determine if the desired method(s) are accessible and the other provides a way to run the method. Examples of how to use this are provided below. 

When a MacroBuilderCompatibilityHelper method is called to check if a specified macroBuilder method is available in the current runtime, the method info is cached by the helper and is then available for use.

Note that this class is declared as internal to the Tekla.Applications.Library assembly so that it cannot be called directly in extensions. This is deliberate as it is necessary that the extension developer copy the class into one of the assemblies distributed with their extension, thereby ensuring that it is available at runtime. However, the internal class does provide documentation using Visual Studio Intellisense.

A copy of the source code for this class will be released with the OpenAPI package.

MacroBuilderCompatibilityHelper examples

Example 1: a macro that closes the UI

This first checks to see if the advanced option XS_USE_OLD_DRAWING_LIST_DIALOG to see which dialog is in use. If Document Manager is selected there, then it checks the loaded version of Tekla.Application.Library.dll using reflection to see if it contains the required methods. If it is a pre-2020 version without them, then it generates and runs macro code that is compatible with the old Drawing list (which of course will not work if Document Manager is running). If the required MethodInfo(s) are available, then they are invoked and macro code compatible with Document Manager is generated and run.

Note that by using the helper and reflection, the code never attempts to call or reference methods that don’t exist in the loaded runtime.

/// <summary>Closes the document manager or drawing list as appropriate</summary>

public static void CloseDocumentManagerOrDrawingList()
{
    if (!TeklaStructures.Connect()) return;

    var builder = new MacroBuilder();
    var helper = new MacroBuilderCompatibilityHelper(builder);

    // Is the loaded version of MacroBuilder capable of handling document manager
    // and is document manager the chosen UI?
    // Also, check that the required MethodInfo is supported.

    if (MacroBuilderCompatibilityHelper.CreateMacroCodeForDocumentManager() &&
        helper.IsSupportedCloseWpfViewMethod())
    {
        helper.InvokeCloseWpfViewMethod("DocumentManager.MainWindow");
        builder.Run();
        MacroBuilder.WaitForMacroToRun();
        return;
    }
    else if (MacroBuilderCompatibilityHelper.IsOldDrawingListInUse())
    {
        builder.PushButton("dia_draw_select_cancel", "Drawing_selection");
        builder.Run();
        MacroBuilder.WaitForMacroToRun();
        return;
    }
 
    // XS_USE_OLD_DRAWING_LIST_DIALOG is set to false, but unable to find all the macrobuilder methods needed
    MessageBox.Show("This version of Tekla Structures does not fully support automation of Document Manager using this macro.");
    return;
}

Example 2: a macro that displays only single part drawings for objects selected in the model

This first checks to see if the advanced option XS_USE_OLD_DRAWING_LIST_DIALOG to see which dialog is in use.

If Document Manager is selected there, then it checks the loaded version of Tekla.Application.Library.dll using reflection to see if it contains the required methods.

If the required MethodInfo(s) are available, then they are invoked and macro code compatible with Document Manager is generated and run.

/// <summary>
/// Select single part drawing for the selected part in model using document manager 
/// or drawing list as appropriate.
/// Assumes document manager or drawing list is open.
/// </summary>

public static void SelectSinglePartDrawingForSelectedPartInDocumentManagerOrDrawingList()
{
    if (!TeklaStructures.Connect()) return;

    // requires that either drawing list or document manager is already open
    var builder = new MacroBuilder();
    var helper = new MacroBuilderCompatibilityHelper(builder);
 
    // Is the loaded version of MacroBuilder capable of handling document manager and is document manager the chosen UI?
    // Also, check that the required MethodInfo(s) are supported.

    if (MacroBuilderCompatibilityHelper.CreateMacroCodeForDocumentManager() &&
        helper.IsSupportedPushWpfButtonMethod() &&
        helper.IsSupportedSelectWpfListViewItemsByNameMethod() &&
        helper.IsSupportedSelectWpfDataGridRowsMethod())
    {
        helper.InvokePushWpfButtonMethod(
    "DocumentManager.MainWindow",
            new[] { "AID_DOCMAN_ShowAllDocuments" });
        helper.InvokePushWpfButtonMethod(
             "DocumentManager.MainWindow",
             new[] {"AID_DOCMAN_ButtonSelectDrawings" });
        helper.InvokeSelectWpfListViewItemsByNameMethod(
              "DocumentManager.MainWindow",
              new[] { "AID_DOCMAN_CategoryList" },
              new[] { "albl_single_part_drawings" });
        helper.InvokeSelectWpfDataGridRowsMethod(
              "DocumentManager.MainWindow",
              new[] { "AID_DOCMAN_DataGridControl" },
              new[] { 0 });

        builder.Run();
        MacroBuilder.WaitForMacroToRun();
        return;
    }

    if (MacroBuilderCompatibilityHelper.IsOldDrawingListInUse())
    {
        builder.ValueChange("Drawing_selection", "diaSavedSearchOptionMenu", "10");
        builder.PushButton("dia_draw_filter_by_parts", "Drawing_selection");
        builder.TableSelect("Drawing_selection", "dia_draw_select_list", 1);
        builder.Run();
        MacroBuilder.WaitForMacroToRun();
        return;
     }

     // XS_USE_OLD_DRAWING_LIST_DIALOG is set to false, but unable to find all the macrobuilder methods needed
     MessageBox.Show("This version of Tekla Structures does not fully support automation of Document Manager using this macro.");
    return;
}




 

Was this helpful?
The feedback you give here is not visible to other users. We use your comments to improve the content.