Support for a RibbonBar interface has been added to PowerPlatform for the CONNECT Edition release. When using the RibbonBar interface, the main window is switched to a Telerik WPF RadRibbonWindow, and is provided a RadRibbonView by the "RibbonView" ClrApp. It is possible for the RadRibbonView object to be obtained by any managed application using the Bentley.MstnPlatformNET.WPF.RibbonBar class. Applications could use the RadRibbonView object to add their own tabs, groups and controls to the Ribbon although this is not the preferred way to extend the Ribbon. Please see IRibbonComponentProviders for the preferred method. The IRibbonComponentProviders interface along with many other supporting ribbon classes are delivered in the MicroStation.Ribbon.dll.

Information on how to define the Ribbon's Backstage can be found at BSWUserInterfaceRibbonBackstage.

The basic components of a Ribbon are shown below.

RibbonView_Visual_Structure.png

Since we are using the Telerik RibbonView, here are a few links to their web site that provides more details about the different components.

Ribbon Layout and Definition XML

The contents of the Ribbon Tabs, Quick Access Toolbar, and Backstage can be specified by using one or more XML files to specify the different ribbon components. There is an XML Schema file name miscdev\mstnappdata\workspace\System\GUI\Ribbon\RibbonDefinitions.xsd. Typically we do not want our users to edit our delivered ribbon configuration so we import the ribbon XML files into a MicroStation resource file(s).The example shown below is from MicroStation and is the file BaseRibbon.cfg.

#--------------------------------------------------------------------------------------
#
# BaseRibbon.cfg - set up base configurations used by the Ribbon
#
# (c) 2015 Bentley Systems, Incorporated. All rights reserved. #
#
#--------------------------------------------------------------------------------------
#----------------------------------------------------------------------
# Primary Ribbon Resources
#----------------------------------------------------------------------
_USTN_PRIMARY_RIBBONRSCLIST = $(MSDIR)Ribbon\*.rsc
#
#----------------------------------------------------------------------
# Verticals and 3rd Party Applications can specify application specific
# Ribbon components in their own RSC file.
#----------------------------------------------------------------------
#MS_RIBBONRSCLIST = $(MS_RIBBONDIR)*.rsc
#
#----------------------------------------------------------------------
# Define capability to always display Admin Workflow. By default the
# Admin Workflow is only available if the active file is a
# configured DGNLIB.
#----------------------------------------------------------------------
#_USTN_CAPABILITY < +CAPABILITY_UI_ALWAYSSHOWADMINWORKFLOW
#
#----------------------------------------------------------------------
# Define location to find Ribbon XML definition files
#----------------------------------------------------------------------
MS_RIBBONXML > $(MS_RIBBONDIR)*.xml
#
#----------------------------------------------------------------------
# Define number of expanded ribbon groups for Task Navigation tab.
# Once this limit is hit then all other tasks are placed in an Overflow
# group.
#----------------------------------------------------------------------
MS_RIBBON_NUMEXPANDEDTASKGROUPS = 8
#
#----------------------------------------------------------------------
# Define Ribbon Component Providers that provide application specific
# components.
#----------------------------------------------------------------------
MS_RIBBONCOMPONENTPROVIDERS < Bentley.Macros.UI
MS_RIBBONCOMPONENTPROVIDERS < Bentley.ViewGroupHistory
MS_RIBBONCOMPONENTPROVIDERS < Bentley.ViewGroupList
MS_RIBBONCOMPONENTPROVIDERS < Bentley.DrawingScale
MS_RIBBONCOMPONENTPROVIDERS < Bentley.Visualization.MaterialsRibbonToolbar
MS_RIBBONCOMPONENTPROVIDERS < Bentley.Visualization.LightsRibbonToolbar
MS_RIBBONCOMPONENTPROVIDERS < Bentley.Visualization.RenderRibbonToolbar
MS_RIBBONCOMPONENTPROVIDERS < Bentley.TableUI

The Basic XML Layout

The XML Layout below shows the major grouping of ribbon items that can be controlled through the XML file.

<?xml version="1.0" encoding="utf-8" ?>
<RibbonItems xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="RibbonDefinitions.xsd">
<Workflows/>
<TabCollections/>
<Tabs/>
<Groups/>
<GroupPanels/>
<ButtonGroups/>
<QuickAccessCollections/>
<Buttons/>
<ToggleButtons/>
<RadioButtons/>
<DropDownButtons/>
<SplitButtons/>
<DropDownPanels/>
<MenuItemCollections/>
<Backstages/>
<BackstageButtonLists/>
<BackstageGroupLists/>
<ContextualTabSets/>
</RibbonItems>

The following chart is a quick summary of what is contained below each major element.

Element

Description

Workflows Contains one or more Workflow definitions. Each Workflow specifies a TabCollection.
TabCollections Contains one or more TabCollection definitions. Each TabCollection specifies the tabs to be included in the Ribbon.
Tabs Contains one or more Tab definitions. Each Tab definition specifies one or more Groups.
Groups Contains one or more Group definitions. Each Group definition can contain GroupPanels, ButtonGroups, Buttons, ToggleButtons, RadionButtons, DropDownButton, SplitButtons, ComboBoxes, or Galleries. Only Application defined ComboBoxes or Galleries are supported.
GroupPanels Contains one or more GroupPanel definitions. Each GroupPanel definition can contain other GroupPanels, or ButtonGroups, Buttons, ToggleButtons, RadionButtons, DropDownButton, SplitButtons, ComboBoxes, or Galleries. Only Application defined ComboBoxes or Galleries are supported.
ButtonGroups Contains one or more ButtonGroup definitions. Each ButtonGroup definition can contain Buttons, ToggleButtons, RadioButtons, DropDownButton, SplitButtons.
QuickAccessCollections Contains one or more QuickAccessCollection definitions. Each QuickAccessCollection definition can contain Buttons, ToggleButtons, RadionButtons, DropDownButton, SplitButtons, or ComboBoxes. Only Application defined ComboBoxes are supported.
Buttons Contains one or more Button definitions. Each Button definition specifies, at a minimum, a command to queue, an icon, a label, and a name.
RadioButtons Contains one or more RadioButton definitions. Each RadioButton definition specifies, at a minimum, a command to queue, an icon, a label, a name, a ToggleExpression, and a RefreshEventMask used to sync the item.
ToggleButtons Contains one or more ToggleButton definitions. Each ToggleButton definition specifies, at a minimum, a command to queue, an icon, a label, a name, a ToggleExpression, and a RefreshEventMask used to sync the item.
DropDownButtons Contains one or more DropDownButton definitions. Each DropDownButton definition specifies, at a minimum, an icon, a label, a name, and either a DropDownPanel or a MenuItemCollection.
SplitButtons Contains one or more SplitButton definitions. Each SplitButton definition specifies, at a minimum, a command to queue, an icon, a label, a name, and either a DropDownPanel or a MenuItemCollection.
DropDownPanels Contains one or more DropDownPanel definitions. Each DropDownPanel definition can contain GroupPanels, or ButtonGroups, Buttons, ToggleButtons, RadionButtons, DropDownButton, SplitButtons, ComboBoxes, or Galleries. Only Application defined ComboBoxes or Galleries are supported.
MenuItemCollections Contains one or more MenuItemCollection definitions. Each MenuItemCollection definitions can contain MenuItems, or SubMenuItems. A MenuItem can be set as a Separator.
Backstages Contains a single Backstage entry that is named "Mstn.Backstage.Default" which defines the set of BackstageItems to be shown in the Backstage.
BackstageGroupLists Contains BackstageGroupList definitions which show two vertical list of buttons, the left list specify the list of buttons to display in the right button list.
BackstageButtonLists Contain BackstageButtonList definitions that specify a single list of buttons that define commands to activate.

The definition type listed are either Ribbon Containers or Ribbon Items. Items typically activate commands when selected. The list of available item definitions supported by XML are: Button, DropDownButton, SplitButton, RadioButton, ToggleButton. These items are the assembled in a container by specifying a reference to the definition. In the XML file item references will contain the suffix "Ref", (ie "ButtonRef", "ToggleButtonRef", etc.). This design allows the component to be defined once and used in multiple places.

In additon to having references to the Ribbon Item definitions above, Application supplied items may also be referenced. Applications can provide definition of any of the types listed above along with two additional types: ComboBox and Gallery. Each definition or reference allows the specification of a Priority and item-specific "Params". The Priority value is used to arrange the items within the containers, and the containers within their parent containers.

It is important to note that any parameter defined in an item reference will override the default "param" value specified in the item defintion. It is important that item references alway set priorirty and key tip values in their item-specific "Params".

KeyTips and Popup KeyTips

KeyTips and Popup KeyTips are set using the tags <AccessText> and <PopupAccessText>. KeyTips are shown when the F2 key is pressed to allow for keyboard access to the items in the Ribbon. F3 will display the KeyTips for the current Tab. The Popup KeyTips are shown when a group is opened as a popup group and the ALT key is pressed. Only items within a Ribbon Group need to specify Popup KeyTips (i.e. QAT items and drop down menu items do not require the specification of PopupAccessText). The name "AccessText" was used to be consistent with the property name used in the Telerik API.

Validation of KeyTips and Popup KeyTips

It is important to be sure that the KeyTips and Popup KeyTips are unique with in a specified scope.

Type Scope

Typical Range

QAT Tips QATCollection 1-9 (single character)
Tab KeyTips Tab Collection A-Z (single character)
Group Items KeyTips Tab AA-ZZ (two characters)
Group Launcher Tab 1-9
DropDown Menu Item KeyTips Submenu 1-9-A-Z (single character)
Popup KeyTips Group A-Z

To Assist in creating unique KeyTips, MicroStation provides the command RIBBON VALIDATE. This command will produce a report that shows any duplicate or missing KeyTip definitions along with a summary of KeyTip usage. This command will use locale specific Ribbon definitions. Currently there is an issue with Application Provided Ribbon Groups that do not properly expose their items' AccessText, we will be working to address this situation. There are situations where two item definitions have duplicate KeyTip specifications and should not be reported as an issue when RIBBON VALIDATE is run. That is when the designer has set up buttons' visibility specifications to be visible at different times. To keep these items from being reported, the designer can add an ApplicationParameter that defines these situations. Below is an example.

<ButtonParams Size="Small">
<AccessText>AX</AccessText>
<PopupAccessText>X</PopupAccessText>
<ApplicationParams>
<ApplicationParam>
<Name>MatchingKeyTipItems</Name>
<Value>Mstn.DrawingAids.AccuDraw.LockX</Value>
</ApplicationParam>
</ApplicationParams>
</ButtonParams>

MatchProcessing /Merging Rules

As the XML data is loaded into the cache, MatchProcessing rules are used to determine what to do if a defintion with the same name has already been loaded. This means the order of files defined by _USTN_PRIMARY_RIBBONRSCLIST, MS_RIBBONRSCLIST , and MS_RIBBONXML is important. When processing containers such as tabs, if the Match rule is not Override or Ignore then the child items are merged into the matching tab. Definition Match Processing

Option

Description

Override Override the original definition and use this one in its place.
Ignore If the definition is already defined, use it.

Any matched References in child items support the following "item" MatchProcessing values. Reference Match Processing.

Option Description
Ignore If an item is already defined, use it.
Remove Remove the original item.

Example Specification

The Workflow specification defines an entry for the Workflow picker comboBox in the QAT. All the available Workflow definitions are used to populate the Workflow picker. The Label is the localizable text displayed in the combobox's dropdown item list. The Visibility element is used to define when this workflow should be added to the workflow picker. The SyncItemEvent specifies the system event(s) that require the Visibility's ShowExpression to be reevaluated. The TabCollectionRef element specifies which TabCollection to use to populate the Ribbon Tabs when the Workflow is made active.

<Workflows>
<Workflow Name="AdminWorkflow" Priority ="100">
<Label>Admin</Label>
<WorkflowParams>
<SyncItemEvent>SystemEvent.ActiveModelChanged</SyncItemEvent>
<ShowExpression>[Session]Session.ActiveFileIsAConfiguredDgnlib() or Session.IsCapabilityEnabled("UI_AlwaysShowAdminWorkflow")</ShowExpression>
<FeatureAspect>
</FeatureAspect>
</WorkflowParams>
<Ribbon>
<TabCollectionRef>AdminTabs</TabCollectionRef>
</Ribbon>
</Workflow>
</Workflows>

Example Specification

The example below defines the TabCollection "AdminTabs" with three tabs. The tabs are specified using a <TabRef> which means that the tab definition is either specified under the <Tabs> element in one of the XML files or it is supplied by an application. The Priority attribute is used to order the Tabs from left to right. The increment of 10,000 is used below to provide sufficient room for match process to merge tabs from other definition locations such as other ribbon XML files or DGNLib's used to store Ribbon Customizations.

<TabCollections>
<TabCollection Name="AdminTabs">
<TabRef Name="Admin.Home" Priority="10000">
<TabParams>
<AccessText>H</AccessText>
</TabParams>
</TabRef>
<TabRef Name="Drawing.View" Priority="20000">
<TabParams>
<AccessText>V</AccessText>
</TabParams>
</TabRef>
<TabRef Name="Admin.Interface" Priority="30000">
<TabParams>
<AccessText>I</AccessText>
</TabParams>
</TabRef>
</TabCollection>
</TabCollections>

Example Specification

This example defines the tab with the name "Admin.Home". This Tab specifies the Ribbon Groups to use to populate the tab.

<Tab Name="Admin.Home" Priority="100">
<Label>Home</Label>
<TabParams>
<AccessText>H</AccessText>
</TabParams>
<TabItems>
<GroupRef Name="Mstn.AttributeGroup" Priority="-10000">
<GroupParams>
<GroupLauncherAccessText>1</GroupLauncherAccessText>
<AccessText>1</AccessText>
</GroupParams>
</GroupRef>
<GroupRef Name="Mstn.PrimaryGroup" Priority="-9000">
<GroupParams>
<GroupLauncherAccessText>2</GroupLauncherAccessText>
<AccessText>2</AccessText>
</GroupParams>
</GroupRef>
<!-- Other GroupRefs removed for brevity --->
<GroupRef Name="Admin.Home.DrawingSetup" Priority="800000">
<GroupParams>
<GroupLauncherAccessText>8</GroupLauncherAccessText>
<AccessText>8</AccessText>
</GroupParams>
</GroupRef>
<GroupRef Name="Admin.Home.Entry" Priority="900000">
<GroupParams>
<GroupLauncherAccessText>9</GroupLauncherAccessText>
<AccessText>9</AccessText>
</GroupParams>
</GroupRef>
</TabItems>
</Tab>

Example Specification

This example defines the group named "Mstn.PrimaryGroup". The PanelType attribute is optional and is used to automatically create a panel to hold the GroupItems. This attribute should not be used if the group contains an explict GroupPanels specification.

<Group GroupZone="Primary" Name="Mstn.PrimaryGroup" PanelType="Collapsible" Priority="100">
<Label>Primary</Label>
<Icon Type="NamedIcon">
<NamedIcon>RibbonGroupPrimary</NamedIcon>
</Icon>
<GroupParams>
<AccessText>PP</AccessText>
</GroupParams>
<GroupItems>
<ButtonRef Name="Mstn.ProjectNavigation.Explorer" Priority="10000">
<ButtonParams CollapseToSmall="WhenGroupIsSmall" Size="Large">
<AccessText>RE</AccessText>
<PopupAccessText>E</PopupAccessText>
</ButtonParams>
</ButtonRef>
<SplitButtonRef Name="Mstn.Primary.References" Priority="20000">
<SplitButtonParams CollapseToMedium="WhenGroupIsMedium" CollapseToSmall="WhenGroupIsSmall" Size="Large">
<AccessText>RR</AccessText>
<PopupAccessText>R</PopupAccessText>
</SplitButtonParams>
</SplitButtonRef>
<!-- Other items removed for brevity --->
<SplitButtonRef Name="Mstn.Primary.More" Priority="70000">
<SplitButtonParams Size="Small">
<AccessText>RO</AccessText>
<PopupAccessText>O</PopupAccessText>
</SplitButtonParams>
</SplitButtonRef>
</GroupItems>

Example Specification

If a GroupDialogSpec element is defined, typically after the specification of GroupParams, it will enable the Dialog Launcher for the group. The launcher button will have a ToolTip showing the Label and Description defined in the specified command, or using the TipTitle and TipDescription if defined.

<GroupDialogSpec>
<TipTitle />
<TipDescription />
<CommandData Type="NamedCommand">
<NamedCommand>Mstn.DrawingAids.AccuDraw.Settings</NamedCommand>
</CommandData>
</GroupDialogSpec>

Example Group Specification using GroupPanelRefs

A GroupPanelRef element is used to hold a reference to a <GroupPanel> definition. This is commonly used when there are groups of items to hide and show (typically based on if the active model is 2D or 3D).

<Group Name="Mstn.DrawingAids.AccuDraw" Priority="100">
<Label>AccuDraw</Label>
<Icon Type="NamedIcon">
<NamedIcon>RibbonGroupAccudraw</NamedIcon>
</Icon>
<GroupParams>
<AccessText>GA</AccessText>
<FeatureAspect>DrawingAids_AccuDraw</FeatureAspect>
</GroupParams>
<GroupDialogSpec>
<CommandData Type="NamedCommand">
<NamedCommand>Mstn.DrawingAids.AccuDraw.Settings</NamedCommand>
</CommandData>
</GroupDialogSpec>
<GroupItems>
<GroupPanelRef Name="AccuDraw.LeftPanel" Priority="10000" />
<GroupPanelRef Name="AccuDraw.2d.Locks.Panel" Priority="30000" />
<GroupPanelRef Name="AccuDraw.3d.Locks.Panel" Priority="30000" />
<GroupPanelRef Name="AccuDraw.MiddlePanel" Priority="40000" />
<GroupPanelRef Name="AccuDraw.RightPanel" Priority="80000" />
</GroupItems>

Example Specification

A GroupPanel is a panel that holds items. There are four panel types: Collapsible, OrderedWrap, VerticalStacked, and HorizontalStacked. The GroupSizing rules are only applied to items in Collapsible panels.The example GroupPanel below specifies references to two different type of buttons. Then <ButtonRef> element defines a reference to a simple push button that activates a command. The <ToggleButtonRef> defines a reference to a toggle button.

Panel Separators

It is possible to insert a separator between a root level GroupPanel and items to its left, by setting the GroupPanelParams attribute AddSeparatorBefore to true. A root panel is one added under GroupItems either by definition or reference. The Group definition must not specify the optional PanelType attribute if AddSeparatorBefore is used on a child GroupPanel. The visibility of the separator is tied to the visibility of the group panel and to the existence of an item to the left of the panel.

<GroupPanel CustomizationFlags="ComponentContentsLocked" Name="AccuDraw.3d.Rotation.Panel" PanelType="VerticalStacked" Priority="50000">
<GroupPanelParams>
<SyncItemEvent>SystemEvent.ActiveModelChanged</SyncItemEvent>
<ShowExpression>[Session]Session.TreatActiveModelAs3D()</ShowExpression>
<FeatureAspect />
</GroupPanelParams>
<ButtonRef Name="Mstn.DrawingAids.AccuDraw.Top" Priority="10000">
<ButtonParams CollapseToSmall="WhenGroupIsSmall" Size="Medium">
<AccessText>AT</AccessText>
<PopupAccessText>P</PopupAccessText>
</ButtonParams>
</ButtonRef>
<ButtonRef Name="Mstn.DrawingAids.AccuDraw.Front" Priority="20000">
<ButtonParams CollapseToSmall="WhenGroupIsSmall" Size="Medium">
<AccessText>AF</AccessText>
<PopupAccessText>F</PopupAccessText>
</ButtonParams>
</ButtonRef>
<ButtonRef Name="Mstn.DrawingAids.AccuDraw.Side" Priority="30000">
<ButtonParams CollapseToSmall="WhenGroupIsSmall" Size="Medium">
<AccessText>AS</AccessText>
<PopupAccessText>S</PopupAccessText>
</ButtonParams>
</ButtonRef>
</GroupPanel>

GroupPanels can include any of the following types:

Special GroupPanel Elements

NOTE: The following elements are used to include Dgnlib-based Named Tools, Task Tools, and Main Task tools. The support for these elements were added and used in very early versions of the Ribbon and have not been used since Ribbon Customization was added and these types of items could be imported and converted to Groups and Buttons. If used, be sure to do thorough testing.

Named Tool(s)

<NamedToolboxRef Path="_[Town]_\_[Road]_">
<DgnlibToolsParams Size="Small" CollapseToSmall="WhenGroupIsSmall" CollapseToMedium="Never">
<AccessText>GU3</AccessText>
</DgnlibToolsParams>
</NamedToolboxRef>

Task Tool(s)

<TaskToolboxRef Name="Linears" Path="_[Drawing]_\_[Linear]_" Priority="0">
<DgnlibToolsParams Size="Small" CollapseToSmall="WhenGroupIsSmall" CollapseToMedium="Never" UseSizingRule="true">
<AccessText>GG</AccessText>
</DgnlibToolsParams>
</TaskToolboxRef>

Main Task Tool(s)

<MainTaskToolboxRef Name="Main" Path="_[Main]_" Priority="1">
<DgnlibToolsParams Size="Small" CollapseToSmall="WhenGroupIsSmall" CollapseToMedium="Never" UseSizingRule="true">
<AccessText>M</AccessText>
</DgnlibToolsParams>
</MainTaskToolboxRef>

Specifying Collapse Rules for Ribbon Buttons

All the different types of buttons that can be defined in the Ribbon XML can specify how they are to resize as the size of the Ribbon Grows and Shrinks in their button-specific Params element. The button params element supports the following attributes:

If a button is in a Collapsible Panel then the button is resized based on the GroupVariant which may change as the Window containing the Ribbon is Resized. If the GroupVariant is set to large then all buttons are sized to match their specified sizes. If the group variant is set to medium, then any large buttons will be resized based on the CollapseToMedium setting. If the GroupVariant is Small then all Large and Medium buttons are resized to their CollapseToSmall setting. If the Collapse settings are not specified then the default for CollapseToSmall is WhenGroupIsSmall and CollapseToMedium is WhenGroupIsMedium.

<ButtonParams Size="Large" CollapseToSmall="Never" CollapseToMedium="WhenGroupIsMedium">
</ButtonParams>

Example Specification

A Button is used to queue a MicroStation command. The command that is activated is defined using the <CommandData> element. The options for CommandData are shown later in this article. In the example beow the optional Label and Decsription elements are populated. If these elements are empty or eliminated then the Label and Decription defined in the Command is used. In this example the button is only shown if the specified Feature Aspect is enabled.

<Button Name="Mstn.DrawingAids.AccuDraw.EnableDisableAccuDraw" Priority="100">
<Label>Toggle AccuDraw</Label>
<Description>Enable or disable AccuDraw input.</Description>
<ButtonParams CollapseToSmall="WhenGroupIsSmall" Size="Large">
<AccessText>TA</AccessText>
<FeatureAspect>DrawingAids_AccuDraw</FeatureAspect>
</ButtonParams>
<ButtonData>
<CommandData Type="NamedCommand">
<NamedCommand>Mstn.DrawingAids.AccuDraw.Activate</NamedCommand>
</CommandData>
</ButtonData>
</Button>

Example Specification

ToggleButton definitions contain <ToggleButtonParams> to define common parameters and <ToggleButtonData> to define that action associated with a toggle button.

<ToggleButton Name="Mstn.DawingAids.Snaps.ToggleAccuSnap" Priority="100">
<ToggleButtonParams Size="Large">
<AccessText />
</ToggleButtonParams>
<ToggleButtonData>
<CommandData Type="UserKeyinCommand">
<UserKeyinCommand>
<Keyin>ACCUSNAP TOGGLE</Keyin>
<Label>AccuSnap</Label>
<ToggleExpression>[Session]Session.IsAccuSnapEnabled()</ToggleExpression>
<SyncItemEvent>ActiveLock.AccuSnap</SyncItemEvent>
<Icon Type="NamedIcon">
<NamedIcon>AccuSnap</NamedIcon>
</Icon>
</UserKeyinCommand>
</CommandData>
</ToggleButtonData>
</ToggleButton>

Example ToggleButton that specified Checked and Unchecked Icons

ToggleButton are shown in two states, either checked or unchecked. The <ToggleButtonData> element defines the icon(s), and the commands associated with the button. The button can support either a single <Icon> definition or separate icon specifications for each state of the Toggle. The ToggleExpression is used to determine the state. When the button is clicked the specified command is sent to MicroStation. The SyncItemEvent specifies what MicroStation events should trigger the button to be "synced/refreshed". When this event occurs the expression is reevaluated to determine if the button is checked or not.

<ToggleButton Name="Popset" Priority="0">
<ToggleButtonParams Size="Large">
<AccessText></AccessText>
</ToggleButtonParams>
<ToggleButtonData>
<SyncItemEvent>SystemEvent.PopSetStateChanged</SyncItemEvent>
<CheckedIcon Type="NamedIcon">
<NamedIcon>PopsetEnabled</NamedIcon>
</CheckedIcon>
<UncheckedIcon Type="NamedIcon">
<NamedIcon>PopsetDisabled</NamedIcon>
</UncheckedIcon>
<CommandData Type="UserKeyinCommand">
<UserKeyinCommand>
<Keyin>POPSET TOGGLE</Keyin>
<Label>Popset</Label>
<ToggleExpression>[Session]Session.IsPopsetEnabled()</ToggleExpression>
</UserKeyinCommand>
</CommandData>
</ToggleButtonData>
</ToggleButton>

Example Specification

Currently their are no RadioButton definitions used in the Ribbon in MicroStation. The example below shows how to define a radio button. The specification of SyncItemEvent is important as it determines when to refresh the state of the RadioButton. RadioButtons within the same Ribbon Group refresh when the state of another is changed.

<RadioButton Name="TextJustTopLeft" Priority="0">
<Label>Top Left</Label>
<Description>Set Text Justification to Top-Left</Description>
<RadioButtonParams Size="Small">
<AccessText>JTL</AccessText>
</RadioButtonParams>
<RadioButtonData>
<SyncItemEvent>TextParamChanged.TextJust</SyncItemEvent>
<Icon Type="NamedIcon">
<NamedIcon>TextJustTopLeft</NamedIcon>
</Icon>
<CommandData Type="UserKeyinCommand">
<UserKeyinCommand>
<Keyin>NAMEDEXPRESSION ASSIGN [Session]Session.SetTextElementJustification(0)</Keyin>
<ToggleExpression>[Session]0=Session.GetTextElementJustification()</ToggleExpression>
</UserKeyinCommand>
</CommandData>
</RadioButtonData>
</RadioButton
@endnote
<H3>Example <SplitButton> Specification </H3>
<P>The first example shows how to define the menu items for the DropDownContent. </P>
@code{.unparsed}
<SplitButton Name="Mstn.DrawingAids.AccuDraw.EnableDisable" Priority="100">
<Label>Enable or Disable AccuDraw</Label>
<SplitButtonParams Size="Small">
</SplitButtonParams>
<SplitButtonData>
<DropDownContent Type="MenuItems">
<MenuItems>
<MenuItem Name="Mstn.DrawingAids.AccuDraw.EnableDisable.Enable" Priority="5000">
<MenuItemParams ShowCmdIcon="true">
<AccessText>1</AccessText>
<Visibility><FeatureAspect>DrawingAids_AccuDraw</FeatureAspect></Visibility></MenuItemParams>
<MenuItemData>
<CommandData Type="NamedCommand">
<NamedCommand>Mstn.DrawingAids.AccuDraw.Activate</NamedCommand>
</CommandData>
</MenuItemData>
</MenuItem>
<MenuItem Name="Mstn.DrawingAids.AccuDraw.EnableDisable.Disable" Priority="10000">
<MenuItemParams ShowCmdIcon="true">
<AccessText>2</AccessText>
<Visibility><FeatureAspect>DrawingAids_AccuDraw</FeatureAspect></Visibility></MenuItemParams>
<MenuItemData>
<CommandData Type="NamedCommand">
<NamedCommand>Mstn.DrawingAids.AccuDraw.Quit</NamedCommand>
</CommandData>
</MenuItemData>
</MenuItem>
</MenuItems>
</DropDownContent>
</SplitButtonData>
</SplitButton>
<SplitButton Name="TestMenuDropDown" Priority="0">
<Label>SplitMenu</Label>
<Description>Sample Split Button</Description>
<SplitButtonParams Size="Large" CollapseToSmall="WhenGroupIsSmall" CollapseToMedium="WhenGroupIsMedium">
<AccessText></AccessText>
</SplitButtonParams>
<SplitButtonData>
<CommandData Type="NamedCommand">
<NamedCommand>Models.Dialog</NamedCommand>
</CommandData>
<DropDownContent Type="MenuItems">
<MenuItems>
<MenuItemCollectionRef Name="ClipboardPasteMenu"/>
</MenuItems>
</DropDownContent>
</SplitButtonData>
</SplitButton>

The second example shows how to specify MenuItemCollection used to populate the split-button DropDown. This is useful when the same content is to be shown in different Split or DropDown buttons.

<SplitButton Name="TestMenuDropDown" Priority="0">
<Label>SplitMenu</Label>
<Description>Sample Split Button</Description>
<SplitButtonParams Size="Large" CollapseToSmall="WhenGroupIsSmall" CollapseToMedium="WhenGroupIsMedium">
<AccessText></AccessText>
</SplitButtonParams>
<SplitButtonData>
<CommandData Type="NamedCommand">
<NamedCommand>Models.Dialog</NamedCommand>
</CommandData>
<DropDownContent Type="MenuItems">
<MenuItems>
<MenuItemCollectionRef Name="ClipboardPasteMenu"/>
</MenuItems>
</DropDownContent>
</SplitButtonData>
</SplitButton>

Example Specification

DropDownButtons are very similar to SplitButtons with the exception that the user must select an item from the drop down content to activate a command. The example below shows how locks can be shown and set from a drop down menu. In this example menu items reference named commands that specify ToggleExpression definitions. This allows the menu entries to be shown with a check mark if the ToggleExpression evaluates to true. If the command data also contains an icon specification the icon is shown in a selected button state in-lieu of replacing the icon with a checkmark.

<DropDownButton Name="LocksDropDown" Priority="1000">
<Label>Locks</Label>
<Description>Set Locks</Description>
<DropDownButtonParams Size="Large">
<AccessText>LD</AccessText>
</DropDownButtonParams>
<DropDownButtonData>
<Icon Type="NamedIcon">
<NamedIcon>Lock</NamedIcon>
</Icon>
<DropDownContent Type="MenuItems">
<MenuItems>
<MenuItem Name="DrawingAids.Locks.Axis" Priority="0">
<MenuItemData>
<SyncItemEvent>ActiveLock.Axis</SyncItemEvent>
<CommandData Type="NamedCommand">
<NamedCommand>DrawingAids.Locks.Axis</NamedCommand>
</CommandData>
</MenuItemData>
</MenuItem>
<!-- removed for brevity -->
<MenuItem Name="DrawingAids.Locks.ConstructSnap" Priority="0">
<MenuItemData>
<SyncItemEvent>ActiveLock.ConstructionSnap</SyncItemEvent>
<CommandData Type="NamedCommand">
<NamedCommand>DrawingAids.Locks.ConstructSnap</NamedCommand>
</CommandData>
</MenuItemData>
</MenuItem>
</MenuItems>
</DropDownContent>
</DropDownButtonData>
</DropDownButton>

Example Specification

MenuItemCollections can be referenced in Split and DropDown Button to populate the drop-down content. This collection would only be used when the collection of menu items can be referenced by multiple buttons. The collection may contain menu items that activate a commad, display as a separator and provide a submenu of more menu items.

<MenuItemCollection Name="ClipboardPasteMenu" MatchProcessing="Override" Priority="1000">
<MenuItem Name="Paste" Priority="0">
<MenuItemParams ShowCmdIcon="true">
<AccessText>1</AccessText>
</MenuItemParams>
<MenuItemData>
<CommandData Type="NamedCommand">
<NamedCommand>Clipboard.Paste</NamedCommand>
</CommandData>
</MenuItemData>
</MenuItem>
<MenuItem Name="Separator" IsSeparator="true" Priority="0"/>
<MenuItem Name="PasteSpecial" Priority="0">
<MenuItemParams ShowCmdIcon="false">
<AccessText>2</AccessText>
</MenuItemParams>
<MenuItemData>
<CommandData Type="NamedCommand">
<NamedCommand>Clipboard.Paste</NamedCommand>
</CommandData>
</MenuItemData>
</MenuItem>
<SubMenuItem Name="TestSubMenu" Priority="0">
<SubMenuItemParams>
<AccessText>1</AccessText>
</SubMenuItemParams>
<MenuItems>
<MenuItemCollectionRef Name="ClipboardPaste_TestSubMenu"/>
</MenuItems>
</SubMenuItem>
</MenuItemCollection>
@endname
<H3>XCommands</H3>
<P>
XCommands are "Named" command definitions typically defined in application resource. The single commands definition can then be used in the ribbon or toolbox.The command may also be activated by the user using the key-in: RUNXCOMMAND commandName (ie RUNXCOMMAND DrawingAids.Locks.Axis or RUNXCOMMAND PlaceThinShape).</P>
<H4>Application defined XCommands</H4>
<P>
Application defined XCommands are defined in .r files and compiled into esource definition for an XCommand contains the following data. </P>
@code{.unparsed}
/*----------------------------------------------------------------------+
| XCommand structure, defined in PublicAPI/Mstn/MdlApi/dlogbox.r.h |
| |
| char name[]; |
| CommandRsc command; |
| char iconName[]; |
| Utf8Char title[]; |
| Utf8Char description[]; |
| char featureTrackingId[]; |
| char showExpression[]; |
| char enableExpression[]; |
| char toggleExpression[]; |
| char markExpression[]; |
| char uiSyncEvents[]; |
| UInt32 accelerator; |
| UInt32 featureAspectId; |
| |
+----------------------------------------------------------------------*/

And an example of XCommand definition is shown below.

{"DrawingAids.Locks.Axis",
{CMD_LOCK_AXIS_TOGGLE, MTASKID, ""},
"AxisLock",
TXT6_SettingsSubLockAxis,
"",
"DrawingAids.Locks.Axis", "", "", "[ActiveLock]ActiveLock.AxisLockEnabled()", "", NOACCEL,
PP_ID_FeatureAspects_DrawingAids_Locks_Axis}, // @fadoc DrawingAids.Locks.Axis XCommand
{"DrawingAids.Locks.ConstructSnap",
{CMD_LOCK_SNAP_ACS_TOGGLE, MTASKID, ""},
"ACSSnapLock",
TXT6_SettingsSubLockConstructSnap,
"",
"DrawingAids.Locks.ConstructSnap", "", "", "[ActiveLock]ActiveLock.ConstructionPlaneLockEnabled()", "", NOACCEL,
PP_ID_FeatureAspects_DrawingAids_Locks_SettingsMenu}, // @fadoc DrawingAids.Locks.ConstructSnap XCommand

User Defined XCommands

User defined XCommands are defined in XML files. Below is an example of XML defined commands.

<UserNamedCommands>
<UserNamedCommand>
<Name>GeneralExample.PlaceThickCircle</Name>
<Label>Thick Circle</Label>
<Description>Place thick circle</Description>
<Keyin>[CONSGEOM]PLACE CIRCLE ICON;co=4;wt=4;lc=0</Keyin>
<IconName>PlaceCircle</IconName>
</UserNamedCommand>
<UserNamedCommand>
<Name>GeneralExample.ToolWithNoIcon</Name>
<Label>Place Line</Label>
<Description>Place Line (No Icon)</Description>
<Keyin>PLACE LINE;co=4;wt=4;lc=0</Keyin>
</UserNamedCommand>
<UserNamedCommand>
<Name>GeneralExample.PlaceThinShape</Name>
<Label>Thin Shape</Label>
<Description>Place thin shape</Description>
<Keyin>[CONSGEOM]PLACE SHAPE CONSTRAINED;co=1;wt=0;lc=0</Keyin>
<IconName>PlaceShape</IconName>
</UserNamedCommand>
<UserNamedCommand>
<Name>GeneralExample.PlaceSolidSlab</Name>
<Label>Place Slab</Label>
<Description>Place slab</Description>
<Keyin>[SOLIDMODELING]PLACE SLAB ICON;co=5;wt=2;lc=0</Keyin>
<IconName>PlaceSlab</IconName>
<VisibilityExpression>[Session]Session.TreatActiveModelAs3D()</VisibilityExpression>
</UserNamedCommand>
</UserNamedCommands>

Example Specifications

The XML format supports multiple types of Command specification. The recommended Command Type is NamedCommand.

NamedCommand

The example below shows that in addition to specifying the name of the XCommand, the XML can also define Command Parameters and supply an icon to override the one defined in the XCommand resource.

<CommandData Type="NamedCommand">
<NamedCommand>Mstn.Cells.CellSelector
<CmdParameters>CELLSEL DIALOG CELLSEL</CmdParameters>
<Icon Type="NamedIcon">
<NamedIcon>Cell</NamedIcon>
</Icon>
</NamedCommand>
</CommandData>

UserKeyinCommand

The example below shows the definition of the key-in to queue, the icon to use, and the label and desciption to use for the Button Label and ToolTip text.

<CommandData Type="UserKeyinCommand">
<UserKeyinCommand>
<Label>Database Disconnect</Label>
<Description>Disconnect from the database</Description>
<Keyin>DBCONMGR DISCONNECT</Keyin>
<Icon Type="NamedIcon">
<NamedIcon>Database-Disconnect</NamedIcon>
</Icon>
</UserKeyinCommand>
</CommandData>

Example Specifications

Named Ico - Ico is either in ustnicons.rsc or in a DGNLIB.

<Icon Type="NamedIcon">
<NamedIcon>Circle</NamedIcon>
</Icon>

Mdl-based IconCmd Resource from MicroStation or MDL Application.

<Icon Type="RscIcon">
<RscIcon>
<RscType>IconCmd</RscType>
<RscId>-384</RscId>
<TaskId>USTN</TaskId>
</RscIcon>
</Icon>

Specification

The Visibility element can contain multiple ShowExpressions and FeatureAspect elements. During a refresh event the expressions are evaluated to determine if the item should be visible. If any Show Expression returns false, the item is hidden. If the command specifies a ShowExpression it will be appended to any user defined entries. If the FeatureAspect is specified it is evaluated to determine if the item should be allowed. If the item specifies a named command (XCommand) and that command specifies a FeatureAspect then the Ribbon Component must also specify the same FeatureAspect.

<ShowExpression>[Session]not(Session.TreatActiveModelAs3D())</ShowExpression>
<FeatureAspect>Surfaces_Modify</FeatureAspect>
</Visibility>

UI Synchronization Event System

One of the major needs in adding a new UI to an existing CAD engine is keeping the UI in sync with the actual settings/parameters used by the engine. The UISyncManager has been added in MstnPlatform to satisfy this need. This manager will maintain a list of all available registered sync event types. These event types are defined in MicroStation resources and can be referenced by SyncGroupRscId and SyncItemId or by name. The name is in the format "[SyncGroupName].[SyncItemName]", for example: " ActiveLock.Snap" and "SystemEvent.ActiveModelChanged". The native DialogManager uses the UISyncManager to validate and look up available sync events. Native applications can trigger a sync event by calling the following method:

DialogManager::SendUISyncMessage (UInt32 syncGroupRscId, MdlDescP mdlDesc, UInt32 syncItemId);

From a managed application a caller would use the following method.

UMDialogManager::SendUISyncMessage (System::String^ synchItemString);

When the SendUISyncMessage method is called, the message will be passed along to all ribbon components that specify interest in that event. When this occurs the ViewModel that is associated with the component will have its Refresh method called. The Refresh method will reevaluate any visibility or enable expression that are defined for the components. Typically the Ribbon's XML will have a SyncItemEvent tag anytime a ShowExpression or EnableExpression is defined. In the example below, any time the active model changes sync event, the system processes the following call.

DialogManager::SendUISyncMessage (SYNCGROUPID_SystemEvent, NULL, SYNCITEM_SystemEvent_ActiveModelChanged);

This in turn tells every component that has specified interest in the ActiveModelChanged event.

<SplitButtonRef Name="Mstn.Placement.CreateCurves3D" Priority="500000">
<SplitButtonParams Size="Small">
<AccessText>PU</AccessText>
<PopupAccessText>U</PopupAccessText>
<ApplicationParams>
<ApplicationParam>
<Name>MatchingKeyTipItems</Name>
<Value>Mstn.Placement.CreateCurves2D</Value>
</ApplicationParam>
</ApplicationParams>
<SyncItemEvent>SystemEvent.ActiveModelChanged</SyncItemEvent>
<ShowExpression>[Session]Session.TreatActiveModelAs3D()</ShowExpression>
<FeatureAspect />
</SplitButtonParams>
</SplitButtonRef>

The Sync Resources are loaded into MicroStation as it starts in the function guiDialog_startupInit (mstn/mscore/gui/guidlog.cpp).

Sync Group Resource Specification

The following list of sync events are available for R1 of MicroStation CONNECT:

// Maximum of 32 SyncItems per group
{
"ActiveLock",
{
{SYNCITEM_ActiveLock_UseAnnotationScale, "UseAnnotationScale"},
{SYNCITEM_ActiveLock_ConstructionSnap, "ConstructionSnap"},
{SYNCITEM_ActiveLock_ElementTemplateAssociation, "ElementTemplateAssociation"},
}
};
// Maximum of 32 SyncItems per group
{
"SystemEvent",
{
{SYNCITEM_SystemEvent_ActiveModelChanged, "ActiveModelChanged"},
{SYNCITEM_SystemEvent_AvailableModelsChanged, "AvailableModelsChanged"},
{SYNCITEM_SystemEvent_ModelPropertiesChanged, "ModelPropertiesChanged"},
{SYNCITEM_SystemEvent_AvailableViewsChanged, "AvailableViewsChanged"},
{SYNCITEM_SystemEvent_AvailableLevelsChanged, "AvailableLevelsChanged"},
{SYNCITEM_SystemEvent_PopSetStateChanged, "PopSetStateChanged"},
{SYNCITEM_SystemEvent_SelectionSetChanged, "SelectionSetChanged"},
{SYNCITEM_SystemEvent_ContextualTabSetKeyChanged, "ContextualTabSetKeyChanged"},
{SYNCITEM_SystemEvent_TableCellSelectionChanged, "TableCellSelectionChanged"},
{SYNCITEM_SystemEvent_LuxologyRenderStateChanged, "LuxologyRenderStateChanged"},
{SYNCITEM_SystemEvent_ApplicationSyncUIItem, "ApplicationSyncUIItem"},
{SYNCITEM_SystemEvent_AnimationPreviewStateChanged, "AnimationPreviewStateChanged"},
{SYNCITEM_SystemEvent_DgnHistoryStateChange, "DgnHistoryStateChange"},
{SYNCITEM_SystemEvent_AvailableLineStylesChanged, "AvailableLineStylesChanged"},
{SYNCITEM_SystemEvent_ImmediateLineStylesRefreshRequired, "ImmediateLineStylesRefreshRequired"},
{SYNCITEM_SystemEvent_ACSOperationProcessed, "ACSOperationProcessed"},
{SYNCITEM_SystemEvent_RefreshAvailableViewGroupList, "RefreshAvailableViewGroupList"},
{SYNCITEM_SystemEvent_ViewGroupHistoryStateChange, "ViewGroupHistoryStateChange"},
{SYNCITEM_SystemEvent_ContextualTabActivated, "ContextualTabActivated"},
{SYNCITEM_SystemEvent_UserSigninStateChanged, "UserSigninStateChanged"},
}
};
// Maximum of 32 SyncItems per group
{
"SessionEvent",
{
{SYNCITEM_SessionEvent_RibbonDefinitionCfgVarChanged, "RibbonDefinitionCfgVarChanged"},
{SYNCITEM_SessionEvent_RibbonQATToolListChanged, "RibbonQATToolListChanged"},
{SYNCITEM_SessionEvent_RibbonWorkflowChanged, "RibbonWorkflowChanged"},
}
};
// Maximum of 32 SyncItems per group
{
"CustomizationChanged",
{
}
};
// Maximum of 32 SyncItems per group
{
"SymbologyChanged",
{
}
};
// Maximum of 32 SyncItems per group
{
"ActiveParamChanged",
{
{SYNCITEM_ActiveParamChanged_DimCompat, "DimCompatibility"},
{SYNCITEM_ActiveParamChanged_MlineCompat, "MlineCompatibility"},
}
};
// Maximum of 32 SyncItems per group
{
"ModelParamChanged",
{
}
};
{
"TextParamChanged",
{
}
};

Using Sync Events in your WPF ViewModels

Example of a ViewModel that is interested in monitoring ActiveModelChanges in MicroStation.

internal class ExampleViewModel : Bentley.MstnPlatformNET.Ribbon.ViewModels.RibbonItemViewModel
{
UISyncInterest m_ActiveModelChangedInterests;
public ExampleViewModel()
{
UISyncInterestList refreshMasks = new UISyncInterestList ();
refreshMasks.AddSyncInterest (m_ActiveModelChangedInterests = UMDialogManager.GetSyncInterest ("SystemEvent.ActiveModelChanged"));
SyncInterestList = refreshMasks;
}
public override void OnUserInterfaceSyncEvent (System.UInt32[] eventMasks)
{
if (m_ActiveModelChangedInterests.IsSpecifiedInEventMasks (eventMasks))
Refresh();
}
public override void Refresh ()
{
if (null != VisibilitySpec)
Visibility = VisibilitySpec.IsVisible () ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
else
Visibility = System.Windows.Visibility.Visible;
if (this is ISupportsEnableExpression)
{
ISupportsEnableExpression supportsEnableExpression = this as ISupportsEnableExpression;
if (!String.IsNullOrWhiteSpace (supportsEnableExpression.EnableExpression))
IsEnabled = Bentley.Internal.MstnPlatformNET.NamedExpressionManager.EvaluateAsCriteria (supportsEnableExpression.EnableExpression, null, false);
}
}
}

Application Provided Sync Group Resources

Applications that want to add their own sync events can do so. The steps involved are the following:

  1. Create a .r file that defines your own SyncGroupRsc. If your application contains a cmd.r file you can simply add the SyncGroupRsc in the same .r file.
  2. Ensure the application containing the SyncGroupRsc is specified in the configuration variable MS_SYNCGROUP_APPS so the resource is loaded prior to the Ribbon. An easy way to do this is by using the <LoadConfig> tag in the part building the application. See the example part definition below.

    <Part Name="myapp" BMakeFile="apps\myapp.mke">
    <Bindings>
    <Files ProductDirectoryName="MdlsysAsneeded">
    Delivery\myapp.ma
    <TransKit SourceDirectory="Delivery\TransKit\MdlApps\myapp" />
    <LoadConfig AppName="myapp" ConfigVars="MS_CMDTABLE_LOAD,MS_SYNCGROUP_APPS" />
    </Files>
    </Bindings>
    </Part>
         <Li>See test application ..\MstnPlatform\mstn\testapps\RibbonTestApp for example of setting up and firing Application provided Sync Events. </LI>
    

ContextualTabSet definition

The following shows two example definitions. The first shows a TabSet to activate when a single element type is selected. The second definition shows how to set up a contextual tab to activate or deactivate used API calls. This second type of activation would allow placement or edit tools to display contextual tabs.

<ContextualTabSets>
<!?Example that activates/deactivates a contextual tab based on the selected element -->
<ContextualTabSet Name="Mstn.TabSet.Table" Color="Coral" SelectFirstTabOnActivation="false">
<Label>Table Tools</Label>
<TabSetParams>
<SyncItemEvent>SystemEvent.SelectionSetChanged</SyncItemEvent>
<ShowExpression>[Selection](Selection.ContainsSingleElement() AND Selection.ContainsText() AND Selection.OnlyContainsActiveModelElements())</ShowExpression>
<FeatureAspect></FeatureAspect>
</Visibility>
</TabSetParams>
<ContextualTabs>
<TabRef Name="DrawingTasks" Priority="10">
<TabParams>
<AccessText>TD</AccessText>
</TabParams>
</TabRef>
</ContextualTabs>
</ContextualTabSet>
<!?Example that activates/deactivates a contextual tab based on a key. This would be useful during tool processing -->
<ContextualTabSet Name="Mstn.Context.Drawing" Color="Coral" SelectFirstTabOnActivation="false">
<Label>Drawing Tools</Label>
<TabSetParams>
<SyncItemEvent>SystemEvent.ContextualTabKeyChanged</SyncItemEvent>
<Visibility>
<ShowExpression>[Session](Session.IsContextualTabKeyActive("DrawingTest"))</ShowExpression>
<FeatureAspect></FeatureAspect>
</Visibility>
</TabSetParams>
<ContextualTabs>
<TabRef Name="DrawingTasks" Priority="10">
<TabParams>
<AccessText>TD</AccessText>
</TabParams>
</TabRef>
</ContextualTabs>
</ContextualTabSet>
</ContextualTabSets>

Native API - The following native calls will add or remove keys into a vector of active ContextualTabSet keys. It then triggers the ContextualTabKeyChanged synch event.

Managed API - wrapper around native methods shown above.

Telerik RadRibbonView

RadRibbonView Features

The RadRibbonView control has these key features:

More Ribbon Related Controls

The "RadControls for WPF" suite contains several class related to the Ribbon:

Ribbon Events

You can find more information about the Telerik RadRibbonView at the Telerik Page.


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