Thank you to Sreekar Devatha, from DevTech India, for writing this article for the recently published ADN Platform Technologies Customization Newsletter. This article talks about the new Ribbon API referenced in this overview of the new APIs in AutoCAD 2009. A complete sample demonstrating the use of this API is provided as part of the ObjectARX 2009 SDK, under samples/dotNet/Ribbon.
Introduction
Most of the AutoCAD® UI was redesigned in this release. Ribbon, Menu browser and Tooltips are some of the prominent UI features to list. As you might already know the UI enhancements are based on the new Windows® Presentation Foundation (WPF) programming model introduced by Microsoft. So, let’s start with a small introduction to WPF and then we'll move on to the finer points of customizing the Ribbon bar.
What is WPF?
Windows Presentation Foundation (WPF) is a programming model introduced by Microsoft to build rich Windows client applications.
This graphical subsystem introduced in .NET Framework 3.0 provides a clear separation between appearance and behavior of applications. You generally use eXtensible Application Markup Language (XAML) to implement the appearance of an application while using managed programming languages (code-behind) to implement its behavior. XAML is the new XML-based UI definition language from Microsoft, and as such is a core part of WPF.
Without wasting too much time on WPF let us move quickly on to the Ribbon APIs. If you are new to WPF then you could go through the basics of WPF using the links below before starting with the Ribbon APIs.
Windows Presentation Foundation - MSDN
Windows Presentation Foundation - CodeProject
AutoCAD Ribbon
Before diving into the Ribbon APIs it's necessary to understand the Ribbon layout and its terminology which are covered in this and the following section.
The AutoCAD Ribbon provides a single, compact placement for operations that are relevant to the current workspace. It overcomes the need to display multiple toolbars, reducing clutter in the application window. The Ribbon maximizes the area available for work using a single compact interface.
The Ribbon was built using WPF (part of .NET Framework 3.0) and a comprehensive set of APIs have been provided by Autodesk to help external developers customize it.
Ribbon Layout
The different components of the AutoCAD Ribbon are shown below. Also, depicted in the snapshot are the classes corresponding to each component.
Figure: the AutoCAD Ribbon and its layout
The Ribbon control is the top level control which contains everything in the Ribbon. It is composed of a series of panels, which are organized into tabs labeled by task.
Ribbon tabs control the display and order of Ribbon panels on the Ribbon. You add Ribbon tabs to a workspace to control which Ribbon tabs are displayed on the Ribbon. Ribbon tabs do not contain any commands or controls like a Ribbon panel does; instead, they manage the display of Ribbon panels on the Ribbon. Once a Ribbon tab is created, a panel can then be added to it. Ribbon tabs are of two types, standard and contextual. Standard tabs are always displayed while contextual tabs are displayed based on a particular context: for instance a Block Editor Tab is displayed while editing a Block.
Contextual tabs can appear in two modes:
Replace mode: In this mode the contextual tab gets added to the standard tabs as a regular tab. When the contextual tab is clicked it becomes active and the panels in the contextual tabs replace the panels in the previously active tab.
Append mode: In this mode contextual tabs form another tab set similar to the standard tab set and is displayed side-by-side with the standard tabs and panels. There are two tabs active any time and activating a tab in one tab set does not affect active tab in the other tab set.
Ribbon panels are organized by rows, sub-panels, and panel separators. Rows and sub-panels are used to organize how commands and controls are displayed on the Ribbon panel. A row, similar to a toolbar, determines the order and position that commands and controls appear on the Ribbon panel. Rows run horizontally on a Ribbon panel. If all the commands and controls cannot be displayed on the Ribbon panel, a gray down arrow is displayed for expanding the Ribbon panel. Rows can be divided using a sub-panel which, holds rows to order and position commands and controls. Commands and controls can be added to rows and sub-panels, you can remove the commands and controls that you use infrequently, and rearrange the order of commands and controls. Along with commands and controls, you can also create flyouts that contain multiple commands and only take up the space of a single command.
Prerequisites
- .NET Framework 3.0 installs WPF – The UI components in AutoCAD were built using .NET 3.0
- Visual Studio 2005 (with/without SP1)
- Visual Studio 2005 extensions for .NET Framework 3.0 (WCF & WPF), November 2006 CTP
- Visual Studio 2005 or Expression Blend or any text editor like notepad, etc. could be used to edit the XAML files.
Modules, Namespaces & Classes
The core UI framework for AutoCAD is present in AdWindows and AcRibbon contains the Ribbon specific implementation. These are managed UI class libraries developed using .NET 3.0 and WPF. Only .NET APIs are available and no C++ wrappers are provided.
AdWindows.dll
This library implements the framework for the following Autodesk UI features.
- Ribbon classes
- Autodesk controls
- Tooltips
- Menu browser
- Task dialog, etc.
These are the Ribbon-specific classes under the Autodesk.Windows namespace of this DLL.
- RibbonControl
- RibbonTab
- RibbonPanel
- RibbonPanelSource
- RibbonRow
- RibbonItem
- RibbonButton
- RibbonDropDownButton
- RibbonSeperator
- RibbonForm
- RibbonHwnd
- RibbonRowPanel, etc.
For more details regarding these classes refer the ObjectARX® Managed Reference guide available in the ObjectARX 2009 SDK.
AcRibbon.dll
This library was actually meant to be an internal-only DLL except for the very few APIs which are discussed in this article below. All other APIs included in the DLL should be considered as internal-only.
Classes
- Palette that hosts the AutoCAD Ribbon control
Autodesk.AutoCAD.Ribbon.RibbonPaletteSet
- AutoCAD Ribbon control
Autodesk.AutoCAD.Ribbon.RibbonServices. RibbonPaletteSet.RibbonControl
Properties
- Property to access the default Ribbon host window which is a palette
Autodesk.AutoCAD.Ribbon.RibbonServices. RibbonPaletteSet
Note: You are advised not to use any of the internal APIs as they are unsupported and could be changed or dropped without prior notice.
Custom Ribbon Tab
As discussed above in the Ribbon Layout section, we need to create panels with Ribbon items placed on them. Then, these panels should be categorized based on their usage and hosted on your application-specific Ribbon tabs. To demonstrate this we'll now look into the finer points of the API by adding a simple button to a panel and then host the panel on a tab (Custom Tab).
Button
In this section we'll add a button to the ribbon bar. If we take a look at the classes listed above we have the RibbonButton class which can be used to create a button to be placed on the Ribbon. So, let’s start with the creation of a RibbonButton instance as below:
RibbonButton button = new RibbonButton();
button.Text = "Click Me";
// resourceDictionary
// A XAML resource dictionary that defines a ButtonImage
button.LargeImage =
resourceDictionary["ButtonImage"] as BitmapImage;
button.Orientation = Orientation.Vertical;
button.Size = RibbonItemSize.Large;
button.ShowText = true;
button.ShowImage = true;
button.Id = "ClickMe_1";
Now, this button instance should be placed on a panel that can then be hosted by a tab. But before actually creating the panel we need a row in which to place the button, as discussed earlier.
// Create a Row to add the RibbonButton
RibbonRow row = new RibbonRow();
row.Items.Add(button);
// Create a Ribbon panel source in which to
// place ribbon items
RibbonPanelSource panelSource =
new RibbonPanelSource();
panelSource.Title = "Custom Panel";
panelSource.Rows.Add(row);
// Create a panel for holding the panel
// source content
RibbonPanel panel = new RibbonPanel();
panel.Source = panelSource;
The panel should be hosted on the tab which in turn should be added to the Ribbon control and the equivalent code to achieve this is below:
// Create a tab to manage the above panel
RibbonTab tab = new RibbonTab();
tab.Title = "Custom Tab";
tab.Id = "CustomTab";
tab.IsContextualTab = false;
tab.Panels.Add(panel);
// Now add the tab to AutoCAD Ribbon bar...
RibbonControl ribbonControl =
Autodesk.AutoCAD.Ribbon.RibbonServices.
RibbonPaletteSet.RibbonControl;
ribbonControl.Tabs.Add(tab);
// ... and activate the tab
ribbonControl.ActiveTab = tab;
The below snapshot shows the button added to AutoCAD's Ribbon.
Figure: the button added to the Ribbon bar
One more item that was missing in the above code was an event to identify the click of the button. The following code implements the click event.
button.Click += new RoutedEventHandler(button_Click);
private static void button_Click(
object sender, RoutedEventArgs e)
{
RibbonButton button = sender as RibbonButton;
if (button != null && (button.Id == "ClickMe_1")
{
MessageBox.Show("Click Me clicked ", "Click Me");
e.Handled = true;
}
}
The above code might also be implemented using a combination of XAML and C# code-behind as shown below.
XAML that defines the RibbonTab
<adw:RibbonTab
x:Key="TabXaml" Title="Custom Tab XAML" Id="CustomTabXaml">
<adw:RibbonPanel >
<adw:RibbonPanelSource Title="Custom Panel XAML" >
<!--Add a ribbon row-->
<!--Note: You could add only rows
to the panel source content-->
<adw:RibbonRow x:Uid="adw:RibbonRow_1">
<!--Add Ribbon Items here-->
<!--The items could be any RibbonItem derived classes-->
<!--Like RibbonButton
RibbonDropDownButton,
RibbonForm,
RibbonHwnd,
RibbonLabel
RibbonMenuButton,
RibbonRowPanel,
RibbonSeperator,
RibbonToggleButton
or any RibbonItem derived custom controls-->
<adw:RibbonButton Id="ClickMe_2" ShowText="true">
<adw:RibbonButton.Orientation>
<Orientation>
Vertical
</Orientation>
</adw:RibbonButton.Orientation>
<adw:RibbonButton.Image>
<BitmapImage
UriSource="Images/bitmap1.bmp"/>
</adw:RibbonButton.Image>
<adw:RibbonButton.LargeImage>
<BitmapImage
UriSource="Images/bitmap1.bmp"/>
</adw:RibbonButton.LargeImage>
<adw:RibbonButton.Size>
<adw:RibbonItemSize>
Large
</adw:RibbonItemSize>
</adw:RibbonButton.Size>
<adw:RibbonButton.Text>
Click Me
</adw:RibbonButton.Text>
<adw:RibbonButton.ToolTip>
<src:RibbonToolTip
BasicText = "Click Me basic help"
CommandName = "ClickMe"
ExtendedURISource =
"/MyRibbon;component/Dictionary1.xaml"
ExtendedURISourceKey = "ClickMe_ToolTip"
HelpSource = "./Help/readme.chm"
HelpTopic =
"WS1a9193826455f5ff1dbc298511635bea8752e2f"/>
</adw:RibbonButton.ToolTip>
</adw:RibbonButton>
</adw:RibbonRow>
</adw:RibbonPanelSource>
</adw:RibbonPanel>
</adw:RibbonTab>
C# code-behind to add a button to the ribbon bar using the tab defined in XAML
[CommandMethod("AddButtonXAML")]
public static void AddButtonXAML()
{
// Create a RibbonTab using the resourceDictionary
RibbonTab tab =
resourceDictionary["TabXaml"] as RibbonTab;
// Find the ribbon button and add the event
RibbonRow row = tab.Panels[0].Source.Rows[0];
RibbonItemCollection coll = row.Items;
foreach (RibbonItem item in coll)
{
if (item is RibbonButton)
{
RibbonButton button = (RibbonButton)item;
if (button.Id == "ClickMe_2")
{
button.Click +=
new RoutedEventHandler(button_Click);
}
}
}
// Now add the tab to AutoCAD Ribbon bar and activate it
ribbonControl.Tabs.Add(tab);
ribbonControl.ActiveTab = tab;
}
ToolTip
The next thing you would want to do once you add your objects to the Ribbon bar is to display a tooltip for these objects.
The ToolTip property of the RibbonItem class accepts an object so, we could assign a control object to it to display the control’s content as a tooltip. In this example here we define a Grid control. The control intern uses the Autodesk.Windows.ProgressivePanel class to implement the extended tooltip feature that is available with the AutoCAD tooltips.
XAML
<Grid x:Key="ClickMe_ToolTip">
<StackPanel>
<!--Header Part-->
<StackPanel Orientation="Horizontal" Margin="5,5,5,5">
<TextBlock Text="ClickMe">
<TextBlock.FontWeight>
<FontWeight>
Bold
</FontWeight>
</TextBlock.FontWeight>
</TextBlock>
</StackPanel>
<!--Basic help information -->
<StackPanel Margin="5,5,5,5">
<TextBlock
Text="This is basic help of click me command">
<TextBlock.TextWrapping>
<TextWrapping>
Wrap
</TextWrapping>
</TextBlock.TextWrapping>
</TextBlock>
</StackPanel>
<!--Extended help information -->
<adw:ProgressivePanel Margin="5,5,5,5">
<StackPanel/>
<!--Click Me Extended Tooltip-->
<Grid>
<StackPanel Orientation="Vertical" Margin="0,0,0,0">
<TextBlock>
Click Me extended ToolTip
</TextBlock>
<Image Margin="40,10,0,0"
Width="150" Height="150"
Source="/MyRibbon;component/Images/Smiley.png" />
</StackPanel>
</Grid>
</adw:ProgressivePanel>
<!-- Footer Part -->
<Line Stroke="Black" StrokeThickness="2" X2="250"/>
<StackPanel Orientation="Horizontal" Margin="5,5,5,5">
<Grid VerticalAlignment="Center"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="21" />
<ColumnDefinition Width="179" />
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" Grid.Column="0"
Width="16" Height="16">
<Image.Source>
/MyRibbon;component/Images/Help.gif
</Image.Source>
</Image>
<TextBlock HorizontalAlignment="Left" Grid.Column="1"
FontWeight="Bold">
Press F1 for more help
</TextBlock>
</Grid>
</StackPanel>
</StackPanel>
</Grid>
button.ToolTip = resourceDictionary["ClickMe_ToolTip"];
We can do away with this statement above if we define the button in the XAML file by adding the tooltip to RibbonButton in the XAML as below:
<adw:RibbonButton.ToolTip>
<!-- Define tooltip here, above XAML without
x:Key value could be used -->
</adw:RibbonButton.ToolTip>
Here's a snapshot of the extended tooltip:
Figure: Ribbon object tooltip
Although we can display tooltip using a control as done above, we will not be able to implement the F1 event-handling mechanism using this approach. The ToolTip UI controls like Autodesk.Windows.ToolTip or System.Windows.Controls.ToolTip with F1 event handlers will not help us here because the Ribbon bar does not accept them similar to the way we could not use the Button class to add a button to the Ribbon bar. This particular feature could easily run into an article in itself, so we'll stop at this point to continue in a future article.