The ski tour season in the alps has opened again, and I went on my first tour of the year last weekend, to the Ramoz hut and the Arosa Rothorn:
Many thanks to Chris for the beautiful pictures!
Here is another idea from Rudolf Honke of acadGraph CADstudio GmbH. He says:
I previously explained how you can use UIAutomation event handlers in Revit.
When playing around a bit further with this, I thought about how to display the events.
Using good old P/Invoke, you can simply display any text in the Revit status bar (http://www.pinvoke.net helps a lot):
[DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )] static extern int SetWindowText( IntPtr hWnd, string lpString ); [DllImport( "user32.dll", SetLastError = true )] static extern IntPtr FindWindowEx( IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow ); public static void SetStatusText( string text ) { IntPtr statusBar = FindWindowEx( m_mainWndFromHandle, IntPtr.Zero, "msctls_statusbar32", "" ); if( statusBar != IntPtr.Zero ) { SetWindowText( statusBar, text ); } }
This is a comfortable way to show the events being fired. Here is an example of resizing the main Revit window:
Resizing the main Revit window again:
Selecting the Home ribbon tab:
Selecting the Insert ribbon tab:
Selecting a button on the Annotation ribbon tab:
One point to keep in mind is that Revit will overwrite your text as soon as it sees fit, which may be within a few milliseconds, depending on the command you invoked.
Here is another button being selected and the corresponding UI Automation event displayed:
In this case, it is immediately overwritten by Revit:
Actually, this function could be combined with another function to replace the global variable 'm_mainWndFromHandle':
public static void SetStatusText( string text ) { IntPtr mainWindowHandle = IntPtr.Zero; Process[] processes = Process.GetProcessesByName( "Revit" ); if( 0 < processes.Length ) { mainWindowHandle = processes[0].MainWindowHandle; IntPtr statusBar = FindWindowEx( mainWindowHandle, IntPtr.Zero, "msctls_statusbar32", "" ); if( statusBar != IntPtr.Zero ) { SetWindowText( statusBar, text ); } } }
So, this function could be called without filling the global Revit app handle before. Or even shorter:
public static void SetStatusText( string text ) { Process[] processes = Process.GetProcessesByName( "Revit" ); if( 0 < processes.Length ) { IntPtr statusBar = FindWindowEx( processes[0].MainWindowHandle, IntPtr.Zero, "msctls_statusbar32", "" ); if( statusBar != IntPtr.Zero ) { SetWindowText( statusBar, text ); } } }
Think of a situation there more than one instance of Revit is running, RAC and MEP, for example. Using the original code, the wrong window might be addressed.
Jeremy adds: I went ahead and implemented a minimal new external command CmdStatusBar for The Building Coder samples to demonstrate this. I actually make use of the GetCurrentProcess method instead, since I am inside the Revit process, like this:
public static void SetStatusText( IntPtr mainWindow, string text ) { IntPtr statusBar = FindWindowEx( mainWindow, IntPtr.Zero, "msctls_statusbar32", "" ); if( statusBar != IntPtr.Zero ) { SetWindowText( statusBar, text ); } } public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { IntPtr revitHandle = System.Diagnostics.Process .GetCurrentProcess().MainWindowHandle; SetStatusText( revitHandle, "Kilroy was here." ); return Result.Succeeded; }
It works fine. Here is version 2011.0.83.0 of The Building Coder samples including the complete source code and Visual Studio solution with the new command.