// // (C) Copyright 2003-2019 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notify appears in all copies and // that both that copyright notify and the limited warranty and // restricted rights notify below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. // using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Autodesk.Revit; using Autodesk.Revit.DB; using Autodesk.Revit.UI; namespace Revit.SDK.Samples.TransactionControl.CS { using SystemColor = System.Drawing.Color; using TaskDialog = Autodesk.Revit.UI.TaskDialog; /// /// A Form used to deal with transaction and create, move or delete a wall /// public partial class TransactionForm : System.Windows.Forms.Form { /// /// An enum used to indicate which operation is executed /// private enum OperationType { StartTransactionGroup, RollbackTransactionGroup, CommitTransactionGroup, StartTransaction, CommitTransaction, RollbackTransaction, ObjectModification, ObjectDeletion } /// /// A reference to the external command data. /// private ExternalCommandData m_commandData; /// /// The active document /// private Document m_document; /// /// The root node of the operation tree /// private TreeNode m_rootNode; /// /// A reference to active transaction group node where sub transaction node will be added /// private TreeNode m_transGroupNode; /// /// A reference to the active sub transaction node /// private TreeNode m_transNode; /// /// fore color of tree node before a transaction is over /// private SystemColor m_startedColor = SystemColor.Green; /// /// fore color of tree node after ending a transaction /// private SystemColor m_committedColor = SystemColor.Black; /// /// fore color of tree node after aborting a transaction /// private SystemColor m_rolledbackColor = SystemColor.Gray; /// /// fore color of a tree node of a created or modified element /// private SystemColor m_normalColor = SystemColor.Blue; /// /// fore color of a tree node of a deleted element /// private SystemColor m_deletedColor = SystemColor.Red; /// /// The currently active transaction group /// private TransactionGroup m_transactionGroup = null; /// The main, hidden outer transaction group /// private TransactionGroup m_mainTtransactionGroup = null; /// /// The active transaction /// private Transaction m_transaction = null; /// /// The number of transactions /// private int m_transCount = 0; /// /// The number of transaction groups /// private int m_transGroupCount = 0; /// /// The last created wall in the active transaction /// private Wall m_lastCreatedWall = null; /// /// Constructor /// /// the external command data public TransactionForm(ExternalCommandData commandData) { m_commandData = commandData; m_document = m_commandData.Application.ActiveUIDocument.Document; if (m_document == null) { TaskDialog.Show("Revit", "There is no active document."); } InitializeComponent(); // created the root node m_rootNode = new TreeNode("Command history"); this.transactionsTreeView.Nodes.Add(m_rootNode); // set availability of form buttons UpdateButtonsStatus(); // start the main transaction group (will be hidden to the user) m_mainTtransactionGroup = new TransactionGroup(m_document); m_mainTtransactionGroup.Start(); } /// /// Begin a transaction, append transaction node to tree view /// /// /// private void startTransButton_Click(object sender, EventArgs e) { int transNo = m_transCount + 1; m_transaction = new Transaction(m_document, "Transaction " + transNo.ToString()); if (m_transaction.Start() == TransactionStatus.Started) { m_transCount++; AddNode(OperationType.StartTransaction); m_lastCreatedWall = null; UpdateButtonsStatus(); } else { m_transaction = null; TaskDialog.Show("Revit", "Starting transaction failed"); } } /// /// Commit a transaction /// /// /// private void commitTransButton_Click(object sender, EventArgs e) { if ((m_transaction != null) && (m_transaction.GetStatus() == TransactionStatus.Started)) { m_transaction.Commit(); if (m_transaction.HasEnded()) { // in theory, committing a transaction can turn out to a be roll-back instead // depending on how model failures, if any, are resolved by the user if (m_transaction.GetStatus() == TransactionStatus.Committed) AddNode(OperationType.CommitTransaction); else AddNode(OperationType.RollbackTransaction); m_transaction = null; m_lastCreatedWall = null; UpdateButtonsStatus(); } else { TaskDialog.Show("Revit", "Committing transaction failed"); } } } /// /// Rollback a transaction /// /// /// private void rollbackTransButton_Click(object sender, EventArgs e) { if ((m_transaction != null) && (m_transaction.GetStatus() == TransactionStatus.Started)) { if (m_transaction.RollBack() == TransactionStatus.RolledBack) { m_transaction = null; m_lastCreatedWall = null; AddNode(OperationType.RollbackTransaction); UpdateButtonsStatus(); } else { TaskDialog.Show("Revit", "Rolling Back transaction failed"); } } } /// /// Create a wall, append a node to tree view /// /// /// private void createWallbutton_Click(object sender, EventArgs e) { // a sub-transaction is not necessary in this case // it is used for illustration purposes only using (SubTransaction subTransaction = new SubTransaction(m_document)) { // if not handled explicitly, the sub-transaction will be rolled back when leaving this block try { if (subTransaction.Start() == TransactionStatus.Started) { using (CreateWallForm createWallForm = new CreateWallForm(m_commandData)) { createWallForm.ShowDialog(); if (DialogResult.OK == createWallForm.DialogResult) { updateModel(true); // immediately update the view to see the changes if (subTransaction.Commit() == TransactionStatus.Committed) { m_lastCreatedWall = createWallForm.CreatedWall; AddNode(OperationType.ObjectModification, "Created wall " + m_lastCreatedWall.Id.ToString()); UpdateButtonsStatus(); return; } } else { subTransaction.RollBack(); return; } } } } catch (System.Exception ex) { TaskDialog.Show("Revit", "Exception when creating a wall: " + ex.Message); } } TaskDialog.Show("Revit", "Creating wall failed"); } /// /// Move a wall, append a node to tree view /// /// /// private void moveWallButton_Click(object sender, EventArgs e) { if (m_lastCreatedWall == null) return; // a sub-transaction is not necessary in this case // it is used for illustration purposes only using (SubTransaction subTransaction = new SubTransaction(m_document)) { // if not handled explicitly, the sub-transaction will be rolled back when leaving this block try { if (subTransaction.Start() == TransactionStatus.Started) { Autodesk.Revit.DB.XYZ translationVec = new Autodesk.Revit.DB.XYZ(10, 10, 0); ElementTransformUtils.MoveElement(m_document, m_lastCreatedWall.Id, translationVec); updateModel(true); // immediately update the view to see the changes if (subTransaction.Commit() == TransactionStatus.Committed) { AddNode(OperationType.ObjectModification, "Moved wall " + m_lastCreatedWall.Id.ToString()); return; } } } catch (System.Exception ex) { TaskDialog.Show("Revit", "Exception when moving a wall: " + ex.Message); } } TaskDialog.Show("Revit", "Moving wall failed."); } /// /// Delete a wall, append a node to tree view /// /// /// private void deleteWallButton_Click(object sender, EventArgs e) { if (m_lastCreatedWall == null) return; // a sub-transaction is not necessary in this case // it is used for illustration purposes only using (SubTransaction subTransaction = new SubTransaction(m_document)) { // if not handled explicitly, the sub-transaction will be rolled back when leaving this block try { String wallId = m_lastCreatedWall.Id.ToString(); if (TaskDialogResult.No == TaskDialog.Show("Warning", "Do you really want to delete wall with id " + wallId + "?", TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No)) { return; } if (subTransaction.Start() == TransactionStatus.Started) { m_document.Delete(m_lastCreatedWall.Id); updateModel(false); // immediately update the view to see the changes if (subTransaction.Commit() == TransactionStatus.Committed) { AddNode(OperationType.ObjectDeletion, "Deleted wall " + wallId); m_lastCreatedWall = null; UpdateButtonsStatus(); return; } } } catch (System.Exception ex) { TaskDialog.Show("Revit", "Exception when deleting a wall: " + ex.Message); } } TaskDialog.Show("Revit", "Deleting wall failed."); } /// /// Start transaction group button click event /// /// /// private void btnStartTransGroup_Click(object sender, EventArgs e) { m_transGroupCount++; m_transactionGroup = new TransactionGroup(m_document, "Transaction Group " + m_transGroupCount.ToString()); m_transactionGroup.Start(); AddNode(OperationType.StartTransactionGroup); UpdateButtonsStatus(); } /// /// Commit transaction group button click event /// /// /// private void btnCommitTransGroup_Click(object sender, EventArgs e) { if (m_transactionGroup != null) { m_transactionGroup.Commit(); AddNode(OperationType.CommitTransactionGroup); m_transaction = null; m_lastCreatedWall = null; UpdateButtonsStatus(); } } /// /// Rollback transaction group button click event /// /// /// private void btnRollbackTransGroup_Click(object sender, EventArgs e) { if (m_transactionGroup != null) { m_transactionGroup.RollBack(); AddNode(OperationType.RollbackTransactionGroup); m_transaction = null; m_lastCreatedWall = null; UpdateButtonsStatus(); } } /// /// Accept the changes and and close this form /// /// If any transaction group or the active transaction is still open /// give the user the option to either commit them or roll them back first. /// /// private void okButton_Click(object sender, EventArgs e) { //if any transaction or transaction group is not finished, notify user to deal with it if (null != m_transaction || null != m_transactionGroup) { TaskDialogResult dialogResult = TaskDialog.Show("Warning", "Some transaction groups or the active transaction is not committed." + " Choose Yes to commit them, or No to roll them back.", TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No | TaskDialogCommonButtons.Cancel); if (TaskDialogResult.Cancel == dialogResult) { return; } else if (TaskDialogResult.Yes == dialogResult) { if (m_transaction != null && (m_transaction.GetStatus() == TransactionStatus.Started)) { m_transaction.Commit(); } HandleNestedTransactionGroups(OperationType.CommitTransactionGroup); } else { if (m_transaction != null && (m_transaction.GetStatus() == TransactionStatus.Started)) { m_transaction.RollBack(); } HandleNestedTransactionGroups(OperationType.RollbackTransactionGroup); } } // silently commit the main (hidden) transaction group if ((null != m_mainTtransactionGroup) && (m_mainTtransactionGroup.GetStatus() == TransactionStatus.Started)) { m_mainTtransactionGroup.SetName("SDK Transaction Sample"); m_mainTtransactionGroup.Assimilate(); } this.DialogResult = DialogResult.OK; this.Close(); } /// /// Rollback all changes and close this form /// /// /// private void cancelButton_Click(object sender, EventArgs e) { if (m_transCount > 0) { if (TaskDialogResult.No == TaskDialog.Show("Warning", "By canceling this dialog, all modifications to the model will be discarded." + " Do you want to proceed?", TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No)) { this.DialogResult = DialogResult.None; return; } } // if there is still an active transaction, roll it back now, silently if ((null != m_transaction) && (m_transaction.GetStatus() == TransactionStatus.Started)) { m_transaction.RollBack(); } // if there are still transaction groups open, roll them back now, silently if ((null != m_transactionGroup) && (m_transactionGroup.GetStatus() == TransactionStatus.Started)) { HandleNestedTransactionGroups(OperationType.RollbackTransactionGroup); } // silently roll back the main (hidden) transaction group if ((null != m_mainTtransactionGroup) && (m_mainTtransactionGroup.GetStatus() == TransactionStatus.Started)) { m_mainTtransactionGroup.RollBack(); } this.DialogResult = DialogResult.Cancel; this.Close(); } /// /// Commit or rollback nested transaction groups /// /// The operation type determines whether to commit or to rollback transaction groups private void HandleNestedTransactionGroups(OperationType operationType) { while (m_transactionGroup != null) { if (operationType.Equals(OperationType.CommitTransactionGroup)) { m_transactionGroup.Commit(); } else { m_transactionGroup.RollBack(); } if (m_transactionGroup.HasEnded()) { m_transGroupNode = m_transGroupNode.Parent; if (m_transGroupNode.Equals(m_rootNode)) { m_transactionGroup = null; } else { m_transactionGroup = m_transGroupNode.Tag as TransactionGroup; } } else { throw new System.ApplicationException("Could not end a transaction group"); } } } /// /// Update form buttons depending on the status of existing transaction and groups /// private void UpdateButtonsStatus() { // there can be only one active transaction at any given time btnStartTrans.Enabled = (m_transaction == null); // only active transaction can be committed btnCommitTrans.Enabled = ((m_transaction != null) && (m_transaction.GetStatus() == TransactionStatus.Started)); // the same rule applies to rolling back a transaction btnRollbackTrans.Enabled = btnCommitTrans.Enabled; // transaction group cannot be started inside an active transaction btnStartTransGroup.Enabled = (m_transaction == null); // transaction group cannot be committed while still inside an active transaction // also, the group must be active in order to be committed btnCommitTransGroup.Enabled = ((m_transaction == null) && (m_transactionGroup != null) && (m_transactionGroup.GetStatus() == TransactionStatus.Started)); // the same rule applies to rolling back a transaction group btnRollbackTransGroup.Enabled = btnCommitTransGroup.Enabled; // wall can be created only in an active transaction btnCreateWall.Enabled = btnCommitTrans.Enabled; // the same rule applies to deleting a wall, plus, naturally, the wall must exist btnDeleteWall.Enabled = (btnCreateWall.Enabled && (m_lastCreatedWall != null)); // the same applies to moving a wall btnMoveWall.Enabled = btnDeleteWall.Enabled; } /// /// add node to tree view control /// /// indicate operation type private void AddNode(OperationType type) { AddNode(type, null); } /// /// add node to tree view control /// /// indicate operation type /// tree node text private void AddNode(OperationType type, string info) { //add tree node according to operation type if (type == OperationType.StartTransactionGroup) { if (m_transGroupNode == null) { m_transGroupNode = new TreeNode(m_transactionGroup.GetName()); int index = m_rootNode.Nodes.Add(m_transGroupNode); m_rootNode.Nodes[index].Tag = m_transactionGroup; m_rootNode.Expand(); UpdateTreeNode(m_transGroupNode, type); } else { TreeNode newTransGroupNode = new TreeNode(m_transactionGroup.GetName()); int index = m_transGroupNode.Nodes.Add(newTransGroupNode); m_transGroupNode.Nodes[index].Tag = m_transactionGroup; m_transGroupNode.Expand(); m_transGroupNode = newTransGroupNode; m_transGroupNode.Expand(); UpdateTreeNode(m_transGroupNode, type); } m_transNode = null; m_transaction = null; } else if (type == OperationType.RollbackTransactionGroup) { UpdateTreeNode(m_transGroupNode, type); if (m_transGroupNode.Parent.Equals(m_rootNode)) { m_rootNode.Expand(); m_transactionGroup = null; m_transGroupNode = null; } else { m_transGroupNode = m_transGroupNode.Parent; m_transGroupNode.Expand(); m_transactionGroup = m_transGroupNode.Tag as TransactionGroup; } m_transNode = null; m_transaction = null; } else if (type == OperationType.CommitTransactionGroup) { UpdateTreeNode(m_transGroupNode, type); if (m_transGroupNode.Parent.Equals(m_rootNode)) { m_rootNode.Expand(); m_transactionGroup = null; m_transGroupNode = null; } else { m_transGroupNode.Expand(); m_transGroupNode = m_transGroupNode.Parent; m_transactionGroup = m_transGroupNode.Tag as TransactionGroup; } m_transNode = null; m_transaction = null; } else if (type == OperationType.StartTransaction) { m_transNode = new TreeNode(m_transaction.GetName()); m_transNode.ForeColor = m_startedColor; TreeNode node = m_transGroupNode == null ? m_rootNode : m_transGroupNode; node.Nodes.Add(m_transNode); node.Expand(); UpdateTreeNode(m_transNode, type); } else if (type == OperationType.CommitTransaction) { UpdateTreeNode(m_transNode, type); TreeNode node = m_transGroupNode == null ? m_rootNode : m_transGroupNode; node.Expand(); m_transNode = null; } else if (type == OperationType.RollbackTransaction) { UpdateTreeNode(m_transNode, type); TreeNode node = m_transGroupNode == null ? m_rootNode : m_transGroupNode; node.Expand(); m_transNode = null; } else { string childNodeText = null; if (String.IsNullOrEmpty(info)) { childNodeText = "Operation"; } else { childNodeText = info; } TreeNode childNode = new TreeNode(childNodeText); if (type == OperationType.ObjectDeletion) childNode.ForeColor = m_deletedColor; else childNode.ForeColor = m_normalColor; m_transNode.Nodes.Add(childNode); m_transNode.Expand(); } } /// /// Update the color of a tree node /// /// indicate child nodes of which will be updated /// operation type private void UpdateTreeNode(TreeNode parentNode, OperationType type) { switch (type) { case OperationType.StartTransaction: case OperationType.StartTransactionGroup: UpdateTreeNode(parentNode, m_startedColor); break; case OperationType.RollbackTransaction: case OperationType.RollbackTransactionGroup: UpdateTreeNode(parentNode, m_rolledbackColor); break; case OperationType.CommitTransaction: case OperationType.CommitTransactionGroup: UpdateTreeNode(parentNode, m_committedColor); break; default: UpdateTreeNode(parentNode, m_normalColor); break; } } /// /// Update the color of tree node including all its sub-nodes /// /// the node of which color will be updated /// Color to change private void UpdateTreeNode(TreeNode parentNode, SystemColor color) { parentNode.ForeColor = color; foreach (TreeNode childNode in parentNode.Nodes) { if (childNode.ForeColor == m_rolledbackColor) { continue; } UpdateTreeNode(childNode, color); } } /// /// Updates the model and refreshes the active view /// /// Whether to also perform auto-join or not private void updateModel(bool autoJoin) { // in order to be able to see changes to the model before // the current transaction is committed, we have to regenerate // the model manually. m_document.Regenerate(); // auto-joining is optional, but may be necessary to see connection details. if (autoJoin) { m_document.AutoJoinElements(); } // to see the changes immediately, we need to refresh the view m_commandData.Application.ActiveUIDocument.RefreshActiveView(); } } }