// // (C) Copyright 2003-2008 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 notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice 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.Data; using System.Windows.Forms; using System.IO; using System.Reflection; using System.Data.OleDb; using Autodesk.Revit; using Autodesk.Revit.Elements; using Autodesk.Revit.Parameters; using Autodesk.Revit.Events; using Autodesk.Revit.Collections; namespace Revit.SDK.Samples.RoomSchedule { /// /// Room Schedule form, used to retrieve data from .xls data source and create new rooms. /// public partial class RoomScheduleForm : Form { #region Class Memeber Variables // Reserve name of data source private String m_dataBaseName; // Revit external command data private ExternalCommandData m_commandData; // Room data information private RoomsData m_roomData; // All levels in Revit document. private List m_allLevels = new List(); // All available phases in Revit document. private List m_allPhases = new List(); // Room work sheet name private String m_roomTableName; // All rooms data from spread sheet private DataTable m_spreadRoomsTable; #endregion #region Class Global Static Variables /// /// Array of documents and mapped Excel file and opened table. /// The Document is document whose OnSave and OnSaveAs have been subscribed. /// private static Dictionary> s_EventSubDocs = new Dictionary>(); /// /// The String array in s_EventSubDocs reserves the Excel file and table sample used, the list should only contain TWO String values; /// The 1st is Excel file path, the 2nd is table name of this Excel file, they are used by sample currently. /// We should reserve and update this String array when user select new Excel or new work sheet (table). /// Use them to update Excel work sheet when Save command is executed. /// private const int fixedArrayNum = 2; #endregion #region Class Constructor Method /// /// Class constructor /// /// Revit external command data public RoomScheduleForm(ExternalCommandData commandData) { // UI initialization InitializeComponent(); // reserve Revit command data and get rooms information, // and then display rooms information in DataGrideView m_commandData = commandData; m_roomData = new RoomsData(commandData); // bind levels and phases data to level and phase ComboBox controls GetAllLevelsAndPhases(); // list all levels and phases this.levelComboBox.DisplayMember = "Name"; this.levelComboBox.DataSource = m_allLevels; this.levelComboBox.SelectedIndex = 0; this.phaseComboBox.DisplayMember = "Name"; this.phaseComboBox.DataSource = m_allPhases; this.phaseComboBox.SelectedIndex = 0; // if there is no phase, newRoomButton will be disabled. if (m_allPhases.Count == 0) { newRoomButton.Enabled = false; } // check to see whether current Revit document was mapped to spreadsheet. UpdateRoomSheetInfo(); } #endregion #region Class Implementatation /// /// Get all available levels and phases from current document /// private void GetAllLevelsAndPhases() { // get all levels Autodesk.Revit.Document document = m_commandData.Application.ActiveDocument; foreach (PlanTopology planTopology in document.PlanTopologies) { m_allLevels.Add(planTopology.Level); } // get all phases ElementIterator iter = m_commandData.Application.ActiveDocument.get_Elements(typeof(Phase)); iter.Reset(); while (iter.MoveNext()) { Phase phase = iter.Current as Phase; if (phase != null) { m_allPhases.Add(phase); } } } /// /// Create shared parameter for Rooms category /// /// True, shared parameter exists; false, doesn't exist private bool CreateMyRoomSharedParameter() { // Create Room Shared Parameter Routine: --> // 1: Check whether the Room shared parameter("External Room ID") has been defined. // 2: Share parameter file locates under sample directory of this .dll module. // 3: Add a group named "SDKSampleRoomScheduleGroup". // 4: Add a shared parameter named "External Room ID" to "Rooms" category, which is visible. // The "External Room ID" parameter will be used to map to spreadsheet based room ID(which is unique) try { // check whether shared parameter exists if (ShareParameterExists(RoomsData.SharedParam)) { return true; } // create shared parameter file String modulePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); String paramFile = modulePath + "\\RoomScheduleSharedParameters.txt"; if (File.Exists(paramFile)) { File.Delete(paramFile); } FileStream fs = File.Create(paramFile); fs.Close(); // cache application handle Autodesk.Revit.Application revitApp = m_commandData.Application; // prepare shared parameter file m_commandData.Application.Options.SharedParametersFilename = paramFile; // open shared parameter file DefinitionFile parafile = revitApp.OpenSharedParameterFile(); // create a group DefinitionGroup apiGroup = parafile.Groups.Create("SDKSampleRoomScheduleGroup"); // create a visible "External Room ID" of text type. Definition roomSharedParamDef = apiGroup.Definitions.Create(RoomsData.SharedParam, ParameterType.Text, true); // get Rooms category Category roomCat = revitApp.ActiveDocument.Settings.Categories.get_Item("Rooms"); CategorySet categories = revitApp.Create.NewCategorySet(); categories.Insert(roomCat); // insert the new parameter InstanceBinding binding = revitApp.Create.NewInstanceBinding(categories); revitApp.ActiveDocument.ParameterBindings.Insert(roomSharedParamDef, binding); return false; } catch (Exception ex) { throw new Exception("Failed to create shared parameter: " + ex.Message); } } /// /// Test if the Room binds a specified shared parameter /// /// Parameter name to be checked /// true, the definition exists, false, doesn't exist. private bool ShareParameterExists(String paramName) { Autodesk.Revit.Document doc = m_commandData.Application.ActiveDocument; BindingMap bindingMap = doc.ParameterBindings; DefinitionBindingMapIterator iter = bindingMap.ForwardIterator(); iter.Reset(); while (iter.MoveNext()) { Definition tempDefinition = iter.Key; // find the definition of which the name is the appointed one if (String.Compare(tempDefinition.Name, paramName) != 0) { continue; } // get the category which is bound ElementBinding binding = bindingMap.get_Item(tempDefinition) as ElementBinding; CategorySet bindCategories = binding.Categories; foreach (Category category in bindCategories) { if (category.Name == "Rooms") { // the definition with appointed name was bound to Rooms, return true return true; } } } // // return false if shared parameter doesn't exist return false; } /// /// My custom message box /// /// message to be popped up /// icon to be displayed private static void MyMessageBox(String strMsg, MessageBoxIcon icon) { MessageBox.Show(strMsg, "Room Schedule", MessageBoxButtons.OK, icon); } /// /// Update control display of form /// call this method when create new rooms or switch the room show(show all or show by level) /// /// whether retrieve all rooms from Revit project once more private void UpdateFormDisplay(bool bUpdateAllRooms) { // update Revit Rooms data when there is room creation if (bUpdateAllRooms) { m_roomData.UpdateRoomsData(); } revitRoomDataGridView.DataSource = null; if (showAllRoomsCheckBox.Checked) { // show all rooms in Revit project revitRoomDataGridView.DataSource = new DataView(m_roomData.GenRoomsDataTable(null)); } else { // show all rooms in specified level levelComboBox_SelectedIndexChanged(null, null); } // update this DataGridView revitRoomDataGridView.Update(); } /// /// Display current Room sheet information: Excel path /// private void UpdateRoomSheetInfo() { if (s_EventSubDocs.ContainsKey(m_commandData.Application.ActiveDocument)) { List xlsAndTable; s_EventSubDocs.TryGetValue(m_commandData.Application.ActiveDocument, out xlsAndTable); if (fixedArrayNum == xlsAndTable.Count) { roomExcelTextBox.Text = "Mapped Sheet: " + xlsAndTable[0] + ": " + xlsAndTable[1]; } } } /// /// Set shared parameter (whose name is "External Room ID") value to Room.Id.Value /// /// The room used to get the room which to be updated private void SetExternalRoomIdToRoomId(Room room) { ElementId elemId = room.Id; Room updateRoom = m_commandData.Application.ActiveDocument.get_Element(ref elemId) as Room; foreach (Parameter sharedParam in room.Parameters) { if (String.Compare(sharedParam.Definition.Name, RoomsData.SharedParam) == 0) { // It's safer to encapsulate shared parameter update in transaction. m_commandData.Application.ActiveDocument.BeginTransaction(); sharedParam.Set(room.Id.Value.ToString()); m_commandData.Application.ActiveDocument.EndTransaction(); return; } } } #endregion #region Revit DocuemntEvents Methods Implementation /// /// Revit document Save and SaveAs event method. It will be raised as soon as Save / SaveAs is called /// This method will update spread sheet room data([Area] column) with actual area value of mapped Revit Room. /// or add Revit room to spreadsheet if it is not mapped to room of spreadsheet. /// private void OnDocumentSave() { // OnDocumentSave Programming Routine: // // 1: Update spreadsheet when: // a: there is room work sheet table; // b: there is rooms data; // c: shared parameter exists; // d: user does want to update the spreadsheet Revit rooms map. // 2: Skip update and insert operations for below rooms: // a: the rooms are not placed or located; // b: the rooms whose shared parameter(defined by sample) are not retrieved, // some rooms maybe don't have shared parameter at all, despite user create for Rooms category. // 3: Update spreadsheet rooms values by Revit room actual values. // a: if shared parameter exists(is not null), update row by using this parameter's value; // b: if shared parameter doesn't exist (is null), update row by Id value of room, which will avoid the duplicate // ID columns occur in spreadsheet. // 4: Insert Revit rooms data to spreadsheet if: // a: failed to update values of rooms (maybe there no matched ID value in spread sheet rows). // #region Check Whether Update Spreadsheet Data // // check which table to be updated. List mappedXlsAndTable; s_EventSubDocs.TryGetValue(m_commandData.Application.ActiveDocument, out mappedXlsAndTable); if (fixedArrayNum != mappedXlsAndTable.Count || String.IsNullOrEmpty(mappedXlsAndTable[0]) || String.IsNullOrEmpty(mappedXlsAndTable[1])) { return; } // retrieve all rooms in project(maybe there are new rooms created manually by user) m_roomData.UpdateRoomsData(); if (m_roomData.Rooms.Count <= 0) { return; } // find the shared parameter definition Parameter externalIdSharedParam = null; String externalId = String.Empty; bool bExist = RoomsData.ShareParameterExists(m_roomData.Rooms[0], RoomsData.SharedParam, ref externalIdSharedParam); if (false == bExist) { return; } else { externalId = externalIdSharedParam.AsString(); } // ask whether update the spread sheet data mapped to Revit rooms. if (MessageBox.Show("Revit rooms are mapped to spread sheet, do you want to update this sheet?", "Room Schedule", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) { return; } #endregion // create a connection and update values of spread sheet int updatedRows = 0; // number of rows which were updated int newRows = 0; // number of rows which were added into spread sheet XlsDBConnector dbConnectoor = new XlsDBConnector(mappedXlsAndTable[0]); // check whether there is room table. // get all available rooms in current document once more foreach (Room room in m_roomData.Rooms) { #region Skip Operation For Some Rooms // // skip directly if the room is not placed yet if (null == room.Location || null == room.Level) { continue; } // get Area of room, if converting to double value fails, skip this. // if the area is zero to less than zero, skip the update too double roomArea; try { // get area without unit, then converting it to double will be ok. String areaStr = m_roomData.GetProperty(room, BuiltInParameter.ROOM_AREA, false); roomArea = Double.Parse(areaStr); if (roomArea <= double.Epsilon) { continue; } } catch { // parse double value failed, continue the loop continue; } // get the shared parameter value of room bExist = RoomsData.ShareParameterExists(room, RoomsData.SharedParam, ref externalIdSharedParam); if (false == bExist) { return; } else { externalId = externalIdSharedParam.AsString(); } #endregion // try to update try { #region Update Spreadsheet Room // flag used to indicate whether update is successful bool bUpdateFailed = false; // reserve whether this room updated successfully. // if room comment is empty, use for mapped room, use for not mapped room in spread sheet. bool bCommnetIsNull = false; // get comments of room String comments; Parameter param = room.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS); comments = (null != param) ? (param.AsString()) : (""); if (String.IsNullOrEmpty(comments)) { // this room doesn't have comment value bCommnetIsNull = true; // use for room with empty comment by default when updating spread sheet comments = ""; } // create update SQL clause, // when filtering row to be updated, use Room.Id.Value if "External Room ID" is null. String updateStr = String.Format( "Update [{0}$] SET [{1}] = '{2}', [{3}] = '{4}', [{5}] = '{6}', [{7}] = {8} Where {9} = {10}", mappedXlsAndTable[1], // mapped table name RoomsData.RoomName, room.Name, RoomsData.RoomNumber, room.Number, RoomsData.RoomComments, comments, RoomsData.RoomArea, roomArea, RoomsData.RoomID, String.IsNullOrEmpty(externalId) ? room.Id.Value.ToString() : externalId); // execute the command and check the size of updated rows int afftectedRows = dbConnectoor.ExecuteCommnand(updateStr); if (afftectedRows == 0) { bUpdateFailed = true; } else { // count how many rows were updated updatedRows += afftectedRows; // if "External Room ID" is null but update successfully, which means: // in spreadsheet there is existing row whose "ID" value equals to room.Id.Value, so we should // set Revit room's "External Room ID" value to Room.Id.Value for consistence after update . if(String.IsNullOrEmpty(externalId)) { SetExternalRoomIdToRoomId(room); } } #endregion #region Insert Revit Room // Add this new room to spread sheet if fail to update spreadsheet if (bUpdateFailed) { // try to insert this new room to spread sheet, some rules: // a: if the "External Room ID" exists, set ID column to this external id value, // if the "External Room ID" doesn't exist, use the actual Revit room id as the ID column value. // b: use comments in room if room's description exists, // else, use constant string: "" for Comments column in spreadsheet. String insertStr = String.Format("Insert Into [{0}$] ([{1}], [{2}], [{3}], [{4}], [{5}]) Values({6}, '{7}', '{8}', '{9}', {10})", mappedXlsAndTable[1], // mapped table name RoomsData.RoomID, RoomsData.RoomComments, RoomsData.RoomName, RoomsData.RoomNumber, RoomsData.RoomArea, (String.IsNullOrEmpty(externalId)) ? (room.Id.Value.ToString()) : (externalId), // Room id (bCommnetIsNull || String.IsNullOrEmpty(comments)) ? ("") : (comments), room.Name, room.Number, roomArea); // try to insert it afftectedRows = dbConnectoor.ExecuteCommnand(insertStr); if (afftectedRows != 0) { // remember the number of new rows newRows += afftectedRows; // if the Revit room doesn't have external id value(may be a room created manually) // set its "External Room ID" value to Room.Id.Value, because the room was added/mapped to spreadsheet, // and the value of ID column in sheet is just the Room.Id.Value, we should keep this consistence. if (String.IsNullOrEmpty(externalId) && null != externalIdSharedParam) { SetExternalRoomIdToRoomId(room); } } } #endregion } catch (Exception ex) { // close the connection dbConnectoor.CloseConnector(); MyMessageBox(ex.Message, MessageBoxIcon.Warning); return; } } // close the connection dbConnectoor.CloseConnector(); // output the affected result message String message = String.Format("{0}: \r\n{1} rows were updated and {2} rows were added into successfully.", mappedXlsAndTable[0] + ": " + mappedXlsAndTable[1], updatedRows, newRows); MyMessageBox(message, MessageBoxIcon.Information); } /// /// Document close event handler /// Remove the document object which is to be closed /// void OnDocumentClose() { // remove current document from array of documents which have some events being subscribed s_EventSubDocs.Remove(m_commandData.Application.ActiveDocument); } #endregion #region Class Events Implmentation /// /// Import room spread sheet and display them in form /// /// /// private void importRoomButton_Click(object sender, EventArgs e) { using (OpenFileDialog sfdlg = new OpenFileDialog()) { // file dialog initialization sfdlg.Title = "Import Excel File"; sfdlg.Filter = "Excel File(*.xls)|*.xls"; sfdlg.RestoreDirectory = true; if(s_EventSubDocs.ContainsKey(m_commandData.Application.ActiveDocument)) { List xlsAndTable; s_EventSubDocs.TryGetValue(m_commandData.Application.ActiveDocument, out xlsAndTable); if (fixedArrayNum == xlsAndTable.Count) { sfdlg.FileName = xlsAndTable[0]; } } if (DialogResult.OK == sfdlg.ShowDialog()) { try { // create xls data source connector and retrieve data from it m_dataBaseName = sfdlg.FileName; XlsDBConnector xlsCon = new XlsDBConnector(m_dataBaseName); // bind table data to grid view and ComboBox control tablesComboBox.DataSource = xlsCon.RetrieveAllTables(); // close the connection xlsCon.CloseConnector(); } catch (Exception ex) { tablesComboBox.DataSource = null; MyMessageBox(ex.Message, MessageBoxIcon.Warning); } } } } /// /// Select one table(work sheet) and display its data to DataGridView control. /// after selection, generate data table from data source /// /// /// private void tablesComboBox_SelectedIndexChanged(object sender, EventArgs e) { // update spread sheet based rooms sheetDataGridView.DataSource = null; m_roomTableName = tablesComboBox.SelectedValue as String; XlsDBConnector xlsCon = null; try { if (null != m_spreadRoomsTable) { m_spreadRoomsTable.Clear(); } // get all rooms table then close this connection immediately xlsCon = new XlsDBConnector(m_dataBaseName); // generate room data table from room work sheet. m_spreadRoomsTable = xlsCon.GenDataTable(m_roomTableName); newRoomButton.Enabled = (0 == m_spreadRoomsTable.Rows.Count) ? false : true; // close connection xlsCon.CloseConnector(); // update data source of DataGridView sheetDataGridView.DataSource = new DataView(m_spreadRoomsTable); } catch (Exception ex) { // close connection and update data source xlsCon.CloseConnector(); sheetDataGridView.DataSource = null; MyMessageBox(ex.Message, MessageBoxIcon.Warning); return; } // update the static s_EventSubDocs variable when user changes the Excel and room table if (s_EventSubDocs.ContainsKey(m_commandData.Application.ActiveDocument)) { s_EventSubDocs.Remove(m_commandData.Application.ActiveDocument); List xlsAndTable = new List(); xlsAndTable.Add(m_dataBaseName); xlsAndTable.Add(m_roomTableName); s_EventSubDocs.Add(m_commandData.Application.ActiveDocument, xlsAndTable); // update current mapped room sheet information, only show this when Revit rooms were mapped to Excel sheet. UpdateRoomSheetInfo(); } } /// /// Filter rooms by specified level. /// /// /// private void levelComboBox_SelectedIndexChanged(object sender, EventArgs e) { // get the selected level, by comparing its name and ComboBox selected item's name Level selLevel = null; foreach(Level level in m_allLevels) { if(0 == String.Compare(level.Name, levelComboBox.Text)) { selLevel = level; break; } } if (selLevel == null) { MyMessageBox("There is no available level to get rooms.", MessageBoxIcon.Warning); return; } // update data source of DataGridView this.revitRoomDataGridView.DataSource = null; this.revitRoomDataGridView.DataSource = new DataView(m_roomData.GenRoomsDataTable(selLevel)); } /// /// Create new rooms according to spreadsheet based rooms data and specified phase. /// /// /// private void newRoomButton_Click(object sender, EventArgs e) { // Create room process: // 1: Create shared parameter for "Room" category elements if it doesn't exist. // 2: Create rooms by using spread sheet's data: // a: We should make sure that each of spreadsheet room is mapped by only one Revit room; // if not, many Revit rooms map to one spreadsheet room will confuse user; // b: Set Name, Number and comment values of new rooms by spreadsheet relative data. // 3: Subscribe document Save, SaveAs and Close event handlers. // 4: Update all rooms data and pop up message int nNewRoomsSize = 0; try { // check to see whether there is available spread sheet based rooms to create if (null == m_spreadRoomsTable || null == m_spreadRoomsTable.Rows || m_spreadRoomsTable.Rows.Count == 0) { MyMessageBox("There is no available spread sheet based room to create.", MessageBoxIcon.Warning); return; } // create shared parameter for "Room" category elements CreateMyRoomSharedParameter(); // create Revit rooms by using spread sheet based rooms // add "ID" data of spread sheet to Room element's share parameter: "External Room ID" DataColumn column = m_spreadRoomsTable.Columns[RoomsData.RoomID]; if (column == null) { MyMessageBox("Failed to get ID data of spread sheet rooms.", MessageBoxIcon.Warning); return; } // get phase used to create room Phase curPhase = null; foreach(Phase phase in m_allPhases) { if (String.Compare(phase.Name, phaseComboBox.Text) == 0) { curPhase = phase; break; } } if (null == curPhase) { MyMessageBox("No available phase used to create room.", MessageBoxIcon.Warning); return; } // get all existing rooms which have mapped to spreadsheet rooms. // we should skip the creation for those spreadsheet rooms which have been mapped by Revit rooms. Dictionary existingRooms = new Dictionary(); foreach (Room room in m_roomData.Rooms) { Parameter sharedParameter = room.get_Parameter(RoomsData.SharedParam); if (null != sharedParameter && false == String.IsNullOrEmpty(sharedParameter.AsString())) { existingRooms.Add(room.Id.Value, sharedParameter.AsString()); } } // transaction is used to cancel room creation when exception occurs m_commandData.Application.ActiveDocument.BeginTransaction(); // create rooms with spread sheet based rooms data for (int i = 0; i < m_spreadRoomsTable.Rows.Count; i++) { // get the ID column value and use it to check whether this spreadsheet room is mapped by Revit room. String externaId = m_spreadRoomsTable.Rows[i][RoomsData.RoomID].ToString(); if (existingRooms.ContainsValue(externaId)) { // skip the spreadsheet room creation if it's mapped by Revit room continue; } // create rooms in specified phase, but without placing them. Room newRoom = m_commandData.Application.ActiveDocument.Create.NewRoom(curPhase); if (null == newRoom) { // abort the room creation and pop up failure message m_commandData.Application.ActiveDocument.AbortTransaction(); MyMessageBox("Create room failed.", MessageBoxIcon.Warning); return; } // set the shared parameter's value of Revit room Parameter sharedParam = newRoom.get_Parameter(RoomsData.SharedParam); if (null == sharedParam) { MyMessageBox("Failed to get shared parameter, please try again.", MessageBoxIcon.Warning); return; } else { sharedParam.Set(externaId); } // check if "Name", "Number", "Comments" exist // if exist, set new rooms with values in spreadsheet // (currently, there are three columns need to be set) String[] constantColumns = { RoomsData.RoomName, RoomsData.RoomNumber, RoomsData.RoomComments }; for (int col = 0; col < constantColumns.Length; col++) { // check to see whether the column exists in table if (m_spreadRoomsTable.Columns.IndexOf(constantColumns[col]) != -1) { // if value is not null or empty, set new rooms related parameter. String colValue = m_spreadRoomsTable.Rows[i][constantColumns[col]].ToString(); if (String.IsNullOrEmpty(colValue)) { continue; } switch (constantColumns[col]) { case RoomsData.RoomName: newRoom.Name = colValue; break; case RoomsData.RoomNumber: newRoom.Number = colValue; break; case RoomsData.RoomComments: Parameter commentParam = newRoom.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS); if (null != commentParam) { commentParam.Set(colValue); } break; default: // no action for other parameter break; } } } // remember how many new rooms were created, based on spread sheet data nNewRoomsSize++; } // end this transaction if create all rooms successfully. m_commandData.Application.ActiveDocument.EndTransaction(); } catch (Exception ex) { // cancel this time transaction when exception occurs m_commandData.Application.ActiveDocument.AbortTransaction(); MyMessageBox(ex.Message, MessageBoxIcon.Warning); return; } // Subscribe document Save, SaveAs and Close event handlers // if the event handler of document was subscribed, it's not allowed to subscribe it again. if (RoomScheduleForm.s_EventSubDocs.ContainsKey(m_commandData.Application.ActiveDocument) == false) { // reserves this document and current .xls file and table. List xlsAndTable = new List(); xlsAndTable.Add(m_dataBaseName); xlsAndTable.Add(m_roomTableName); RoomScheduleForm.s_EventSubDocs.Add(m_commandData.Application.ActiveDocument, xlsAndTable); // show current Excel and sheet name sample is mapped to, only show them after unplaced rooms were created. UpdateRoomSheetInfo(); // subscribe Save, SaveAs and Close event handlers m_commandData.Application.ActiveDocument.OnSave += new DocumentSaveEventHandler(OnDocumentSave); m_commandData.Application.ActiveDocument.OnSaveAs += new DocumentSaveAsEventHandler(OnDocumentSave); m_commandData.Application.ActiveDocument.OnClose += new DocumentCloseEventHandler(OnDocumentClose); } // output unplaced rooms creation message String strMessage = string.Empty; int nSkippedRooms = m_spreadRoomsTable.Rows.Count - nNewRoomsSize; if (nSkippedRooms > 0) { strMessage = string.Format("{0} unplaced {1} created successfully.\r\n{2} skipped, {3}", nNewRoomsSize, (nNewRoomsSize > 1) ? ("rooms were") : ("room was"), nSkippedRooms.ToString() + ( (nSkippedRooms > 1) ? (" were") : (" was") ), (nSkippedRooms > 1) ? ("because they were already mapped by Revit rooms.") : ("because it was already mapped by Revit rooms.") ); } else { strMessage = string.Format("{0} unplaced {1} created successfully.", nNewRoomsSize, (nNewRoomsSize > 1) ? ("rooms were") : ("room was")); } // update Revit rooms data and display of controls. if (nNewRoomsSize > 0) { UpdateFormDisplay(true); } // output creation message MyMessageBox(strMessage, MessageBoxIcon.Information); } /// /// Show all rooms in current document /// /// /// private void showAllRoomsCheckBox_CheckedChanged(object sender, EventArgs e) { // disable and enable some controls levelComboBox.Enabled = !showAllRoomsCheckBox.Checked; // update room display, there is no new creation, so it's not necessary to retrieve all rooms UpdateFormDisplay(false); } /// /// Close the form. /// /// /// private void closeButton_Click(object sender, EventArgs e) { this.Close(); } /// /// Clear all values of shared parameters /// Allow user to create more unplaced rooms and update map relationships between Revit and spreadsheet rooms. /// /// /// private void clearIDButton_Click(object sender, EventArgs e) { int nCount = 0; foreach(Room room in m_roomData.Rooms) { Parameter param = null; bool bExist = RoomsData.ShareParameterExists(room, RoomsData.SharedParam, ref param); if(bExist && null != param && false == String.IsNullOrEmpty(param.AsString())) { param.Set(String.Empty); nCount++; } } // update Revit rooms display if(nCount > 0) { UpdateFormDisplay(false); } } #endregion } }