Command Framework And XCommands
Note
XCommands are Cross-UI-Platform Command definitions that can be used by the Ribbon, Menus and XCmdButtons.

User Interface Development Index: BSWUserInterfaceDevelopment

Legacy Command Support

The Command is one of the most basic building blocks in PowerPlatform. Without Commands, you couldn't open a file, save a file, start the Element Selection tool, etc. Commands are identified by two pieces of information: 1) an ID number (64-bits) and 2) the ID/Name of the Task owning that Command. When used in context, there is an additional command parameter string, or "unparsed" string, passed to the command's function. So in effect, these 3 pieces of information together form a unique command.

Command Tables

For the PowerPlatform core and MDL applications, Commands are defined in a CommandTable resource in a .r file that is compiled using the Bentley Resource Compiler. For .NET AddIns, Commands are defined in XML files.

This is an example command definition from cmdtable.r in the PowerPlatform core:

#define CT_NONE 0
#define CT_MAIN 1
#define CT_DIALOG 228
. . .
CommandTable CT_MAIN =
{
. . .
{119, CT_DIALOG, DIALOGOPEN, TRY|IMM, "DIALOG" },
. . .
CommandTable CT_DIALOG =
{
. . .
{ 68, CT_NONE, INHERIT, NONE, "OPENFILE", 580, },
. . .

In the CommandTable definition above, notice the string "DIALOG". This is used as the first part of the generated C #define identifier for the CommandNumber. 119 is in the first column and it will be used in the actual numeric CommandNumber. CT_DIALOG ID is in second column on that same line. That ID is used for a subsequent CommandTable definition, which contains an entry with the string "OPENFILE", a 68 and CT_NONE. 68 will be used in the CommandNumber, and the CT_NONE indicates there are no more CommandTables to use when generating the CommandNumber. The following example shows how the hexidecimal represtations of the previous numbers are concatenated together to form the numeric CommandNumber.

Example CommandNumbers from the generated cmdlist.r.h command number header file:

#define CMD_DIALOG_OPENFILE 0x7744000000000000UI64 /* DIALOGOPEN CmdString = 580 */

Example command function setup:

static void myapp_openFile (WCharCP unparsedCP)
{
}
. . .
static MdlCommandNumber commandNumber[] =
{
{myapp_openFile, CMD_DIALOG_OPENFILE },
0
};
. . .
extern "C" void MdlMain (int argc, WCharCP argv[])
{

To be thorough and not short change .NET AddIns, here is an example setup for AddIn commands:

-- XML File --
<?xml version="1.0" encoding="utf-8" ?>
<KeyinTree xmlns="http://www.bentley.com/schemas/1.0/MicroStation/AddIn/KeyinTree.xsd">
<RootKeyinTable ID="root">
<Keyword SubtableRef="props" CommandClass="MacroCommand" CommandWord="MSGCENTER">
<Options Required="true"/>
</Keyword>
</RootKeyinTable>
<SubKeyinTables>
<KeyinTable ID="props">
<Keyword CommandWord="OPEN" />
. . .
</KeyinTable>
</SubKeyinTables>
<KeyinHandlers>
<KeyinHandler Keyin="MSGCENTER OPEN" Function="MsgCenter.MessageCenterApp.Open" />
. . .
</KeyinHandlers>
</KeyinTree>
-- C# File --
namespace MsgCenter
{
[Bentley.MstnPlatformNET.AddInAttribute(MdlTaskID = "MsgCenter")]
public sealed class MessageCenterApp : Bentley.MstnPlatformNET.AddIn
{
. . .
/*------------------------------------------------------------------------------------**/
/*--------------+---------------+---------------+---------------+---------------+------*/
public static void Open
(
string unparsed
)
{
MainWindow.OpenWindow();
}

IconCmds and Menus

IconCmds and Menus are two types of "entry points" that activate commands using CommandNumbers. IconCmds are actually made up of 3 separate resources. They contain the following:

An example of an IconCmd using the CMD_DIALOG_OPENFILE CommandNumber:

{
CMD_DIALOG_OPENFILE, MTASKID, "",
""
}
extendedAttributes
{{
{EXTATTR_FLYTEXT, TXT_Flyover_OpenFile},
{EXTATTR_BALLOON, TXT_Balloon_OpenFile}
}};
IconCmdSmallRsc ICONCMDID_OpenFile =
{
24, 24, ICONFORMAT_WINDOWS, BLACK_INDEX, "Open File", "FileOpen"
};
IconCmdLargeRsc ICONCMDID_OpenFile =
{
32, 32, ICONFORMAT_WINDOWS, BLACK_INDEX, "Open File", "FileOpen"
};

Menu items contain information similar to IconCmds, but with a few additions:

In addition, a Feature Aspect ID check can surround the menu item definition to include or exclude the menu item for Power Products.

An example of a menu item using the CMD_DIALOG_OPENFILE CommandNumber:

@if (FeatureAspect (PP_ID_FeatureAspects_File_Open)) // @fadoc File | Open menu item
{TXT2_FileOpenOption, 'O'|VBIT_CTRL, ON, NOMARK, 0, NOSUBMENU,
CMD_DIALOG_OPENFILE, MTASKID, "",
ICONCMDID_OpenFile, Icon, MTASKID},
@endif

In CONNECT edition, the Ribbon interface has been introduced. We're using the Telerik WPF RadRibbonView as the implementation of the ribbon control. To activate commands from the ribbon, we needed a new .NET-friendly way to get to PowerPlatform command information. IconCmds and Menu items already had duplicate information, and Ribbon Buttons would also need that same information. Instead of gathering the information from those existing resources, we decided to create a new resource that consolidates the duplicate information. The identifier for the new common resource is a name string. This new resource is called an XCommand because it's a common way of defining Cross-UI-Platform command information. The information includes:

The "Name / Id" is used to uniquely identify the XCommand and is used throughout PowerPlatform. Multiple XCommands are listed in a XCommandListRsc, which can be in core or in an application. The XCommands are quickly loaded during PowerPlatform 's start up and are cached.

The XCommand structures in dialogbox.r.h:

typedef struct xcommandrsc
{
char name[];
CommandRsc command;
char iconName[];
Utf8Char title[];
Utf8Char description[];
char featureTrackingId[];
char showExpression[];
char enableExpression[];
char toggleExpression[];
char markExpression[];
char uiSynchEvents[];
UInt32 accelerator;
UInt32 featureAspectId;
} XCommandRsc;
typedef struct commandrsc
{
CommandNumber commandNumber;
char commandTaskId[];
char unparsed[];

An example XCommand Name is "Mstn.File.Open". "Mstn.File.Open" is given the CommandNumber of CMD_DIALOG_OPENFILE from the "Ustn" task (MTASKID), which opens the file open dialog. The following is an example of an XCommand in a .r file:

XCommandListRsc XCMDLISTID_MstnPlatform =
{
{
. . .
{"Mstn.File.Open", // File Open Dialog
{CMD_DIALOG_OPENFILE, MTASKID, ""},
"Open",
TXT_Balloon_OpenFile,
TXT_Flyover_OpenFile,
"ec375142-3726-11e3-bf6e-6894231a2840",
"", "", "", "", "",
'O'|VBIT_CTRL,
PP_ID_FeatureAspects_File_Open}, // @fadoc Mstn.File.Open XCommand
. . .
}
};
Note
The Feature Tracking Id is a GUID and must be unique.

XCmdButton

The XCmdButton item is similar to an IconCmd or IconCmdX, which adds an item hook capability, but the XCmdButton refers to an XCommand. In a ToolBox or DialogItemList, use the XCmdButton item type instead of IconCmd.

The XCmdButton structures in dlogbox.r.h:

typedef struct ditem_xcmdbuttonrsc
char xCommandName[];
long itemHookId;
UInt64 itemHookArg;
UInt32 attributes;

An example of an XCmdButton in a .r file:

// Definition --
DItem_XCmdButtonRsc ICONCMDID_OpenFile =
{
"Mstn.File.Open", 0, 0, 0
};
// Reference --
{{ 0, 0, 0, 0}, XCmdButton, ICONCMDID_OpenFile, ON, 0, "", ""},

PartFile and MS_XCOMMAND_APPS

Any application that contains XCommand resources should add itself to the MS_XCOMMAND_APPS configuration variable, which goes in the applicationload.cfg configuration file. This is accomplished by adding an entry into the <Part> for the application in its PartFile. The specific entry goes in this element hierarchy: <Part>, <Bindings>, <Files>, <LoadConfig>, ConfigVars ="...,MS_XCOMMAND_APPS" attribute.

An example of including MX_XCOMMAND_APPS in the ConfigVars attribute of the <LoadConfig> element:

<Part Name="MdlApp-2ddraw" BMakeFile="apps\2ddraw\2ddraw.mke">
<SubPart PartName="2dModelingDll" />
<Bindings>
<Files ProductDirectoryName="MdlsysAsneeded" FeatureAspect="PP_ID_FeatureAspects_BaseGeometry">
Delivery\2ddraw.ma
<TransKit SourceDirectory="Delivery\TransKit\MdlApps\2dModeling\2ddraw" />
<LoadConfig AppName="2ddraw" ConfigVars="MS_CMDTABLE_LOAD,MS_XCOMMAND_APPS" />
</Files>
</Bindings>
</Part>

Native API

The native API for XCommands is in the following interface and classes:

The following is an example of using the native XCommand API to activate/execute a command:

#include <Mstn\XCommand.h>
. . .
IXCommandP xCommandP;
xCommandP = XCommandManager::GetManager().FindXCommand (L"Mstn.File.Open");
if (NULL != xCommandP)
xCommand->ExecuteCmd (NULL, NULL);

The Ribbon and XCommands

The PowerPlatform Ribbon uses the RadRibbonView from Telerik and is WPF based. Any discussion of WPF commands starts with an introduction to Microsoft's ICommand interface.

ICommand and RelayCommand

ICommand is in the System.Windows.Input namespace. It is in PresentationCore.dll in .NET 4 and earlier and in System.dll starting with .NET 4.5. ICommand contains the CanExecute and Execute methods and the CanExecuteChanged event. The CanExecute method controls whether a WPF button or menu item is enabled or disabled. The Execute method fires off the method associated with the command. The CanExecuteChanged event is fired when changes occur that affect whether or not the command can execute.ICommand Interface.

The RelayCommand class in Bentley.UI.Mvvm is an implementation of the ICommand interface. The RelayCommand 's sole purpose is to relay its functionality to another object by invoking delegates. Our RelayCommand implementation came from Josh Smith's library of MVVM classes.

IMstnCommand and NamedMstnCommand

The IMstnCommand interface is a managed interface for PowerPlatform commands. IMstnCommand is UI technology neutral. IMstnCommand, and several of its implementations, are in the MstnPlatformNET.Commands namespace in MicroStation.dll. IMstnCommand contains these properties:

The CanExecute property is all that overlaps with something in the ICommand interface. Everything else is unique to PowerPlatform. The MstnCommand class is an abstract base class that adds Named Expressions for Show, Enable, Toggle and Menu Mark. The NamedMstnCommand class is a subclass of MstnCommand and is the managed wrapper around the native XCommand.

MstnRelayCommand

The MstnRelayCommand class is a subclass of RelayCommand. It is WPF specific and is in the Bentley.MicroStation.WPF.dll assembly. It forms the connection between the Microsoft ICommand interface and the Bentley IMstnCommand interface introduced above. MstnRelayCommand contains:

Ribbon Definitions

To include anything in the PowerPlatform ribbon, it needs to be defined in a "Ribbon Definition". Ribbon Definitions are specified in an XML file conforming to the RibbonDefinitions.xsd file. There are definitions for numerous objects making up the ribbon, including Buttons, DropDownButtons, SplitButtons, Groups, Tabs, etc. Buttons, DropDownButtons, SplitButtons definitions may contain "Command Data" that refers to a "Named Command", which is really a reference to a NamedMstnCommand instance.

An example of a <Button> element in an XML file:

<Buttons>
<Button Name="Mstn.File.Open" Priority="0">
<ButtonData>
<CommandData Type="NamedCommand">
<NamedCommand>Mstn.File.Open</NamedCommand>
</CommandData>
</ButtonData>
</Button>
. . .
</Buttons>

An example of a <ButtonRef> element referring to a Named <Button> in an XML file:

<QuickAccessCollections>
<QuickAccessCollection Name="Mstn.QuickAccessItems" . . .
<ButtonRef Name="Mstn.File.Open" Priority="5">
<ButtonParams Size="Small" CollapseToSmall="Never" CollapseToMedium="Never">
<AccessText>2</AccessText>
</ButtonParams>
</ButtonRef>
. . .

Using NamedMstnCommands and MstnRelayCommands in C# and XAML

An example of exposing an XCommand to C# or XAML via the NamedMstnCommand and MstnRelayCommand classes:

-- MyCommands.cs --
using BM = Bentley.MstnPlatformNET;
class MyCommands
{
private MstnRelayCommand m_FileOpen;
public MstnRelayCommand FileOpen
{
get
{
if (m_FileOpen == null)
m_FileOpen = new MstnRelayCommand(new BM.Commands.NamedMstnCommand ("Mstn.File.Open"));
return m_FileOpen;
}
}
-- MyViewModel.cs --
public MyCommands MyCommands;

An example of referencing an MstnRelayCommand in XAML for the Backstage:

-- MyView.xaml --
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
<telerik:RadRibbonBackstageItem Header="{x:Static props:Resources.Open_Label}"
IsSelectable="False"
CloseOnClick="True"
Command="{Binding MyCommands.FileOpen}"
Visibility="{Binding MyCommands.FileOpen.Visibility}"
telerik:KeyTipService.AccessText="O"/>

XCommands for .NET AddIns

We have implemented a way for managed applications to supply XCommandRsc ’s using a Bentley resource manager resource (.r) file. Here are the steps for using this capability.

  1. Create resource file containing an XCommandRscList with the same name as your managed assembly.For example, if your assembly is Bentley.ECUISupport.dll, then your resource file should be Bentley.ECUISupport.r. See Bentley.ECUISupport.r for an example XCommand resource file. Here is a section of the Bentley.ECUISupport.r file:

    #include <ribbontext.h>
    #include <rsrctxt2.h>
    #include <TransKitGenSrc\bentley.ecuisupportcmd.h>
    #include <TransKitGenSrc\PowerPlatform.FeatureAspects.h>
    XCommandListRsc 1 =
    {
    {
    {"Mstn.File.Properties",
    {CMD_ELEMENT_INFO_FILEPROPERTIES, "Bentley.ECUISupport", ""},
    "Properties",
    TXT_RibbonLabel_FileProperties,
    TXT_RibbonDesc_FileProperties,
    "ec383b8d-3726-11e3-8f85-6894231a2840", "", "", "", "", "", NOACCEL,
    PP_ID_FeatureAspects_File_Properties}, // @fadoc File.Properties XCommand
    {"Mstn.ProjectNavigation.Explorer",
    {CMD_DIALOG_EXPLORER_TOGGLE, "Bentley.ECUISupport", ""},
    "ProjectExplorer",
    TXT2_FileProjectOption,
    TXT_RibbonDesc_Explorer,
    "ec3a104e-3726-11e3-a7ad-6894231a2840", "", "", "", "", "", NOACCEL,
    PP_ID_FeatureAspects_Properties_ECBrowser}, // @fadoc ProjectNavigation.Explorer XCommand
    }
    };

    Note that the XCommandListRsc ’s in MicroStation ’s source files use defined constants such as “TXT_RibbonLabel_FileProperties” because those text strings are also used in other places. It is not necessary to do that (you can just put the English strings directly into the resource) because a .rsc.mui.xliff file is generated from this file for localization purposes.

    The field following the CMD_xxx entry must be the Add-in’s TaskId. You can find out what that is by looking in the application dropdown in MicroStation ’s keyin browser. For XCommandRsc ’s added through the mechanism described here, it must be filled in.

    The "#include <TransKitGenSrc\bentley.ecuisupportcmd.h>" and CMD_ELEMENT_INFO_FILEPROPERTIES sections are related to step 4 below.

  2. Modify your transkit.xml file to specify that the XCommands resource file specified above is included in your translatable resources. For example, the <BentleyRsc> element was added to the ECUISupport transkit.xml file:

    <?xml version="1.0" encoding="utf-8"?>
    <Transkit>
    <SatelliteAssembly DllName="Bentley.ECUISupport" DeliverEnglish="False" RightsCompliant="True">
    <ResxFile ResourceName="Bentley.ECUISupport" FileName="ECUISupport.resx" />
    </SatelliteAssembly>
    <BentleyRsc Name="Bentley.ECUISupport.rsc.mui" >
    <ResourceSource>Bentley.ECUISupport.r</ResourceSource>
    </BentleyRsc>
    </Transkit>

  3. Modify the part in your xxx.PartFile.xml file to tell BentleyBuild that you want to deliver the compiled XCommand resource file alongside your assembly. You also need to add your .rsc file to the MS_XCOMMAND_RSRCS configuration file. If you are working with a MicroStation part, that can be done in the part file. The "Delivery\Bentley.ECUISupport.rsc" and "MS_XCOMMAND_RSCS" additions were made to the Bentley.ECUISupport part in PowerPlatform.Partfile.xml:

    <Part Name="ClrApp-ECUISupport" BMakeFile="mstn\clrapps\ECUISupport\ECUISupport.mke">
    <SubPart PartName="DgnDisplayNet" PartFile="DgnDisplayNet" Repository="DgnDisplayNet" />
    <SubPart PartName="ClrApp-propertymanager" />
    <SubPart PartName="Componentsets" PartFile="PPModules\Componentsets\Componentsets" />
    <Bindings>
    <Assemblies ProductDirectoryName="PPManagedAssemblies">
    Delivery\Bentley.ECUISupport.dll
    Delivery\Bentley.ECUISupport.rsc
    <TransKit SourceDirectory="Delivery\TransKit\clrapps\Bentley.ECUISupport" />
    <LoadConfig AppName="Bentley.ECUISupport" ConfigVars="MS_CMDTABLE_LOAD,MS_XCOMMAND_RSCS" />
    </Assemblies>
    </Bindings>
    </Part>

  4. In your bmake file, make sure you are generating the .h file that specifies the CMD_xxx constants from your command table.If you are interested in making XCommand resources, that means you have specified the command structure of your application in an xml file, usually in a file named something like <appname>.commands.xml. You need to generate an include file that defines constants that you can use in the XCommands resource. In ECUISupport.mke, this was added:

    #----------------------------------------------------------------------
    # Create the command table include file.
    #----------------------------------------------------------------------
    $(gensrcTransKit)$(appName)cmd.h : $(baseDir)src/Resources/ECUISupport.commands.xml

    If you look back at Step 1, you will see where the generated header file is #included, and where the constants are used in the XCommand specification.

  5. Modify your bmake file to set up processing of the BentleyRsc section of the transkit.xml file. The following is in a new section that was added near the end of ECUISupport.mke.

    #----------------------------------------------------------------------
    # Create and SymLink the XCommand .rsc file
    #----------------------------------------------------------------------
    MuiBaseName = $(appName).rsc
    MuiTranskitDeliveryDir = $(ContextDeliveryDir)TransKit/clrapps/$(appName)/
    MuiAdditionalIncludes = $(MSCore)TransKit/SharedInc/ $(gensrc)
    MuiTranskitSourceDir = $(baseDir)src/Resources/
    # This tells CreateAndSymLinkMui to symlink the resource file itself
    ForClrAppXCommand = 1>
    # This tells CreateAndSymLinkMui to avoid symlinking the transkit.xml file - SymLinkResxTrankitFiles.py does that.
    DontSymLinkTranskitXml = 1
    %include $(SharedMki)CreateAndSymlinkMui.mki

    Since this new section uses only bmake variables that are usually defined in every bmake file (e.g., appName, ContextDeliveryDir, baseDir), you can pretty much copy that section. If you are not working in the MicroStation source, you probably need to change the definition of MuiAdditionalIncludes, often to just . In the example, Transkit/SharedInc/ is there so that ribbontext.h is found to resolve the constants like TXT_RibbonLable_FileProperties.

If you follow the steps above, you should find the <appName>.rsc file delivered in the same directory as your <appName>.dll file. In the en subdirectory, there should be an <appName>.rsc.mui file, and an <appName>.rsc.mui.xliff file is created to be added to the transkit.


Copyright © 2017 Bentley Systems, Incorporated. All rights reserved.