This is a bit of a niche question, as generally it would be preferable to protect the sheet, but I would argue in some cases you simply need to protect the contents of the cell to avoid accidental edits and deletions without limiting editability of the sheet as a whole through Protect Sheet, such as creating new filters.
I have a need to export Revit schedules through a script to an excel spreadsheet for editing, then to import back into Revit. This is because editing elements, especially rooms, can be taxing with the size of our projects and lack of intuitive and quick ways of sorting and replacing contents within Revit schedules. I created a macro that will save the Excel file's fullpath (UNC path for shared network drives), among other things, to a config file to essentially "link" the schedule to that Excel file. If an excel file is not found in the config file, then I create one with through OpenFileDialog()
with CheckFileExists = false
, and so arises the issue.
If I create a new Excel file and use the Microsoft.Office.Interop.Excel method sheet.Protect("password") when saving the file, the user who export to the newly created file needs to know where to look through the macro in order to unlock the sheet to do some setup that a protected sheet disallows. This would also allow them to alter any protected data, purposefully or otherwise, and compromise the validity of the data. What's worse, if/when I have time to make this into a more developed addin to Revit rather than a macro, the used password would be hidden, disallowing even more trustworthy, advanced users from setting up the sheet. If I allow the user to set the password on creation of the new Excel file, then they can just unlock it whenever they want.
The data exported with this script contains an ElementId for the Revit element in addition to what is contained within the Revit schedule, which is needed to tie the Excel data back to a Revit element with 100% accuracy (this is the only unique property of an element that does not change while the element exists). If this is altered in a way that creates a duplicate value in the Excel file, the validity of the import is ruined, and a rollback necessitated (which increases in severity the longer this error goes unnoticed).
Simply hiding the ElmentId column does nothing to prevent user nosiness from unhiding it, and Protect Sheet disallows a small set of needed alterations to the sheet when a new Excel file is created.
So, I need to "protect" against accidental alterations the ElementId column, but still allow for the alteration of that small set of features disallowed by Protect Sheet.
Again, I realize this is pretty niche need and not generally a desired limitation for protecting data. I also realize this does nothing to protect against deletion of the row as with Protect Sheet. That said, my goal is to prevent the alteration of an ElementId value in Excel from being edited into another valid ElementId value, nothing more. As such, deletion of a row would simply mean that the Revit element associated with that specific ElementId would not receive any form of update upon importing the data, as the cell is either null which is skipped or contains an invalid ElementId which can't be associated to an existing element, thus protecting the validity of said element data.
There are two solutions to this. The first was through my own experimentation. The second is a line of thought spawned thanks Solar Mike reminding me of an all too useful feature that I forgot about due to lack of use (generally do not have a need for it): Very Hidden Sheets. The avenue he seemed to be suggesting may not work exactly as he intended as, ideally, this Revit macro and Excel file combo should be usable by anyone in my company, but there is a method to make it work for anyone. Below are the two methods to resolve my issue, and hopefully anyone else in a similar bind that is, for some reason, unable to use Protect Sheet to do this job.
By setting Allow: Custom
and Formula: ""
, I can trigger the Error Alert feature with Style set to "Stop" that will disallow any alterations.
Even better, if I want to disallow the deletion of cell data, unchecking the "Ignore blank" box causes an Error Alert upon hitting the Delete key on the cell as well.
Using this in conjunction with hiding the column obfuscates the process for editing these cells through sheer volume of steps to enable editing, discouraging users from bothering to mess with cell contents.
Deletion of row:
This does not matter as much for me with this solution, as I am reading data from excel row by row, using the ElementId cell to find the Revit element. If an Excel row was deleted, the associated Revit element simply misses get data with that import, and is then re-added into the Excel file from the schdule upon the next export run, as long as the element still exists in Revit. But if row data needs to be maintained to avoid data corruption in your situation, you can use the linked macro to disable it.
Alteration by code:
At least external code; I have not tested this against a VBA macro from within Excel, but I assume the same to be true. I reason this is due to the fact that Data Validation is activated by a user interacting with the cell on an interface level, but when writing to the cell using the Microsoft.Office.Interop.Excel library in C# you are bypassing the interface and interacting directly with the cell data, thus not triggering Data Validation. I am not concerned about security or validation issues arising from this as few in our AEC firm know how to code, and the ones that do have no reason to interact with these files and/or lack the permission to access the folder (seperation of disciplines). While imperfect for most cases, this is the best solution I can come up with for our current needs.
This method utilizes two features of Excel:
Specifically, the macro I am referring to is the link from Solution 1's Con section, Prevent Row/Column Removal.
The solution is to have your script write to two different sheets, one for your overall data, and the second for your unique identifiers. The writing of these should be coded in such a way that you would write to a line in each sheet before moving to the next one to ensure the identifier and data are placed on the same row and to reduce errors when editing the code in the future.
When saving the newly generated and format- and filter-less Excel file, set the identifier sheet's visibility to xlSheetVeryHidden, or 2. This setting should be exposed through the Interop.Excel interface as a worksheet property. This way, only advanced users who can use macros can unhide the sheet, and that's if they even know to look for it.
While I prefer Solution 2 and the usability it would provide, I can't in good conscience use it. Our firm's OOTB Excel install disables macros by default, and several people consider them non-essential to their tasks in the file and just ignore the warning at the top of the file all together. The risk of data becoming compromised purely because someone doesn't want to enable macros is too great.
I'd also like to add: If you are not in exactly the same situation as me, I do NOT readily recommend either solution. Protect Sheet is the strongest way to protect cell data, and does not disallow the use of existing Sort/Filter features. I am only using these as my go-to solutions due to the experience and/or knowledge level of those in the office who could potentially use my macro (and hopefully future addin), and recognize that neither route is a perfect defense.
If anyone has any other suggestions, I'd appreciate hearing them and I'll definitely update this answer if/when I have a better working version of either solution going.