ADN Open CIS
Сообщество программистов Autodesk в СНГ

08/05/2015

Переключение между вариантами конструкции с помощью UI Automation

Переключение между вариантами конструкции с помощью UI Automation

Рассмотрим приложение, демонстрирующее функциональность по определению, перечислению и установки варианта конструкции с помощью самостоятельного отдельного приложения.

Приложение было создано пользователем Revitalizer. Вот его комментарии:

Вот небольшая утилита по переключению между вариантами конструкции.

Это отдельное приложение, которое извлекает и устанавливает Вариант конструкции.

Так как оно работает очень, очень медленно, оно не очень полезно для ежедневного профессионального использования.

 

Приложение просто считывает и заполняет список вариантов конструкции, но чтение занимает довольно продолжительное время.

 

Для тестирования приложения откройте проект Revit, в котором содержится несколько вариантов конструкций. Затем запустите приложение. При этом фактически вы будете управлять Revit вне контекста процесса Revit.

Тестовый запуск

Приложение представляет из себя единственную форму на которой находится две кнопки:

Получить все возможные варианты конструкции

Установить выбранный вариант конструкции в Revit.

Вот пример того как это выглядит, после того как была нажата кнопка Get.

 

После того, как будет выбран нужный вариант конструкции и нажата кнопка Set, этот вариант конструкции станет активным в Revit.

 

Реализация

Реализация достаточно тривиально, так как все действия происходят в коде формы

В коде демонстрируется как:

  • Использование механизма P/Invoke для доступа к методам Windows API, определенных в библиотеке User32.dll для поиска нужного окна
  • Поиск списка с вариантами конструкции в строке состояния
  • Чтение текущего варианта конструкции
  • Обновление выбранного варианта конструкции
  • Обработка нажатия кнопок на форме

 

Вот исходный код формы:

Код - C#: [Выделить]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Diagnostics;
  6. using System.Drawing;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using System.Windows.Automation;
  10. using System.Windows.Forms;
  11. using System.Windows.Input;
  12.  
  13. namespace DesignOptionModifier
  14. {
  15.   public partial class Form1 : Form
  16.   {
  17.     #region Windows API
  18.  
  19.     [DllImport( "user32.dll" )]
  20.     [return: MarshalAs( UnmanagedType.Bool )]
  21.     public static extern bool SetForegroundWindow(
  22.       IntPtr hWnd );
  23.  
  24.     [DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )]
  25.     static extern int GetWindowText(
  26.       IntPtr hWnd, [Out] StringBuilder lpString,
  27.       int nMaxCount );
  28.  
  29.     [DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )]
  30.     static extern int GetWindowTextLength(
  31.       IntPtr hWnd );
  32.  
  33.     [DllImport( "user32.dll" )]
  34.     [return: MarshalAs( UnmanagedType.Bool )]
  35.     public static extern bool EnumChildWindows(
  36.       IntPtr window, EnumWindowProc callback, IntPtr i );
  37.  
  38.     [DllImport( "user32.dll", EntryPoint = "GetClassName" )]
  39.     public static extern int GetClass(
  40.       IntPtr hWnd, StringBuilder className, int nMaxCount );
  41.  
  42.     public delegate bool EnumWindowProc(
  43.       IntPtr hWnd, IntPtr parameter );
  44.  
  45.     [DllImport( "user32.dll", SetLastError = true )]
  46.     [return: MarshalAs( UnmanagedType.Bool )]
  47.     private static extern bool GetWindowRect(
  48.       IntPtr hWnd, out RECT lpRect );
  49.  
  50.     public static string GetText( IntPtr hWnd )
  51.     {
  52.       int length = GetWindowTextLength( hWnd );
  53.       StringBuilder sb = new StringBuilder( length + 1 );
  54.       GetWindowText( hWnd, sb, sb.Capacity );
  55.       return sb.ToString();
  56.     }
  57.  
  58.     private static bool EnumWindow(
  59.       IntPtr handle,
  60.       IntPtr pointer )
  61.     {
  62.       GCHandle gch = GCHandle.FromIntPtr( pointer );
  63.       List<IntPtr> list = gch.Target as List<IntPtr>;
  64.       if( list != null )
  65.       {
  66.         list.Add( handle );
  67.       }
  68.       return true;
  69.     }
  70.  
  71.     public static List<IntPtr> GetChildWindows(
  72.       IntPtr parent )
  73.     {
  74.       List<IntPtr> result = new List<IntPtr>();
  75.       GCHandle listHandle = GCHandle.Alloc( result );
  76.       try
  77.       {
  78.         EnumWindowProc childProc = new EnumWindowProc( EnumWindow );
  79.         EnumChildWindows( parent, childProc, GCHandle.ToIntPtr( listHandle ) );
  80.       }
  81.       finally
  82.       {
  83.         if( listHandle.IsAllocated )
  84.           listHandle.Free();
  85.       }
  86.       return result;
  87.     }
  88.  
  89.     internal struct RECT
  90.     {
  91.       public int Left;
  92.       public int Top;
  93.       public int Right;
  94.       public int Bottom;
  95.     }
  96.     #endregion
  97.  
  98.     /// <summary>
  99.     /// Кэжширование списка
  100.     /// </summary>
  101.     private AutomationElement comboBoxElement = null;
  102.  
  103.     public Form1()
  104.     {
  105.       InitializeComponent();
  106.  
  107.       GetComboBox();
  108.     }
  109.  
  110.     private void button_ok_Click(
  111.       object sender,
  112.       EventArgs e )
  113.     {
  114.       Close();
  115.     }
  116.  
  117.     private void GetComboBox()
  118.     {
  119.       int maxX = -1;
  120.  
  121.       Process[] revits = Process.GetProcessesByName(
  122.         "Revit" );
  123.  
  124.       IntPtr cb = IntPtr.Zero;
  125.  
  126.       if( revits.Length > 0 )
  127.       {
  128.         List<IntPtr> children = GetChildWindows(
  129.           revits[0].MainWindowHandle );
  130.  
  131.         foreach( IntPtr child in children )
  132.         {
  133.           StringBuilder classNameBuffer
  134.             = new StringBuilder( 100 );
  135.  
  136.           int className = GetClass( child,
  137.             classNameBuffer, 100 );
  138.  
  139.           if( classNameBuffer.ToString().Contains(
  140.             "msctls_statusbar32" ) )
  141.           {
  142.             List<IntPtr> grandChildren
  143.               = GetChildWindows( child );
  144.  
  145.             foreach( IntPtr grandChild in grandChildren )
  146.             {
  147.               StringBuilder classNameBuffer2
  148.                 = new StringBuilder( 100 );
  149.  
  150.               int className2 = GetClass( grandChild,
  151.                 classNameBuffer2, 100 );
  152.  
  153.               if( classNameBuffer2.ToString().Contains(
  154.                 "ComboBox" ) )
  155.               {
  156.                 RECT r;
  157.  
  158.                 GetWindowRect( grandChild, out r );
  159.  
  160.                 // в строке состояния два списка,
  161.                 // нам нужен тот, который находится правее
  162.  
  163.                 if( r.Left > maxX )
  164.                 {
  165.                   maxX = r.Left;
  166.                   cb = grandChild;
  167.                 }
  168.               }
  169.             }
  170.           }
  171.         }
  172.       }
  173.  
  174.       if( cb != IntPtr.Zero )
  175.       {
  176.         comboBoxElement = AutomationElement.FromHandle(
  177.           cb );
  178.       }
  179.     }
  180.  
  181.     private void buttonGet_Click(
  182.       object sender,
  183.       EventArgs e )
  184.     {
  185.       if( ( comboBoxElement != null )
  186.         && ( comboBoxElement.Current.IsEnabled ) )
  187.       {
  188.         ExpandCollapsePattern expandPattern
  189.           = (ExpandCollapsePattern) comboBoxElement
  190.             .GetCurrentPattern(
  191.               ExpandCollapsePattern.Pattern );
  192.  
  193.         expandPattern.Expand();
  194.  
  195.         listBox1.Items.Clear();
  196.  
  197.         CacheRequest cacheRequest = new CacheRequest();
  198.         cacheRequest.Add( AutomationElement.NameProperty );
  199.  
  200.         cacheRequest.TreeScope = TreeScope.Element
  201.           | TreeScope.Children;
  202.  
  203.         AutomationElement comboboxItems = comboBoxElement
  204.           .GetUpdatedCache( cacheRequest );
  205.  
  206.         foreach( AutomationElement item
  207.           in comboboxItems.CachedChildren )
  208.         {
  209.           if( item.Current.Name == "" )
  210.           {
  211.             CacheRequest cacheRequest2 = new CacheRequest();
  212.             cacheRequest2.Add( AutomationElement.NameProperty );
  213.             cacheRequest2.TreeScope = TreeScope.Element
  214.               | TreeScope.Children;
  215.  
  216.             AutomationElement comboboxItems2
  217.               = item.GetUpdatedCache( cacheRequest );
  218.  
  219.             foreach( AutomationElement item2
  220.               in comboboxItems2.CachedChildren )
  221.             {
  222.               listBox1.Items.Add( item2.Current.Name );
  223.             }
  224.           }
  225.         }
  226.         expandPattern.Collapse();
  227.       }
  228.       GetSelection();
  229.     }
  230.  
  231.     private void GetSelection()
  232.     {
  233.       if( ( comboBoxElement != null )
  234.         && ( comboBoxElement.Current.IsEnabled ) )
  235.       {
  236.         SelectionPattern selPattern = comboBoxElement
  237.           .GetCurrentPattern( SelectionPattern.Pattern )
  238.             as SelectionPattern;
  239.  
  240.         AutomationElement[] items = selPattern.Current
  241.           .GetSelection();
  242.  
  243.         foreach( AutomationElement item in items )
  244.         {
  245.           int index = 0;
  246.  
  247.           foreach( var listItem in listBox1.Items )
  248.           {
  249.             if( (string) listItem == item.Current.Name )
  250.             {
  251.               listBox1.SelectedIndex = index;
  252.               break;
  253.             }
  254.             index++;
  255.           }
  256.         }
  257.       }
  258.       SetForegroundWindow( this.Handle );
  259.     }
  260.  
  261.     private void SetSelection( string option )
  262.     {
  263.       if( ( comboBoxElement != null )
  264.         && ( comboBoxElement.Current.IsEnabled ) )
  265.       {
  266.         AutomationElement sel = null;
  267.  
  268.         ExpandCollapsePattern expandPattern
  269.           = (ExpandCollapsePattern) comboBoxElement
  270.             .GetCurrentPattern(
  271.               ExpandCollapsePattern.Pattern );
  272.  
  273.         expandPattern.Expand();
  274.  
  275.         CacheRequest cacheRequest = new CacheRequest();
  276.         cacheRequest.Add( AutomationElement.NameProperty );
  277.         cacheRequest.TreeScope = TreeScope.Element
  278.           | TreeScope.Children;
  279.  
  280.         AutomationElement comboboxItems
  281.           = comboBoxElement.GetUpdatedCache(
  282.             cacheRequest );
  283.  
  284.         foreach( AutomationElement item
  285.           in comboboxItems.CachedChildren )
  286.         {
  287.           if( item.Current.Name == "" )
  288.           {
  289.             CacheRequest cacheRequest2 = new CacheRequest();
  290.             cacheRequest2.Add( AutomationElement.NameProperty );
  291.             cacheRequest2.TreeScope = TreeScope.Element
  292.               | TreeScope.Children;
  293.  
  294.             AutomationElement comboboxItems2
  295.               = item.GetUpdatedCache( cacheRequest );
  296.  
  297.             foreach( AutomationElement item2
  298.               in comboboxItems2.CachedChildren )
  299.             {
  300.               if( item2.Current.Name == option )
  301.               {
  302.                 sel = item2;
  303.               }
  304.             }
  305.           }
  306.         }
  307.  
  308.         if( sel != null )
  309.         {
  310.           SelectionItemPattern select =
  311.             (SelectionItemPattern) sel.GetCurrentPattern(
  312.               SelectionItemPattern.Pattern );
  313.  
  314.           select.Select();
  315.         }
  316.         expandPattern.Collapse();
  317.       }
  318.       SetForegroundWindow( this.Handle );
  319.     }
  320.  
  321.     private void buttonSet_Click(
  322.       object sender,
  323.       EventArgs e )
  324.     {
  325.       if( listBox1.Items.Count > 0
  326.         && listBox1.SelectedIndex > -1 )
  327.       {
  328.         string option = (string)
  329.           listBox1.Items[listBox1.SelectedIndex];
  330.  
  331.         SetSelection( option );
  332.       }
  333.       SetForegroundWindow( this.Handle );
  334.     }
  335.   }
  336. }
]

Загрузка

Скачать исходный код с проектом для Visual Studio можно из репозитория на GitHub. Версия, обсуждаемая в статье – 2015.0.0.0

Помните, что это неофициальные методы, поэтому используйте их с осторожностью на свой страх и риск.

Источник: http://thebuildingcoder.typepad.com/blog/2015/03/list-and-switch-design-options-using-ui-automation.html

Автор перевода: Виктор Чекалин

Обсуждение: http://adn-cis.org/forum/index.php?topic=2695

Опубликовано 08.05.2015