Refreshing External References palette using Microsoft UI Automation

<?xml encoding=”UTF-8″>By Balaji Ramamoorthy

In this blog post we will look at refreshing the external references palette in AutoCAD. Before we get into the details, here is some background information on why it might be required to refresh that palette.

The external references palette in AutoCAD turns into an Enhanced Standard Window (ESW) when the Vault plugin for AutoCAD is installed. Since the check-in/ check-out status of the files are displayed in the external references palette, it is necessary for a way to refresh the palette to display the current status in case the file status got modified externally using Vault client.

Because there is no public API to do this, we will look at using Microsoft’s UI Automation to simulate a click on the Refresh button. Please note that the approach suggested here is unsupported by Autodesk as it relies on Win32 and UI Automation API. If you need to use this approach, please test it more completely with your application.

To get the click on Refresh button to work, the Microsoft UI Automation has all the necessary API. Unfortunately, the nature of the Refresh button in the external references palette in AutoCAD posed a problem. By looking at the layout of the External References palette using Spy++, it becomes evident that the Refresh button is part of a Toolbar inside the palette. Also, the Refresh button is a drop-down with Reload and Refresh options. By using the UI Automation’s Invoke pattern, it was not possible to simulate a mouse click on the Refresh button.

To work around this, we can resort to Win32 API’s SendInput method to simulate a mouse click and gather the inputs required by that method using Microsoft UI Automation API. Here is a sample code that works ok in AutoCAD 2016. For other AutoCAD versions, you may need to identify the control-id of the Refresh button using Spy++ and update the code accordingly.

 <span>// Add reference to UIAutomationClient.dll </span><span> </span>
 <span>// and UIAutomationTypes.dll</span><span> </span>
 <span>using</span><span>  System.Windows.Automation;</span>
 <span>using</span><span>  System.Diagnostics;</span>
 <span>using</span><span>  System.Runtime.InteropServices;</span>
 
 <span>using</span><span>  Autodesk.AutoCAD.Runtime;</span>
 <span>using</span><span>  Autodesk.AutoCAD.EditorInput;</span>
 <span>using</span><span>  Autodesk.AutoCAD.DatabaseServices;</span>
 <span>using</span><span>  Autodesk.AutoCAD.ApplicationServices;</span>
 
 
 <span>// Based on http://www.pinvoke.net</span><span> </span>
 <span>// /default.aspx/user32/SendInput.html</span><span> </span>
 <span>// /default.aspx/Structures/MOUSEINPUT.html</span><span> </span>
 <span>// /default.aspx/user32/mouse_event.html</span><span> </span>
 
 [DllImport(<span>"user32.dll"</span><span> , SetLastError = <span>true</span><span> )]</span></span>
 <span>static</span><span>  <span>extern</span><span>  uint SendInput(uint nInputs, </span></span>
 	INPUT[] pInputs, <span>int</span><span>  cbSize);</span>
 
 [StructLayout(LayoutKind.Sequential)]
 <span>internal</span><span>  <span>struct</span><span>  MOUSEINPUT</span></span>
 <span>{</span>
     <span>public</span><span>  <span>int</span><span>  dx;</span></span>
     <span>public</span><span>  <span>int</span><span>  dy;</span></span>
     <span>public</span><span>  uint mouseData;</span>
     <span>public</span><span>  uint dwFlags;</span>
     <span>public</span><span>  uint time;</span>
     <span>public</span><span>  IntPtr dwExtraInfo;</span>
 <span>}</span>
 
 [StructLayout(LayoutKind.Sequential)]
 <span>internal</span><span>  <span>struct</span><span>  INPUT</span></span>
 <span>{</span>
     <span>public</span><span>  <span>int</span><span>  type;</span></span>
     <span>public</span><span>  MOUSEINPUT mi;</span>
     <span>public</span><span>  INPUT(uint flag)</span>
     <span>{</span>
         type = 0; <span>// Mouse input</span><span> </span>
         mi.dx = 0;
         mi.dy = 0;
         mi.mouseData = 0;
         mi.time = 0;
         mi.dwExtraInfo = IntPtr.Zero;
         mi.dwFlags = flag;
     <span>}</span>
 <span>}</span>
         
 <span>private</span><span>  <span>const</span><span>  <span>int</span><span>  MOUSEEVENTF_LEFTDOWN = 0x0002;</span></span></span>
 <span>private</span><span>  <span>const</span><span>  <span>int</span><span>  MOUSEEVENTF_LEFTUP = 0x0004;</span></span></span>
 
 [CommandMethod(<span>"XRefPalRefresh"</span><span> )]</span>
 <span>public</span><span>  <span>void</span><span>  XRefPalRefresh()</span></span>
 <span>{</span>
     Editor ed = 
 		Application.DocumentManager.MdiActiveDocument.Editor;
     <span>try</span><span> </span>
     <span>{</span>
         System.Diagnostics.Process p 
 			= Process.GetCurrentProcess();
         AutomationElement acadAutoElem = 
 			AutomationElement.RootElement.FindFirst(
 			TreeScope.Children, 
             <span>new</span><span>  PropertyCondition(</span>
 			AutomationElement.ProcessIdProperty, p.Id));
 
         <span>// Control Id retreived for the Refresh button </span><span> </span>
 		<span>// in AutoCAD 2016 using Spy++</span><span> </span>
         string btnRefreshhexID = <span>"000075FB"</span><span> ;</span>
         string btnRefreshdecimalID = 
 			System.Convert.ToInt32(btnRefreshhexID, 16)
 			.ToString();
         AutomationElement refreshBtnAutoElem 
 			= acadAutoElem.FindFirst(
             TreeScope.Descendants, 
             <span>new</span><span>  PropertyCondition(</span>
 			AutomationElement.AutomationIdProperty, 
 			btnRefreshdecimalID));
 
         <span>if</span><span>  (refreshBtnAutoElem == null)</span>
         <span>{</span>
             ed.WriteMessage(<span>"Refresh button in </span><span> </span>
 				External References
 				palette was not identified !<span>");</span><span> </span>
             <span>return</span><span> ;</span>
         <span>}</span>
                 
         <span>// Using UI's Invoke pattern</span><span> </span>
         <span>// Does work for simple buttons but not for </span><span> </span>
 		<span>// the Refresh button in </span><span> </span>
         <span>//'s external references palette.</span><span> </span>
         <span>//InvokePattern ipClickRefreshBtn = </span><span> </span>
 		<span>// (InvokePattern)refreshBtnAutoElem.GetCurrentPattern</span><span> </span>
 		<span>// (InvokePattern.Pattern);</span><span> </span>
         <span>//ipClickRefreshBtn.Invoke();</span><span> </span>
 
         <span>//'s resort to clicking by location.</span><span> </span>
         System.Windows.Point point 
 			= refreshBtnAutoElem.GetClickablePoint();
         System.Windows.Forms.Cursor.Position 
             = <span>new</span><span>  System.Drawing.Point(</span>
 			(<span>int</span><span> )point.X, (<span>int</span><span> )point.Y);</span></span>
 
         INPUT input1 = <span>new</span><span>  INPUT(MOUSEEVENTF_LEFTDOWN);</span>
         INPUT input2 = <span>new</span><span>  INPUT(MOUSEEVENTF_LEFTUP);</span>
         SendInput(2, <span>new</span><span> [] <span>{</span> input1, input2 <span>}</span>, </span>
 			Marshal.SizeOf(typeof(INPUT)));
     <span>}</span>
     <span>catch</span><span>  (System.Exception ex)</span>
     <span>{</span>
         ed.WriteMessage(ex.Message);
     <span>}</span>
 <span>}</span>
 

Before we end this blog post, a known limitation of this approach is that the external palette must be open and the Refresh button in it must not be masked by any other window.


Comments

Leave a Reply

Discover more from Autodesk Developer Blog

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

Continue reading