Simon Del Faveo of RevitTV Limited asked an interesting question which I decided to implement a sample application for, since it may be of general interest and even general use. It demonstrates a specific use of the new events provided by the Revit 2010 API.
Question: We are trying to save loaded family files after some parameters have been added. When using the Save or Close functions, Revit always prompts you with the confirm message box asking 'Do you want to save changes to filename.rfa?' and offering Yes, No, and Cancel options. We are processing a whole series of RFA files, so we don't want that message box appearing hundreds of times. Is there any way via the API to prevent the message box from appearing?
Answer: You can request notification on the display of certain dialogue boxes by registering to the Application.DialogBoxShowing event. Its event handler takes two arguments, the sender and a DialogBoxShowingEventArgs instance. The sender argument is actually the Revit application object. The event args parameter provides a HelpId property to enable you to identify the dialogue, and an OverrideResult method to define how the dialogue should be handled.
Note that not all dialogues in Revit raise a DialogBoxShowing event. Only dialogs derived from a specific internal class do so, unless a specific derived class overrides that default behaviour. I do not have a list of exceptions, but the at least the file open dialogs are derived from a different class, so these events are not raised for the SaveAs dialog. Luckily, this does not affect your case.
The event argument DialogBoxShowingEventArgs class is actually a parent class for more specialised subclasses MessageBoxShowingEventArgs and TaskDialogShowingEventArgs. The actual instance class depends on the dialogue being handled.
The confirm message box is a task dialogue, so the latter subclass is used and the help id is set to -1.
The task dialogue event args provides additional properties to access the dialogue id and message being displayed. For the confirm message box, these are TaskDialog_Save_File and something similar to 'Do you want to save changes to filename.rvt?'
Therefore, we can install an external application which registers for this event, keeps its eyes open for this task dialogue being displayed, and then uses the OverrideResult method to answer Yes to the confirmation message.
The OverrideResult method takes an integer argument to specify the dialogue result, and is described a follows in the API documentation:
The range of valid result values depends on the type of dialog as follows:
- DialogBox: Any non-zero value will cause a dialog to be dismissed.
- MessageBox: Standard Message Box IDs, such as IDOK and IDCANCEL, are accepted. For all possible IDs, please refer to the Windows API documentation. Naturally, the ID used must be relevant to the buttons in a message box.
- TaskDialog: Either standard Message Box IDs or Revit Custom IDs are accepted depending on the buttons used in a dialog. Standard buttons, such as OK and Cancel, have standard IDs as described in Windows API documentation. Buttons with custom text have custom IDs with incremental values starting at 1001 for the left-most or top-most button in a task dialog.
The options displayed by the confirm message are Yes, No and Cancel, which correspond to standard Windows API message box ids. These are accessible through the DialogResult enumeration in the System.Windows.Forms namespace.
To explore and test this issue, I implemented an external application named AutoConfirmSave for you. It implements the IExternalApplication interface as follows, registering an event handler named a_DialogBoxShowing to react to Revit dialogues popping up:
public IExternalApplication.Result OnShutdown( ControlledApplication a ) { return IExternalApplication.Result.Succeeded; } public IExternalApplication.Result OnStartup( ControlledApplication a ) { a.DialogBoxShowing += new EventHandler<DialogBoxShowingEventArgs>( a_DialogBoxShowing ); return IExternalApplication.Result.Succeeded; }
Here is the implementation of the event handler:
void a_DialogBoxShowing( object sender, DialogBoxShowingEventArgs e ) { TaskDialogShowingEventArgs e2 = e as TaskDialogShowingEventArgs; string s = string.Empty; if( null != e2 ) { s = string.Format( ", dialog id {0}, message '{1}'", e2.DialogId, e2.Message ); bool isConfirm = e2.DialogId.Equals( "TaskDialog_Save_File" ); if ( isConfirm ) { e2.OverrideResult( (int) WinForms.DialogResult.Yes ); s += ", auto-confirmed."; } } Debug.Print( "DialogBoxShowing: help id {0}, cancellable {1}{2}", e.HelpId, e.Cancellable ? "Yes" : "No", s ); }
This method checks whether the dialogue being displayed is a task dialogue by attempting to cast the event arguments to TaskDialogShowingEventArgs. If this succeeds, it is a task dialogue and we can use the event argument DialogId to check whether it is the confirm message. If so, we specify DialogResult.Yes as the result of the dialogue, which causes it not to be displayed to the user at all. Finally, we print out some of the event argument properties, regardless of which dialogue it was.
Opening and closing a project named wall_footing.rvt on my system displays the following message in the Visual Studio debug output window and automatically confirms the save, so the dialogue box is not displayed to the user:
DialogBoxShowing: help id -1, cancellable No, dialog id TaskDialog_Save_File, message 'Do you want to save changes to wall_footing.rvt?', auto-confirmed.
So all you need to do to achieve your task is to install the AutoConfirmSave external application alongside any existing applications, and the question whether to save your project will always be automatically answered with Yes. You obviously need to be careful to remove the external application from Revit.ini again once you are finished processing your files, or you will never again see the message box asking you to confirm changes.
Here is the complete AutoConfirmSave Visual Studio solution implementing this external application.
Jeremy,
Another great post. Do you think I could implement something similar for the duplicate instances notification? I would like to automatically remove the second instance, which requires you to click on a + to expand the list of duplicate instances and then check the one you want to delete and then click on delete. I am going to look into this too but wondered if you had ran across anything like this.
Joel
Posted by: Joel | June 12, 2009 at 12:02
Dear Joel,
I'm very glad you like it, so do it. One of my favourites posts lately :-)
I expect you can use it for the situation you describe as well. I developed the code above for exploration purposes while searching for the solution, so if you just remove the lines dealing with the isConfirm variable, or set it to a constant false value, the whole thing is reduced to a dialogue event logging system.
You could probably also use the Revit SDK EventsMonitor sample to monitor the dialogue events. Oh no, I just checked, and it does not handle the DialogBoxShowing event. Well, then you are stuck with my contribution above.
If you like, please keep your solution minimal and completely generic when you implement it and send it to me afterwards, and I'll post it as well :-)
Cheers, Jeremy.
Posted by: Jeremy Tammik | June 12, 2009 at 14:10
Hi Jeremy, great article! Have you ever attempted to hook onto when the 'Save As' dialog opens?
Thanks,
Dan
Posted by: Dan Tartaglia | January 20, 2010 at 21:11
Dear Dan,
Thank you for your appreciation, I am very glad that you like it!
Not personally. The topic of raising an event on Save As is mentioned briefly in
http://thebuildingcoder.typepad.com/blog/2009/03/more-questions.html
I also remember a developer support case concerning this issue. Looking a bit closer, I discover that the case was raised by Mr. Dan Tartaglia :-)
So you know all about it. I see there that we found no support for your needs in the Revit API, and I don't know how to achieve it using the Windows API, though I do believe it is possible, and probably not very hard if you just know the optimal approach. Sorry.
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 21, 2010 at 01:28
I didn't know if you had any updated info on the issue. I'm sure this is something a number of users would love to do.
Cheers,
Dan
Posted by: Dan Tartaglia | January 21, 2010 at 17:31
Dear Dan,
Well, as said, I do believe it is possible to hook into the Save As dialogue using the Windows API.
What exactly was it that you wanted again?
By the way, have you had a close look at
http://thebuildingcoder.typepad.com/blog/2009/10/dismiss-dialogue-using-windows-api.html
That may already provide all you need to solve this.
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 21, 2010 at 18:08
Hi Jeremy,
I want to hook onto the Save As dialog, cancel it, get the revit Document object then open my own dialog. I'll see if I can do the Windows part with the info in the above link.
Posted by: Dan Tartaglia | January 22, 2010 at 13:21
Hi Jeremy, I'm not sure how to capture the child dialog of Revit with the JtClicker utility.
Posted by: Dan Tartaglia | January 22, 2010 at 14:28
Dear Dan,
Well, capturing the dialogue should not be a big issue, it is simply identified by its title or whatever widgets it contains. You are pretty free to identify it in any way that you see fit.
I still don't know whether that will be much use to you, though, you want to do more than just click a button.
I suggest you ask a Windows API expert. This has nothing to do with the Revit API as such.
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 25, 2010 at 10:46
Gee thanks.
Posted by: Dan Tartaglia | January 25, 2010 at 13:04
I apoligize for my sarcastic response. It's just very frustrating trying to accomplish this task using the API. I appreciate all your help.
Thanks,
Dan
Posted by: Dan Tartaglia | January 25, 2010 at 14:11
Dear Dan,
:-) I love a bit of sarcasm now and then.
Sorry that I can't think of anything more helpful.
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 25, 2010 at 15:02