Back to main Spotfire C# extensions page
Introduction
A custom tool is a generic Spotfire® extension type, which is used to perform an action in a given context. The context may for example be the Spotfire application itself, the document, or a particular visualization type. Custom tools can be grouped both on menu and context menu level, but they may also be hidden from the user interface.
Prerequisites
- Spotfire® Developer (SDK), see download instructions here.
- Spotfire® Analyst, download from edelivery.tibco.com.
- Microsoft Visual Studio® 2013 or higher. The free Community edition is available for download here.
See also
- SDK example: SpotfireDeveloper.CustomToolsExample - contains several tool examples for different context (Application, Document, Page and VisualContent).
- Create a custom export tool
Overview
The extension point for tools is the CustomTool class, which can be inherited from to implement a custom tool. This class is then registered in the RegisterTools method on the AddIn class. At this point it is also possible to specify that the tool should be placed in a custom menu group using the CustomMenuGroup class.
When creating a CustomTool class, the tool context is specified as a type parameter, for example CustomTool<Document> for a document tool or CustomTool<Page> for a page tool.
Apart from the tools created from the CustomTool class, there are two special-purpose tools that have specific extension points:
- Export tools, derived from CustomExportTool. See Create a Custom Export Tool.
- Share tools, derived from CustomShareTool.
It is possible to add prompting to the tool. Prompt dialogs may be added for both the Windows client and the web clients.
When implementing a custom tool, consider the general tool characteristics:
-
Tools are static.
There is only one instance of a tool. The instance receives a handle to its context from the context argument of the Execute() method invoking it.
-
Tools are stateless.
A state can only be saved in the document from an instance of a class that is derived from DocumentNode and attached to the Document Model. Use the CustomNode class and the Document.CustomNodes property.
-
Tools perform an action.
Usually, the action ought to result in one undo state on the undo stack. Therefore all tools that change the state of the document should wrap their action in a Transaction.
Tool Contexts
Application
public sealed class MyApplicationTool : CustomTool<AnalysisApplication>
AnalysisApplication tools are available from the Tools menu and are enabled when Spotfire has been started.
Document
public sealed class MyDocumentTool : CustomTool<Document>
Page
public sealed class MyPageTool : CustomTool<Page>
The Page context is not available for the web client.
Visual
public sealed class MyVisualTool : CustomTool<VisualContent>
The VisualContent context enables a tool for all visuals on a page. There are also sub contexts for visual tools applying to specific visualization types:
- Visualization: All visualizations, but not the plot area.
- BarChart: Only bar charts.
- BoxPlot: Only box plots.
- CombinationChart: Only combination charts.
- CrossTablePlot: Only cross table plots.
- GraphicalTable: Only graphical table plots.
- KPIChart: only KPI charts.
- LineChart: Only line charts.
- MapChart: Only map charts.
- ParallelCoordinatePlot:
- PieChart: Only pie charts.
- ScatterPlot: Only scatter plots.
- ScatterPlot3D: Only 3D scatter plots.
- SummaryTable: Only summary tables.
- TablePlot: Only table plots.
- TextArea: Only text areas.
- WaterfallChart: Only waterfall charts.
Sub contexts may be combined by overriding the IsVisibleCore method, thereby hiding the tool for all sub contexts but the explicitly defined ones. For example having a tool that is only available for scatter plots and bar charts, and is enabled when there is a marking (see the SDK example Examples\Extensions\SpotfireDeveloper.CustomToolsExample\PlotTool.cs).
Table Plot Contexts
TablePlotColumn
public sealed class MyTablePlotColumnTool : CustomTool<TablePlotColumnContext>
Available in the context menu shown when right clicking on a column header.
TablePlotCell
public sealed class MyTablePlotCellTool : CustomTool<TablePlotCellContext>
Available in the context menu shown when right-clicking a cell in the table.
TablePlotCopyCellValue
public sealed class MyTablePlotCopyCellValueTool : CustomTool<TablePlotCopyCellValueContext>
Available in the Copy Cell sub menu of the context menu shown when right-clicking a cell in the table. Depending on the content of the right-clicked cell, and without any added extensions, the sub menu title contains one or more of the following entries:
- Value: Available when the clicked cell contains text. The text is placed on the clipboard.
- Link: Available when a column renderer has supplied a link for the clicked cell.
- Image: Available when a renderer has supplied an image for the clicked cell. The image placed on the clipboard is identical to the one seen in the table.
The TablePlotCopyCellValue context is not available for the web client.
Map Chart Coordinates Context (7.9)
public sealed class MyMapChartCoordinatesTool : CustomTool<MapChartCoordinatesContext>
A custom tool with the MapChartCoordinatesContext will automatically be called when the user right clicks in a map chart. The coordinates is available as a property on the context object.
- SDK example: SpotfireDeveloper.CustomTools.MapChartRighClickCoordinatesTool
public sealed class MyFilterTool : CustomTool<FilterBase>
The FilterBase context enables the tool for all filters in the filter panel. There are also sub contexts for filter tools applying to specific filter types:
- CheckboxFilter: Check box filters.
- ItemFilter: Item filters.
- ListBoxFilter: List box filters.
- RadioButtonFilter: Radio button filters.
- RangeFilter: Range filters.
- TextFilter: Text box filters.
- HierarchyFilter: Hierarchy filters.
- ColumnFilter: All filter types, but the hierarchy filters.
- SingleValueColumnFilter: Filter types selecting one value at a time, that is item filters and radio button filters.
Grouping Tools
Tools can be grouped in the location where they appear:
- The Transformation Tool Example is registered without specifying a menu group. Since it is a Document tool, it will appear in the Tools menu.
- The Navigate to Page tool is registered with a specified menu sub group. Since it also is a Document tool, it will appear in the menu group of the Tools menu, in this case the Spotfire Custom Tools group.
To prevent accidental or unmanaged adding of tools from other projects, only tools that are implemented in the same Spotfire extension project can be grouped together in a menu sub group. Collecting the tools in one AddIn, the group is created and then passed to the tools to be included in the group when registering them. Tools are registered using the AddIn.ToolRegistrar.Register methods:
public sealed class CustomToolsAddIn : AddIn { protected override void RegisterTools(ToolRegistrar registrar) { base.RegisterTools(registrar); CustomMenuGroup menuGroup = new CustomMenuGroup("My menu sub group"); registrar.Register(new MyTool(), menuGroup); } ... }
Custom Top Level Menus
Application and document tools can also be placed in custom top level menus. Read more here.
The CustomTool Class
The Constructor of the CustomTool class has a mandatory string parameter that is the text shown in menus and tooltips. It is possible to override this text with GetMenuTextCore to get a text that adapts to the localization settings on the web client. Optional parameters to the constructor include the license required to run the tool, a menu category and a custom icon to be displayed in the menu.
protected CustomTool(string menuText, LicensedFunction requiredLicense, MenuCategory menuCategory, Image image)
The tool logic is implemented by overriding one of the methods ExecuteCore or ExecuteAndPromptCore, where the latter is used when the tool needs to prompt for user input.
protected virtual void ExecuteCore(TContext context) protected virtual IEnumerable<Object> ExecuteAndPromptCore(TContext context)
The context parameter will give the tool access to the document, a page, visual or other object depending on what context the tool operates on.
The signature of the ExecuteAnPromptCore method requires some explanation: Note that it is supposed to return an enumerable over objects. This is achieved by the yield construct to return any object that is used as a model for prompting. Use AddIn.RegisterViews to register the corresponding prompt views for any prompt models used by the tool.
If the tool uses prompting, the GetSupportsPromptingCore needs to be overridden to return true.
Tools in the Web Client
Tools in the web client is expected to support prompting and must implement ExecuteAndPromptCore (ExecuteCore is never called by the framework). It is however possible to implement a web client tool without prompting using the following workaround in ExecuteAndPromptCore that calls ExecuteCore and returns no prompt model:
protected override IEnumerable<object> ExecuteAndPromptCore(Visualization context) { this.ExecuteCore(context); yield break; }
Specify Visibility and Appearance
There are two methods you can override to specify when the tool should be visible and when the tool should be enabled.
- IsEnabledCore returns a boolean to specify whether or not the tool should be enabled in the menu.
-
protected override bool IsEnabledCore(TContext context)
- IsVisibleCore returns a boolean to specify whether or not the tool should be visible in the menu.
-
protected override bool IsVisibleCore(TContext context)
Examples
Page Tool
This example shows a tool that moves the active page to the end of the page collection. The tool inherits from CustomTool<Page>:
/// <summary> /// A tool that moves the active page to the end of the pages collection. /// </summary> public sealed class PageTool : CustomTool<Page> { /// <summary> /// Initializes a new instance of class <see cref="PageTool"/>. /// </summary> public PageTool() : base(Properties.Resources.PageToolTitle) { // Empty } /// <summary> /// This method contains the logic of the tool, which involves moving /// the context of this tool (i.e. the page that was clicked) to the /// end of the page collection of the current document. /// </summary> protected override void ExecuteCore(Page page) { Document document = page.Context.GetAncestor<Document>(); int index = document.Pages.IndexOf(page); document.Pages.Move(index, document.Pages.Count - 1); } /// <summary>We override this method to /// ensure that the tool is only enabled when /// there is an active page, and it is not already the last one. /// </summary> /// <param name="page">The page that the user clicked (i.e. the /// context of the tool.</param> /// <returns><c>true</c> if this tool is to be enabled; /// otherwise <c>false</c>.</returns> protected override bool IsEnabledCore(Page page) { Document document = page.Context.GetAncestor<Document>(); int index = document.Pages.IndexOf(page); return index >= 0 && index < document.Pages.Count - 1; } }
Visual Tool
The example shows a tool that writes out the marked values in a visualization. The tool inherits from CustomTool<VisualContent>:
/// <summary> /// A tool that writes out the marked values from the first /// data column to the console when it is executed. /// </summary> /// /// <remarks>This tool is only available for /// <see cref="BarChart">Bar Charts</see> and /// <see cref="ScatterPlot">Scatter Plots</see>. /// </remarks> public sealed class PlotTool : CustomTool<VisualContent> { /// <summary> /// Initializes a new instance of class <see cref="PlotTool"/>. /// </summary> public PlotTool() : base(Properties.Resources.PlotToolTitle) { // Empty } /// <summary> /// Executes the tool. Prints the values for the marked records in the /// first column of the plot data table. /// </summary> /// <param name="context">The context of the tool, i.e. the plot that /// was clicked.</param> protected override void ExecuteCore(VisualContent context) { Visualization plot = (Visualization)context; DataManager dataManager = context.Context.GetService<DataManager>(); DataTable dataTable = plot.Data.DataTableReference; DataColumn firstColumn = dataManager.Tables[dataTable.Id].Columns[0]; IndexSet markedRows = dataManager.Markings.DefaultMarkingReference.GetSelection(dataTable).AsIndexSet(); var cursor = DataValueCursor.Create(firstColumn); foreach (var row in dataTable.GetRows(markedRows, cursor)) { Trace.WriteLine(cursor.CurrentDataValue.ValidValue); } } /// <summary> /// Determines when the tools is to be enabled. The implementation /// enables the tool when one or more rows are marked in the plot. /// </summary> /// <param name="context">The context of the tool, i.e. the /// plot that was clicked.</param> /// <returns></returns> protected override bool IsEnabledCore(VisualContent context) { Visualization plot = context as Visualization; DataManager dataManager = context.Context.GetService<DataManager>(); DataMarkingSelection marking = dataManager.Markings.DefaultMarkingReference; // Check if there is a valid data table if (plot.Data.DataTableReference == null) { return false; } else { RowSelection selectedRows = marking.GetSelection(plot.Data.DataTableReference); return !selectedRows.IsEmpty; } } /// <summary> /// Determines when the tool is to be visible in the plot context /// menus. /// </summary> /// <param name="context">The context of the tool, i.e. the plot /// that was clicked.</param> /// <returns><c>true</c> when the context is a <see cref="ScatterPlot"/> /// or a <see cref="BarChart"/>.</returns> protected override bool IsVisibleCore(VisualContent context) { return context is BarChart || context is ScatterPlot; } }
Table Context Menu Entry
When a context menu is opened on a table plot cell, any custom tool registered for one the TablePlotCellContext or CopyCellValueContext are added to the menu.
Cell Value Tool
Right-clicking a Spotfire table cell displaying an image selecting Copy Cell > Image, adds the image as scaled in the table to the clipboard. This example implements a tool for copying the full-size image to the clipboard. It is accessible from the same location as the generic Spotfire image copy tool:
The tool inherits from CustomTool<CopyCellValueContext>:
/// <summary> /// A copy cell value tool which will show on context menu when activated for an image /// value. /// </summary> public sealed class CopyCellValueTool : CustomTool<CopyCellValueContext> { /// <summary> /// Initializes a new instance of the <see cref="CopyCellValueTool"/> class. /// </summary> public CopyCellValueTool() : base("Image (fullsize)") { } /// <summary> /// Executes the tool. Copies an image blob to the clipboard. /// </summary> /// <param name="context">The context of the tool. Is never <c>null</c>.</param> /// <remarks>Note that this method is only called if /// <paramref name="context"/> is not <c>null</c> and /// <see cref="M:Spotfire.Dxp.Application.Tool`1.IsEnabled(`0)"/> /// returns <c>true</c>.</remarks> protected override void ExecuteCore(CopyCellValueContext context) { BinaryLargeObject blob = context.DataValue.Value as BinaryLargeObject; if (blob != null) { try { Image img = Image.FromStream(blob.GetByteStream()); Clipboard.Clear(); Clipboard.SetImage(img); } catch (ArgumentException) { // Unable to convert to image, ignore } } } /// <summary> /// CopyCellValueTool is only visible when the <see cref="DataType"/> in context /// is binary, and content type in context begins with "image/". /// </summary> /// <param name="context">The context of the tool. Can be <c>null</c>.</param> /// <returns> /// Returns <c>true</c> if type is binary and content type beings with "image/", /// <c>false</c> otherwise. /// </returns> protected override bool IsVisibleCore(CopyCellValueContext context) { return context.DataType != null && context.ContentType != null && !context.DataType.IsSimple && context.ContentType.StartsWith("image/", true, CultureInfo.InvariantCulture); } /// <summary> /// CopyCellValueTool is only enabled if the DataValue in the context is valid. /// When working with a virtual column, a invalid datavalues are recieved if /// the value has not yet been fetched. /// </summary> /// <param name="context">The context of the tool. Is never <c>null</c>.</param> /// <returns> /// Returns <c>true</c> if the DataValue is valid, otherwise <c>false</c>. /// </returns> /// <remarks>Note that this method is only called if /// <paramref name="context"/> is not <c>null</c></remarks> protected override bool IsEnabledCore(CopyCellValueContext context) { return context.DataValue != null && context.DataValue.IsValid; } }
Table Plot Cell Context Tool
This example implements a tool adding the Show cell information entry to the cell context menu. The tool dispalys a message box naming the column of the selected cell and the cell value.
This tool inherits from CustomTool<TablePlotCellContext>:
/// <summary> /// A table plot context menu tool tool that display information about /// the cell upon which the context menu was opened. /// </summary> /// /// <remarks>This tool is only available for /// <see cref="TablePlot">Table Plots</see>. /// </remarks> public sealed class TablePlotCellContextExampleTool : CustomTool<TablePlotCellContext> { /// <summary> /// Initializes a new instance of the <see cref="TablePlotCellContextExampleTool"/> class. /// </summary> public TablePlotCellContextExampleTool() : base(Properties.Resources.TablePlotCellToolTitle) { } /// <summary> /// Executes the tool. Shows a messagebox that display information about /// the cell upon which the context menu was opened. /// </summary> /// <param name="tablePlotCellContext">The table plot cell context.</param> protected override void ExecuteCore(TablePlotCellContext tablePlotCellContext) { string dataValue = tablePlotCellContext.DataValue.Value.ToString(); string info = string.Format( System.Threading.Thread.CurrentThread.CurrentCulture, Properties.Resources.TablePlotCellToolInfo, new object[] { tablePlotCellContext.TableColumn.Name, dataValue }); System.Windows.Forms.MessageBox.Show(info, Properties.Resources.TablePlotCellToolTitle); } /// <summary> /// TablePlotCellContextTool is only enabled if the DataValue in the context /// is valid. When working with a virtual column, a invalid datavalues are /// recieved if the value has not yet been fetched. /// </summary> /// <param name="context">The context of the tool. Is never <c>null</c>.</param> /// <returns> /// Returns <c>true</c> if the DataValue is valid, otherwise <c>false</c>. /// </returns> /// <remarks>Note that this method is only called if /// <paramref name="context"/> is not <c>null</c></remarks> protected override bool IsEnabledCore(TablePlotCellContext context) { return context.DataValue != null && context.DataValue.IsValid; } }
Recommended Comments
There are no comments to display.