Skip to main content

Transactions

A transaction encapsulates a sequence of modifications to the model that make up a single action in the view. The small set of transaction types together cover all possible actions in the view.

Overview

Every change to the state of the model is logged and stored in a file called the undo stack. When the user undoes an action, the top of the stack is used to restore the model to its previous state. Then, events are sent to the view, updating it to reflect the state of the model. Redo is handled analogously.

Using transactions

Transaction phases

Transactions have two general purposes:

  • They control what constitutes a unit on the undo stack.
  • They control when external events are sent to the view: When all changes to the model have been performed, the external events are fired in an undefined order.
    Note: The transaction does not affect internal events. Refer to Events for details.

Implementing transactions

To change a property of the document, start a transaction with the ExecuteTransaction method. It is available via the Transactions property that exists on every the document node. Modify the property of the object within the transaction:

page.Transactions.ExecuteTransaction(delegate {
	page.Name = page.Name.ToUpper();} );

The transaction mechanism creates an entry on the undo stack and notifies the user interface to update itself.

Implementing transactions wisely

Since the transaction mechanism controls the granularity of undo, improper use of ExecuteTransaction may have adverse side effects. Consider for instance the task of changing the names of all pages to uppercase. Wrapping a transaction around a loop performing the name change will put all document modifications into one transaction, one entry on the undo stack:

document.Transactions.ExecuteTransaction(delegate {
	foreach (Page page in document.Pages)
	{
		page.Name = page.Name.ToUpper()
	}
	} );

If the user undoes the action, all names are restored in one action and the user interface is updated once at the end of the transaction.

Suppose the transaction was implemented within the loop rather than wrapping it: The loop would result in as many transactions as there are pages. The following example does this, but it does not take effect since the transaction is nested, and the transaction with the larger scope takes precedence:

document.Transactions.ExecuteTransaction(delegate {
	foreach (Page page in root.pages)
	{
		page.Transactions.ExecuteTransaction (delegate {
			page.Name = page.Name.ToUpper()
	}
	} );

Transaction Types

There are a different types of transactions. Apart from the normal transaction, there are three special purpose types. Which one to use depends on the action to encapsulate:

  • Aggregated transactions are used to encapsulate an action consisting of several distinct actions, like the often haphazard entering and changing of values in a modal dialog, until the moment when the OK button is hit.
  • Invisible transactions provide an opportunity to include actions not necessarily noticed by the user, but still include a change of model state. Typical cases in point are change of active page, change of active visualization and changes to the selection of filters.
  • Sticky transactions are used to capture an action that is made up of a sequence of similar actions, like the setting of a range filter. When the user moves the filter, a range of values that should not be included are passed.

Refer to the ITransactions Methods for details.

Examples

The following examples cover typical cases when normal, aggregated and sticky transactions respectively are used.

Transaction: New Visualization

Creating and a configuring a bar chart:

Page template = document.ActivePageReference;
Page page = document.Pages.AddNew("Sales Overview");
page.AutoConfigure(template);
BarChart barChart = page.Visuals.AddNew<BarChart>();
barChart.Title = "Sales per Product";
barChart.XAxis.Expression = "<[Product]>";
barChart.YAxis.Expression = "Sum([Sales Total])";

Encapsulating the commands in one action:

document.Transactions.ExecuteTransaction(delegate {
    Page template = document.ActivePageReference;
    Page page = document.Pages.AddNew("Sales Overview");
    page.AutoConfigure(template);
    BarChart barChart = page.Visuals.AddNew<BarChart>();
    barChart.Title = "Sales per Product";
    barChart.XAxis.Expression = "<[Product]>";
    barChart.YAxis.Expression = "Sum([Sales Total])";
    });

Aggregated Transaction: Modal Dialog

Problem: Consider the column property dialog in Spotfire. If the user deletes a column, the user interface is updated immediately without the user having to click OK. If the user makes additional changes and then clicks OK, and clicks Undo, all actions should be undone in one step. Similarly, if the user presses Cancel then all actions should be undone.

Solution: Start an aggregated transaction when the dialog opens. Now all transactions within the dialog will execute individually but will be aggregated into one unit. Then the user either clicks OK, in which case the aggregated transaction should be committed, or the user clicks Cancel, in which case the transaction should be rolled back.

using (AggregatedTransactionHandle transactionHandle = page.Transactions.BeginAggregatedTransaction()
{
	if (dialog.ShowDialog(this) == DialogResult.OK)
	{
		transactionHandle.Commit();
	}
	else
	{
		transactionHandle.Rollback();
	}
}

Note: The using statement disposes the transaction handle even if an exception is thrown. If the handle is disposed without committing the transaction results in rollback.

Invisible Transaction: Filter Selection

A filter selection is preferably executed within a transaction:

filter.Transactions.ExecuteInvisibleTransaction(delegate {...} );

Sticky Transaction: Range Filter

Consider a range filter: When the user moves the slider it should be updated continuously. The slider updates the corresponding range filter document node as the user moves the slider. If the slider movement is encapsulated in ordinary transactions, it will yield a large number or entries on the undo stack and the start to stop movement of the slider can not be undone in one step.
The defining feature of sticky transactions is that they glue together on the undo stack, provided that they are associated with the same GUID. The GUID is passed in as an additional parameter to the sticky transaction.

rangefilter.Transactions.ExecuteStickyTransaction(guid, delegate {...} );

Note: Range sliders must use distinct GUID:s, otherwise the transactions from one slider will glue together with those from the other. Analogously, if the user drags the same range filter twice different GUID:s should be used to prevent the two distinct actions from merging into one undo entry.