Dismiss Dialogue using Windows API

I am often confronted with unwanted dialogues popping up when I am trying to drive some application programmatically.
In Revit, many dialogues raise an event when they are displayed and allow us to implement an add-in application to handle them automatically.
Unfortunately, as noted in the discussion on

using the DialogBoxShowing event
,
some of the Revit dialogues do not raise such an event when displayed, so we cannot use the standard Revit API to handle or dismiss them.

Also, there are many other applications besides Revit that I am trying to drive programmatically, and they often do not provide any API at all, let alone a possibility to disable or automatically handle any of their user interface message boxes.

Furthermore, there may be situations within Revit where the DialogBoxShowing event is raised and the dialogue can be handled, but this causes problems within Revit.
For instance, one developer reports that the DialogBoxShowing event handler crashes when trying to hide the dialog using an override result after the dialogue has been triggered due to closing a transaction using EndTransaction.
In that case, Revit says that the document was modified outside the transaction and generates an exception.

Question: What can I do to automatically handle or dismiss dialogues in these situations?

Answer: As said, most Revit messages can be caught and responded to programmatically by a plug-in application by handling the Revit dialogue box DialogBoxShowing event, and
source code and a description for exploring this kind of situation is provided in the

discussion
mentioned above.

The easiest way to find out whether you can use this event to handle your specific dialogue is to:

  • Implement and install the dialogue box handler.
  • Set a breakpoint in the event handler.
  • Reproduce the situation in which the dialogue is displayed by Revit.
  • Check whether the event was fired or not.

Inside the event handler, the debugger will show you what data is being passed in and thus how you can identify this dialogue in your code for automatic processing.

On the other hand, if the specific dialogue you are confronted with is not accessible in the Revit call-back, or you are dealing with some completely different application lacking the DialogBoxShowing event, I will explain below how you can use the
.NET framework in C# and
VB or the
native Windows API to handle it automatically instead.
In short:

  • In the .NET framework, you can use methods from the WinApi.User32 namespace like FindWindow, EnumWindows, and GetWindowText from inside a System.Timers.Timer instance to determine that a certain window is being displayed by the Windows OS and close it using WinApi.User32.SendMessage.
  • In the native Windows API, similar functionality can be achieved by creating a hook in the main Revit window with SetWindowsHookEx and sending a WM_CLOSE or some other message to a dialogue when it is displayed.

.NET Dialogue Clicker in C#

Here is the C# source code and entire Visual Studio solution for a little utility named
JtClicker that
I have used many times in the past and is very near and dear to me.
It was especially useful during my time in Autodesk Consulting, when we were always automating a large number of different applications which unfortunately were equipped with user interfaces as well as APIs.
The user interfaces would often insist on popping up various irritating dialogue boxes which could not be suppressed programmatically.

The solution that I developed for this is a Windows API based stand-alone command-line utility that simply sits and waits for a dialogue with certain characteristics to appear.
If and when such a dialogue is displayed, the utility immediately dismisses it in a certain predefined way.

This solution I implemented is thus a generic dialogue clicker in C# using the .NET framework to access the underlying Windows API.
In fact, it has nothing at all to do with the Revit API.

Since every dialogue is different, we need a lot of flexibility in the details of identifying and handling various specific dialogues.
The two main requirements for a high degree of flexibility are:

  • How to identify that the dialogue we are interested in has been displayed.
  • How the dialogue should be dismissed, once it has been identified.

Therefore, this utility will always require some tweaking before use and is only useful in source code format for that reason.
Check out the source code and the following discussion I had on that topic with my colleague Greg Wesner of Autodesk Consulting for details:

.NET Dialogue Clicker in VB

I was prompted to dive into this subject again recently by Greg Wesner of Autodesk Consulting.
Here is an example of my interaction with him when he made use of JtClicker, in which Greg also provides a VB version of the original C# implementation:

[Q] I heard you might have a sample of code for how to ‘spoof’ a click of the OK button in a windows dialog.
Specifically, I need to launch the options dialog in AutoCAD and then close it immediately with an OK click, i.e. I cannot cancel it.
Closing the dialog with OK results in AutoCAD refreshing all the paths.
Launching is obviously no problem but the OK click is tougher.
I’m working in VB.NET, so a .NET sample is really what I’m after.

[A] Yes, I am appending my little dialogue clicker to this mail.
There are comments inside form1 explaining its function and usage.
It searches for a dialogue using certain characteristics.
Once the dialogue appears, it sends windows messages to it in EnumChildProc.
You will need to tweak those messages to hit the button you need.

[Q] Thanks Jeremy. That was exactly what I needed. Hooked it all up and it is working great.

[A] Very glad to hear that it works for you!
Have you changed or improved anything in it?
If so, could you show me what you fixed or improved?

[Q] Well, I wish I could say that I improved on it, but you pretty much had all the parts I needed.
I was actually able to ‘dumb’ it down some because I could hard code the dialog name “Options” and the button “OK”.
I also tested that “Options” and “OK” would dismiss the Options dialog in AutoCAD as expected with the original project files you sent.

I was hoping that I wouldn’t need to use the timer and would be able to just make the call straight to EnumWindows.
But that didn’t work because the Options dialog doesn’t actually come up until my function returns.
So I had to use the timer as well to delay the call to EnumWindows.
I found dropping the interval to 300ms was better though, because you barely see the dialog come up.
I call Timer.Stop() as soon as I find the button I’m looking for, because I will only need it to happen once every time the dialog comes up.
I am thinking about adding some defensive code that counts how many times we call EnumWindows in the Timer, and if we go above a certain number of times (maybe 500), we stop trying.
That way if anything happens and the Options dialog never comes up, we don’t chew up CPU cycles forever.

One thing I did do was translate WinApi.vb and the EnumWindowsProc() and EnumChildProc() methods to VB.
So I could pass that on to you in case you get requests for this in VB.
First, here is the module WinApi.vb providing access to the Windows API calls:


Imports System
Imports System.Runtime.InteropServices
Imports System.Text
 
Module User32
  Delegate Function EnumWindowsProc(ByVal hWnd As Integer, ByVal lParam As Integer) As Boolean
  <DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
  Function FindWindow(ByVal className As String, ByVal windowName As String) As Integer
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
  Function EnumWindows(ByVal callbackFunc As EnumWindowsProc, ByVal lParam As Integer) As Integer
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
  Function EnumChildWindows(ByVal hwnd As Integer, ByVal callbackFunc As EnumWindowsProc, ByVal lParam As Integer) As Integer
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
  Function GetWindowText(ByVal hwnd As Integer, ByVal buff As StringBuilder, ByVal maxCount As Integer) As Integer
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
  Function GetLastActivePopup(ByVal hwnd As Integer) As Integer
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
  Function SendMessage(ByVal hwnd As Integer, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
  End Function
  Public BM_SETSTATE As Integer = 243
  Public WM_LBUTTONDOWN As Integer = 513
  Public WM_LBUTTONUP As Integer = 514
End Module

Here is the main application:


Private timer1 As Timer
Private timer_interval As Integer = 300  ' in milliseconds so 1000 = 1 second
Private timer_attempts As Integer
Public Function EnumWindowsProc(ByVal hwnd As Integer, ByVal lParam As Integer) As Boolean
  Dim sbTitle As New StringBuilder(256)
  Dim test As Integer = User32.GetWindowText(hwnd, sbTitle, sbTitle.Capacity)
  Dim title As String = sbTitle.ToString()
  If title.Length = 0 Then
    Return True
  End If
  If title = "Options" Then
  Else
    Return True
  End If
  User32.EnumChildWindows(hwnd, New User32.EnumWindowsProc(AddressOf EnumChildProc), 0)
  Return False
End Function
Public Function EnumChildProc(ByVal hwnd As Integer, ByVal lParam As Integer) As Boolean
  Dim sbTitle As New StringBuilder(256)
  User32.GetWindowText(hwnd, sbTitle, sbTitle.Capacity)
  Dim title As String = sbTitle.ToString()
  If title.Length = 0 Then
    Return True
  End If
  If title = "OK" Then
  Else
    Return True
  End If
  User32.SendMessage(hwnd, User32.BM_SETSTATE, 1, 0)
  User32.SendMessage(hwnd, User32.WM_LBUTTONDOWN, 0, 0)
  User32.SendMessage(hwnd, User32.WM_LBUTTONUP, 0, 0)
  User32.SendMessage(hwnd, User32.BM_SETSTATE, 1, 0)
  timer1.Stop()
  timer1 = Nothing
  Return False
End Function
Public Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs)
  'timer_attempts is a failsafe to make sure that if for some reason the Options dialog
  ' never comes up, we don't keep looking for it forever. We usually find the dialog on the
  ' first few tries, so if it doesn't come up in about the first minute, we give up
  If timer_attempts < 300 Then
    User32.EnumWindows(New User32.EnumWindowsProc(AddressOf EnumWindowsProc), 0)
  Else
    timer1.Stop()
  End If
  timer_attempts += 1
  Debug.Print(timer_attempts.ToString())
End Sub
' The options dialog will not come up until our function returns, so we setup a timer
' that will check every 300 milliseconds to see if it can find the dialog. Once it finds
' the dialog and clicks OK, it stops.
Public Sub closeOptionsDialog()
  timer_attempts = 0
  If timer1 Is Nothing Then
    timer1 = New Timer()
  End If
  timer1.Interval = timer_interval
  AddHandler timer1.Tick, New EventHandler(AddressOf timer1_Tick)
  timer1.Start()
End Sub

If the source code lines are truncated by your browser, you can copy and paste the text to a text editor.

Using a native Windows API Hook

The JtClicker dialogue clicker application described above is using the .NET framework to identify the dialogue and send messages to dismiss it.
Since the Win32 portion of the .NET framework is just a wrapper around native Windows API calls, the same functionality can also be implemented more directly using the native API.

Here is a report on using a Windows hook to dismiss a dialogue triggered by closing a Revit transaction.

I investigated using a hook, and this is the solution :-)
I created a hook in the main Revit window with SetWindowsHookEx and send messages to the dialogue when it is detected.
Initially I sent a WM_CLOSE message to close it, later I changed it to click the OK button instead.
This works really well, even if the dialog is shown when ending a transaction, in which case the DialogBoxShowing event generates an exception if I try to hide the dialog with OverrideResult.

My hook function is:


private static void ProcessWindow(
IntPtr hwnd )
{
  if (App.OcultarMensajesRevit)
  {
    // Buscamos el botón de aceptar y lo pulsamos.
    int ID_OK = 1;
    IntPtr hwndOk = Win32.Functions.GetDlgItem(
      hwnd, ID_OK );
    if (hwndOk != IntPtr.Zero)
    {
      Win32.Functions.SendMessage(
        hwndOk, (uint)Win32.Messages.WM_LBUTTONDOWN,
        (int)Win32.KeyStates.MK_LBUTTON, 0 );
      Win32.Functions.SendMessage(
        hwndOk, (uint)Win32.Messages.WM_LBUTTONUP,
        (int)Win32.KeyStates.MK_LBUTTON, 0 );
    }
    //Win32.Functions.SendMessage(
    //  hwnd, (uint)Win32.Messages.WM_CLOSE, 0, 0 );
  }
}

App.OcultarMensajesRevit is a variable I created to enable dialogue auto closing.
Instead of sending a WM_CLOSE message to the window to cancel it, I observed I needed to push the OK button for all things to work, so I search for that button and send it a button down and a button up message to simulate a mouse click.

To connect the hook function, I used the

WindowInterceptor class

I found at CodeProject.

Then I add the following in the Startup event of my application:


Process process = Process.GetCurrentProcess();
IntPtr hwnd = process.MainWindowHandle;
App._windowsInterceptor = new WindowInterceptor(
hwnd, ProcessWindow );

To clean up at the end, I add the following on shutdown:


App._windowsInterceptor.Stop();

Comments

13 responses to “Dismiss Dialogue using Windows API”

  1. hello!
    is it possible having a timer loading a new view every X seconds? lets say that for example the computer is used for a presentation and i would like to load a different view ever 30 seconds. would that be possible? i tried but since the timer runs on its own thread i get a message telling me that there is no istance for the application (uiapp)
    thanks
    igor

  2. forgot to mention: if possible without using the SLEEP function since it locks up revit. using a timer would be nice!

  3. Dear Igor,
    This is perfectly possible.
    You would have to make use of the Idling or an external event:
    http://thebuildingcoder.typepad.com/blog/2012/04/idling-enhancements-and-external-events.html
    I implemented the a webcam viewer for Revit based on the AVF analysis visualisation framework:
    http://thebuildingcoder.typepad.com/blog/2010/06/display-webcam-image-on-building-element-face.html
    http://thebuildingcoder.typepad.com/blog/2012/02/revit-webcam-2012.html
    If you do choose to make use of Idling or an external event, be sure to base your code on the appropriate new 2013 SDK sample in order to ensure you set it all up correctly.
    Cheers, Jeremy.

  4. Dear Jeremy,
    thanks for your quick and kind answer, i tried to understand the example you posted but i am very green in programming apis. here is what i am working on: i want to render a sequence of views overnight. so i made a form where i choose from all the available 3D-Views the ones i want to render, and put them in a list. when i confirm that the application starts rendering (so far so good..) but then i have to check every X seconds if the “rendering progress” window is still open. when the plugin detects that the rendering finished it loads the new view and starts rendering. its all progged except the part with the timer because i am able to check the render window, but i am not able to change view from inside the timer. could you PLEASE make a very short example on how to change view with the idle function? all that webcam stuff is very confusing for me. sorry if i ask you too much!
    thanks
    regards
    Igor

  5. Dear Igor,
    I would love to help you with this, and I can even promise that I will look at it when I have some time left over.
    Unfortunately, that means that you are in for a very long wait.
    I should point out that this is not a suitable project to begin learning the API with.
    I would suggest doing some other stuff to get to know the Revit API a bit better.
    Once you know a bit of programming in general, or better still have some experience in .NET, then the Revit API is not hard to understand at all.
    From what you explain above, you know more than enough to get to grips with it.
    Have a look at the ModelessDialog ModelessForm_ExternalEvent and ModelessForm_IdlingEvent SDK samples, and also take a gander at
    http://thebuildingcoder.typepad.com/blog/2010/11/pattern-for-semi-asynchronous-idling-api-access.html
    The latter is older, and still provides some interesting insight on what it is all about.
    Good luck!
    Cheers, Jeremy.

  6. great! thank you very much! will check it out right away!

  7. sorry for bothering you again! i dont know if i understood the logic behind all this right:
    -i execute my addin, and the “execute” function opens a modeless form. by using the “while using” inside the execute function i prevent it from finishing and remains active as long as the modeless form is active.
    -in the modeless form i can place a timer or whatever i want to execute my code. and to access the revit app i use the idle method. idle has a list of “thigs to do”. and in order to make revit do something i add sort of tasks to this list..
    is this correct?
    thanks
    ps: sorry for all the questions, i used to program inventor apis and that was way easier and better documented then the revit apis. i feel like in open ocean..

  8. Dear Igor,
    Nope, not quite. Your Execute method displays the modeless form and then returns immediately. As far as Revit is concerned, your command has ended. Before returning, you subscribe to the Idling event. The rest is correct: in the event handler, you check whether your modeless form has submitted some task to be executed, and so on.
    Cheers, Jeremy.

  9. hi Jeremy! thanks for your help, i have been busy for a week but finally i managed to make a modeless form and subscribe successfully to the idle event. i placed just to try a label on the form and update it like this inside the ideling event:
    label2.Text = System.DateTime.Now.Second.ToString();
    its working, but there is something i dont get: when i do nothing i would expect it to update continuously, instead it updates only every 2/3 seconds, and ONLY if i move the mouse. if i do absolutely nothing it doesn’t update.
    why?
    thank you again for your help!

  10. For some reason its working as expected now! but i have a new problem.. you said i can use the idle event to see when the rendering is finished. i suppose that you meant that as soon as the rendering is finished the idleing is triggered. problem is, when the rendering finishes the rendersettings window remains open, and as long as the rendersettings window is open, revit is not ideling!even if it isnt doing anything.. how can i know that the rendering is finished even if the revitsettings window is open?
    thanks
    Igor

  11. Dear Igor,
    Have you read about and understood the new Idling event repetition functionality?
    http://thebuildingcoder.typepad.com/blog/2012/04/idling-enhancements-and-external-events.html#2
    Have you based your add-in on the ModelessForm_IdlingEvent SDK sample?
    http://thebuildingcoder.typepad.com/blog/2012/03/new-revit-2013-sdk-samples.html#1
    Cheers, Jeremy.

  12. Dan Tartaglia Avatar
    Dan Tartaglia

    Hi Jeremy,
    I’m trying to use jtclicker to Cancel the Revit 2014 Print dialog but can’t seem to get it to work. Should the caption in the example be Print? Am I missing something?
    Thanks,
    Dan

  13. Dear Dan,
    Glad to hear that you are trying to make use of it. I am looking forward to hearing how it goes.
    I have no idea what you should optimally search for.
    Basically, for any dialogue that you wish to use this for, you would need to explore its innards and adapt the caption and other details based on information obtained from the dialogue itself using some Windows debugging tool like Spy or Spy++:
    http://lmgtfy.com/?q=spy+windows
    Good luck, and please let us know how you fare. Thank you!
    Cheers, Jeremy.

Leave a Reply to igorCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading